// ==UserScript==
// @name Bilibili-Gate-TagRec
// @namespace https://github.com/yunomega
// @version 1.1.5
// @author yunomega
// @description Bilibili 自定义首页 —— TagRec 标签组收藏推荐 / 指定标签挖掘 / 标签组分享集市 (fork 自 Bilibili-Gate)
// @icon https://www.bilibili.com/favicon.ico
// @homepage https://www.tagrec.cn
// @homepageURL https://www.tagrec.cn
// @source https://github.com/yunomega/Bilibili-Gate-TagRec.git
// @supportURL https://github.com/yunomega/Bilibili-Gate-TagRec/issues
// @match https://www.bilibili.com/
// @match https://www.bilibili.com/?*
// @match https://www.bilibili.com/index.html
// @match https://www.bilibili.com/index.html?*
// @match https://www.bilibili.com/video/*
// @match https://www.bilibili.com/list/watchlater?*
// @match https://www.bilibili.com/bangumi/play/*
// @match https://space.bilibili.com/*
// @match https://search.bilibili.com/*
// @match https://www.tagrec.cn/*
// @require https://registry.npmmirror.com/axios/1.20.0/files/dist/axios.min.js
// @require https://registry.npmmirror.com/ua-parser-js/1.0.41/files/dist/ua-parser.min.js
// @require https://registry.npmmirror.com/localforage/1.10.0/files/dist/localforage.min.js
// @require https://registry.npmmirror.com/pinyin-match/1.2.10/files/dist/main.js
// @require https://registry.npmmirror.com/spark-md5/3.0.2/files/spark-md5.min.js
// @tag bilibili
// @connect app.bilibili.com
// @connect api.bilibili.com
// @connect tagrec.cn
// @connect *.tagrec.cn
// @grant GM.deleteValue
// @grant GM.getValue
// @grant GM.listValues
// @grant GM.openInTab
// @grant GM.registerMenuCommand
// @grant GM.setClipboard
// @grant GM.setValue
// @grant GM.xmlHttpRequest
// @grant GM_addStyle
// @grant GM_addValueChangeListener
// @grant GM_info
// @grant unsafeWindow
// @run-at document-body
// @original-author magicdawn
// @original-license MIT
// @original-script https://github.com/magicdawn/Bilibili-Gate
// @license GPL-3.0
// ==/UserScript==
// TagRec distribution notices
// # TagRec distribution notices
//
// The project uses the following separately licensed components. Their original licences continue to apply.
//
// ## Project licence
//
// GNU GENERAL PUBLIC LICENSE
// Version 3, 29 June 2007
//
// Copyright (C) 2007 Free Software Foundation, Inc.
// Everyone is permitted to copy and distribute verbatim copies
// of this license document, but changing it is not allowed.
//
// Preamble
//
// The GNU General Public License is a free, copyleft license for
// software and other kinds of works.
//
// The licenses for most software and other practical works are designed
// to take away your freedom to share and change the works. By contrast,
// the GNU General Public License is intended to guarantee your freedom to
// share and change all versions of a program--to make sure it remains free
// software for all its users. We, the Free Software Foundation, use the
// GNU General Public License for most of our software; it applies also to
// any other work released this way by its authors. You can apply it to
// your programs, too.
//
// When we speak of free software, we are referring to freedom, not
// price. Our General Public Licenses are designed to make sure that you
// have the freedom to distribute copies of free software (and charge for
// them if you wish), that you receive source code or can get it if you
// want it, that you can change the software or use pieces of it in new
// free programs, and that you know you can do these things.
//
// To protect your rights, we need to prevent others from denying you
// these rights or asking you to surrender the rights. Therefore, you have
// certain responsibilities if you distribute copies of the software, or if
// you modify it: responsibilities to respect the freedom of others.
//
// For example, if you distribute copies of such a program, whether
// gratis or for a fee, you must pass on to the recipients the same
// freedoms that you received. You must make sure that they, too, receive
// or can get the source code. And you must show them these terms so they
// know their rights.
//
// Developers that use the GNU GPL protect your rights with two steps:
// (1) assert copyright on the software, and (2) offer you this License
// giving you legal permission to copy, distribute and/or modify it.
//
// For the developers' and authors' protection, the GPL clearly explains
// that there is no warranty for this free software. For both users' and
// authors' sake, the GPL requires that modified versions be marked as
// changed, so that their problems will not be attributed erroneously to
// authors of previous versions.
//
// Some devices are designed to deny users access to install or run
// modified versions of the software inside them, although the manufacturer
// can do so. This is fundamentally incompatible with the aim of
// protecting users' freedom to change the software. The systematic
// pattern of such abuse occurs in the area of products for individuals to
// use, which is precisely where it is most unacceptable. Therefore, we
// have designed this version of the GPL to prohibit the practice for those
// products. If such problems arise substantially in other domains, we
// stand ready to extend this provision to those domains in future versions
// of the GPL, as needed to protect the freedom of users.
//
// Finally, every program is threatened constantly by software patents.
// States should not allow patents to restrict development and use of
// software on general-purpose computers, but in those that do, we wish to
// avoid the special danger that patents applied to a free program could
// make it effectively proprietary. To prevent this, the GPL assures that
// patents cannot be used to render the program non-free.
//
// The precise terms and conditions for copying, distribution and
// modification follow.
//
// TERMS AND CONDITIONS
//
// 0. Definitions.
//
// "This License" refers to version 3 of the GNU General Public License.
//
// "Copyright" also means copyright-like laws that apply to other kinds of
// works, such as semiconductor masks.
//
// "The Program" refers to any copyrightable work licensed under this
// License. Each licensee is addressed as "you". "Licensees" and
// "recipients" may be individuals or organizations.
//
// To "modify" a work means to copy from or adapt all or part of the work
// in a fashion requiring copyright permission, other than the making of an
// exact copy. The resulting work is called a "modified version" of the
// earlier work or a work "based on" the earlier work.
//
// A "covered work" means either the unmodified Program or a work based
// on the Program.
//
// To "propagate" a work means to do anything with it that, without
// permission, would make you directly or secondarily liable for
// infringement under applicable copyright law, except executing it on a
// computer or modifying a private copy. Propagation includes copying,
// distribution (with or without modification), making available to the
// public, and in some countries other activities as well.
//
// To "convey" a work means any kind of propagation that enables other
// parties to make or receive copies. Mere interaction with a user through
// a computer network, with no transfer of a copy, is not conveying.
//
// An interactive user interface displays "Appropriate Legal Notices"
// to the extent that it includes a convenient and prominently visible
// feature that (1) displays an appropriate copyright notice, and (2)
// tells the user that there is no warranty for the work (except to the
// extent that warranties are provided), that licensees may convey the
// work under this License, and how to view a copy of this License. If
// the interface presents a list of user commands or options, such as a
// menu, a prominent item in the list meets this criterion.
//
// 1. Source Code.
//
// The "source code" for a work means the preferred form of the work
// for making modifications to it. "Object code" means any non-source
// form of a work.
//
// A "Standard Interface" means an interface that either is an official
// standard defined by a recognized standards body, or, in the case of
// interfaces specified for a particular programming language, one that
// is widely used among developers working in that language.
//
// The "System Libraries" of an executable work include anything, other
// than the work as a whole, that (a) is included in the normal form of
// packaging a Major Component, but which is not part of that Major
// Component, and (b) serves only to enable use of the work with that
// Major Component, or to implement a Standard Interface for which an
// implementation is available to the public in source code form. A
// "Major Component", in this context, means a major essential component
// (kernel, window system, and so on) of the specific operating system
// (if any) on which the executable work runs, or a compiler used to
// produce the work, or an object code interpreter used to run it.
//
// The "Corresponding Source" for a work in object code form means all
// the source code needed to generate, install, and (for an executable
// work) run the object code and to modify the work, including scripts to
// control those activities. However, it does not include the work's
// System Libraries, or general-purpose tools or generally available free
// programs which are used unmodified in performing those activities but
// which are not part of the work. For example, Corresponding Source
// includes interface definition files associated with source files for
// the work, and the source code for shared libraries and dynamically
// linked subprograms that the work is specifically designed to require,
// such as by intimate data communication or control flow between those
// subprograms and other parts of the work.
//
// The Corresponding Source need not include anything that users
// can regenerate automatically from other parts of the Corresponding
// Source.
//
// The Corresponding Source for a work in source code form is that
// same work.
//
// 2. Basic Permissions.
//
// All rights granted under this License are granted for the term of
// copyright on the Program, and are irrevocable provided the stated
// conditions are met. This License explicitly affirms your unlimited
// permission to run the unmodified Program. The output from running a
// covered work is covered by this License only if the output, given its
// content, constitutes a covered work. This License acknowledges your
// rights of fair use or other equivalent, as provided by copyright law.
//
// You may make, run and propagate covered works that you do not
// convey, without conditions so long as your license otherwise remains
// in force. You may convey covered works to others for the sole purpose
// of having them make modifications exclusively for you, or provide you
// with facilities for running those works, provided that you comply with
// the terms of this License in conveying all material for which you do
// not control copyright. Those thus making or running the covered works
// for you must do so exclusively on your behalf, under your direction
// and control, on terms that prohibit them from making any copies of
// your copyrighted material outside their relationship with you.
//
// Conveying under any other circumstances is permitted solely under
// the conditions stated below. Sublicensing is not allowed; section 10
// makes it unnecessary.
//
// 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
//
// No covered work shall be deemed part of an effective technological
// measure under any applicable law fulfilling obligations under article
// 11 of the WIPO copyright treaty adopted on 20 December 1996, or
// similar laws prohibiting or restricting circumvention of such
// measures.
//
// When you convey a covered work, you waive any legal power to forbid
// circumvention of technological measures to the extent such circumvention
// is effected by exercising rights under this License with respect to
// the covered work, and you disclaim any intention to limit operation or
// modification of the work as a means of enforcing, against the work's
// users, your or third parties' legal rights to forbid circumvention of
// technological measures.
//
// 4. Conveying Verbatim Copies.
//
// You may convey verbatim copies of the Program's source code as you
// receive it, in any medium, provided that you conspicuously and
// appropriately publish on each copy an appropriate copyright notice;
// keep intact all notices stating that this License and any
// non-permissive terms added in accord with section 7 apply to the code;
// keep intact all notices of the absence of any warranty; and give all
// recipients a copy of this License along with the Program.
//
// You may charge any price or no price for each copy that you convey,
// and you may offer support or warranty protection for a fee.
//
// 5. Conveying Modified Source Versions.
//
// You may convey a work based on the Program, or the modifications to
// produce it from the Program, in the form of source code under the
// terms of section 4, provided that you also meet all of these conditions:
//
// a) The work must carry prominent notices stating that you modified
// it, and giving a relevant date.
//
// b) The work must carry prominent notices stating that it is
// released under this License and any conditions added under section
// 7. This requirement modifies the requirement in section 4 to
// "keep intact all notices".
//
// c) You must license the entire work, as a whole, under this
// License to anyone who comes into possession of a copy. This
// License will therefore apply, along with any applicable section 7
// additional terms, to the whole of the work, and all its parts,
// regardless of how they are packaged. This License gives no
// permission to license the work in any other way, but it does not
// invalidate such permission if you have separately received it.
//
// d) If the work has interactive user interfaces, each must display
// Appropriate Legal Notices; however, if the Program has interactive
// interfaces that do not display Appropriate Legal Notices, your
// work need not make them do so.
//
// A compilation of a covered work with other separate and independent
// works, which are not by their nature extensions of the covered work,
// and which are not combined with it such as to form a larger program,
// in or on a volume of a storage or distribution medium, is called an
// "aggregate" if the compilation and its resulting copyright are not
// used to limit the access or legal rights of the compilation's users
// beyond what the individual works permit. Inclusion of a covered work
// in an aggregate does not cause this License to apply to the other
// parts of the aggregate.
//
// 6. Conveying Non-Source Forms.
//
// You may convey a covered work in object code form under the terms
// of sections 4 and 5, provided that you also convey the
// machine-readable Corresponding Source under the terms of this License,
// in one of these ways:
//
// a) Convey the object code in, or embodied in, a physical product
// (including a physical distribution medium), accompanied by the
// Corresponding Source fixed on a durable physical medium
// customarily used for software interchange.
//
// b) Convey the object code in, or embodied in, a physical product
// (including a physical distribution medium), accompanied by a
// written offer, valid for at least three years and valid for as
// long as you offer spare parts or customer support for that product
// model, to give anyone who possesses the object code either (1) a
// copy of the Corresponding Source for all the software in the
// product that is covered by this License, on a durable physical
// medium customarily used for software interchange, for a price no
// more than your reasonable cost of physically performing this
// conveying of source, or (2) access to copy the
// Corresponding Source from a network server at no charge.
//
// c) Convey individual copies of the object code with a copy of the
// written offer to provide the Corresponding Source. This
// alternative is allowed only occasionally and noncommercially, and
// only if you received the object code with such an offer, in accord
// with subsection 6b.
//
// d) Convey the object code by offering access from a designated
// place (gratis or for a charge), and offer equivalent access to the
// Corresponding Source in the same way through the same place at no
// further charge. You need not require recipients to copy the
// Corresponding Source along with the object code. If the place to
// copy the object code is a network server, the Corresponding Source
// may be on a different server (operated by you or a third party)
// that supports equivalent copying facilities, provided you maintain
// clear directions next to the object code saying where to find the
// Corresponding Source. Regardless of what server hosts the
// Corresponding Source, you remain obligated to ensure that it is
// available for as long as needed to satisfy these requirements.
//
// e) Convey the object code using peer-to-peer transmission, provided
// you inform other peers where the object code and Corresponding
// Source of the work are being offered to the general public at no
// charge under subsection 6d.
//
// A separable portion of the object code, whose source code is excluded
// from the Corresponding Source as a System Library, need not be
// included in conveying the object code work.
//
// A "User Product" is either (1) a "consumer product", which means any
// tangible personal property which is normally used for personal, family,
// or household purposes, or (2) anything designed or sold for incorporation
// into a dwelling. In determining whether a product is a consumer product,
// doubtful cases shall be resolved in favor of coverage. For a particular
// product received by a particular user, "normally used" refers to a
// typical or common use of that class of product, regardless of the status
// of the particular user or of the way in which the particular user
// actually uses, or expects or is expected to use, the product. A product
// is a consumer product regardless of whether the product has substantial
// commercial, industrial or non-consumer uses, unless such uses represent
// the only significant mode of use of the product.
//
// "Installation Information" for a User Product means any methods,
// procedures, authorization keys, or other information required to install
// and execute modified versions of a covered work in that User Product from
// a modified version of its Corresponding Source. The information must
// suffice to ensure that the continued functioning of the modified object
// code is in no case prevented or interfered with solely because
// modification has been made.
//
// If you convey an object code work under this section in, or with, or
// specifically for use in, a User Product, and the conveying occurs as
// part of a transaction in which the right of possession and use of the
// User Product is transferred to the recipient in perpetuity or for a
// fixed term (regardless of how the transaction is characterized), the
// Corresponding Source conveyed under this section must be accompanied
// by the Installation Information. But this requirement does not apply
// if neither you nor any third party retains the ability to install
// modified object code on the User Product (for example, the work has
// been installed in ROM).
//
// The requirement to provide Installation Information does not include a
// requirement to continue to provide support service, warranty, or updates
// for a work that has been modified or installed by the recipient, or for
// the User Product in which it has been modified or installed. Access to a
// network may be denied when the modification itself materially and
// adversely affects the operation of the network or violates the rules and
// protocols for communication across the network.
//
// Corresponding Source conveyed, and Installation Information provided,
// in accord with this section must be in a format that is publicly
// documented (and with an implementation available to the public in
// source code form), and must require no special password or key for
// unpacking, reading or copying.
//
// 7. Additional Terms.
//
// "Additional permissions" are terms that supplement the terms of this
// License by making exceptions from one or more of its conditions.
// Additional permissions that are applicable to the entire Program shall
// be treated as though they were included in this License, to the extent
// that they are valid under applicable law. If additional permissions
// apply only to part of the Program, that part may be used separately
// under those permissions, but the entire Program remains governed by
// this License without regard to the additional permissions.
//
// When you convey a copy of a covered work, you may at your option
// remove any additional permissions from that copy, or from any part of
// it. (Additional permissions may be written to require their own
// removal in certain cases when you modify the work.) You may place
// additional permissions on material, added by you to a covered work,
// for which you have or can give appropriate copyright permission.
//
// Notwithstanding any other provision of this License, for material you
// add to a covered work, you may (if authorized by the copyright holders of
// that material) supplement the terms of this License with terms:
//
// a) Disclaiming warranty or limiting liability differently from the
// terms of sections 15 and 16 of this License; or
//
// b) Requiring preservation of specified reasonable legal notices or
// author attributions in that material or in the Appropriate Legal
// Notices displayed by works containing it; or
//
// c) Prohibiting misrepresentation of the origin of that material, or
// requiring that modified versions of such material be marked in
// reasonable ways as different from the original version; or
//
// d) Limiting the use for publicity purposes of names of licensors or
// authors of the material; or
//
// e) Declining to grant rights under trademark law for use of some
// trade names, trademarks, or service marks; or
//
// f) Requiring indemnification of licensors and authors of that
// material by anyone who conveys the material (or modified versions of
// it) with contractual assumptions of liability to the recipient, for
// any liability that these contractual assumptions directly impose on
// those licensors and authors.
//
// All other non-permissive additional terms are considered "further
// restrictions" within the meaning of section 10. If the Program as you
// received it, or any part of it, contains a notice stating that it is
// governed by this License along with a term that is a further
// restriction, you may remove that term. If a license document contains
// a further restriction but permits relicensing or conveying under this
// License, you may add to a covered work material governed by the terms
// of that license document, provided that the further restriction does
// not survive such relicensing or conveying.
//
// If you add terms to a covered work in accord with this section, you
// must place, in the relevant source files, a statement of the
// additional terms that apply to those files, or a notice indicating
// where to find the applicable terms.
//
// Additional terms, permissive or non-permissive, may be stated in the
// form of a separately written license, or stated as exceptions;
// the above requirements apply either way.
//
// 8. Termination.
//
// You may not propagate or modify a covered work except as expressly
// provided under this License. Any attempt otherwise to propagate or
// modify it is void, and will automatically terminate your rights under
// this License (including any patent licenses granted under the third
// paragraph of section 11).
//
// However, if you cease all violation of this License, then your
// license from a particular copyright holder is reinstated (a)
// provisionally, unless and until the copyright holder explicitly and
// finally terminates your license, and (b) permanently, if the copyright
// holder fails to notify you of the violation by some reasonable means
// prior to 60 days after the cessation.
//
// Moreover, your license from a particular copyright holder is
// reinstated permanently if the copyright holder notifies you of the
// violation by some reasonable means, this is the first time you have
// received notice of violation of this License (for any work) from that
// copyright holder, and you cure the violation prior to 30 days after
// your receipt of the notice.
//
// Termination of your rights under this section does not terminate the
// licenses of parties who have received copies or rights from you under
// this License. If your rights have been terminated and not permanently
// reinstated, you do not qualify to receive new licenses for the same
// material under section 10.
//
// 9. Acceptance Not Required for Having Copies.
//
// You are not required to accept this License in order to receive or
// run a copy of the Program. Ancillary propagation of a covered work
// occurring solely as a consequence of using peer-to-peer transmission
// to receive a copy likewise does not require acceptance. However,
// nothing other than this License grants you permission to propagate or
// modify any covered work. These actions infringe copyright if you do
// not accept this License. Therefore, by modifying or propagating a
// covered work, you indicate your acceptance of this License to do so.
//
// 10. Automatic Licensing of Downstream Recipients.
//
// Each time you convey a covered work, the recipient automatically
// receives a license from the original licensors, to run, modify and
// propagate that work, subject to this License. You are not responsible
// for enforcing compliance by third parties with this License.
//
// An "entity transaction" is a transaction transferring control of an
// organization, or substantially all assets of one, or subdividing an
// organization, or merging organizations. If propagation of a covered
// work results from an entity transaction, each party to that
// transaction who receives a copy of the work also receives whatever
// licenses to the work the party's predecessor in interest had or could
// give under the previous paragraph, plus a right to possession of the
// Corresponding Source of the work from the predecessor in interest, if
// the predecessor has it or can get it with reasonable efforts.
//
// You may not impose any further restrictions on the exercise of the
// rights granted or affirmed under this License. For example, you may
// not impose a license fee, royalty, or other charge for exercise of
// rights granted under this License, and you may not initiate litigation
// (including a cross-claim or counterclaim in a lawsuit) alleging that
// any patent claim is infringed by making, using, selling, offering for
// sale, or importing the Program or any portion of it.
//
// 11. Patents.
//
// A "contributor" is a copyright holder who authorizes use under this
// License of the Program or a work on which the Program is based. The
// work thus licensed is called the contributor's "contributor version".
//
// A contributor's "essential patent claims" are all patent claims
// owned or controlled by the contributor, whether already acquired or
// hereafter acquired, that would be infringed by some manner, permitted
// by this License, of making, using, or selling its contributor version,
// but do not include claims that would be infringed only as a
// consequence of further modification of the contributor version. For
// purposes of this definition, "control" includes the right to grant
// patent sublicenses in a manner consistent with the requirements of
// this License.
//
// Each contributor grants you a non-exclusive, worldwide, royalty-free
// patent license under the contributor's essential patent claims, to
// make, use, sell, offer for sale, import and otherwise run, modify and
// propagate the contents of its contributor version.
//
// In the following three paragraphs, a "patent license" is any express
// agreement or commitment, however denominated, not to enforce a patent
// (such as an express permission to practice a patent or covenant not to
// sue for patent infringement). To "grant" such a patent license to a
// party means to make such an agreement or commitment not to enforce a
// patent against the party.
//
// If you convey a covered work, knowingly relying on a patent license,
// and the Corresponding Source of the work is not available for anyone
// to copy, free of charge and under the terms of this License, through a
// publicly available network server or other readily accessible means,
// then you must either (1) cause the Corresponding Source to be so
// available, or (2) arrange to deprive yourself of the benefit of the
// patent license for this particular work, or (3) arrange, in a manner
// consistent with the requirements of this License, to extend the patent
// license to downstream recipients. "Knowingly relying" means you have
// actual knowledge that, but for the patent license, your conveying the
// covered work in a country, or your recipient's use of the covered work
// in a country, would infringe one or more identifiable patents in that
// country that you have reason to believe are valid.
//
// If, pursuant to or in connection with a single transaction or
// arrangement, you convey, or propagate by procuring conveyance of, a
// covered work, and grant a patent license to some of the parties
// receiving the covered work authorizing them to use, propagate, modify
// or convey a specific copy of the covered work, then the patent license
// you grant is automatically extended to all recipients of the covered
// work and works based on it.
//
// A patent license is "discriminatory" if it does not include within
// the scope of its coverage, prohibits the exercise of, or is
// conditioned on the non-exercise of one or more of the rights that are
// specifically granted under this License. You may not convey a covered
// work if you are a party to an arrangement with a third party that is
// in the business of distributing software, under which you make payment
// to the third party based on the extent of your activity of conveying
// the work, and under which the third party grants, to any of the
// parties who would receive the covered work from you, a discriminatory
// patent license (a) in connection with copies of the covered work
// conveyed by you (or copies made from those copies), or (b) primarily
// for and in connection with specific products or compilations that
// contain the covered work, unless you entered into that arrangement,
// or that patent license was granted, prior to 28 March 2007.
//
// Nothing in this License shall be construed as excluding or limiting
// any implied license or other defenses to infringement that may
// otherwise be available to you under applicable patent law.
//
// 12. No Surrender of Others' Freedom.
//
// If conditions are imposed on you (whether by court order, agreement or
// otherwise) that contradict the conditions of this License, they do not
// excuse you from the conditions of this License. If you cannot convey a
// covered work so as to satisfy simultaneously your obligations under this
// License and any other pertinent obligations, then as a consequence you may
// not convey it at all. For example, if you agree to terms that obligate you
// to collect a royalty for further conveying from those to whom you convey
// the Program, the only way you could satisfy both those terms and this
// License would be to refrain entirely from conveying the Program.
//
// 13. Use with the GNU Affero General Public License.
//
// Notwithstanding any other provision of this License, you have
// permission to link or combine any covered work with a work licensed
// under version 3 of the GNU Affero General Public License into a single
// combined work, and to convey the resulting work. The terms of this
// License will continue to apply to the part which is the covered work,
// but the special requirements of the GNU Affero General Public License,
// section 13, concerning interaction through a network will apply to the
// combination as such.
//
// 14. Revised Versions of this License.
//
// The Free Software Foundation may publish revised and/or new versions of
// the GNU General Public License from time to time. Such new versions will
// be similar in spirit to the present version, but may differ in detail to
// address new problems or concerns.
//
// Each version is given a distinguishing version number. If the
// Program specifies that a certain numbered version of the GNU General
// Public License "or any later version" applies to it, you have the
// option of following the terms and conditions either of that numbered
// version or of any later version published by the Free Software
// Foundation. If the Program does not specify a version number of the
// GNU General Public License, you may choose any version ever published
// by the Free Software Foundation.
//
// If the Program specifies that a proxy can decide which future
// versions of the GNU General Public License can be used, that proxy's
// public statement of acceptance of a version permanently authorizes you
// to choose that version for the Program.
//
// Later license versions may give you additional or different
// permissions. However, no additional obligations are imposed on any
// author or copyright holder as a result of your choosing to follow a
// later version.
//
// 15. Disclaimer of Warranty.
//
// THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
// APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
// HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
// IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
// ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
//
// 16. Limitation of Liability.
//
// IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
// WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
// THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
// GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
// USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
// DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
// PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
// EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGES.
//
// 17. Interpretation of Sections 15 and 16.
//
// If the disclaimer of warranty and limitation of liability provided
// above cannot be given local legal effect according to their terms,
// reviewing courts shall apply local law that most closely approximates
// an absolute waiver of all civil liability in connection with the
// Program, unless a warranty or assumption of liability accompanies a
// copy of the Program in return for a fee.
//
// END OF TERMS AND CONDITIONS
//
// How to Apply These Terms to Your New Programs
//
// If you develop a new program, and you want it to be of the greatest
// possible use to the public, the best way to achieve this is to make it
// free software which everyone can redistribute and change under these terms.
//
// To do so, attach the following notices to the program. It is safest
// to attach them to the start of each source file to most effectively
// state the exclusion of warranty; and each file should have at least
// the "copyright" line and a pointer to where the full notice is found.
//
//
// Copyright (C)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
// Also add information on how to contact you by electronic and paper mail.
//
// If the program does terminal interaction, make it output a short
// notice like this when it starts in an interactive mode:
//
// {{ project }} Copyright (C) {{ year }} {{ organization }}
// This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
// This is free software, and you are welcome to redistribute it
// under certain conditions; type `show c' for details.
//
// The hypothetical commands `show w' and `show c' should show the appropriate
// parts of the General Public License. Of course, your program's commands
// might be different; for a GUI interface, you would use an "about box".
//
// You should also get your employer (if you work as a programmer) or school,
// if any, to sign a "copyright disclaimer" for the program, if necessary.
// For more information on this, and how to apply and follow the GNU GPL, see
// .
//
// The GNU General Public License does not permit incorporating your program
// into proprietary programs. If your program is a subroutine library, you
// may consider it more useful to permit linking proprietary applications with
// the library. If this is what you want to do, use the GNU Lesser General
// Public License instead of this License. But first, please read
// .
//
// ## Upstream Bilibili-Gate (magicdawn/Bilibili-Gate)
//
// Source: https://github.com/magicdawn/Bilibili-Gate
//
// The MIT License (MIT)
//
// Copyright (c) 2024 Magicdawn(magicdawn@qq.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Ancestral Bilibili home script (indefined/UserScripts)
//
// Source: https://github.com/indefined/UserScripts
//
// MIT License
//
// Copyright (c) 2018 indefined
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @ant-design/colors@8.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/colors/v/8.0.1
//
// --- LICENSE ---
// MIT LICENSE
//
// Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @ant-design/cssinjs-utils@2.1.2 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/cssinjs-utils/v/2.1.2
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @ant-design/cssinjs@2.1.2 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/cssinjs/v/2.1.2
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @ant-design/fast-color@3.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/fast-color/v/3.0.1
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @ant-design/icons-svg@4.6.0 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/icons-svg/v/4.6.0
//
// --- Source review ---
// https://raw.githubusercontent.com/ant-design/ant-design-icons/7f2516ac91226d2b41f93b35cb5197c8d94f7189/LICENSE
// The npm artifact omits LICENSE; retained from the matching upstream release commit.
// MIT LICENSE
//
// Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @ant-design/icons@6.3.4 (MIT)
//
// Source: https://www.npmjs.com/package/@ant-design/icons/v/6.3.4
//
// --- LICENSE ---
// MIT LICENSE
//
// Copyright (c) 2018-present Ant UED, https://xtech.antfin.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @babel/runtime@7.29.2 (MIT)
//
// Source: https://www.npmjs.com/package/@babel/runtime/v/7.29.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2014-present Sebastian McKenzie and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @dnd-kit/accessibility@3.1.1 (MIT)
//
// Source: https://www.npmjs.com/package/@dnd-kit/accessibility/v/3.1.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021, Claudéric Demers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @dnd-kit/core@6.3.1 (MIT)
//
// Source: https://www.npmjs.com/package/@dnd-kit/core/v/6.3.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021, Claudéric Demers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @dnd-kit/modifiers@9.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@dnd-kit/modifiers/v/9.0.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021, Claudéric Demers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @dnd-kit/sortable@10.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@dnd-kit/sortable/v/10.0.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021, Claudéric Demers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @dnd-kit/utilities@3.2.2 (MIT)
//
// Source: https://www.npmjs.com/package/@dnd-kit/utilities/v/3.2.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021, Claudéric Demers
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/cache@11.14.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/cache/v/11.14.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/css@11.13.5 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/css/v/11.13.5
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/hash@0.8.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/hash/v/0.8.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/hash@0.9.2 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/hash/v/0.9.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/memoize@0.9.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/memoize/v/0.9.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/react@11.14.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/react/v/11.14.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/serialize@1.3.3 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/serialize/v/1.3.3
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/sheet@1.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/sheet/v/1.4.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/unitless@0.10.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/unitless/v/0.10.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/unitless@0.7.5 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/unitless/v/0.7.5
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/use-insertion-effect-with-fallbacks@1.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/use-insertion-effect-with-fallbacks/v/1.2.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @emotion/utils@1.4.2 (MIT)
//
// Source: https://www.npmjs.com/package/@emotion/utils/v/1.4.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Emotion team and other contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @mgdn/bvid@0.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/@mgdn/bvid/v/0.4.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2023 Magicdawn(magicdawn@qq.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @rc-component/async-validator@6.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/async-validator/v/6.0.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/checkbox@2.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/checkbox/v/2.0.0
//
// --- LICENSE.md ---
// The MIT License (MIT) Copyright (c) 2016 React Components
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/collapse@1.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/collapse/v/1.2.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/color-picker@3.1.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/color-picker/v/3.1.1
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present alipay.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/context@2.0.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/context/v/2.0.2
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/dialog@1.10.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/dialog/v/1.10.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/drawer@1.4.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/drawer/v/1.4.2
//
// --- LICENSE ---
// MIT LICENSE
//
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/dropdown@1.0.3 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/dropdown/v/1.0.3
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/form@1.8.6 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/form/v/1.8.6
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @rc-component/input-number@1.6.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/input-number/v/1.6.2
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/input@1.3.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/input/v/1.3.1
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/menu@1.5.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/menu/v/1.5.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/mini-decimal@1.1.4 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/mini-decimal/v/1.1.4
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @rc-component/motion@1.3.3 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/motion/v/1.3.3
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/notification@2.0.8 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/notification/v/2.0.8
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/overflow@1.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/overflow/v/1.0.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/pagination@1.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/pagination/v/1.4.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/picker@1.12.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/picker/v/1.12.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/portal@2.2.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/portal/v/2.2.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @rc-component/qrcode@2.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/qrcode/v/2.0.0
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/resize-observer@1.1.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/resize-observer/v/1.1.2
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/segmented@1.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/segmented/v/1.4.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2019-present afc163
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/select@1.10.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/select/v/1.10.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present alipay.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/slider@1.1.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/slider/v/1.1.1
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/switch@1.0.3 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/switch/v/1.0.3
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/table@1.11.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/table/v/1.11.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// MIT LICENSE
//
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/tabs@1.13.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/tabs/v/1.13.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/tooltip@1.5.2 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/tooltip/v/1.5.2
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/tree@1.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/tree/v/1.4.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019-present react-component
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- LICENSE.md ---
// MIT LICENSE
//
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/trigger@3.10.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/trigger/v/3.10.1
//
// --- LICENSE ---
// The MIT License (MIT)
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/util@1.13.0 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/util/v/1.13.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2014-present yiminghe
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @rc-component/virtual-list@1.5.1 (MIT)
//
// Source: https://www.npmjs.com/package/@rc-component/virtual-list/v/1.5.1
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## @tanstack/hotkeys@0.8.0 (MIT)
//
// Source: https://www.npmjs.com/package/@tanstack/hotkeys/v/0.8.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2026 Tanner Linsley
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @tanstack/react-hotkeys@0.10.0 (MIT)
//
// Source: https://www.npmjs.com/package/@tanstack/react-hotkeys/v/0.10.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2026 Tanner Linsley
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @tanstack/store@0.11.0 (MIT)
//
// Source: https://www.npmjs.com/package/@tanstack/store/v/0.11.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021 Tanner Linsley
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## @trim21/gm-fetch@0.3.0 (MIT)
//
// Source: https://www.npmjs.com/package/@trim21/gm-fetch/v/0.3.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2022 Trim21
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## ahooks@3.10.0 (MIT)
//
// Source: https://www.npmjs.com/package/ahooks/v/3.10.0
//
// --- Source review ---
// https://raw.githubusercontent.com/alibaba/hooks/39a07ee8385cc61d080913bef4655e29a8765f0c/LICENSE
// The npm artifact omits LICENSE; retained from the official v3.10.0 release tag, whose package version was checked. The npm gitHead points to an older version and was not used.
// MIT License
//
// Copyright (c) 2019-present ahooks
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## antd@6.6.4 (MIT)
//
// Source: https://www.npmjs.com/package/antd/v/6.6.4
//
// --- LICENSE ---
// MIT LICENSE
//
// Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## axios@1.20.0 (MIT)
//
// Source: https://www.npmjs.com/package/axios/v/1.20.0
//
// --- LICENSE ---
// # Copyright (c) 2014-present Matt Zabriskie & Collaborators
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## better-result@3.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/better-result/v/3.0.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2026 Dillon Mulroy
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## clsx@2.1.1 (MIT)
//
// Source: https://www.npmjs.com/package/clsx/v/2.1.1
//
// --- license ---
// MIT License
//
// Copyright (c) Luke Edwards (lukeed.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## dayjs@1.11.23 (MIT)
//
// Source: https://www.npmjs.com/package/dayjs/v/1.11.23
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2018-present, iamkun
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## emittery@2.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/emittery/v/2.0.0
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## es-toolkit@1.52.0 (MIT)
//
// Source: https://www.npmjs.com/package/es-toolkit/v/1.52.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2024 Viva Republica, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// --- NOTICE ---
// es-toolkit
//
// Parts of the test suite and compatibility layer in es-toolkit/compat are
// derived from Lodash (https://github.com/lodash/lodash).
//
// Lodash copyright notice and MIT permission notice:
//
// Copyright OpenJS Foundation and other contributors
//
// Based on Underscore.js, copyright Jeremy Ashkenas,
// DocumentCloud and Investigative Reporters & Editors
//
// This software consists of voluntary contributions made by many
// individuals. For exact contribution history, see the revision history
// available at https://github.com/lodash/lodash
//
// The following license applies to all parts of this software except as
// documented below:
//
// ====
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## fast-sort-lens@1.0.2 (MIT)
//
// Source: https://www.npmjs.com/package/fast-sort-lens/v/1.0.2
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2024 Magicdawn(magicdawn@qq.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## fast-sort@3.4.1 (MIT)
//
// Source: https://www.npmjs.com/package/fast-sort/v/3.4.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2017 Stefan Novaković
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## framer-motion@12.43.0 (MIT)
//
// Source: https://www.npmjs.com/package/framer-motion/v/12.43.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2018 Framer B.V.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## hoist-non-react-statics@3.3.2 (BSD-3-Clause)
//
// Source: https://www.npmjs.com/package/hoist-non-react-statics/v/3.3.2
//
// --- LICENSE.md ---
// Software License Agreement (BSD License)
// ========================================
//
// Copyright (c) 2015, Yahoo! Inc. All rights reserved.
// ----------------------------------------------------
//
// Redistribution and use of this software in source and binary forms, with or
// without modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of Yahoo! Inc. nor the names of YUI's contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission of Yahoo! Inc.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// ## is-mobile@5.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/is-mobile/v/5.0.0
//
// --- Source review ---
// https://raw.githubusercontent.com/juliangruber/is-mobile/2708dd8f07d91f354a46b8dff3053abb6b840067/README.md
// The complete licence is supplied in the matching release README, rather than a standalone LICENSE file.
// Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## is-network-error@1.3.2 (MIT)
//
// Source: https://www.npmjs.com/package/is-network-error/v/1.3.2
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## localforage@1.10.0 (Apache-2.0)
//
// Source: https://www.npmjs.com/package/localforage/v/1.10.0
//
// --- LICENSE ---
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "{}"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright 2014 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## lodash@4.18.1 (MIT)
//
// Source: https://www.npmjs.com/package/lodash/v/4.18.1
//
// --- LICENSE ---
// Copyright OpenJS Foundation and other contributors
//
// Based on Underscore.js, copyright Jeremy Ashkenas,
// DocumentCloud and Investigative Reporters & Editors
//
// This software consists of voluntary contributions made by many
// individuals. For exact contribution history, see the revision history
// available at https://github.com/lodash/lodash
//
// The following license applies to all parts of this software except as
// documented below:
//
// ====
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ====
//
// Copyright and related rights for sample code are waived via CC0. Sample
// code is defined as all source code displayed within the prose of the
// documentation.
//
// CC0: http://creativecommons.org/publicdomain/zero/1.0/
//
// ====
//
// Files located in the node_modules and vendor directories are externally
// maintained libraries used by this software which have their own
// licenses; we recommend you read them, as their terms may differ from the
// terms above.
//
// ## mimic-function@5.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/mimic-function/v/5.0.1
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## motion-dom@12.43.0 (MIT)
//
// Source: https://www.npmjs.com/package/motion-dom/v/12.43.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2024 [Motion](https://motion.dev) B.V.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## motion-utils@12.39.0 (MIT)
//
// Source: https://www.npmjs.com/package/motion-utils/v/12.39.0
//
// --- LICENSE.md ---
// The MIT License (MIT)
//
// Copyright (c) 2024 [Motion](https://motion.dev) B.V.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## ms@2.1.3 (MIT)
//
// Source: https://www.npmjs.com/package/ms/v/2.1.3
//
// --- license.md ---
// The MIT License (MIT)
//
// Copyright (c) 2020 Vercel, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## obug@3.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/obug/v/3.0.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright © 2025-PRESENT Kevin Deng (https://github.com/sxzz)
// Copyright (c) 2014-2017 TJ Holowaychuk
// Copyright (c) 2018-2021 Josh Junon
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## p-memoize@8.0.0 (MIT)
//
// Source: https://www.npmjs.com/package/p-memoize/v/8.0.0
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## p-retry@8.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/p-retry/v/8.0.1
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## pinyin-match@1.2.10 (MIT)
//
// Source: https://www.npmjs.com/package/pinyin-match/v/1.2.10
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2020 All contributors to pinyin-match
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## promise.map@1.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/promise.map/v/1.2.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2016 magicdawn
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## proxy-compare@3.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/proxy-compare/v/3.0.1
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2020 Daishi Kato
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ## quick-lru@7.3.0 (MIT)
//
// Source: https://www.npmjs.com/package/quick-lru/v/7.3.0
//
// --- license ---
// MIT License
//
// Copyright (c) Sindre Sorhus (https://sindresorhus.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## react-dom@19.3.0 (MIT)
//
// Source: https://www.npmjs.com/package/react-dom/v/19.3.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## react-intersection-observer@11.0.1 (MIT)
//
// Source: https://www.npmjs.com/package/react-intersection-observer/v/11.0.1
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2025 React Intersection Observer authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## react-is@16.13.1 (MIT)
//
// Source: https://www.npmjs.com/package/react-is/v/16.13.1
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Facebook, Inc. and its affiliates.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## react-is@19.2.8 (MIT)
//
// Source: https://www.npmjs.com/package/react-is/v/19.2.8
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## react-virtuoso@4.18.13 (MIT)
//
// Source: https://www.npmjs.com/package/react-virtuoso/v/4.18.13
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2020 Petyo Ivanov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## react@19.3.0 (MIT)
//
// Source: https://www.npmjs.com/package/react/v/19.3.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## ring-buffer-ts@1.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/ring-buffer-ts/v/1.2.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2019 - 2022 Dominik Geng (domske@github)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ## scheduler@0.28.0 (MIT)
//
// Source: https://www.npmjs.com/package/scheduler/v/0.28.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) Meta Platforms, Inc. and affiliates.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## spark-md5@3.0.2 ((WTFPL OR MIT))
//
// Source: https://www.npmjs.com/package/spark-md5/v/3.0.2
//
// --- LICENSE ---
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// Version 2, December 2004
//
// Copyright (C) 2015 André Cruz
//
// Everyone is permitted to copy and distribute verbatim or modified
// copies of this license document, and changing it is allowed as long
// as the name is changed.
//
// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
//
// 0. You just DO WHAT THE FUCK YOU WANT TO.
//
// ## stylis@4.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/stylis/v/4.2.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2016-present Sultan Tarimo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## stylis@4.4.0 (MIT)
//
// Source: https://www.npmjs.com/package/stylis/v/4.4.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2016-present Sultan Tarimo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## systemjs@6.15.1 (MIT)
//
// Source: https://www.npmjs.com/package/systemjs/v/6.15.1
//
// --- LICENSE ---
// MIT License
// -----------
//
// Copyright (C) 2013-2022 Guy Bedford
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## throttle-debounce@5.0.2 (MIT)
//
// Source: https://www.npmjs.com/package/throttle-debounce/v/5.0.2
//
// --- LICENSE.md ---
// Copyright (c) Ivan Nikolić
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ---
//
// Copyright (c) 2010 "Cowboy" Ben Alman
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// ## ts-pattern@5.9.0 (MIT)
//
// Source: https://www.npmjs.com/package/ts-pattern/v/5.9.0
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2021 Gabriel Vergnaud
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## tslib@2.8.1 (0BSD)
//
// Source: https://www.npmjs.com/package/tslib/v/2.8.1
//
// --- CopyrightNotice.txt ---
// /******************************************************************************
// Copyright (c) Microsoft Corporation.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
// ***************************************************************************** */
//
// --- LICENSE.txt ---
// Copyright (c) Microsoft Corporation.
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
//
// ## ua-parser-js@1.0.41 (MIT)
//
// Source: https://www.npmjs.com/package/ua-parser-js/v/1.0.41
//
// --- license.md ---
// MIT License
//
// Copyright (c) 2012-2025 Faisal Salman <>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## unocss-merge@0.10.0 (MIT)
//
// Source: https://www.npmjs.com/package/unocss-merge/v/0.10.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2025 Magicdawn(magicdawn@qq.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## valtio-reactive@0.2.0 (MIT)
//
// Source: https://www.npmjs.com/package/valtio-reactive/v/0.2.0
//
// --- LICENSE ---
// The MIT License (MIT)
//
// Copyright (c) 2024 Daishi Kato
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ## valtio-select@0.3.0 (MIT)
//
// Source: https://www.npmjs.com/package/valtio-select/v/0.3.0
//
// --- LICENSE ---
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// --- Source review ---
// https://raw.githubusercontent.com/ts-zen/valtio-select/fe7825045b51a8e185c7e69bf70c5b225ce3c5b8/README.md
// The locked release README explicitly states Copyright © 2025 tszen and MIT. Its LICENSE contains only the disclaimer, preserved above. The distributor supplies standard MIT terms below based on that declaration; this is a supplement, not an upstream LICENSE file.
// MIT License
//
// Copyright © 2025 tszen
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## valtio@2.3.2 (MIT)
//
// Source: https://www.npmjs.com/package/valtio/v/2.3.2
//
// --- LICENSE ---
// MIT License
//
// Copyright (c) 2020 Poimandres
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## vite-plugin-monkey@8.1.1 (MIT)
//
// Source: https://www.npmjs.com/package/vite-plugin-monkey/v/8.1.1
//
// --- LICENCE ---
// MIT License
//
// Copyright (c) 2020 二刺螈
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: akar-icons (MIT)
//
// Author: Arturo Wibawa
// Source: https://github.com/artcoholic/akar-icons
// Licence: https://github.com/artcoholic/akar-icons/blob/master/LICENSE
// Used icons: miniplayer
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2020 Arturo Wibawa
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: bi (MIT)
//
// Author: The Bootstrap Authors
// Source: https://github.com/twbs/icons
// Licence: https://github.com/twbs/icons/blob/main/LICENSE
// Used icons: ui-checks-grid
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// The MIT License (MIT)
//
// Copyright (c) 2019-2024 The Bootstrap Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ## Icons: carbon (Apache-2.0)
//
// Author: IBM
// Source: https://github.com/carbon-design-system/carbon/tree/main/packages/icons
// Licence: https://github.com/carbon-design-system/carbon/blob/main/LICENSE
// Used icons: star, star-filled
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "{}"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright 2015 IBM Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: cuida (Apache-2.0)
//
// Author: Sysvale
// Source: https://github.com/Sysvale/cuida-icons
// Licence: https://github.com/Sysvale/cuida-icons/blob/main/LICENSE
// Used icons: shuffle-outline, sort-ascending-duotone, sort-descending-duotone
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: eos-icons (MIT)
//
// Author: SUSE UX/UI team
// Source: https://gitlab.com/SUSE-UIUX/eos-icons
// Licence: https://gitlab.com/SUSE-UIUX/eos-icons/-/blob/master/LICENSE
// Used icons: background-tasks
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2020 SUSE Software Solutions Germany GmbH
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: fluent (MIT)
//
// Author: Microsoft Corporation
// Source: https://github.com/microsoft/fluentui-system-icons
// Licence: https://github.com/microsoft/fluentui-system-icons/blob/main/LICENSE
// Used icons: select-all-on16-regular, text-sort-ascending20-filled, text-sort-descending20-filled
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2020 Microsoft Corporation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: ic (Apache-2.0)
//
// Author: Material Design Authors
// Source: https://github.com/material-icons/material-icons
// Licence: https://github.com/material-icons/material-icons/blob/master/LICENSE
// Used icons: outline-cloud, outline-swap-horiz
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: icon-park-outline (Apache-2.0)
//
// Author: ByteDance
// Source: https://github.com/bytedance/IconPark
// Licence: https://github.com/bytedance/IconPark/blob/master/LICENSE
// Used icons: add-two, arrow-left, arrow-right, clear, close-small, computer, concern, config, copy, distraught-face, down-c, drag, fire, help, info, ipad, iphone, loading, more, people-delete, people-minus, pin, play-two, preview-open, return, right, rotation-horizontal, thumbs-up, time, tips, transfer-data, tv, video-two, write
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright 2019-present Bytedance Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: ion (MIT)
//
// Author: Ben Sperry
// Source: https://github.com/ionic-team/ionicons
// Licence: https://github.com/ionic-team/ionicons/blob/main/LICENSE
// Used icons: exit-outline, layers-outline
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// The MIT License (MIT)
//
// Copyright (c) 2015-present Ionic (http://ionic.io/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// ## Icons: lets-icons (CC-BY-4.0)
//
// Author: Leonid Tsvetkov
// Source: https://www.figma.com/community/file/886554014393250663
// Licence: https://creativecommons.org/licenses/by/4.0/
// Used icons: view-hide
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Lets Icons by Leonid Tsvetkov. Original source: https://www.figma.com/community/file/886554014393250663 . Licensed under CC BY 4.0: https://creativecommons.org/licenses/by/4.0/ . Obtained via Iconify. SVG data is compiled into React components and displayed with interface size/colour settings. The author does not endorse this project.
//
// ## Icons: line-md (MIT)
//
// Author: Vjacheslav Trushkin
// Source: https://github.com/cyberalien/line-md
// Licence: https://github.com/cyberalien/line-md/blob/main/license.txt
// Used icons: download-outline-loop, uploading-loop
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Copyright 2020 Vjacheslav Trushkin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## Icons: lucide (ISC)
//
// Author: Lucide Contributors
// Source: https://github.com/lucide-icons/lucide
// Licence: https://github.com/lucide-icons/lucide/blob/main/LICENSE
// Used icons: circle, circle-check, clock, folder, folder-lock, folder-open, list
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// ISC License
//
// Copyright (c) 2026 Lucide Icons and Contributors
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// ---
//
// The following Lucide icons are derived from the Feather project:
//
// airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
//
// The MIT License (MIT) (for the icons listed above)
//
// Copyright (c) 2013-present Cole Bemis
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: material-symbols (Apache-2.0)
//
// Author: Google
// Source: https://github.com/google/material-design-icons
// Licence: https://github.com/google/material-design-icons/blob/master/LICENSE
// Used icons: align-horizontal-center, align-horizontal-left, align-horizontal-right, bar-chart, close-rounded, delete-outline-rounded
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: material-symbols-light (Apache-2.0)
//
// Author: Google
// Source: https://github.com/google/material-design-icons
// Licence: https://github.com/google/material-design-icons/blob/master/LICENSE
// Used icons: open-in-new, open-in-new-off
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: mdi (Apache-2.0)
//
// Author: Pictogrammers
// Source: https://github.com/Templarian/MaterialDesign
// Licence: https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
// Used icons: pinwheel-outline, play-box-outline, shuffle-disabled, sort-clock-ascending, sort-clock-descending, subtitles-outline
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Pictogrammers Free License
// --------------------------
//
// This icon collection is released as free, open source, and GPL friendly by
// the [Pictogrammers](http://pictogrammers.com/) icon group. You may use it
// for commercial projects, open source projects, or anything really.
//
// # Icons: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
// Some of the icons are redistributed under the Apache 2.0 license. All other
// icons are either redistributed under their respective licenses or are
// distributed under the Apache 2.0 license.
//
// # Fonts: Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0)
// All web and desktop fonts are distributed under the Apache 2.0 license. Web
// and desktop fonts contain some icons that are redistributed under the Apache
// 2.0 license. All other icons are either redistributed under their respective
// licenses or are distributed under the Apache 2.0 license.
//
// # Code: MIT (https://opensource.org/licenses/MIT)
// The MIT license applies to all non-font and non-icon files.
//
// Referenced Apache-2.0 licence:
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "{}"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright 2014 Mozilla
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: mingcute (Apache-2.0)
//
// Author: MingCute Design
// Source: https://github.com/Richard9394/MingCute
// Licence: https://github.com/Richard9394/MingCute/blob/main/LICENSE
// Used icons: key1-line, lightning-fill
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: mynaui (MIT)
//
// Author: Praveen Juge
// Source: https://github.com/praveenjuge/mynaui-icons
// Licence: https://github.com/praveenjuge/mynaui-icons/blob/main/LICENSE
// Used icons: users-group
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) Praveen Juge
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: ph (MIT)
//
// Author: Phosphor Icons
// Source: https://github.com/phosphor-icons/core
// Licence: https://github.com/phosphor-icons/core/blob/main/LICENSE
// Used icons: crown-fill, thumbs-down-duotone, thumbs-up-duotone
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2023 Phosphor Icons
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: radix-icons (MIT)
//
// Author: WorkOS
// Source: https://github.com/radix-ui/icons
// Licence: https://github.com/radix-ui/icons/blob/master/LICENSE
// Used icons: cross2, lock-closed, lock-open1, open-in-new-window, person
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2022 WorkOS
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// ## Icons: ri (Apache-2.0)
//
// Author: Remix Design
// Source: https://github.com/cyberalien/RemixIcon
// Licence: https://github.com/cyberalien/RemixIcon/blob/master/License
// Used icons: fullscreen-fill
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// Apache License
// Version 2.0, January 2004
// http://www.apache.org/licenses/
//
// TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
//
// 1. Definitions.
//
// "License" shall mean the terms and conditions for use, reproduction,
// and distribution as defined by Sections 1 through 9 of this document.
//
// "Licensor" shall mean the copyright owner or entity authorized by
// the copyright owner that is granting the License.
//
// "Legal Entity" shall mean the union of the acting entity and all
// other entities that control, are controlled by, or are under common
// control with that entity. For the purposes of this definition,
// "control" means (i) the power, direct or indirect, to cause the
// direction or management of such entity, whether by contract or
// otherwise, or (ii) ownership of fifty percent (50%) or more of the
// outstanding shares, or (iii) beneficial ownership of such entity.
//
// "You" (or "Your") shall mean an individual or Legal Entity
// exercising permissions granted by this License.
//
// "Source" form shall mean the preferred form for making modifications,
// including but not limited to software source code, documentation
// source, and configuration files.
//
// "Object" form shall mean any form resulting from mechanical
// transformation or translation of a Source form, including but
// not limited to compiled object code, generated documentation,
// and conversions to other media types.
//
// "Work" shall mean the work of authorship, whether in Source or
// Object form, made available under the License, as indicated by a
// copyright notice that is included in or attached to the work
// (an example is provided in the Appendix below).
//
// "Derivative Works" shall mean any work, whether in Source or Object
// form, that is based on (or derived from) the Work and for which the
// editorial revisions, annotations, elaborations, or other modifications
// represent, as a whole, an original work of authorship. For the purposes
// of this License, Derivative Works shall not include works that remain
// separable from, or merely link (or bind by name) to the interfaces of,
// the Work and Derivative Works thereof.
//
// "Contribution" shall mean any work of authorship, including
// the original version of the Work and any modifications or additions
// to that Work or Derivative Works thereof, that is intentionally
// submitted to Licensor for inclusion in the Work by the copyright owner
// or by an individual or Legal Entity authorized to submit on behalf of
// the copyright owner. For the purposes of this definition, "submitted"
// means any form of electronic, verbal, or written communication sent
// to the Licensor or its representatives, including but not limited to
// communication on electronic mailing lists, source code control systems,
// and issue tracking systems that are managed by, or on behalf of, the
// Licensor for the purpose of discussing and improving the Work, but
// excluding communication that is conspicuously marked or otherwise
// designated in writing by the copyright owner as "Not a Contribution."
//
// "Contributor" shall mean Licensor and any individual or Legal Entity
// on behalf of whom a Contribution has been received by Licensor and
// subsequently incorporated within the Work.
//
// 2. Grant of Copyright License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// copyright license to reproduce, prepare Derivative Works of,
// publicly display, publicly perform, sublicense, and distribute the
// Work and such Derivative Works in Source or Object form.
//
// 3. Grant of Patent License. Subject to the terms and conditions of
// this License, each Contributor hereby grants to You a perpetual,
// worldwide, non-exclusive, no-charge, royalty-free, irrevocable
// (except as stated in this section) patent license to make, have made,
// use, offer to sell, sell, import, and otherwise transfer the Work,
// where such license applies only to those patent claims licensable
// by such Contributor that are necessarily infringed by their
// Contribution(s) alone or by combination of their Contribution(s)
// with the Work to which such Contribution(s) was submitted. If You
// institute patent litigation against any entity (including a
// cross-claim or counterclaim in a lawsuit) alleging that the Work
// or a Contribution incorporated within the Work constitutes direct
// or contributory patent infringement, then any patent licenses
// granted to You under this License for that Work shall terminate
// as of the date such litigation is filed.
//
// 4. Redistribution. You may reproduce and distribute copies of the
// Work or Derivative Works thereof in any medium, with or without
// modifications, and in Source or Object form, provided that You
// meet the following conditions:
//
// (a) You must give any other recipients of the Work or
// Derivative Works a copy of this License; and
//
// (b) You must cause any modified files to carry prominent notices
// stating that You changed the files; and
//
// (c) You must retain, in the Source form of any Derivative Works
// that You distribute, all copyright, patent, trademark, and
// attribution notices from the Source form of the Work,
// excluding those notices that do not pertain to any part of
// the Derivative Works; and
//
// (d) If the Work includes a "NOTICE" text file as part of its
// distribution, then any Derivative Works that You distribute must
// include a readable copy of the attribution notices contained
// within such NOTICE file, excluding those notices that do not
// pertain to any part of the Derivative Works, in at least one
// of the following places: within a NOTICE text file distributed
// as part of the Derivative Works; within the Source form or
// documentation, if provided along with the Derivative Works; or,
// within a display generated by the Derivative Works, if and
// wherever such third-party notices normally appear. The contents
// of the NOTICE file are for informational purposes only and
// do not modify the License. You may add Your own attribution
// notices within Derivative Works that You distribute, alongside
// or as an addendum to the NOTICE text from the Work, provided
// that such additional attribution notices cannot be construed
// as modifying the License.
//
// You may add Your own copyright statement to Your modifications and
// may provide additional or different license terms and conditions
// for use, reproduction, or distribution of Your modifications, or
// for any such Derivative Works as a whole, provided Your use,
// reproduction, and distribution of the Work otherwise complies with
// the conditions stated in this License.
//
// 5. Submission of Contributions. Unless You explicitly state otherwise,
// any Contribution intentionally submitted for inclusion in the Work
// by You to the Licensor shall be under the terms and conditions of
// this License, without any additional terms or conditions.
// Notwithstanding the above, nothing herein shall supersede or modify
// the terms of any separate license agreement you may have executed
// with Licensor regarding such Contributions.
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor,
// except as required for reasonable and customary use in describing the
// origin of the Work and reproducing the content of the NOTICE file.
//
// 7. Disclaimer of Warranty. Unless required by applicable law or
// agreed to in writing, Licensor provides the Work (and each
// Contributor provides its Contributions) on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied, including, without limitation, any warranties or conditions
// of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
// PARTICULAR PURPOSE. You are solely responsible for determining the
// appropriateness of using or redistributing the Work and assume any
// risks associated with Your exercise of permissions under this License.
//
// 8. Limitation of Liability. In no event and under no legal theory,
// whether in tort (including negligence), contract, or otherwise,
// unless required by applicable law (such as deliberate and grossly
// negligent acts) or agreed to in writing, shall any Contributor be
// liable to You for damages, including any direct, indirect, special,
// incidental, or consequential damages of any character arising as a
// result of this License or out of the use or inability to use the
// Work (including but not limited to damages for loss of goodwill,
// work stoppage, computer failure or malfunction, or any and all
// other commercial damages or losses), even if such Contributor
// has been advised of the possibility of such damages.
//
// 9. Accepting Warranty or Additional Liability. While redistributing
// the Work or Derivative Works thereof, You may choose to offer,
// and charge a fee for, acceptance of support, warranty, indemnity,
// or other liability obligations and/or rights consistent with this
// License. However, in accepting such obligations, You may act only
// on Your own behalf and on Your sole responsibility, not on behalf
// of any other Contributor, and only if You agree to indemnify,
// defend, and hold each Contributor harmless for any liability
// incurred by, or claims asserted against, such Contributor by reason
// of your accepting any such warranty or additional liability.
//
// END OF TERMS AND CONDITIONS
//
// APPENDIX: How to apply the Apache License to your work.
//
// To apply the Apache License to your work, attach the following
// boilerplate notice, with the fields enclosed by brackets "[]"
// replaced with your own identifying information. (Don't include
// the brackets!) The text should be enclosed in the appropriate
// comment syntax for the file format. We also recommend that a
// file or class name and description of purpose be included on the
// same "printed page" as the copyright notice for easier
// identification within third-party archives.
//
// Copyright [yyyy] [name of copyright owner]
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ## Icons: svg-spinners (MIT)
//
// Author: Utkarsh Verma
// Source: https://github.com/n3r4zzurr0/svg-spinners
// Licence: https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE
// Used icons: bars-rotate-fade, bars-scale-middle
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// The MIT License (MIT)
//
// Copyright (c) Utkarsh Verma
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// ## Icons: tabler (MIT)
//
// Author: Paweł Kuna
// Source: https://github.com/tabler/tabler-icons
// Licence: https://github.com/tabler/tabler-icons/blob/master/LICENSE
// Used icons: arrow-forward-up, arrow-narrow-left, arrow-narrow-right, building-store, clock-play, coin-yen, eye-search, face-id-error, file-export, file-import, heart-filled, layout-grid, list-details, message-circle, plus, reload, restore, sort-ascending, sort-descending
// Changes: SVG data compiled into React components; display size and colour follow the interface.
//
// MIT License
//
// Copyright (c) 2020-2026 Paweł Kuna
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
!function(){function e(e,t){return(t||"")+" (SystemJS Error#"+e+" https://github.com/systemjs/systemjs/blob/main/docs/errors.md#"+e+")"}function t(e,t){if(-1!==e.indexOf("\\")&&(e=e.replace(j,"/")),"/"===e[0]&&"/"===e[1])return t.slice(0,t.indexOf(":")+1)+e;if("."===e[0]&&("/"===e[1]||"."===e[1]&&("/"===e[2]||2===e.length&&(e+="/"))||1===e.length&&(e+="/"))||"/"===e[0]){var n,r=t.slice(0,t.indexOf(":")+1);if(n="/"===t[r.length+1]?"file:"!==r?(n=t.slice(r.length+2)).slice(n.indexOf("/")+1):t.slice(8):t.slice(r.length+("/"===t[r.length])),"/"===e[0])return t.slice(0,t.length-n.length-1)+e;for(var i=n.slice(0,n.lastIndexOf("/")+1)+e,o=[],s=-1,u=0;un.length&&"/"!==r[r.length-1]))return r+e.slice(n.length);u("W2",n,r,"should have a trailing '/'")}}function u(t,n,r,i){console.warn(e(t,"Package target "+i+", resolving target '"+r+"' for "+n))}function c(e,t,n){for(var r=e.scopes,i=n&&o(n,r);i;){var u=s(t,r[i]);if(u)return u;i=o(i.slice(0,i.lastIndexOf("/")),r)}return s(t,e.imports)||-1!==t.indexOf(":")&&t}function a(){this[M]={}}function f(e){return e.id}function l(e,t,n,r){if(e.onload(n,t.id,t.d&&t.d.map(f),!!r),n)throw n}function d(t,n,r,i){var o=t[M][n];if(o)return o;var s=[],u=Object.create(null);P&&Object.defineProperty(u,P,{value:"Module"});var c=Promise.resolve().then((function(){return t.instantiate(n,r,i)})).then((function(r){if(!r)throw Error(e(2,"Module "+n+" did not instantiate"));var i=r[1]((function(e,t){o.h=!0;var n=!1;if("string"==typeof e)e in u&&u[e]===t||(u[e]=t,n=!0);else{for(var r in e)t=e[r],r in u&&u[r]===t||(u[r]=t,n=!0);e&&e.__esModule&&(u.__esModule=e.__esModule)}if(n)for(var i=0;i-1){var n=document.createEvent("Event");n.initEvent("error",!1,!1),t.dispatchEvent(n)}return Promise.reject(e)}))}else if("systemjs-importmap"===t.type){t.sp=!0;var r=t.src?(System.fetch||fetch)(t.src,{integrity:t.integrity,priority:t.fetchPriority,passThrough:!0}).then((function(e){if(!e.ok)throw Error("Invalid status code: "+e.status);return e.text()})).catch((function(n){return n.message=e("W4","Error fetching systemjs-import map "+t.src)+"\n"+n.message,console.warn(n),"function"==typeof t.onerror&&t.onerror(),"{}"})):t.innerHTML;W=W.then((function(){return r})).then((function(n){!function(t,n,r){var o={};try{o=JSON.parse(n)}catch(s){console.warn(Error(e("W5","systemjs-importmap contains invalid JSON")+"\n\n"+n+"\n"))}i(o,r,t)}(N,n,t.src||g)}))}}))}var g,y="undefined"!=typeof Symbol,b="undefined"!=typeof self,S="undefined"!=typeof document,w=b?self:global;if(S){var O=document.querySelector("base[href]");O&&(g=O.href)}if(!g&&"undefined"!=typeof location){var E=(g=location.href.split("#")[0].split("?")[0]).lastIndexOf("/");-1!==E&&(g=g.slice(0,E+1))}var x,j=/\\/g,P=y&&Symbol.toStringTag,M=y?Symbol():"@",I=a.prototype;I.import=function(e,t,n){var r=this;return t&&"object"==typeof t&&(n=t,t=void 0),Promise.resolve(r.prepareImport()).then((function(){return r.resolve(e,t,n)})).then((function(e){var t=d(r,e,void 0,n);return t.C||p(r,t)}))},I.createContext=function(e){var t=this;return{url:e,resolve:function(n,r){return Promise.resolve(t.resolve(n,r||e))}}},I.onload=function(){},I.register=function(e,t,n){x=[e,t,n]},I.getRegister=function(){var e=x;return x=void 0,e};var L=Object.freeze(Object.create(null));w.System=new a;var C,R,W=Promise.resolve(),N={imports:{},scopes:{},depcache:{},integrity:{}},T=S;if(I.prepareImport=function(e){return(T||e)&&(m(),T=!1),W},I.getImportMap=function(){return JSON.parse(JSON.stringify(N))},S&&(m(),window.addEventListener("DOMContentLoaded",m)),I.addImportMap=function(e,t){i(e,t||g,N)},S){window.addEventListener("error",(function(e){J=e.filename,_=e.error}));var A=location.origin}I.createScript=function(e){var t=document.createElement("script");t.async=!0,e.indexOf(A+"/")&&(t.crossOrigin="anonymous");var n=N.integrity[e];return n&&(t.integrity=n),t.src=e,t};var J,_,k={},U=I.register;I.register=function(e,t){if(S&&"loading"===document.readyState&&"string"!=typeof e){var n=document.querySelectorAll("script[src]"),r=n[n.length-1];if(r){C=e;var i=this;R=setTimeout((function(){k[r.src]=[e,t],i.import(r.src)}))}}else C=void 0;return U.call(this,e,t)},I.instantiate=function(t,n){var r=k[t];if(r)return delete k[t],r;var i=this;return Promise.resolve(I.createScript(t)).then((function(r){return new Promise((function(o,s){r.addEventListener("error",(function(){s(Error(e(3,"Error loading "+t+(n?" from "+n:""))))})),r.addEventListener("load",(function(){if(document.head.removeChild(r),J===t)s(_);else{var e=i.getRegister(t);e&&e[0]===C&&clearTimeout(R),o(e)}})),document.head.appendChild(r)}))}))},I.shouldFetch=function(){return!1},"undefined"!=typeof fetch&&(I.fetch=fetch);var $=I.instantiate,B=/^(text|application)\/(x-)?javascript(;|$)/;I.instantiate=function(t,n,r){var i=this;return this.shouldFetch(t,n,r)?this.fetch(t,{credentials:"same-origin",integrity:N.integrity[t],meta:r}).then((function(r){if(!r.ok)throw Error(e(7,r.status+" "+r.statusText+", loading "+t+(n?" from "+n:"")));var o=r.headers.get("content-type");if(!o||!B.test(o))throw Error(e(4,'Unknown Content-Type "'+o+'", loading '+t+(n?" from "+n:"")));return r.text().then((function(e){return e.indexOf("//# sourceURL=")<0&&(e+="\n//# sourceURL="+t),(0,eval)(e),i.getRegister(t)}))})):$.apply(this,arguments)},I.resolve=function(n,r){return c(N,t(n,r=r||g)||n,r)||function(t,n){throw Error(e(8,"Unable to resolve bare specifier '"+t+(n?"' from "+n:"'")))}(n,r)};var F=I.instantiate;I.instantiate=function(e,t,n){var r=N.depcache[e];if(r)for(var i=0;i{const _=UAParser;('default' in _)||(_.default=_);return _})());
System.set("user:axios", (()=>{const _=axios;('default' in _)||(_.default=_);return _})());
System.set("user:spark-md5", (()=>{const _=SparkMD5;('default' in _)||(_.default=_);return _})());
System.set("user:pinyin-match", (()=>{const _=PinyinMatch;('default' in _)||(_.default=_);return _})());
System.set("user:localforage", (()=>{const _=localforage;('default' in _)||(_.default=_);return _})());
System.register("./___monkey.entry.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports,module){'use strict';var __toESM,require_react,tagRecBackoff,appWarn,baseDebug,IN_BILIBILI_HOMEPAGE,appError,__commonJSMin,APP_CLS_CARD,APP_CLS_CARD_ACTIVE,__exportAll,require_react_dom,TVKeyInfo,APP_CLS_ROOT,createReactRoot,BiliDomain,baseVerboseDebug,APP_CLS_CARD_COVER,APP_CLS_GRID,APP_NAMESPACE,APP_KEY_PREFIX,APP_CLS_CARD_RECOMMEND_REASON,HOST_APP,APP_SHORT_PREFIX,IN_BILIBILI_SPACE_PAGE,IN_BILIBILI_SEARCH_PAGE,APP_CLS_TAB_BAR,appLog,APP_NAME,genStyleHooks,operationUnit,isPlainObject,omit,clsx,textEllipsis,gold,unit$1,useLocale,useControlledState,useLayoutEffect$2,useDelayState,RefResizeObserver,composeRef,_toConsumableArray,useComponentConfig,useSemanticRootStyle,useMergeSemantic,useEvent$1,isFunction,TextArea,cloneElement,Tooltip,toArray$2,isNonNullable$1,canUseDom$1,KeyCode,isReactRenderable,Icon,RefIcon$12,RefIcon$1$2,wrapperRaf,isWindow$1,isDocument$1,isHTMLElement$1,isNumber$1,proxy,dedupeTagIdEntries,proxyWithGmStorageLazy,subscribe,proxyMapWithGmStorageLazy,proxySetWithGmStorageLazy,perfMark,perfMarkBytes,newTagRecCombination,validateTagRecCombination,cloneDeep,makeTagL1Group,toast,makeTagTheme,normalizeTagName,setGroupNegativeTagIds,groupNegativeTagIds,tagRecConditionKey,ETab,hasTagRecCombination,findGroupByTagId,canSplitTagId,removeTagIdFromGroup,tagRecConditionAtoms,removeTagRecCondition,tagRecCombinationTagIds,seedTagRecCombination,findGroupsByNegativeTagId,excludeTagRecCondition,tagRecConditionAtom,computeNegativeAdd,addTagRecCondition,summarizeGroupNames,computeMergeGroups,clampWeight,findGroupsByTagIds,makeTagGroup,intersectCheck,settings,merge,slideDownOut,slideUpOut,slideDownIn,slideUpIn,resetComponent,genFocusOutline,genFocusStyle,initSlideMotion,ConfigContext,RefIcon$7,RefIcon$4,useSize,RefIcon$8,useCSSVarCls,getTransitionName,getIsMobile,es_default$5,useComposeRef,useLayoutUpdateEffect,ExportMenu,MenuItem_default,es_default$4,jsxs,jsx,useId_default,useOrientation,cloneTagRecFilterCore,resolveTagRecEffective,isEqual$1,TAG_REC_ALL_POOL_KEYS,getNewestValueOfSettingsInnerArray,updateSettingsInnerArray,updateSettings,setNicknameCache,require_ms,proxyWithGmStorage,watch,initLoginStorePromise,whenIdle,ae,fetchFullBlacklist,blacklistMidSetReplaceAllWith,fetchFullFollowings,followedMidSetReplaceAllWith,followedMidSet,blacklistMidSet,useHotkey,get$2,set$2,antMessage,pick,WebApiError,difference,getOnlyTab,getEntryTab,r,useSettingsSnapshot,useSnapshot,useLoginStatus,shouldShowTagRecCustomDot,useMemoizedFn,getLoginStatus,toastNeedLogin,Radio,Fragment,HelpInfo,IconForHistory,IconForLike,advancedFilterHelpInfo,IconForSpaceUpload,IconForLive,isHotTabUsingShuffle,IconForHot,favStore,IconForFav,IconForWatchlater,IconForDynamicFeed,IconForFollowedOnly,IconForPc,IconForPhone,css,getSettingsSnapshot,getCachedTagRecVideoEvidence,SessionFetchCache,fetchVideoDetail,rememberTagRecVideoEvidence,button_default,Modal,Select,ref,prepareTagRecWorkingGroup,tagRecCombinationOverflows,nameTagRecCombination,tagRecWorkingGroupSignature,findTagRecCondition,fetchTagInfoById,fetchTagInfoByName,theme,VideoCardActionButton,stripRelatedSuffix,findGroupsByTagId,Checkbox,collapse_default,Popover,usePanelRef,useZIndex,useMergedMask,useFocusable$1,ContextIsolator,ZIndexContext,getPersistedVideoTags,tagRecCombinationOf,poolOfVia,tagRecConditionLabel,mergeTagRecCombinationFilters,graftTagRecConditionTree,es_default$4$1,useClosable,pickClosable,skeleton_default,Dropdown,Tag,IconForFaved,TAG_REC_CONDITION_FIELDS,Input,estimateTagRecConditionImpact,pickAttrs,useLockFocus,warningOnce$1,useRequest_default,IconForAccessKey,IconForReload,Popconfirm,IconForDelete,Space,useRecSelfContext,useMount,useUnmount,useEmitterOn,uniqBy,EGridDisplayMode,IconForHelp,getServiceFromRegistry,assertNever,invariant$1,isRecTab,checkTagRecContextDrift,takePrewarmedTabService,restorePrewarmedTabService,filterRecItems,loadTagRecServiceClasses,createServiceMap,useDefaultHotkeysOptions,getHotkeyManager,detectPlatform,normalizeRegisterableHotkey,isRef,getSpaceUploadServiceConfig,hotStore,EHotSubTab,RankRecService,rankStore,getFavServiceConfig,getDynamicFeedServiceConfig,EApiType,PcRecService,AppRecService,_defineProperty,Emittery,FastColor,normalizeCardData,checkIsDynamicFeed,DynamicFeedItemHelper,delay$1,request,isWebApiSuccess,appSign,once,AppRoot,BaseModalClassNames,ModalClose,BaseModal,useToken,Button,_extends$24,Spin,useLockFn,useUpdateEffect_default,openNewTab,getRecItemDimension,HotkeyDisplay,ForwardRef$31,ForwardRef$32,IconForLoading,useEventListener,pMemoize,QuickLRU,useDelegatedRef,orderBy,throttle,motion$1,clsZVideoCardLargePreview,useLatest,useEffectWithTarget,reusePendingPromise,getCsrfToken,getVideoPageList,antNotification,classListToSelector,appPrimaryColorValue,getTargetElement,fastOrderBy,require_interopRequireDefault,preloadImg,useSidebarVisible,ConfigProvider,useCreation,multiSelectStore,useSetupGridState,isSafari,querySupportsLoadToEnd,$headerHeight,useOnRefresh,WatchlaterItemsOrder,IconForRoll,clsAntdButton,setCurrentGridItems,useLinkTarget,AntdTooltip,IconForOpenExternalLink,NeedValidAccessKeyError,useIsDisplayAsList,useInBlacklist,useWatchlaterState,useMultiSelectState,displayAsListCss,useConstant,MotionConfigContext,n$2,x,appBgLv1Value,videoCardBorderRadiusValue,appBorderColorValue,checkIsTagRec,checkIsAppRecommend,checkIsPcRecommend,getSnapshotAuthorFilter,snapshot,appBgValue,appBgLv2Value,isDisplayAsList,defaultVideoCardEmitter,normalizeDislikeReason,UserBlacklistService,parseUpRepresent,IconForBlacklist,KNOWN_GOTO,store$2,checkIsWatchlater,buildWatchlaterVideoCardUrl,checkIsSpaceUpload,buildSpaceUploadVideoCardUrl,SpaceUploadItemHelper,useLessFrequentFn,useOpenRelated,isClickInAntdOverlay,checkIsCardBadgeValid,checkIsRank,checkIsLive,checkIsHistory,ELiveStatus,PcRecGoto,checkIsFav,RankNumBadge,LiveBadge,ApiTypeBadge,VolBadge,GeneralCardBadge,C$1,isFirefoxLike,Picture,StatItemDisplay,defaultRecSharedEmitter,Divider,proxyMap,IconForReset,handleRequestError,watchlaterState,IconAnimatedChecked,pickDislikeReason,UserFavApi,isNotNil,getFollowedStatus,useTrackedSnapshot,queryFollowedStatus,handleMultiSelectWithShiftKey,usePopoverBorderColor,tagRecPartitionParent,PARTITION_NAME_TO_PARENT,copyContent,isApiRecLike,antModal,UserFollowService,dfStore,QUERY_DYNAMIC_UP_MID,DynamicFeedQueryKey,getCurrentGridItems,IconForDesc,IconForAsc,SHOW_SPACE_UPLOAD_ONLY,SpaceUploadQueryKey,defineAntMenus,copyVideoLinks,IconForCopy,copyBvidsSingleLine,copyBvidInfos,getBvidInfo,IconForDislike,removeSingleHistoryItem,checkIsNormalVideo,startModifyFavItemToTargetFolders,startPickFavFolders,mapModalFavManagerResult,clsZVideoCardContextMenu,hasReducedMotionListener,initPrefersReducedMotion,prefersReducedMotion,removeItem,fetchAppRecommendFollowedPubDate,getAvatarSrc,Avatar,minmax,PcDislikeReasons,fetchVideoTags,invalidateVideoTagsCache,getLinkTarget,defaultFavFolderTitle,formatFavFolderUrl,FavQueryKey,clearFavFolderAllItemsCache,formatFavCollectionSelfSpaceUrl,motionValue,animateSingleValue,visualElementStore,animateTarget,require_dayjs_min,formatSpaceUrl,formatAccurateTimestamp,pickTagGroupsByIds,mapFavFolderIds,joinFavFolderTitles,handleModifyFavItemToFolders,getMultiSelectedItems,isMotionValue,resolveElements,gmrequest,appClsDarkSelector,isRecentTimestamp,defaultOffset,createGeneratorEasing,fillOffset,reverseEasing,isSVGElement$1,isSVGSVGElement,SVGVisualElement,HTMLVisualElement,formatRecentTimestamp,isGenerator,isEasingArray,mixNumber$1,VisualElement,createBox,valtioFactory,secondsToMilliseconds,progress,spring,getGenericCardDatas,useHover_default,initFadeMotion,proxyWithLocalStorage,IN_BILIBILI_VIDEO_PLAY_PAGE,genCssVar,Badge,tryToRemove,tryAction,registerSettingsGmCommand,inGateEntry,DisabledContext,poll$1,SetupForPage,prewarmTabService,useGridDisplayModeChecker,usePlainShortcutEnabled,useInitRecSelf,ESidebarAlign,clsForTwoColumnModeAlign,RecSelfContext,toggleModalSettings,toggleMultiSelecting,getElementOffset,$usingEvolevdHeader,useIsDarkMode,showModalSettings,IconForConfig,MultiSelectButton,getGateEntryHref,clsZGateFloatEntry,showModalFeed,isMac,isPresetSize;return{setters:[function(module){__toESM=module._;require_react=module.r;tagRecBackoff=module.t;appWarn=module.a;baseDebug=module.n;IN_BILIBILI_HOMEPAGE=module.I;appError=module.E;__commonJSMin=module.y;APP_CLS_CARD=module.W;APP_CLS_CARD_ACTIVE=module.X;__exportAll=module.U;require_react_dom=module.b;TVKeyInfo=module.T;APP_CLS_ROOT=module.D;createReactRoot=module.m;BiliDomain=module.o;baseVerboseDebug=module.Y;APP_CLS_CARD_COVER=module.Z;APP_CLS_GRID=module.$;APP_NAMESPACE=module.q;APP_KEY_PREFIX=module.a0;APP_CLS_CARD_RECOMMEND_REASON=module.a1;HOST_APP=module.z;APP_SHORT_PREFIX=module.O;IN_BILIBILI_SPACE_PAGE=module.a2;IN_BILIBILI_SEARCH_PAGE=module.a3;APP_CLS_TAB_BAR=module.V;appLog=module.a4;APP_NAME=module.A;},function(module){genStyleHooks=module.b3;operationUnit=module.eW;isPlainObject=module.el;omit=module.eI;clsx=module.m;textEllipsis=module.eX;gold=module.fF;unit$1=module.bv;useLocale=module.b0;useControlledState=module.av;useLayoutEffect$2=module.by;useDelayState=module.fG;RefResizeObserver=module.fp;composeRef=module.ep;_toConsumableArray=module.bU;useComponentConfig=module.ao;useSemanticRootStyle=module.at;useMergeSemantic=module.au;useEvent$1=module.b1;isFunction=module.b6;TextArea=module.fH;cloneElement=module.fo;Tooltip=module.d7;toArray$2=module.eR;isNonNullable$1=module.em;canUseDom$1=module.ff;KeyCode=module.f2;isReactRenderable=module.en;Icon=module.f7;RefIcon$12=module.fI;RefIcon$1$2=module.fn;wrapperRaf=module.fh;isWindow$1=module.fJ;isDocument$1=module.fK;isHTMLElement$1=module.fL;isNumber$1=module.eM;proxy=module.aP;dedupeTagIdEntries=module.cR;proxyWithGmStorageLazy=module.fM;subscribe=module.c_;proxyMapWithGmStorageLazy=module.fN;proxySetWithGmStorageLazy=module.fO;perfMark=module.cQ;perfMarkBytes=module.fP;newTagRecCombination=module.fE;validateTagRecCombination=module.cO;cloneDeep=module.aQ;makeTagL1Group=module.fQ;toast=module.ak;makeTagTheme=module.fR;normalizeTagName=module.bB;setGroupNegativeTagIds=module.fS;groupNegativeTagIds=module.bg;tagRecConditionKey=module.cN;ETab=module.E;hasTagRecCombination=module.aE;findGroupByTagId=module.fT;canSplitTagId=module.fU;removeTagIdFromGroup=module.fV;tagRecConditionAtoms=module.b_;removeTagRecCondition=module.bL;tagRecCombinationTagIds=module.d3;seedTagRecCombination=module.fW;findGroupsByNegativeTagId=module.eG;excludeTagRecCondition=module.c0;tagRecConditionAtom=module.bZ;computeNegativeAdd=module.fX;addTagRecCondition=module.c1;summarizeGroupNames=module.cS;computeMergeGroups=module.fY;clampWeight=module.fZ;findGroupsByTagIds=module.f_;makeTagGroup=module.f$;intersectCheck=module.g0;settings=module.t;merge=module.b4;slideDownOut=module.g1;slideUpOut=module.g2;slideDownIn=module.g3;slideUpIn=module.g4;resetComponent=module.ex;genFocusOutline=module.fs;genFocusStyle=module.ey;initSlideMotion=module.g5;ConfigContext=module.eJ;RefIcon$7=module.g6;RefIcon$4=module.eD;useSize=module.as;RefIcon$8=module.g7;useCSSVarCls=module.b5;getTransitionName=module.g8;getIsMobile=module.g9;es_default$5=module.eo;useComposeRef=module.ga;useLayoutUpdateEffect=module.gb;ExportMenu=module.gc;MenuItem_default=module.gd;es_default$4=module.ge;jsxs=module.j;jsx=module.a;useId_default=module.fm;useOrientation=module.gf;cloneTagRecFilterCore=module.bI;resolveTagRecEffective=module.bq;isEqual$1=module.al;TAG_REC_ALL_POOL_KEYS=module.cq;getNewestValueOfSettingsInnerArray=module.a$;updateSettingsInnerArray=module.aB;updateSettings=module.q;setNicknameCache=module.gg;require_ms=module.bQ;proxyWithGmStorage=module.gh;watch=module.gi;initLoginStorePromise=module.gj;whenIdle=module.gk;ae=module.bJ;fetchFullBlacklist=module.gl;blacklistMidSetReplaceAllWith=module.gm;fetchFullFollowings=module.gn;followedMidSetReplaceAllWith=module.go;followedMidSet=module.gp;blacklistMidSet=module.gq;useHotkey=module.gr;get$2=module.ai;set$2=module.gs;antMessage=module.ad;pick=module.gt;WebApiError=module.gu;difference=module.am;getOnlyTab=module.gv;getEntryTab=module.gw;r=module.a8;useSettingsSnapshot=module.d;useSnapshot=module.u;useLoginStatus=module.gx;shouldShowTagRecCustomDot=module.gy;useMemoizedFn=module.f;getLoginStatus=module.gz;toastNeedLogin=module.gA;Radio=module.a9;Fragment=module.F;HelpInfo=module.H;IconForHistory=module.gB;IconForLike=module.gC;advancedFilterHelpInfo=module.gD;IconForSpaceUpload=module.dB;IconForLive=module.gE;isHotTabUsingShuffle=module.gF;IconForHot=module.gG;favStore=module.dI;IconForFav=module.gH;IconForWatchlater=module.gI;IconForDynamicFeed=module.dC;IconForFollowedOnly=module.gJ;IconForPc=module.gK;IconForPhone=module.gL;css=module.c;getSettingsSnapshot=module.gM;getCachedTagRecVideoEvidence=module.cG;SessionFetchCache=module.ea;fetchVideoDetail=module.gN;rememberTagRecVideoEvidence=module.gO;button_default=module.n;Modal=module.$;Select=module.S;ref=module.fB;prepareTagRecWorkingGroup=module.gP;tagRecCombinationOverflows=module.gQ;nameTagRecCombination=module.bi;tagRecWorkingGroupSignature=module.gR;findTagRecCondition=module.bK;fetchTagInfoById=module.bF;fetchTagInfoByName=module.bG;theme=module.bo;VideoCardActionButton=module.gS;stripRelatedSuffix=module.dN;findGroupsByTagId=module.eF;Checkbox=module._;collapse_default=module.L;Popover=module.az;usePanelRef=module.gT;useZIndex=module.gU;useMergedMask=module.gV;useFocusable$1=module.gW;ContextIsolator=module.aA;ZIndexContext=module.gX;getPersistedVideoTags=module.dU;tagRecCombinationOf=module.d2;poolOfVia=module.cI;tagRecConditionLabel=module.d4;mergeTagRecCombinationFilters=module.gY;graftTagRecConditionTree=module.gZ;es_default$4$1=module.g_;useClosable=module.g$;pickClosable=module.h0;skeleton_default=module.h1;Dropdown=module.bh;Tag=module.T;IconForFaved=module.h2;TAG_REC_CONDITION_FIELDS=module.bY;Input=module.Y;estimateTagRecConditionImpact=module.h3;pickAttrs=module.ba;useLockFocus=module.h4;warningOnce$1=module.eZ;useRequest_default=module.a1;IconForAccessKey=module.h5;IconForReload=module.a5;Popconfirm=module.a2;IconForDelete=module.ag;Space=module.w;useRecSelfContext=module.R;useMount=module.e;useUnmount=module.h6;useEmitterOn=module.h7;uniqBy=module.h8;EGridDisplayMode=module.cd;IconForHelp=module.h;getServiceFromRegistry=module.h9;assertNever=module.ha;invariant$1=module.hb;isRecTab=module.hc;checkTagRecContextDrift=module.hd;takePrewarmedTabService=module.he;restorePrewarmedTabService=module.hf;filterRecItems=module.hg;loadTagRecServiceClasses=module.hh;createServiceMap=module.hi;useDefaultHotkeysOptions=module.hj;getHotkeyManager=module.hk;detectPlatform=module.hl;normalizeRegisterableHotkey=module.hm;isRef=module.hn;getSpaceUploadServiceConfig=module.ho;hotStore=module.hp;EHotSubTab=module.hq;RankRecService=module.hr;rankStore=module.hs;getFavServiceConfig=module.ht;getDynamicFeedServiceConfig=module.hu;EApiType=module.ck;PcRecService=module.hv;AppRecService=module.hw;_defineProperty=module.hx;Emittery=module.dE;FastColor=module.eY;normalizeCardData=module.hy;checkIsDynamicFeed=module.hz;DynamicFeedItemHelper=module.hA;delay$1=module.c5;request=module.d9;isWebApiSuccess=module.d8;appSign=module.hB;once=module.dF;AppRoot=module.dp;BaseModalClassNames=module.B;ModalClose=module.M;BaseModal=module.b;useToken=module.eL;Button=module.f3;_extends$24=module.hC;Spin=module.dP;useLockFn=module.hD;useUpdateEffect_default=module.hE;openNewTab=module.hF;getRecItemDimension=module.hG;HotkeyDisplay=module.s;ForwardRef$31=module.hH;ForwardRef$32=module.hI;IconForLoading=module.hJ;useEventListener=module.hK;pMemoize=module.hL;QuickLRU=module.hM;useDelegatedRef=module.hN;orderBy=module.hO;throttle=module.du;motion$1=module.hP;clsZVideoCardLargePreview=module.hQ;useLatest=module.hR;useEffectWithTarget=module.hS;reusePendingPromise=module.dx;getCsrfToken=module.hT;getVideoPageList=module.hU;antNotification=module.hV;classListToSelector=module.hW;appPrimaryColorValue=module.hX;getTargetElement=module.hY;fastOrderBy=module.hZ;require_interopRequireDefault=module.h_;preloadImg=module.h$;useSidebarVisible=module.i0;ConfigProvider=module.eP;useCreation=module.i1;multiSelectStore=module.i2;useSetupGridState=module.i3;isSafari=module.i4;querySupportsLoadToEnd=module.i5;$headerHeight=module.i6;useOnRefresh=module.cB;WatchlaterItemsOrder=module.i7;IconForRoll=module.i8;clsAntdButton=module.i9;setCurrentGridItems=module.ia;useLinkTarget=module.ib;AntdTooltip=module.A;IconForOpenExternalLink=module.a3;NeedValidAccessKeyError=module.ic;useIsDisplayAsList=module.id;useInBlacklist=module.ie;useWatchlaterState=module.ig;useMultiSelectState=module.ih;displayAsListCss=module.ii;useConstant=module.ij;MotionConfigContext=module.ik;n$2=module.il;x=module.im;appBgLv1Value=module.io;videoCardBorderRadiusValue=module.ip;appBorderColorValue=module.iq;checkIsTagRec=module.ir;checkIsAppRecommend=module.is;checkIsPcRecommend=module.it;getSnapshotAuthorFilter=module.iu;snapshot=module.dv;appBgValue=module.iv;appBgLv2Value=module.iw;isDisplayAsList=module.ix;defaultVideoCardEmitter=module.iy;normalizeDislikeReason=module.iz;UserBlacklistService=module.iA;parseUpRepresent=module.aY;IconForBlacklist=module.iB;KNOWN_GOTO=module.iC;store$2=module.iD;checkIsWatchlater=module.iE;buildWatchlaterVideoCardUrl=module.iF;checkIsSpaceUpload=module.iG;buildSpaceUploadVideoCardUrl=module.iH;SpaceUploadItemHelper=module.iI;useLessFrequentFn=module.iJ;useOpenRelated=module.iK;isClickInAntdOverlay=module.iL;checkIsCardBadgeValid=module.iM;checkIsRank=module.iN;checkIsLive=module.iO;checkIsHistory=module.iP;ELiveStatus=module.iQ;PcRecGoto=module.iR;checkIsFav=module.iS;RankNumBadge=module.iT;LiveBadge=module.iU;ApiTypeBadge=module.iV;VolBadge=module.iW;GeneralCardBadge=module.iX;C$1=module.iY;isFirefoxLike=module.iZ;Picture=module.i_;StatItemDisplay=module.i$;defaultRecSharedEmitter=module.j0;Divider=module.D;proxyMap=module.j1;IconForReset=module.j2;handleRequestError=module.j3;watchlaterState=module.j4;IconAnimatedChecked=module.z;pickDislikeReason=module.j5;UserFavApi=module.df;isNotNil=module.j6;getFollowedStatus=module.j7;useTrackedSnapshot=module.j8;queryFollowedStatus=module.j9;handleMultiSelectWithShiftKey=module.ja;usePopoverBorderColor=module.ed;tagRecPartitionParent=module.jb;PARTITION_NAME_TO_PARENT=module.b$;copyContent=module.jc;isApiRecLike=module.jd;antModal=module.aH;UserFollowService=module.je;dfStore=module.aC;QUERY_DYNAMIC_UP_MID=module.jf;DynamicFeedQueryKey=module.dA;getCurrentGridItems=module.jg;IconForDesc=module.jh;IconForAsc=module.ji;SHOW_SPACE_UPLOAD_ONLY=module.jj;SpaceUploadQueryKey=module.dz;defineAntMenus=module.jk;copyVideoLinks=module.jl;IconForCopy=module.o;copyBvidsSingleLine=module.jm;copyBvidInfos=module.jn;getBvidInfo=module.jo;IconForDislike=module.jp;removeSingleHistoryItem=module.jq;checkIsNormalVideo=module.jr;startModifyFavItemToTargetFolders=module.dg;startPickFavFolders=module.js;mapModalFavManagerResult=module.jt;clsZVideoCardContextMenu=module.ju;hasReducedMotionListener=module.jv;initPrefersReducedMotion=module.jw;prefersReducedMotion=module.jx;removeItem=module.jy;fetchAppRecommendFollowedPubDate=module.jz;getAvatarSrc=module.jA;Avatar=module.jB;minmax=module.jC;PcDislikeReasons=module.jD;fetchVideoTags=module.dW;invalidateVideoTagsCache=module.jE;getLinkTarget=module.jF;defaultFavFolderTitle=module.jG;formatFavFolderUrl=module.jH;FavQueryKey=module.dt;clearFavFolderAllItemsCache=module.jI;formatFavCollectionSelfSpaceUrl=module.jJ;motionValue=module.jK;animateSingleValue=module.jL;visualElementStore=module.jM;animateTarget=module.jN;require_dayjs_min=module.bt;formatSpaceUrl=module.be;formatAccurateTimestamp=module.jO;pickTagGroupsByIds=module.cL;mapFavFolderIds=module.jP;joinFavFolderTitles=module.jQ;handleModifyFavItemToFolders=module.dh;getMultiSelectedItems=module.jR;isMotionValue=module.jS;resolveElements=module.jT;gmrequest=module.jU;appClsDarkSelector=module.jV;isRecentTimestamp=module.jW;defaultOffset=module.jX;createGeneratorEasing=module.jY;fillOffset=module.jZ;reverseEasing=module.j_;isSVGElement$1=module.j$;isSVGSVGElement=module.k0;SVGVisualElement=module.k1;HTMLVisualElement=module.k2;formatRecentTimestamp=module.k3;isGenerator=module.k4;isEasingArray=module.k5;mixNumber$1=module.k6;VisualElement=module.k7;createBox=module.k8;valtioFactory=module.k9;secondsToMilliseconds=module.ka;progress=module.kb;spring=module.kc;getGenericCardDatas=module.kd;useHover_default=module.g;initFadeMotion=module.ke;proxyWithLocalStorage=module.kf;IN_BILIBILI_VIDEO_PLAY_PAGE=module.kg;genCssVar=module.eV;Badge=module.bk;tryToRemove=module.kh;tryAction=module.dw;registerSettingsGmCommand=module.dD;inGateEntry=module.ki;DisabledContext=module.ap;poll$1=module.dd;SetupForPage=module.dG;prewarmTabService=module.kj;useGridDisplayModeChecker=module.kk;usePlainShortcutEnabled=module.kl;useInitRecSelf=module.ca;ESidebarAlign=module.aa;clsForTwoColumnModeAlign=module.km;RecSelfContext=module.cb;toggleModalSettings=module.kn;toggleMultiSelecting=module.ko;getElementOffset=module.kp;$usingEvolevdHeader=module.kq;useIsDarkMode=module.kr;showModalSettings=module.cE;IconForConfig=module.I;MultiSelectButton=module.ks;getGateEntryHref=module.kt;clsZGateFloatEntry=module.ku;showModalFeed=module.kv;isMac=module.dj;isPresetSize=module.kw;},null,null,null,null,null],execute:(async function(){exports({$:materializeTagRecSessionFilter,A:AccessKeyManage,G:useSortable,J:buildTagRecFavDeepLink,K:buildTagRecCustomDeepLink,R:handleManualRefreshRelationsCache,U:useDeferredTab,V:RecSidebar,W:TagRecDiscoveryLayout,Y:VideoSourceTab,Z:RefreshButton,a:TabIcon,a$:createTagRecSessionFilterBase,a0:resetTagRecFavFilter,a1:parseTagRecShareDeepLink,a2:parseTagRecPkgHash,a3:syncTagRecFavFilterScope,a4:tagRecViewScopeViewedIds,a5:tagRecViewedGroupIds,a6:TagRecRecheckButton,a7:createTagRecGroupScopeDraft,a8:tagRecViewBucketOf,aA:closeTagRecTrial,aB:openTagRecRecent,aC:editTagRecWorkingGroup,aD:saveTagRecWorkingGroup,aE:unblockTagRecConditionGlobally,aF:undoTagRecWorkingGroup,aG:TagRecDiscoveryPools,aH:commitTagRecMaterialDraft,aJ:TagRecVideoMaterials,aK:removeTagRecWorkingCondition,aL:exploreTagRecTagOnly,aM:excludeTagRecWorkingCondition,aN:blockTagRecConditionGlobally,aO:moveTagRecWorkingCondition,aP:importTagRecFavoriteCombinations,aQ:tagRecMaterialKey,aR:selectTagRecMaterial,aS:exploreTagRecMaterialOnly,aT:stageTagRecMaterial,aU:seedTagRecNewTrialWithTag,aV:setupLargePreview,aW:mergeTagGroups,aX:reconcileTagRecMergedGroupContext,aY:fetchTagRecVideoEvidence,aZ:sedimentNoteProbe,a_:sedimentClearDig,aa:materializeTagRecGroupScopeDraft,ab:beginTagRecCustomQuery,ac:pushExpandedIds,ae:mergeTagRecVideoEvidence,ag:parseTagRecDeepLink,ah:switchTagRecGroupContext,ak:tagRecHoldReasonText,ap:activateTagRecWorkbench,aq:currentTagRecTrial,ar:openTagRecFavorite,as:newTagRecTrial,at:requestTagRecMaterialSearch,au:addTagRecWorkingCondition,av:tagRecMaterialWords,aw:tagRecFieldTones,ax:aggregateTagRecCooccur,az:switchTagRecTrial,b0:diffTagRecSessionFilter,b1:clearTagRecTempWrites,b2:keepTagRecTempEntryToGroup,b3:keepTagRecTempEntryToGlobal,b4:canKeepTempEntryToGlobal,b5:removeTagRecTempWriteAt,b6:resolveTagRecBannerState,b7:formatTagRecDroppedText,b8:filterTagRecSeenItems,b9:resetTagRecCustomFilter,bc:parseTagRecGroupIds,c:useSortedTabKeys,g:clampSeenCap,h:useSensors,i:useSensor,j:arrayMove,l:SortableContext,r:tagRecViewScopeLegacyActiveTheme,u:usePrevious$1});const s = new Set;
const _css = async (t) => {
if (s.has(t)) return;
s.add(t);
((c) => {
if (typeof GM_addStyle === "function") GM_addStyle(c);
else (document.head || document.documentElement).appendChild(document.createElement("style")).append(c);
})(t);
};_css(" ._video-grid_qhuuo_1 {\n display: grid;\n position: relative;\n width: 100%;\n grid-gap: 20px 12px;\n --bilibili-gate--video-card--border-radius: 15px;\n}\n\n._grid-template-columns-using-var-col_qhuuo_9 {\n --col: 4;\n grid-template-columns: repeat(var(--col), minmax(0, 1fr));\n}\n\n._grid-template-columns-using-card-min-width_qhuuo_14 {\n --card-min-width: 320px;\n grid-template-columns: repeat(auto-fill, minmax(var(--card-min-width), 1fr));\n}\n\n._video-grid-addon-center-empty_qhuuo_19::before {\n content: \"\";\n grid-column: 3/-3;\n grid-row: 1/span 9999;\n grid-auto-flow: dense;\n}\n\n._video-grid-bili-feed4_qhuuo_26 {\n grid-gap: 20px 20px;\n}\n@media (max-width: 1399px) {\n ._video-grid-bili-feed4_qhuuo_26 {\n --col: 4;\n }\n}\n@media (min-width: 1400px) {\n ._video-grid-bili-feed4_qhuuo_26 {\n --col: 5;\n }\n}\n._video-grid-bili-feed4_qhuuo_26 .ant-divider-horizontal.ant-divider-with-text {\n margin-bottom: -15px;\n margin-top: -15px;\n}\n._video-grid-bili-feed4_qhuuo_26 .ant-divider-horizontal.ant-divider-with-text:first-of-type {\n margin-top: 0;\n}\n\n._video-grid-custom_qhuuo_47 {\n --row-gap: clamp(15px, 1.2vw, 40px);\n column-gap: clamp(10px, 1.1cqw, 20px);\n row-gap: 0;\n}\n._video-grid-custom_qhuuo_47 .bili-video-card {\n margin-bottom: var(--row-gap);\n}\n._video-grid-custom_qhuuo_47 .ant-divider-horizontal.ant-divider-with-text {\n margin-top: calc(10px - var(--row-gap));\n margin-bottom: 5px;\n}\n._video-grid-custom_qhuuo_47 .ant-divider-horizontal.ant-divider-with-text:first-of-type {\n margin-top: 0;\n}\n\n._narrow-mode_qhuuo_63 {\n --col: 2 !important;\n}._cls--gate-video-grid--divider_h4766_1 .ant-divider-inner-text {\n display: inline-flex;\n align-items: center;\n min-height: 30px;\n}\n._cls--gate-video-grid--divider_h4766_1 .ant-divider-inner-text a {\n color: var(--ant-color-link);\n}\n._cls--gate-video-grid--divider_h4766_1 .ant-divider-inner-text a:hover {\n color: var(--ant-color-primary);\n}\n\n._cls--gate-video-card--context-menu--root_h4766_13 .ant-dropdown-menu {\n --ant-dropdown-padding-block: 3px;\n}\n._cls--gate-video-card--context-menu--root_h4766_13 .ant-dropdown-menu:has(:nth-child(12)) {\n --ant-dropdown-padding-block: 2px;\n}.bilibili-gate-root .bili-video-card__skeleton--cover, .bilibili-gate-root .bili-video-card__skeleton--text, .bilibili-gate-root .bili-video-card__skeleton--light, .bilibili-gate-root .bili-video-card__skeleton--avatar {\n background: linear-gradient(135deg, rgba(0, 0, 0, 0.06) 25%, rgba(0, 0, 0, 0.15) 37%, rgba(0, 0, 0, 0.06) 63%);\n}\n.bilibili-gate-root .bili-video-card__skeleton--text.tiny {\n margin-top: 4px;\n width: 15%;\n}\nhtml.bilibili-gate-using-dark .bilibili-gate-root .bili-video-card__skeleton--cover, html.bilibili-gate-using-dark .bilibili-gate-root .bili-video-card__skeleton--text, html.bilibili-gate-using-dark .bilibili-gate-root .bili-video-card__skeleton--light, html.bilibili-gate-using-dark .bilibili-gate-root .bili-video-card__skeleton--avatar {\n background-color: #444;\n}\n\n._skeleton-active_wlycr_12 .bili-video-card__skeleton--cover, ._skeleton-active_wlycr_12 .bili-video-card__skeleton--text, ._skeleton-active_wlycr_12 .bili-video-card__skeleton--light, ._skeleton-active_wlycr_12 .bili-video-card__skeleton--avatar {\n background-size: 400% 100%;\n animation-name: _ant-skeleton-loading_wlycr_1;\n animation-duration: 2s;\n animation-timing-function: ease;\n animation-iteration-count: infinite;\n}\n\n@keyframes _ant-skeleton-loading_wlycr_1 {\n 0% {\n background-position: 100% 50%;\n }\n 80%, 100% {\n background-position: 0 50%;\n }\n}*,::before,::after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / 0.5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: ;}.icon-only-round-button{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:50%;padding:0;}.inline-icon-only-round-button{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:50%;padding:0;}.flex-center{display:flex;align-items:center;justify-content:center;}.flex-v-center{display:flex;align-items:center;}.inline-flex-center{display:inline-flex;align-items:center;justify-content:center;}.\\[refresh\\:scroll\\]{refresh:scroll;}.pointer-events-auto{pointer-events:auto;}.\\[\\&_svg\\]\\:pointer-events-none svg,.pointer-events-none{pointer-events:none;}.visible{visibility:visible;}.invisible{visibility:hidden;}.absolute{position:absolute;}.fixed{position:fixed;}.position-relative,.relative{position:relative;}.sticky{position:sticky;}.static{position:static;}.inset-0{inset:0;}.inset-x-0{left:0;right:0;}.bottom--6px{bottom:-6px;}.bottom-0{bottom:0;}.bottom-40px{bottom:40px;}.left--2px{left:-2px;}.left--34px{left:-34px;}.left-0{left:0;}.left-2,.left-8px{left:8px;}.left-50\\%{left:50%;}.right--34px{right:-34px;}.right-\\[calc\\(8px\\+28px\\+5px\\)\\]{right:calc(8px + 28px + 5px);}.right-0{right:0;}.right-10px{right:10px;}.right-8px{right:8px;}.top--1px,.top-\\[-1px\\]{top:-1px;}.top-\\[-0\\.5px\\]{top:-0.5px;}.top-0{top:0;}.top-0\\.5px{top:0.5px;}.top-10px{top:10px;}.top-4px{top:4px;}.top-50\\%{top:50%;}.top-8px{top:8px;}.hover\\:left--5px:hover{left:-5px;}.hover\\:right--5px:hover{right:-5px;}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;}.line-clamp-3{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;line-clamp:3;}.isolate{isolation:isolate;}.z-1{z-index:1;}.z-10{z-index:10;}.z-10000{z-index:10000;}.z-10004{z-index:10004;}.z-10005{z-index:10005;}.z-1001{z-index:1001;}.z-11000{z-index:11000;}.z-11200{z-index:11200;}.z-11300{z-index:11300;}.z-2{z-index:2;}.z-3{z-index:3;}.z-4{z-index:4;}.z-5{z-index:5;}.z-6{z-index:6;}.z-90000{z-index:90000;}.z-9999{z-index:9999;}.grid{display:grid;}.col-span-full,.grid-col-span-full{grid-column:1/-1;}.grid-cols-\\[250px_1fr\\]{grid-template-columns:250px 1fr;}.grid-cols-\\[max-content_1fr\\]{grid-template-columns:max-content 1fr;}.grid-cols-\\[repeat\\(2\\,max-content\\)_1fr\\]{grid-template-columns:repeat(2,max-content) 1fr;}.grid-cols-\\[repeat\\(2\\,max-content\\)\\]{grid-template-columns:repeat(2,max-content);}.grid-cols-\\[repeat\\(auto-fill\\,minmax\\(225px\\,1fr\\)\\)\\]{grid-template-columns:repeat(auto-fill,minmax(225px,1fr));}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr));}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr));}.float-right{float:right;}.m-0{margin:0;}.m\\[missing\\[i\\]\\.id\\]{margin:missing[i].id;}.m10{margin:40px;}.m12{margin:48px;}.m13{margin:52px;}.m14{margin:56px;}.m15{margin:60px;}.m16{margin:64px;}.m16\\.52{margin:66.08px;}.m160{margin:640px;}.m19{margin:76px;}.m19\\.833{margin:79.332px;}.m20{margin:80px;}.m239\\.82{margin:959.28px;}.m24{margin:96px;}.m27\\.952{margin:111.808px;}.m29\\.756{margin:119.024px;}.m3{margin:12px;}.m33{margin:132px;}.m434\\.8{margin:1739.2px;}.m5{margin:20px;}.m6{margin:24px;}.\\[\\&_\\.ant-radio-wrapper\\]\\:mx-0 .ant-radio-wrapper,.mx-0{margin-left:0;margin-right:0;}.\\[\\&\\.ant-divider-horizontal\\.ant-divider-with-text\\]\\:my-5px.ant-divider-horizontal.ant-divider-with-text,.my-5px{margin-top:5px;margin-bottom:5px;}.\\[\\&\\.ant-empty-normal\\]\\:my-1.ant-empty-normal,.my-1,.my-4px{margin-top:4px;margin-bottom:4px;}.mx-1,.mx-4px{margin-left:4px;margin-right:4px;}.mx-2px{margin-left:2px;margin-right:2px;}.mx-5px{margin-left:5px;margin-right:5px;}.mx-6px{margin-left:6px;margin-right:6px;}.mx-auto{margin-left:auto;margin-right:auto;}.my-0{margin-top:0;margin-bottom:0;}.my-10px{margin-top:10px;margin-bottom:10px;}.my-2px{margin-top:2px;margin-bottom:2px;}.my-7px{margin-top:7px;margin-bottom:7px;}.my-8px{margin-top:8px;margin-bottom:8px;}.\\[\\&_svg\\]\\:mr-5px svg,.mr-5px{margin-right:5px;}.\\[\\&_svg\\]\\:mt--1px svg,.mt--1px{margin-top:-1px;}.mb-0{margin-bottom:0;}.mb-0\\!{margin-bottom:0 !important;}.mb-10px{margin-bottom:10px;}.mb-12px{margin-bottom:12px;}.mb-15px{margin-bottom:15px;}.mb-20px{margin-bottom:20px;}.mb-2px{margin-bottom:2px;}.mb-40px{margin-bottom:40px;}.mb-4px{margin-bottom:4px;}.mb-5px{margin-bottom:5px;}.mb-6px{margin-bottom:6px;}.mb-8px{margin-bottom:8px;}.ml--2px{margin-left:-2px;}.ml-0{margin-left:0;}.ml-1,.ml-4px{margin-left:4px;}.ml-1\\.5,.ml-6px{margin-left:6px;}.ml-10px{margin-left:10px;}.ml-12px,.ml-3{margin-left:12px;}.ml-15px{margin-left:15px;}.ml-16px,.ml-4{margin-left:16px;}.ml-18px{margin-left:18px;}.ml-1px{margin-left:1px;}.ml-2,.ml-8px{margin-left:8px;}.ml-20px{margin-left:20px;}.ml-25px{margin-left:25px;}.ml-2px{margin-left:2px;}.ml-30px{margin-left:30px;}.ml-3px{margin-left:3px;}.ml-5px{margin-left:5px;}.ml-auto{margin-left:auto;}.mr-0{margin-right:0;}.mr-0\\!{margin-right:0 !important;}.mr-1,.mr-4px{margin-right:4px;}.mr-10px{margin-right:10px;}.mr-24px{margin-right:24px;}.mr-2px{margin-right:2px;}.mr-8px{margin-right:8px;}.mr-auto{margin-right:auto;}.ms{margin-inline-start:16px;}.mt--2px{margin-top:-2px;}.mt-0{margin-top:0;}.mt-0\\.5em{margin-top:0.5em;}.mt-1,.mt-4px{margin-top:4px;}.mt-10px{margin-top:10px;}.mt-12px{margin-top:12px;}.mt-14px{margin-top:14px;}.mt-15px{margin-top:15px;}.mt-16px{margin-top:16px;}.mt-2,.mt-8px{margin-top:8px;}.mt-20px{margin-top:20px;}.mt-2px{margin-top:2px;}.mt-3px{margin-top:3px;}.mt-5px{margin-top:5px;}.mt-6px{margin-top:6px;}.first\\:ml-0:first-child{margin-left:0;}.first\\:mt-0:first-child{margin-top:0;}.first\\:mt-0px:first-child{margin-top:0;}.box-border{box-sizing:border-box;}.box-content{box-sizing:content-box;}.inline{display:inline;}.block{display:block;}.inline-block{display:inline-block;}.contents{display:contents;}.\\[\\&\\>\\.ant-checkbox-label\\]\\:hidden>.ant-checkbox-label,.hidden{display:none;}.aspect-1{aspect-ratio:1;}.aspect-16\\/9{aspect-ratio:16/9;}.aspect-ratio-10\\/16{aspect-ratio:10/16;}.\\[\\&_svg\\]\\:size-16px svg,.size-16px{width:16px;height:16px;}.ant-spin .ant-spin-dot.\\[\\.ant-spin_\\.ant-spin-dot\\&\\]\\:size-25px,.size-25px{width:25px;height:25px;}.size-1\\.1em{width:1.1em;height:1.1em;}.size-1\\.3em{width:1.3em;height:1.3em;}.size-1\\.6em{width:1.6em;height:1.6em;}.size-12px{width:12px;height:12px;}.size-13px{width:13px;height:13px;}.size-14px{width:14px;height:14px;}.size-15px{width:15px;height:15px;}.size-17px{width:17px;height:17px;}.size-18px{width:18px;height:18px;}.size-1em{width:1em;height:1em;}.size-2\\.5{width:10px;height:10px;}.size-2\\.5em{width:2.5em;height:2.5em;}.size-20px{width:20px;height:20px;}.size-21px{width:21px;height:21px;}.size-24px{width:24px;height:24px;}.size-26px{width:26px;height:26px;}.size-28px{width:28px;height:28px;}.size-30px{width:30px;height:30px;}.size-32px{width:32px;height:32px;}.size-40px{width:40px;height:40px;}.size-6px{width:6px;height:6px;}.size-full{width:100%;height:100%;}.\\[\\&_\\.ant-radio-button-label\\]\\:h-full .ant-radio-button-label,.h-100\\%,.h-full{height:100%;}.h-\\[calc\\(100vh-30px\\)\\]{height:calc(100vh - 30px);}.h-\\[calc\\(100vh-48px\\)\\]{height:calc(100vh - 48px);}.h-0{height:0;}.h-100vh{height:100vh;}.h-14px{height:14px;}.h-19px{height:19px;}.h-1px{height:1px;}.h-20px{height:20px;}.h-22px{height:22px;}.h-24px{height:24px;}.h-2px{height:2px;}.h-34px{height:34px;}.h-35px{height:35px;}.h-3px{height:3px;}.h-auto{height:auto;}.h-fit{height:fit-content;}.max-h-\\[calc\\(90vh-50px\\)\\]{max-height:calc(90vh - 50px);}.max-h-\\[max\\(362px\\,calc\\(100vh-119px\\)\\)\\]{max-height:max(362px,calc(100vh - 119px));}.max-h-130px{max-height:130px;}.max-h-250px{max-height:250px;}.max-h-260px{max-height:260px;}.max-h-280px{max-height:280px;}.max-h-300px{max-height:300px;}.max-h-320px{max-height:320px;}.max-h-50vh{max-height:50vh;}.max-h-60vh{max-height:60vh;}.max-h-72vh{max-height:72vh;}.max-h-full{max-height:100%;}.max-h-unset{max-height:unset;}.max-w-\\[calc\\(100\\%-6px\\)\\]{max-width:calc(100% - 6px);}.max-w-130px{max-width:130px;}.max-w-250px{max-width:250px;}.max-w-288px{max-width:288px;}.max-w-300px{max-width:300px;}.max-w-320px{max-width:320px;}.max-w-350px{max-width:350px;}.max-w-400px{max-width:400px;}.max-w-450px{max-width:450px;}.max-w-80vw{max-width:80vw;}.max-w-full{max-width:100%;}.min-h-0{min-height:0;}.min-h-100px{min-height:100px;}.min-h-100vh{min-height:100vh;}.min-h-17px{min-height:17px;}.min-h-24px{min-height:24px;}.min-h-25px{min-height:25px;}.min-h-362px{min-height:362px;}.min-h-40px{min-height:40px;}.min-w-0{min-width:0;}.min-w-110px{min-width:110px;}.min-w-120px{min-width:120px;}.min-w-130px{min-width:130px;}.min-w-160px{min-width:160px;}.min-w-16px{min-width:16px;}.min-w-180px{min-width:180px;}.min-w-200px{min-width:200px;}.min-w-240px{min-width:240px;}.min-w-24px{min-width:24px;}.min-w-300px{min-width:300px;}.min-w-32px{min-width:32px;}.min-w-60px{min-width:60px;}.min-w-70px{min-width:70px;}.min-w-95px{min-width:95px;}.w-\\[calc\\(100vw-30px\\)\\]{width:calc(100vw - 30px);}.w-\\[max-content\\],.w-max{width:max-content;}.w-0{width:0;}.w-100\\%,.w-full{width:100%;}.w-100px{width:100px;}.w-108px{width:108px;}.w-110px{width:110px;}.w-114px{width:114px;}.w-120px{width:120px;}.w-128px{width:128px;}.w-130px{width:130px;}.w-140px{width:140px;}.w-150px{width:150px;}.w-160px{width:160px;}.w-16px{width:16px;}.w-170px{width:170px;}.w-190px{width:190px;}.w-200px{width:200px;}.w-204px{width:204px;}.w-215px{width:215px;}.w-220px{width:220px;}.w-22px{width:22px;}.w-240px{width:240px;}.w-260px{width:260px;}.w-280px{width:280px;}.w-310px{width:310px;}.w-320px{width:320px;}.w-34px{width:34px;}.w-460px{width:460px;}.w-48px{width:48px;}.w-500px{width:500px;}.w-50px{width:50px;}.w-55px{width:55px;}.w-58px{width:58px;}.w-64px{width:64px;}.w-70px{width:70px;}.w-720px{width:720px;}.w-72px{width:72px;}.w-740px{width:740px;}.w-75px{width:75px;}.w-76px{width:76px;}.w-80px{width:80px;}.w-90px{width:90px;}.w-96px{width:96px;}.\\[\\&_\\.ant-radio-button-label\\]\\:flex .ant-radio-button-label,.flex{display:flex;}.\\[\\&_\\.ant-checkbox-label\\]\\:inline-flex .ant-checkbox-label,.\\[\\&_\\.ant-radio-button-label\\]\\:inline-flex .ant-radio-button-label,.\\[\\&_\\.ant-radio-label\\]\\:inline-flex .ant-radio-label,.\\[\\&_\\.ant-radio-wrapper\\]\\:inline-flex .ant-radio-wrapper,.inline-flex{display:inline-flex;}.flex-1{flex:1 1 0%;}.flex-none{flex:none;}.flex-shrink-0,.shrink-0{flex-shrink:0;}.flex-shrink-1{flex-shrink:1;}.flex-grow-1{flex-grow:1;}.basis-full,.flex-basis-100\\%{flex-basis:100%;}.flex-row{flex-direction:row;}.flex-row-reverse{flex-direction:row-reverse;}.flex-col{flex-direction:column;}.flex-wrap{flex-wrap:wrap;}.translate-y--50\\%{--un-translate-y:-50%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.translate-y-100\\%{--un-translate-y:100%;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.rotate-0{--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-rotate:0deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.rotate-180{--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-rotate:180deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.hover\\:rotate-90:hover{--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-rotate:90deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.rotate-z-0{--un-rotate:0;--un-rotate-z:0deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.rotate-z-180deg{--un-rotate:0;--un-rotate-z:180deg;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.hover\\:scale-105\\%:hover{--un-scale-x:1.05;--un-scale-y:1.05;transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}.transform{transform:translateX(var(--un-translate-x)) translateY(var(--un-translate-y)) translateZ(var(--un-translate-z)) rotate(var(--un-rotate)) rotateX(var(--un-rotate-x)) rotateY(var(--un-rotate-y)) rotateZ(var(--un-rotate-z)) skewX(var(--un-skew-x)) skewY(var(--un-skew-y)) scaleX(var(--un-scale-x)) scaleY(var(--un-scale-y)) scaleZ(var(--un-scale-z));}@keyframes spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite;}.cursor-\\[edit\\]{cursor:edit;}.cursor-default{cursor:default;}.cursor-pointer{cursor:pointer;}.\\[\\&\\:disabled\\]\\:cursor-wait:disabled{cursor:wait;}.cursor-text{cursor:text;}.cursor-move{cursor:move;}.cursor-not-allowed{cursor:not-allowed;}.cursor-grab{cursor:grab;}.\\[\\&_\\.ant-checkbox-label\\]\\:select-none .ant-checkbox-label,.\\[\\&_svg\\]\\:select-none svg,.select-none{-webkit-user-select:none;user-select:none;}.resize{resize:both;}.list-circle{list-style-type:circle;}.list-disc{list-style-type:disc;}.list-none{list-style-type:none;}.content-start{align-content:flex-start;}.items-start{align-items:flex-start;}.\\[\\&_\\.ant-checkbox-label\\]\\:items-center .ant-checkbox-label,.\\[\\&_\\.ant-radio-button-label\\]\\:items-center .ant-radio-button-label,.\\[\\&_\\.ant-radio-label\\]\\:items-center .ant-radio-label,.\\[\\&_\\.ant-radio-wrapper\\]\\:items-center .ant-radio-wrapper,.items-center{align-items:center;}.items-baseline{align-items:baseline;}.self-start{align-self:flex-start;}.justify-start{justify-content:flex-start;}.justify-end{justify-content:flex-end;}.justify-center{justify-content:center;}.justify-between{justify-content:space-between;}.justify-left{justify-content:left;}.justify-right{justify-content:right;}.gap-0{gap:0;}.gap-1,.gap-4px{gap:4px;}.gap-10px{gap:10px;}.gap-12px{gap:12px;}.gap-14px{gap:14px;}.gap-15px{gap:15px;}.gap-1px{gap:1px;}.gap-2px{gap:2px;}.gap-3px{gap:3px;}.gap-6px{gap:6px;}.gap-8px{gap:8px;}.gap-x-1,.gap-x-4px{column-gap:4px;}.gap-x-10px{column-gap:10px;}.gap-x-12px{column-gap:12px;}.gap-x-14px{column-gap:14px;}.gap-x-15px{column-gap:15px;}.gap-x-2,.gap-x-8px{column-gap:8px;}.gap-x-20px{column-gap:20px;}.gap-x-25px{column-gap:25px;}.gap-x-2px{column-gap:2px;}.gap-x-50px{column-gap:50px;}.gap-x-5px{column-gap:5px;}.gap-x-6px{column-gap:6px;}.gap-x-7px{column-gap:7px;}.gap-y-0{row-gap:0;}.gap-y-0\\.5em{row-gap:0.5em;}.gap-y-1,.gap-y-4px{row-gap:4px;}.gap-y-10px{row-gap:10px;}.gap-y-15px{row-gap:15px;}.gap-y-2,.gap-y-8px{row-gap:8px;}.gap-y-2px{row-gap:2px;}.gap-y-3px{row-gap:3px;}.gap-y-5px{row-gap:5px;}.gap-y-6px{row-gap:6px;}.overflow-hidden{overflow:hidden;}.overflow-x-hidden{overflow-x:hidden;}.overflow-y-auto{overflow-y:auto;}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}.text-ellipsis{text-overflow:ellipsis;}.whitespace-normal{white-space:normal;}.whitespace-nowrap{white-space:nowrap;}.whitespace-pre-wrap{white-space:pre-wrap;}.break-normal{overflow-wrap:normal;word-break:normal;}.break-words{overflow-wrap:break-word;}.break-all{word-break:break-all;}.b,.b-1px,.border{border-width:1px;}.b-0,.border-0{border-width:0px;}.b-2px{border-width:2px;}.b-5px{border-width:5px;}.b-b-1px{border-bottom-width:1px;}.b-l-1px{border-left-width:1px;}.b-r-1px{border-right-width:1px;}.b-t-1px{border-top-width:1px;}.border-b-0{border-bottom-width:0px;}.border-l-2px{border-left-width:2px;}.last\\:b-b-0:last-child{border-bottom-width:0px;}.b-\\#444{--un-border-opacity:1;border-color:rgb(68 68 68 / var(--un-border-opacity));}.b-gate-bg-lv-2{border-color:var(--bilibili-gate--bg--lv2);}.b-gate-border,.bilibili-gate-using-dark .dark\\:b-gate-border{border-color:var(--bilibili-gate--border-color);}.b-gate-primary{border-color:var(--bilibili-gate--primary-color);}.b-transparent{border-color:transparent;}.b-white\\/20\\%{border-color:rgb(255 255 255 / 0.2);}.b-white\\/33\\%{border-color:rgb(255 255 255 / 0.33);}.border-\\#f0f0f0{--un-border-opacity:1;border-color:rgb(240 240 240 / var(--un-border-opacity));}.border-white\\/40{border-color:rgb(255 255 255 / 0.4);}.bilibili-gate-using-light .light\\:hover\\:b-gate-primary:hover{border-color:var(--bilibili-gate--primary-color);}.hover\\:b-gate-primary:hover{border-color:var(--bilibili-gate--primary-color);}.b-b-gate-bg-lv1{border-bottom-color:var(--bilibili-gate--bg--lv1);}.b-b-transparent{border-bottom-color:transparent;}.b-t-\\$bilibili-gate--separator-color{border-top-color:var(--bilibili-gate--separator-color);}.b-t-gate-border{border-top-color:var(--bilibili-gate--border-color);}.rounded,.rounded-4px{border-radius:4px;}.rounded-10px{border-radius:10px;}.rounded-15px{border-radius:15px;}.rounded-18px{border-radius:18px;}.rounded-20px{border-radius:20px;}.rounded-2px{border-radius:2px;}.rounded-3px{border-radius:3px;}.rounded-5px{border-radius:5px;}.rounded-6px,.rounded-md{border-radius:6px;}.rounded-8px,.rounded-lg{border-radius:8px;}.rounded-9px{border-radius:9px;}.rounded-full{border-radius:9999px;}.rounded-b-none{border-bottom-left-radius:0;border-bottom-right-radius:0;}.rounded-t-6px{border-top-left-radius:6px;border-top-right-radius:6px;}.rounded-t-gate-video-card{border-top-left-radius:var(--bilibili-gate--video-card--border-radius);border-top-right-radius:var(--bilibili-gate--video-card--border-radius);}.b-dashed{border-style:dashed;}.b-none,.border-none{border-style:none;}.b-solid,.border-solid{border-style:solid;}.b-b-solid{border-bottom-style:solid;}.b-l-solid{border-left-style:solid;}.b-r-solid{border-right-style:solid;}.b-t-solid{border-top-style:solid;}.\\[\\&\\:has\\(svg\\.peeking\\)\\]\\:bg-transparent:has(svg.peeking),.bg-transparent{background-color:transparent;}.bg-\\[\\#fa8c16\\]{--un-bg-opacity:1;background-color:rgb(250 140 22 / var(--un-bg-opacity));}.bg-\\[rgb\\(33_33_33_\\/_0\\.7\\)\\]{--un-bg-opacity:0.7;background-color:rgb(33 33 33 / var(--un-bg-opacity));}.bg-\\#f9f0ff{--un-bg-opacity:1;background-color:rgb(249 240 255 / var(--un-bg-opacity));}.bg-\\$brand_blue{background-color:var(--brand_blue);}.bg-\\$brand_pink{background-color:var(--brand_pink);}.bg-black\\/10{background-color:rgb(0 0 0 / 0.1);}.bg-black\\/50{background-color:rgb(0 0 0 / 0.5);}.bg-black\\/65{background-color:rgb(0 0 0 / 0.65);}.bg-black\\/90\\%{background-color:rgb(0 0 0 / 0.9);}.bg-gate-bg{background-color:var(--bilibili-gate--bg);}.bg-gate-bg-lv1{background-color:var(--bilibili-gate--bg--lv1);}.bg-gate-bg-lv2{background-color:var(--bilibili-gate--bg--lv2);}.bg-gate-primary{background-color:var(--bilibili-gate--primary-color);}.bg-gate-primary-lv-1{background-color:var(--bilibili-gate--primary-color--lv1);}.bg-gate-primary-lv-3{background-color:var(--bilibili-gate--primary-color--lv3);}.bg-gate-primary\\!{background-color:var(--bilibili-gate--primary-color) !important;}.bg-red-500{--un-bg-opacity:1;background-color:rgb(239 68 68 / var(--un-bg-opacity));}.bg-white\\/14\\%{background-color:rgb(255 255 255 / 0.14);}.bg-white\\/50\\%{background-color:rgb(255 255 255 / 0.5);}.hover\\:bg-gate-bg-lv-3:hover{background-color:var(--bilibili-gate--bg--lv3);}.hover\\:bg-gate-bg-lv1:hover{background-color:var(--bilibili-gate--bg--lv1);}.hover\\:bg-gate-primary-lv1\\!:hover{background-color:var(--bilibili-gate--primary-color--lv1) !important;}.hover\\:bg-gate-primary:hover{background-color:var(--bilibili-gate--primary-color);}.hover\\:bg-white\\/40\\%:hover{background-color:rgb(255 255 255 / 0.4);}.\\[\\&_g\\]\\:fill-inherit g{fill:inherit;}.\\[\\&_path\\]\\:fill-gate-primary path,.fill-gate-primary{fill:var(--bilibili-gate--primary-color);}.fill-none{fill:none;}.\\[\\&_\\.circle\\]\\:stroke-current .circle{stroke:currentColor;}.object-cover{object-fit:cover;}.object-contain{object-fit:contain;}.p-0{padding:0;}.p-1,.p-4px{padding:4px;}.p-10px{padding:10px;}.p-12px{padding:12px;}.p-15px{padding:15px;}.p-1px{padding:1px;}.p-20px{padding:20px;}.p-2px{padding:2px;}.p-8px,.p2{padding:8px;}.p4{padding:16px;}.pa{padding:auto;}.\\[\\&_\\.ant-radio-button-wrapper\\]\\:px-10px .ant-radio-button-wrapper,.px-10px{padding-left:10px;padding-right:10px;}.\\[\\&\\.ant-empty-normal\\]\\:py-5px.ant-empty-normal,.py-5px{padding-top:5px;padding-bottom:5px;}.px,.px-4{padding-left:16px;padding-right:16px;}.px-0{padding-left:0;padding-right:0;}.px-1,.px-4px{padding-left:4px;padding-right:4px;}.px-12px,.px-3{padding-left:12px;padding-right:12px;}.px-15px{padding-left:15px;padding-right:15px;}.px-2,.px-8px{padding-left:8px;padding-right:8px;}.px-24px{padding-left:24px;padding-right:24px;}.px-2px{padding-left:2px;padding-right:2px;}.px-3px{padding-left:3px;padding-right:3px;}.px-5px{padding-left:5px;padding-right:5px;}.px-6px{padding-left:6px;padding-right:6px;}.py-0{padding-top:0;padding-bottom:0;}.py-1,.py-4px{padding-top:4px;padding-bottom:4px;}.py-10px{padding-top:10px;padding-bottom:10px;}.py-12px{padding-top:12px;padding-bottom:12px;}.py-1px{padding-top:1px;padding-bottom:1px;}.py-2,.py-8px{padding-top:8px;padding-bottom:8px;}.py-24px{padding-top:24px;padding-bottom:24px;}.py-2px{padding-top:2px;padding-bottom:2px;}.py-30px{padding-top:30px;padding-bottom:30px;}.py-3px{padding-top:3px;padding-bottom:3px;}.py-6px{padding-top:6px;padding-bottom:6px;}.py-7px{padding-top:7px;padding-bottom:7px;}.py-9px{padding-top:9px;padding-bottom:9px;}.\\!\\[\\&_\\.ant-radio-label\\]\\:pl-4px .ant-radio-label{padding-left:4px !important;}.pb-15px{padding-bottom:15px;}.pb-2px{padding-bottom:2px;}.pb-3px{padding-bottom:3px;}.pb-4px{padding-bottom:4px;}.pb-7px{padding-bottom:7px;}.pe{padding-inline-end:16px;}.pe\\[pool\\]{padding-inline-end:pool;}.pl-0{padding-left:0;}.pl-10px{padding-left:10px;}.pl-14px{padding-left:14px;}.pl-16px{padding-left:16px;}.pl-18px{padding-left:18px;}.pl-20px{padding-left:20px;}.pl-24px{padding-left:24px;}.pl-2px{padding-left:2px;}.pl-40px{padding-left:40px;}.pl-44px{padding-left:44px;}.pl-4px{padding-left:4px;}.pl-6px{padding-left:6px;}.pr-0{padding-right:0;}.pr-10px{padding-right:10px;}.pr-12px{padding-right:12px;}.pr-15px{padding-right:15px;}.pr-18px{padding-right:18px;}.pr-4px{padding-right:4px;}.pr-5px{padding-right:5px;}.pr-6px{padding-right:6px;}.ps{padding-inline-start:16px;}.pt-0{padding-top:0;}.pt-10px{padding-top:10px;}.pt-5px{padding-top:5px;}.pt-8px{padding-top:8px;}.text-center{text-align:center;}.text-left{text-align:left;}.text-right{text-align:right;}.text-end{text-align:end;}.\\[\\&_\\.ant-radio-button-label\\]\\:align-top .ant-radio-button-label,.align-top{vertical-align:top;}.align-middle,.vertical-middle{vertical-align:middle;}.\\[\\&\\.ant-divider-horizontal\\.ant-divider-with-text\\]\\:text-14px.ant-divider-horizontal.ant-divider-with-text,.text-14px,.text-size-14px{font-size:14px;}.text-\\[1\\.5rem\\]{font-size:24px;}.text-1\\.25em{font-size:1.25em;}.text-1\\.2em{font-size:1.2em;}.text-1\\.4em{font-size:1.4em;}.text-1\\.5em,.text-size-1\\.5em{font-size:1.5em;}.text-1\\.6em{font-size:1.6em;}.text-10px{font-size:10px;}.text-11px{font-size:11px;}.font-size-12px,.text-12px,.text-size-12px{font-size:12px;}.text-13px,.text-size-13px{font-size:13px;}.text-15px{font-size:15px;}.text-18px{font-size:18px;}.text-1em{font-size:1em;}.text-20px,.text-size-20px{font-size:20px;}.text-2em{font-size:2em;}.text-xs{font-size:12px;line-height:16px;}.text-size-0{font-size:0;}.text-size-1\\.3em{font-size:1.3em;}.text-size-120\\%{font-size:120%;}.text-size-16px{font-size:16px;}.color-gate-primary,.text-gate-primary{color:var(--bilibili-gate--primary-color);}.color-gate-text,.text-gate-text{color:var(--bilibili-gate--text-color);}.text-red{--un-text-opacity:1;color:rgb(248 113 113 / var(--un-text-opacity));}.color-white,.text-white{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}.text-yellow-400{--un-text-opacity:1;color:rgb(250 204 21 / var(--un-text-opacity));}.color-\\$text_1{color:var(--text_1);}.hover\\:color-gate-primary:hover{color:var(--bilibili-gate--primary-color);}.hover\\:color-white:hover{--un-text-opacity:1;color:rgb(255 255 255 / var(--un-text-opacity));}.color-inherit{color:inherit;}.font-600{font-weight:600;}.font-700,.font-bold{font-weight:700;}.font-medium{font-weight:500;}.font-normal{font-weight:400;}.\\[\\&_\\.ant-btn-icon\\]\\:line-height-0 .ant-btn-icon,.line-height-\\[0\\]{line-height:0;}.leading-\\[0\\.9\\]{line-height:0.9;}.leading-19px{line-height:19px;}.leading-none,.line-height-\\[1\\]{line-height:1;}.line-height-\\[1\\.2\\]{line-height:1.2;}.line-height-\\[unset\\]{line-height:unset;}.line-height-1\\.5{line-height:6px;}.line-height-13px{line-height:13px;}.line-height-17px{line-height:17px;}.line-height-18px{line-height:18px;}.line-height-normal{line-height:1.5;}.line-height-snug{line-height:1.375;}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;}.italic{font-style:italic;}.tab{-moz-tab-size:4;-o-tab-size:4;tab-size:4;}.\\[\\&\\:has\\(svg\\.peeking\\)\\]\\:opacity-10:has(svg.peeking){opacity:0.1;}.opacity-40{opacity:0.4;}.opacity-45{opacity:0.45;}.opacity-50{opacity:0.5;}.opacity-55{opacity:0.55;}.opacity-60{opacity:0.6;}.opacity-65{opacity:0.65;}.opacity-70{opacity:0.7;}.opacity-75{opacity:0.75;}.opacity-80{opacity:0.8;}.shadow{--un-shadow:var(--un-shadow-inset) 0 1px 3px 0 var(--un-shadow-color, rgb(0 0 0 / 0.1)),var(--un-shadow-inset) 0 1px 2px -1px var(--un-shadow-color, rgb(0 0 0 / 0.1));box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}.\\[\\&\\.ant-btn\\:not\\(\\:disabled\\)\\:focus-visible\\]\\:outline-0.ant-btn:not(:disabled):focus-visible{outline-width:0px;}.outline-none\\!{outline:2px solid transparent !important;outline-offset:2px !important;}.ring-1px{--un-ring-width:1px;--un-ring-offset-shadow:var(--un-ring-inset) 0 0 0 var(--un-ring-offset-width) var(--un-ring-offset-color);--un-ring-shadow:var(--un-ring-inset) 0 0 0 calc(var(--un-ring-width) + var(--un-ring-offset-width)) var(--un-ring-color);box-shadow:var(--un-ring-offset-shadow), var(--un-ring-shadow), var(--un-shadow);}.ring-gate-border{--un-ring-color:var(--bilibili-gate--border-color);}.ring-gate-primary{--un-ring-color:var(--bilibili-gate--primary-color);}.backdrop-blur-10px{--un-backdrop-blur:blur(10px);-webkit-backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);backdrop-filter:var(--un-backdrop-blur) var(--un-backdrop-brightness) var(--un-backdrop-contrast) var(--un-backdrop-grayscale) var(--un-backdrop-hue-rotate) var(--un-backdrop-invert) var(--un-backdrop-opacity) var(--un-backdrop-saturate) var(--un-backdrop-sepia);}.filter{filter:var(--un-blur) var(--un-brightness) var(--un-contrast) var(--un-drop-shadow) var(--un-grayscale) var(--un-hue-rotate) var(--un-invert) var(--un-saturate) var(--un-sepia);}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}.transition-250{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:250ms;}.transition-300{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:300ms;}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms;}.transition-duration-150{transition-duration:150ms;}.transition-duration-300{transition-duration:300ms;}.ease,.ease-in-out,.transition-ease,.transition-ease-in-out{transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);}.ease-out{transition-timing-function:cubic-bezier(0, 0, 0.2, 1);}.transition-property-\\[border-color\\]{transition-property:border-color;}.transition-property-all{transition-property:all;}.transition-property-right\\,left{transition-property:right,left;}.transition-property-transform{transition-property:transform;}.\\@container,.\\@container-inline-size{container-type:inline-size;}@media (min-width: 1280px){.xl\\:gap-x-8px{column-gap:8px;}}/*$vite$:1*/ ");var isStyleNameSupport = (styleName) => {
if (canUseDom$1() && window.document.documentElement) {
const styleNameList = Array.isArray(styleName) ? styleName : [styleName];
const { documentElement } = window.document;
return styleNameList.some((name) => name in documentElement.style);
}
return false;
};
function isStyleSupport(styleName, styleValue) {
return isStyleNameSupport(styleName);
}
var import_react$a = /* @__PURE__ */ __toESM(require_react());
var toList = (val, config = {}) => {
if (!isNonNullable$1(val) && config?.skipEmpty) return [];
return Array.isArray(val) ? val : [val];
};
var EditOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 000-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 009.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9zm67.4-174.4L687.8 215l73.3 73.3-362.7 362.6-88.9 15.7 15.6-89zM880 836H144c-17.7 0-32 14.3-32 32v36c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-36c0-17.7-14.3-32-32-32z" }
}]
},
"name": "edit",
"theme": "outlined"
};
function _extends$2$2() {
_extends$2$2 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$2$2.apply(this, arguments);
}
var EditOutlined = (props, ref) => /*#__PURE__*/ import_react$a.createElement(Icon, _extends$2$2({}, props, {
ref,
icon: EditOutlined$1
}));
/** */
var RefIcon$2$1 = /*#__PURE__*/ import_react$a.forwardRef(EditOutlined);
var EnterOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 000 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8z" }
}]
},
"name": "enter",
"theme": "outlined"
};
function _extends$1$3() {
_extends$1$3 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$1$3.apply(this, arguments);
}
var EnterOutlined = (props, ref) => /*#__PURE__*/ import_react$a.createElement(Icon, _extends$1$3({}, props, {
ref,
icon: EnterOutlined$1
}));
/** */
var RefIcon$1$1 = /*#__PURE__*/ import_react$a.forwardRef(EnterOutlined);
var getTitleStyle = (fontSize, lineHeight, color, token) => {
const { titleMarginBottom, fontWeightStrong } = token;
return {
marginBottom: titleMarginBottom,
color,
fontWeight: fontWeightStrong,
fontSize,
lineHeight
};
};
var getTitleStyles = (token) => {
const headings = [
1,
2,
3,
4,
5
];
const styles = {};
headings.forEach((headingLevel) => {
styles[`
h${headingLevel}&,
div&-h${headingLevel},
div&-h${headingLevel} > textarea,
h${headingLevel}
`] = getTitleStyle(token[`fontSizeHeading${headingLevel}`], token[`lineHeightHeading${headingLevel}`], token.colorTextHeading, token);
});
return styles;
};
var getLinkStyles = (token) => {
const { componentCls } = token;
return { [`&${`${componentCls}-link`}`]: {
...operationUnit(token),
userSelect: "text",
[`&[disabled], &${componentCls}-disabled`]: {
color: token.colorTextDisabled,
cursor: "not-allowed",
"&:active, &:hover": { color: token.colorTextDisabled },
"&:active": {
pointerEvents: "none",
[`${componentCls}-actions`]: { pointerEvents: "auto" }
}
}
} };
};
var getResetStyles = (token) => ({
code: {
margin: "0 0.2em",
paddingInline: "0.4em",
paddingBlock: "0.2em 0.1em",
fontSize: "85%",
fontFamily: token.fontFamilyCode,
background: "rgba(150, 150, 150, 0.1)",
border: `${unit$1(token.lineWidth)} ${token.lineType} rgba(100, 100, 100, 0.2)`,
borderRadius: 3
},
kbd: {
margin: "0 0.2em",
paddingInline: "0.4em",
paddingBlock: "0.15em 0.1em",
fontSize: "90%",
fontFamily: token.fontFamilyCode,
background: "rgba(150, 150, 150, 0.06)",
border: `${unit$1(token.lineWidth)} ${token.lineType} rgba(100, 100, 100, 0.2)`,
borderBottomWidth: 2,
borderRadius: 3
},
mark: {
padding: 0,
backgroundColor: gold[2]
},
"u, ins": {
textDecoration: "underline",
textDecorationSkipInk: "auto"
},
"s, del": { textDecoration: "line-through" },
strong: { fontWeight: token.fontWeightStrong },
"ul, ol": {
marginInline: 0,
marginBlock: "0 1em",
padding: 0,
li: {
marginInline: "20px 0",
marginBlock: 0,
paddingInline: "4px 0",
paddingBlock: 0
}
},
ul: {
listStyleType: "circle",
ul: { listStyleType: "disc" }
},
ol: { listStyleType: "decimal" },
"pre, blockquote": { margin: "1em 0" },
pre: {
padding: "0.4em 0.6em",
whiteSpace: "pre-wrap",
wordWrap: "break-word",
background: "rgba(150, 150, 150, 0.1)",
border: `${unit$1(token.lineWidth)} ${token.lineType} rgba(100, 100, 100, 0.2)`,
borderRadius: 3,
fontFamily: token.fontFamilyCode,
code: {
display: "inline",
margin: 0,
padding: 0,
fontSize: "inherit",
fontFamily: "inherit",
background: "transparent",
border: 0
}
},
blockquote: {
paddingInline: "0.6em 0",
paddingBlock: 0,
borderInlineStart: "4px solid rgba(100, 100, 100, 0.2)",
opacity: .85
},
table: {
width: "100%",
textAlign: "start",
borderCollapse: "separate",
borderSpacing: 0,
marginBlock: "1em",
"th, td": {
padding: unit$1(token.padding),
overflowWrap: "break-word",
borderBottom: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorSplit}`
},
"thead > tr:first-child > th:first-child": { borderStartStartRadius: token.borderRadiusLG },
"thead > tr:first-child > th:last-child": { borderStartEndRadius: token.borderRadiusLG },
"thead > tr > th": {
textAlign: "start",
position: "relative",
color: token.colorTextHeading,
fontWeight: token.fontWeightStrong,
backgroundColor: token.colorFillAlter,
transition: `background-color ${token.motionDurationMid} ease`,
"&:not(:last-child)::before": {
position: "absolute",
top: "50%",
insetInlineEnd: 0,
width: 1,
height: "1.6em",
backgroundColor: token.colorSplit,
transform: "translateY(-50%)",
content: "\"\""
}
},
"tbody > tr": {
"> th, > td": { transition: `background-color ${token.motionDurationMid} ease` },
"&:hover > th, &:hover > td": { backgroundColor: token.colorFillAlter }
}
}
});
var getEditableStyles = (token) => {
const { componentCls, paddingSM } = token;
const inputShift = paddingSM;
return { "&-edit-content": {
position: "relative",
"div&": {
insetInlineStart: token.calc(token.paddingSM).mul(-1).equal(),
insetBlockStart: token.calc(inputShift).div(-2).add(1).equal(),
marginBottom: token.calc(inputShift).div(2).sub(2).equal()
},
[`${componentCls}-edit-content-confirm`]: {
position: "absolute",
insetInlineEnd: token.calc(token.marginXS).add(2).equal(),
insetBlockEnd: token.marginXS,
color: token.colorIcon,
fontWeight: "normal",
fontSize: token.fontSize,
fontStyle: "normal",
pointerEvents: "none"
},
[`&${componentCls}-edit-content textarea`]: {
margin: "0!important",
fontSize: "inherit",
lineHeight: "inherit",
fontFamily: "inherit",
fontWeight: "inherit",
MozTransition: "none",
height: "1em"
}
} };
};
var getCopyableStyles = (token) => ({
[`${token.componentCls}-copy-success`]: { "&, &:hover, &:focus": { color: token.colorSuccess } },
[`${token.componentCls}-copy-icon-only`]: { marginInlineStart: 0 }
});
var getEllipsisStyles = () => ({
"a&-ellipsis, span&-ellipsis": {
display: "inline-block",
maxWidth: "100%"
},
"&-ellipsis-single-line": {
...textEllipsis,
"a&, span&": { verticalAlign: "bottom" },
"> code": {
paddingBlock: 0,
maxWidth: "calc(100% - 1.2em)",
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis",
verticalAlign: "bottom",
boxSizing: "content-box"
}
},
"&-ellipsis-multiple-line": {
display: "-webkit-box",
overflow: "hidden",
WebkitLineClamp: 3,
WebkitBoxOrient: "vertical"
}
});
var genTypographyStyle = (token) => {
const { componentCls, titleMarginTop } = token;
return { [componentCls]: {
color: token.colorText,
wordBreak: "break-word",
lineHeight: token.lineHeight,
[`&${componentCls}-secondary, &${componentCls}-link${componentCls}-secondary`]: { color: token.colorTextDescription },
[`&${componentCls}-success, &${componentCls}-link${componentCls}-success`]: { color: token.colorSuccessText },
[`&${componentCls}-warning, &${componentCls}-link${componentCls}-warning`]: { color: token.colorWarningText },
[`&${componentCls}-danger, &${componentCls}-link${componentCls}-danger`]: {
color: token.colorErrorText,
[`&${componentCls}-link:active, &${componentCls}-link:focus`]: { color: token.colorErrorTextActive },
[`&${componentCls}-link:hover`]: { color: token.colorErrorTextHover }
},
[`&${componentCls}-disabled`]: {
color: token.colorTextDisabled,
cursor: "not-allowed",
userSelect: "none"
},
"div&, p": { marginBottom: "1em" },
...getTitleStyles(token),
[`& + h1${componentCls}, & + h2${componentCls}, & + h3${componentCls}, & + h4${componentCls}, & + h5${componentCls}`]: { marginTop: titleMarginTop },
"div, ul, li, p, h1, h2, h3, h4, h5": { "+ h1, + h2, + h3, + h4, + h5": { marginTop: titleMarginTop } },
...getResetStyles(token),
...getLinkStyles(token),
[`${componentCls}-actions`]: { display: "inline" },
[`
${componentCls}-expand,
${componentCls}-collapse,
${componentCls}-edit,
${componentCls}-copy
`]: {
...operationUnit(token),
marginInlineStart: token.marginXXS
},
[`${componentCls}-actions-start`]: { [`
${componentCls}-expand,
${componentCls}-collapse,
${componentCls}-edit,
${componentCls}-copy:not(${componentCls}-copy-icon-only)
`]: {
marginInlineStart: 0,
marginInlineEnd: token.marginXXS
} },
...getEditableStyles(token),
...getCopyableStyles(token),
...getEllipsisStyles(),
"&-rtl": { direction: "rtl" }
} };
};
var prepareComponentToken$5 = () => ({
titleMarginTop: "1.2em",
titleMarginBottom: "0.5em"
});
var style_default$5 = genStyleHooks("Typography", genTypographyStyle, prepareComponentToken$5);
var Editable = (props) => {
const { prefixCls, "aria-label": ariaLabel, className, style, classNames, styles, direction, maxLength, autoSize = true, value, onSave, onCancel, onEnd, component, enterIcon = /*#__PURE__*/ import_react$a.createElement(RefIcon$1$1, null) } = props;
const ref = import_react$a.useRef(null);
const inCompositionRef = import_react$a.useRef(false);
const lastKeyCodeRef = import_react$a.useRef(null);
const [current, setCurrent] = import_react$a.useState(value);
import_react$a.useEffect(() => {
setCurrent(value);
}, [value]);
import_react$a.useEffect(() => {
if (ref.current?.resizableTextArea) {
const { textArea } = ref.current.resizableTextArea;
textArea.focus();
const { length } = textArea.value;
textArea.setSelectionRange(length, length);
}
}, []);
const onChange = ({ target }) => {
setCurrent(target.value.replace(/[\n\r]/g, ""));
};
const onCompositionStart = () => {
inCompositionRef.current = true;
};
const onCompositionEnd = () => {
inCompositionRef.current = false;
};
const onKeyDown = ({ keyCode }) => {
if (inCompositionRef.current) return;
lastKeyCodeRef.current = keyCode;
};
const confirmChange = () => {
onSave(current.trim());
};
const onKeyUp = ({ keyCode, ctrlKey, altKey, metaKey, shiftKey }) => {
if (lastKeyCodeRef.current !== keyCode || inCompositionRef.current || ctrlKey || altKey || metaKey || shiftKey) return;
if (keyCode === KeyCode.ENTER) {
confirmChange();
onEnd?.();
} else if (keyCode === KeyCode.ESC) onCancel();
};
const onBlur = () => {
confirmChange();
};
const [hashId, cssVarCls] = style_default$5(prefixCls);
const textAreaClassName = clsx(prefixCls, `${prefixCls}-edit-content`, {
[`${prefixCls}-rtl`]: direction === "rtl",
[`${prefixCls}-${component}`]: !!component
}, className, classNames.root, hashId, cssVarCls);
return /*#__PURE__*/ import_react$a.createElement("div", {
className: textAreaClassName,
style: {
...styles.root,
...style
}
}, /*#__PURE__*/ import_react$a.createElement(TextArea, {
ref,
maxLength,
value: current,
onChange,
onKeyDown,
onKeyUp,
onCompositionStart,
onCompositionEnd,
onBlur,
"aria-label": ariaLabel,
rows: 1,
autoSize,
className: classNames.textarea,
style: styles.textarea
}), enterIcon !== null ? cloneElement(enterIcon, { className: `${prefixCls}-edit-content-confirm` }) : null);
};
var execCopy = (text, isHtmlFormat) => {
let copySuccess = false;
const onCopy = (event) => {
event.stopPropagation();
event.preventDefault();
event.clipboardData?.clearData();
event.clipboardData?.setData("text/plain", text);
if (isHtmlFormat) event.clipboardData?.setData("text/html", text);
copySuccess = true;
};
try {
document.addEventListener("copy", onCopy, { capture: true });
document.execCommand("copy");
return copySuccess;
} catch {
return false;
} finally {
document.removeEventListener("copy", onCopy, { capture: true });
}
};
var asyncCopy = async (text, isHtmlFormat) => {
try {
if (isHtmlFormat) await navigator.clipboard.write([new ClipboardItem({
"text/html": new Blob([text], { type: "text/html" }),
"text/plain": new Blob([text], { type: "text/plain" })
})]);
else await navigator.clipboard.writeText(text);
return true;
} catch {
return false;
}
};
async function copy(text, config) {
if (typeof text !== "string") return false;
const isHtmlFormat = config?.format === "text/html";
if (await asyncCopy(text, isHtmlFormat)) return true;
if (execCopy(text, isHtmlFormat)) return true;
return false;
}
var useCopyClick = ({ copyConfig, children }) => {
const [copied, setCopied] = useDelayState(false);
const [copyLoading, setCopyLoading] = import_react$a.useState(false);
const copyOptions = {};
if (copyConfig.format) copyOptions.format = copyConfig.format;
return {
copied,
copyLoading,
onClick: useEvent$1(async (e) => {
e?.preventDefault();
e?.stopPropagation();
setCopyLoading(true);
try {
await copy((isFunction(copyConfig.text) ? await copyConfig.text() : copyConfig.text) || toList(children, { skipEmpty: true }).join("") || "", copyOptions);
setCopyLoading(false);
setCopied(true, true);
setCopied(false, { ms: 3e3 });
copyConfig.onCopy?.(e);
} catch (error) {
setCopyLoading(false);
throw error;
}
})
};
};
var useMergedConfig = (propConfig, templateConfig) => {
const support = Boolean(propConfig);
return import_react$a.useMemo(() => {
const config = {
...templateConfig,
...support && isPlainObject(propConfig) ? propConfig : null
};
return [support, config];
}, [
support,
propConfig,
templateConfig
]);
};
var usePrevious$2 = (value) => {
const ref = (0, import_react$a.useRef)(void 0);
(0, import_react$a.useEffect)(() => {
ref.current = value;
});
return ref.current;
};
var useTooltipProps = (tooltip, editConfigText, children) => (0, import_react$a.useMemo)(() => {
if (tooltip === true) return { title: editConfigText ?? children };
if (/*#__PURE__*/ (0, import_react$a.isValidElement)(tooltip)) return { title: tooltip };
if (isPlainObject(tooltip)) return {
title: editConfigText ?? children,
...tooltip
};
return { title: tooltip };
}, [
tooltip,
editConfigText,
children
]);
var useTypographySemantic = (customizePrefixCls, classNames, styles, typographyDirection, props) => {
const { getPrefixCls, direction: contextDirection, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("typography");
const direction = typographyDirection ?? contextDirection;
const prefixCls = getPrefixCls("typography", customizePrefixCls);
const mergedProps = {
...props,
prefixCls,
direction
};
const contextClassNamesObject = (0, import_react$a.useMemo)(() => ({ root: contextClassName }), [contextClassName]);
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const [mergedClassNames, mergedStyles] = useMergeSemantic([
contextClassNamesObject,
contextClassNames,
classNames
], [
contextStyles,
contextStyleRoot,
styles
], { props: mergedProps });
return [
mergedClassNames,
mergedStyles,
prefixCls,
direction
];
};
var InternalTypography = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { component: Component = "article", className, rootClassName, children, direction, style, classNames, styles, prefixCls, ...restProps } = props;
const [hashId, cssVarCls] = style_default$5(prefixCls);
const componentClassName = clsx(prefixCls, { [`${prefixCls}-rtl`]: direction === "rtl" }, className, rootClassName, hashId, cssVarCls, classNames?.root);
const mergedStyle = {
...styles?.root,
...style
};
return /*#__PURE__*/ import_react$a.createElement(Component, {
...restProps,
className: componentClassName,
style: mergedStyle,
ref
}, children);
});
var Typography$1 = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { prefixCls: customizePrefixCls, className, rootClassName, direction: typographyDirection, classNames, styles, ...restProps } = props;
const [mergedClassNames, mergedStyles, mergedPrefixCls, mergedDirection] = useTypographySemantic(customizePrefixCls, classNames, styles, typographyDirection, props);
return /*#__PURE__*/ import_react$a.createElement(InternalTypography, {
ref,
className: clsx(className, rootClassName),
direction: mergedDirection,
classNames: mergedClassNames,
styles: mergedStyles,
prefixCls: mergedPrefixCls,
...restProps
});
});
var CopyOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z" }
}]
},
"name": "copy",
"theme": "outlined"
};
function _extends$6() {
_extends$6 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$6.apply(this, arguments);
}
var CopyOutlined = (props, ref) => /*#__PURE__*/ import_react$a.createElement(Icon, _extends$6({}, props, {
ref,
icon: CopyOutlined$1
}));
/** */
var RefIcon$3 = /*#__PURE__*/ import_react$a.forwardRef(CopyOutlined);
var toCopyConfigList = (val) => {
if (val === false) return [false, false];
return toList(val);
};
function getNode(dom, defaultNode, needDom) {
if (dom === true || dom === void 0) return defaultNode;
return dom || needDom && defaultNode;
}
/**
* Check for element is native ellipsis
* ref:
* - https://github.com/ant-design/ant-design/issues/50143
* - https://github.com/ant-design/ant-design/issues/50414
*/
function isEleEllipsis(ele) {
const childDiv = document.createElement("em");
ele.appendChild(childDiv);
const rect = ele.getBoundingClientRect();
const childRect = childDiv.getBoundingClientRect();
ele.removeChild(childDiv);
return rect.left > childRect.left || childRect.right > rect.right || rect.top > childRect.top || childRect.bottom > rect.bottom;
}
var isValidText = (val) => ["string", "number"].includes(typeof val);
var CopyBtn = (props) => {
const { prefixCls, copied, locale, iconOnly, tooltips, icon, tabIndex, onCopy, loading: btnLoading, className, style } = props;
const tooltipNodes = toCopyConfigList(tooltips);
const iconNodes = toCopyConfigList(icon);
const { copied: copiedText, copy: copyText } = locale ?? {};
const systemStr = copied ? copiedText : copyText;
const copyTitle = getNode(tooltipNodes[copied ? 1 : 0], systemStr);
const ariaLabel = typeof copyTitle === "string" ? copyTitle : systemStr;
return /*#__PURE__*/ import_react$a.createElement(Tooltip, { title: copyTitle }, /*#__PURE__*/ import_react$a.createElement("button", {
type: "button",
className: clsx(`${prefixCls}-copy`, className, {
[`${prefixCls}-copy-success`]: copied,
[`${prefixCls}-copy-icon-only`]: iconOnly
}),
style,
onClick: onCopy,
"aria-label": ariaLabel,
tabIndex
}, copied ? getNode(iconNodes[1], /*#__PURE__*/ import_react$a.createElement(RefIcon$12, null), true) : getNode(iconNodes[0], btnLoading ? /*#__PURE__*/ import_react$a.createElement(RefIcon$1$2, null) : /*#__PURE__*/ import_react$a.createElement(RefIcon$3, null), true)));
};
var MeasureText = /*#__PURE__*/ import_react$a.forwardRef(({ style, children }, ref) => {
const spanRef = import_react$a.useRef(null);
import_react$a.useImperativeHandle(ref, () => ({
isExceed: () => {
const span = spanRef.current;
return span.scrollHeight > span.clientHeight;
},
getHeight: () => spanRef.current.clientHeight
}));
return /*#__PURE__*/ import_react$a.createElement("span", {
"aria-hidden": true,
ref: spanRef,
style: {
position: "fixed",
display: "block",
left: 0,
top: 0,
pointerEvents: "none",
backgroundColor: "rgba(255, 0, 0, 0.65)",
...style
}
}, children);
});
var getNodesLen = (nodeList) => nodeList.reduce((totalLen, node) => totalLen + (isValidText(node) ? String(node).length : 1), 0);
function sliceNodes(nodeList, len) {
let currLen = 0;
const currentNodeList = [];
for (let i = 0; i < nodeList.length; i += 1) {
if (currLen === len) return currentNodeList;
const node = nodeList[i];
const nodeLen = isValidText(node) ? String(node).length : 1;
const nextLen = currLen + nodeLen;
if (nextLen > len) {
const restLen = len - currLen;
currentNodeList.push(String(node).slice(0, restLen));
return currentNodeList;
}
currentNodeList.push(node);
currLen = nextLen;
}
return nodeList;
}
var STATUS_MEASURE_NONE = 0;
var STATUS_MEASURE_PREPARE = 1;
var STATUS_MEASURE_START = 2;
var STATUS_MEASURE_NEED_ELLIPSIS = 3;
var STATUS_MEASURE_NO_NEED_ELLIPSIS = 4;
var lineClipStyle = {
display: "-webkit-box",
overflow: "hidden",
WebkitBoxOrient: "vertical"
};
function EllipsisMeasure(props) {
const { enableMeasure, width, text, children, rows, expanded, measureDeps, miscDeps, onEllipsis } = props;
const nodeList = import_react$a.useMemo(() => toArray$2(text), [text]);
const nodeLen = import_react$a.useMemo(() => getNodesLen(nodeList), [text]);
const fullContent = import_react$a.useMemo(() => children(nodeList, false), [text].concat(_toConsumableArray(measureDeps)));
const [ellipsisCutIndex, setEllipsisCutIndex] = import_react$a.useState(null);
const cutMidRef = import_react$a.useRef(null);
const measureWhiteSpaceRef = import_react$a.useRef(null);
const needEllipsisRef = import_react$a.useRef(null);
const descRowsEllipsisRef = import_react$a.useRef(null);
const symbolRowEllipsisRef = import_react$a.useRef(null);
const [canEllipsis, setCanEllipsis] = import_react$a.useState(false);
const [needEllipsis, setNeedEllipsis] = import_react$a.useState(STATUS_MEASURE_NONE);
const [ellipsisHeight, setEllipsisHeight] = import_react$a.useState(0);
const [parentWhiteSpace, setParentWhiteSpace] = import_react$a.useState(null);
useLayoutEffect$2(() => {
if (enableMeasure && width && nodeLen) setNeedEllipsis(STATUS_MEASURE_PREPARE);
else setNeedEllipsis(STATUS_MEASURE_NONE);
}, [
width,
text,
rows,
enableMeasure,
nodeList
].concat(_toConsumableArray(measureDeps)));
useLayoutEffect$2(() => {
if (needEllipsis === STATUS_MEASURE_PREPARE) {
setNeedEllipsis(STATUS_MEASURE_START);
const nextWhiteSpace = measureWhiteSpaceRef.current && getComputedStyle(measureWhiteSpaceRef.current).whiteSpace;
setParentWhiteSpace(nextWhiteSpace);
} else if (needEllipsis === STATUS_MEASURE_START) {
const isOverflow = !!needEllipsisRef.current?.isExceed();
setNeedEllipsis(isOverflow ? STATUS_MEASURE_NEED_ELLIPSIS : STATUS_MEASURE_NO_NEED_ELLIPSIS);
setEllipsisCutIndex(isOverflow ? [0, nodeLen] : null);
setCanEllipsis(isOverflow);
const baseRowsEllipsisHeight = needEllipsisRef.current?.getHeight() || 0;
const descRowsEllipsisHeight = rows === 1 ? 0 : descRowsEllipsisRef.current?.getHeight() || 0;
const symbolRowEllipsisHeight = symbolRowEllipsisRef.current?.getHeight() || 0;
const maxRowsHeight = Math.max(baseRowsEllipsisHeight, descRowsEllipsisHeight + symbolRowEllipsisHeight);
setEllipsisHeight(maxRowsHeight + 1);
onEllipsis(isOverflow);
}
}, [needEllipsis]);
const cutMidIndex = ellipsisCutIndex ? Math.ceil((ellipsisCutIndex[0] + ellipsisCutIndex[1]) / 2) : 0;
useLayoutEffect$2(() => {
const [minIndex, maxIndex] = ellipsisCutIndex || [0, 0];
if (minIndex !== maxIndex) {
const isOverflow = (cutMidRef.current?.getHeight() || 0) > ellipsisHeight;
let targetMidIndex = cutMidIndex;
if (maxIndex - minIndex === 1) targetMidIndex = isOverflow ? minIndex : maxIndex;
setEllipsisCutIndex(isOverflow ? [minIndex, targetMidIndex] : [targetMidIndex, maxIndex]);
}
}, [ellipsisCutIndex, cutMidIndex]);
const finalContent = import_react$a.useMemo(() => {
if (!enableMeasure) return children(nodeList, false);
if (needEllipsis !== STATUS_MEASURE_NEED_ELLIPSIS || !ellipsisCutIndex || ellipsisCutIndex[0] !== ellipsisCutIndex[1]) {
const content = children(nodeList, false);
if ([STATUS_MEASURE_NO_NEED_ELLIPSIS, STATUS_MEASURE_NONE].includes(needEllipsis)) return content;
return /*#__PURE__*/ import_react$a.createElement("span", { style: {
...lineClipStyle,
WebkitLineClamp: rows
} }, content);
}
return children(expanded ? nodeList : sliceNodes(nodeList, ellipsisCutIndex[0]), canEllipsis);
}, [
expanded,
needEllipsis,
ellipsisCutIndex,
nodeList
].concat(_toConsumableArray(miscDeps)));
const measureStyle = {
width,
margin: 0,
padding: 0,
whiteSpace: parentWhiteSpace === "nowrap" ? "normal" : "inherit"
};
return /*#__PURE__*/ import_react$a.createElement(import_react$a.Fragment, null, finalContent, needEllipsis === STATUS_MEASURE_START && /*#__PURE__*/ import_react$a.createElement(import_react$a.Fragment, null, /*#__PURE__*/ import_react$a.createElement(MeasureText, {
style: {
...measureStyle,
...lineClipStyle,
WebkitLineClamp: rows
},
ref: needEllipsisRef
}, fullContent), /*#__PURE__*/ import_react$a.createElement(MeasureText, {
style: {
...measureStyle,
...lineClipStyle,
WebkitLineClamp: rows - 1
},
ref: descRowsEllipsisRef
}, fullContent), /*#__PURE__*/ import_react$a.createElement(MeasureText, {
style: {
...measureStyle,
...lineClipStyle,
WebkitLineClamp: 1
},
ref: symbolRowEllipsisRef
}, children([], true))), needEllipsis === STATUS_MEASURE_NEED_ELLIPSIS && ellipsisCutIndex && ellipsisCutIndex[0] !== ellipsisCutIndex[1] && /*#__PURE__*/ import_react$a.createElement(MeasureText, {
style: {
...measureStyle,
top: 400
},
ref: cutMidRef
}, children(sliceNodes(nodeList, cutMidIndex), true)), needEllipsis === STATUS_MEASURE_PREPARE && /*#__PURE__*/ import_react$a.createElement("span", {
style: { whiteSpace: "inherit" },
ref: measureWhiteSpaceRef
}));
}
var EllipsisTooltip = ({ enableEllipsis, isEllipsis, disabled, children, tooltipProps }) => {
if (!tooltipProps?.title || !enableEllipsis) return children;
return /*#__PURE__*/ import_react$a.createElement(Tooltip, {
...tooltipProps,
disabled: !isEllipsis || disabled
}, children);
};
function wrapperDecorations({ mark, code, underline, delete: del, strong, keyboard, italic }, content) {
let currentContent = content;
function wrap(tag, needed) {
if (!needed) return;
currentContent = /*#__PURE__*/ import_react$a.createElement(tag, {}, currentContent);
}
wrap("strong", strong);
wrap("u", underline);
wrap("del", del);
wrap("code", code);
wrap("mark", mark);
wrap("kbd", keyboard);
wrap("i", italic);
return currentContent;
}
var ELLIPSIS_STR = "...";
var DECORATION_PROPS = [
"delete",
"mark",
"code",
"underline",
"strong",
"keyboard",
"italic"
];
var Base = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { prefixCls: customizePrefixCls, className, style, classNames, styles, direction: typographyDirection, type, disabled, children, ellipsis, editable, copyable, actions, component, title, onMouseEnter, onMouseLeave, ...restProps } = props;
const [textLocale] = useLocale("Text");
const typographyRef = import_react$a.useRef(null);
const editIconRef = import_react$a.useRef(null);
const [mergedClassNames, mergedStyles, prefixCls, direction] = useTypographySemantic(customizePrefixCls, classNames, styles, typographyDirection, props);
const textProps = omit(restProps, DECORATION_PROPS);
const [enableEdit, editConfig] = useMergedConfig(editable);
const [editing, setEditing] = useControlledState(false, editConfig.editing);
const { triggerType = ["icon"] } = editConfig;
const triggerEdit = (edit) => {
if (edit) editConfig.onStart?.();
setEditing(edit);
};
const prevEditing = usePrevious$2(editing);
useLayoutEffect$2(() => {
if (!editing && prevEditing) editIconRef.current?.focus();
}, [editing]);
const onEditClick = (e) => {
e?.preventDefault();
triggerEdit(true);
};
const onEditChange = (value) => {
editConfig.onChange?.(value);
triggerEdit(false);
};
const onEditCancel = () => {
editConfig.onCancel?.();
triggerEdit(false);
};
const [enableCopy, copyConfig] = useMergedConfig(copyable);
const { placement = "end" } = actions ?? {};
const { copied, copyLoading, onClick: onCopyClick } = useCopyClick({
copyConfig,
children
});
const [isLineClampSupport, setIsLineClampSupport] = import_react$a.useState(false);
const [isTextOverflowSupport, setIsTextOverflowSupport] = import_react$a.useState(false);
const [isJsEllipsis, setIsJsEllipsis] = import_react$a.useState(false);
const [isNativeEllipsis, setIsNativeEllipsis] = import_react$a.useState(false);
const [isNativeVisible, setIsNativeVisible] = import_react$a.useState(true);
const [enableEllipsis, ellipsisConfig] = useMergedConfig(ellipsis, {
expandable: false,
symbol: (isExpanded) => isExpanded ? textLocale?.collapse : textLocale?.expand
});
const [expanded, setExpanded] = useControlledState(ellipsisConfig.defaultExpanded || false, ellipsisConfig.expanded);
const mergedEnableEllipsis = enableEllipsis && (!expanded || ellipsisConfig.expandable === "collapsible");
const { rows = 1 } = ellipsisConfig;
const needMeasureEllipsis = import_react$a.useMemo(() => mergedEnableEllipsis && (ellipsisConfig.suffix !== void 0 || ellipsisConfig.onEllipsis || ellipsisConfig.expandable || enableEdit || enableCopy), [
mergedEnableEllipsis,
ellipsisConfig,
enableEdit,
enableCopy
]);
useLayoutEffect$2(() => {
if (enableEllipsis && !needMeasureEllipsis) {
setIsLineClampSupport(isStyleSupport("webkitLineClamp"));
setIsTextOverflowSupport(isStyleSupport("textOverflow"));
}
}, [needMeasureEllipsis, enableEllipsis]);
const [cssEllipsis, setCssEllipsis] = import_react$a.useState(mergedEnableEllipsis);
const canUseCssEllipsis = import_react$a.useMemo(() => {
if (needMeasureEllipsis) return false;
if (rows === 1) return isTextOverflowSupport;
return isLineClampSupport;
}, [
needMeasureEllipsis,
rows,
isLineClampSupport,
isTextOverflowSupport
]);
useLayoutEffect$2(() => {
setCssEllipsis(canUseCssEllipsis && mergedEnableEllipsis);
}, [canUseCssEllipsis, mergedEnableEllipsis]);
const tooltipProps = useTooltipProps(ellipsisConfig.tooltip, editConfig.text, children);
const needNativeEllipsisMeasure = cssEllipsis && !!tooltipProps.title;
const isMergedEllipsis = mergedEnableEllipsis && (cssEllipsis ? needNativeEllipsisMeasure && isNativeEllipsis : isJsEllipsis);
const cssTextOverflow = mergedEnableEllipsis && rows === 1 && cssEllipsis;
const cssLineClamp = mergedEnableEllipsis && rows > 1 && cssEllipsis;
const onExpandClick = (e, info) => {
setExpanded(info.expanded);
ellipsisConfig.onExpand?.(e, info);
};
const [ellipsisWidth, setEllipsisWidth] = import_react$a.useState(0);
const [isHoveringOperations, setIsHoveringOperations] = useDelayState(false);
const isHoveringTypographyRef = import_react$a.useRef(false);
const onResize = ({ offsetWidth }) => {
setEllipsisWidth(offsetWidth);
};
const onJsEllipsis = (jsEllipsis) => {
setIsJsEllipsis(jsEllipsis);
if (isJsEllipsis !== jsEllipsis) ellipsisConfig.onEllipsis?.(jsEllipsis);
};
const measureNativeEllipsis = import_react$a.useCallback(() => {
const textEle = typographyRef.current;
if (enableEllipsis && needNativeEllipsisMeasure && textEle) {
const currentEllipsis = isEleEllipsis(textEle);
setIsNativeEllipsis((prev) => prev === currentEllipsis ? prev : currentEllipsis);
}
}, [enableEllipsis, needNativeEllipsisMeasure]);
import_react$a.useEffect(() => {
if (isHoveringTypographyRef.current) measureNativeEllipsis();
}, [
measureNativeEllipsis,
children,
cssLineClamp,
isNativeVisible,
ellipsisWidth
]);
import_react$a.useEffect(() => {
const textEle = typographyRef.current;
if (typeof IntersectionObserver === "undefined" || !textEle || !needNativeEllipsisMeasure || !mergedEnableEllipsis) return;
const observer = new IntersectionObserver(() => {
setIsNativeVisible(!!textEle.offsetParent);
});
observer.observe(textEle);
return () => {
observer.disconnect();
};
}, [needNativeEllipsisMeasure, mergedEnableEllipsis]);
const topAriaLabel = import_react$a.useMemo(() => {
if (!enableEllipsis || cssEllipsis) return;
return [
editConfig.text,
children,
title,
tooltipProps.title
].find(isValidText);
}, [
enableEllipsis,
cssEllipsis,
title,
tooltipProps.title,
isMergedEllipsis,
editConfig.text
]);
if (editing) return /*#__PURE__*/ import_react$a.createElement(Editable, {
value: editConfig.text ?? (typeof children === "string" ? children : ""),
onSave: onEditChange,
onCancel: onEditCancel,
onEnd: editConfig.onEnd,
prefixCls,
className,
style,
direction,
component,
maxLength: editConfig.maxLength,
autoSize: editConfig.autoSize,
enterIcon: editConfig.enterIcon,
classNames: mergedClassNames,
styles: mergedStyles
});
const renderExpand = () => {
const { expandable, symbol } = ellipsisConfig;
return expandable ? /*#__PURE__*/ import_react$a.createElement("button", {
type: "button",
key: "expand",
className: clsx(`${prefixCls}-${expanded ? "collapse" : "expand"}`, mergedClassNames.action),
style: mergedStyles.action,
onClick: (e) => onExpandClick(e, { expanded: !expanded }),
"aria-label": expanded ? textLocale.collapse : textLocale?.expand
}, isFunction(symbol) ? symbol(expanded) : symbol) : null;
};
const renderEdit = () => {
if (!enableEdit) return;
const { icon, tooltip, tabIndex } = editConfig;
const editTitle = toArray$2(tooltip)[0] || textLocale?.edit;
const ariaLabel = typeof editTitle === "string" ? editTitle : "";
return triggerType.includes("icon") ? /*#__PURE__*/ import_react$a.createElement(Tooltip, {
key: "edit",
title: tooltip === false ? "" : editTitle
}, /*#__PURE__*/ import_react$a.createElement("button", {
type: "button",
ref: editIconRef,
className: clsx(`${prefixCls}-edit`, mergedClassNames.action),
style: mergedStyles.action,
onClick: onEditClick,
"aria-label": ariaLabel,
tabIndex
}, icon || /*#__PURE__*/ import_react$a.createElement(RefIcon$2$1, { role: "button" }))) : null;
};
const renderCopy = () => {
if (!enableCopy) return null;
return /*#__PURE__*/ import_react$a.createElement(CopyBtn, {
key: "copy",
...copyConfig,
prefixCls,
copied,
locale: textLocale,
onCopy: onCopyClick,
loading: copyLoading,
iconOnly: !isReactRenderable(children),
className: mergedClassNames.action,
style: mergedStyles.action
});
};
const renderOperations = (canEllipsis) => {
const expandNode = canEllipsis && renderExpand();
const editNode = renderEdit();
const copyNode = renderCopy();
if (!expandNode && !editNode && !copyNode) return null;
return /*#__PURE__*/ import_react$a.createElement("span", {
key: "operations",
className: clsx(`${prefixCls}-actions`, mergedClassNames.actions, { [`${prefixCls}-actions-start`]: placement === "start" }),
style: mergedStyles.actions,
onMouseEnter: () => setIsHoveringOperations(true, true),
onMouseLeave: () => setIsHoveringOperations(false, { ms: 500 })
}, expandNode, editNode, copyNode);
};
const renderEllipsis = (canEllipsis) => [canEllipsis && !expanded && /*#__PURE__*/ import_react$a.createElement("span", {
"aria-hidden": true,
key: "ellipsis"
}, ELLIPSIS_STR), ellipsisConfig.suffix];
return /*#__PURE__*/ import_react$a.createElement(RefResizeObserver, {
onResize,
disabled: !mergedEnableEllipsis
}, (resizeRef) => /*#__PURE__*/ import_react$a.createElement(EllipsisTooltip, {
tooltipProps,
enableEllipsis: mergedEnableEllipsis,
isEllipsis: isMergedEllipsis,
disabled: isHoveringOperations
}, /*#__PURE__*/ import_react$a.createElement(InternalTypography, {
onMouseEnter: (e) => {
isHoveringTypographyRef.current = true;
measureNativeEllipsis();
onMouseEnter?.(e);
},
onMouseLeave: (e) => {
isHoveringTypographyRef.current = false;
onMouseLeave?.(e);
},
className: clsx({
[`${prefixCls}-${type}`]: type,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-ellipsis`]: enableEllipsis,
[`${prefixCls}-ellipsis-single-line`]: cssTextOverflow,
[`${prefixCls}-ellipsis-multiple-line`]: cssLineClamp,
[`${prefixCls}-link`]: component === "a"
}, className),
classNames: mergedClassNames,
styles: mergedStyles,
prefixCls,
style: {
...style,
WebkitLineClamp: cssLineClamp ? rows : void 0
},
component,
ref: composeRef(resizeRef, typographyRef, ref),
direction,
onClick: triggerType.includes("text") ? onEditClick : void 0,
"aria-label": topAriaLabel?.toString(),
title,
...textProps
}, /*#__PURE__*/ import_react$a.createElement(EllipsisMeasure, {
enableMeasure: mergedEnableEllipsis && !cssEllipsis,
text: children,
rows,
width: ellipsisWidth,
onEllipsis: onJsEllipsis,
expanded,
measureDeps: [placement],
miscDeps: [
copied,
expanded,
copyLoading,
enableEdit,
enableCopy,
placement,
textLocale
].concat(_toConsumableArray(DECORATION_PROPS.map((key) => props[key])))
}, (node, canEllipsis) => wrapperDecorations(props, /*#__PURE__*/ import_react$a.createElement(import_react$a.Fragment, null, placement === "start" ? renderOperations(canEllipsis) : null, node.length > 0 && canEllipsis && !expanded && topAriaLabel ? /*#__PURE__*/ import_react$a.createElement("span", {
key: "show-content",
"aria-hidden": true
}, node) : node, renderEllipsis(canEllipsis), placement === "start" ? null : renderOperations(canEllipsis)))))));
});
var Link = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { ellipsis, rel, children, navigate: _navigate, ...restProps } = props;
const mergedProps = {
...restProps,
rel: rel === void 0 && restProps.target === "_blank" ? "noopener noreferrer" : rel
};
return /*#__PURE__*/ import_react$a.createElement(Base, {
...mergedProps,
ref,
ellipsis: !!ellipsis,
component: "a"
}, children);
});
var Paragraph = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { children, ...restProps } = props;
return /*#__PURE__*/ import_react$a.createElement(Base, {
ref,
...restProps,
component: "div"
}, children);
});
var Text = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { ellipsis, children, ...restProps } = props;
const mergedEllipsis = import_react$a.useMemo(() => {
if (isPlainObject(ellipsis)) return omit(ellipsis, ["expandable", "rows"]);
return ellipsis;
}, [ellipsis]);
return /*#__PURE__*/ import_react$a.createElement(Base, {
ref,
...restProps,
ellipsis: mergedEllipsis,
component: "span"
}, children);
});
var TITLE_ELE_LIST = [
1,
2,
3,
4,
5
];
var Title = /*#__PURE__*/ import_react$a.forwardRef((props, ref) => {
const { level = 1, children, ...restProps } = props;
const component = TITLE_ELE_LIST.includes(level) ? `h${level}` : `h1`;
return /*#__PURE__*/ import_react$a.createElement(Base, {
ref,
...restProps,
component
}, children);
});
var Typography = exports("e",Typography$1);
Typography.Text = Text;
Typography.Link = Link;
Typography.Title = Title;
Typography.Paragraph = Paragraph;var getScroll = (target) => {
if (typeof window === "undefined")
/* istanbul ignore next */
return 0;
let result = 0;
if (isWindow$1(target)) result = target.pageYOffset;
else if (isDocument$1(target)) result = target.documentElement.scrollTop;
else if (isHTMLElement$1(target)) result = target.scrollTop;
else if (target) result = target["scrollTop"];
if (target && !isWindow$1(target) && !isNumber$1(result)) result = (target.ownerDocument ?? target).documentElement?.scrollTop;
return result;
};
function easeInOutCubic(t, b, c, d) {
const cc = c - b;
t /= d / 2;
if (t < 1) return cc / 2 * t * t * t + b;
return cc / 2 * ((t -= 2) * t * t + 2) + b;
}
var scrollTo = exports("ba",(y, options = {}) => {
const { getContainer = () => window, callback, duration = 450 } = options;
const container = getContainer();
const scrollTop = getScroll(container);
const scroll = (top) => {
if (isWindow$1(container)) container.scrollTo(window.pageXOffset, top);
else if (isDocument$1(container)) container.documentElement.scrollTop = top;
else container.scrollTop = top;
};
if (duration <= 0) {
scroll(y);
if (isFunction(callback)) callback();
return () => {};
}
const startTime = Date.now();
let rafId;
const frameFunc = () => {
const time = Date.now() - startTime;
const nextScrollTop = easeInOutCubic(time > duration ? duration : time, scrollTop, y, duration);
scroll(nextScrollTop);
if (time < duration) rafId = wrapperRaf(frameFunc);
else if (isFunction(callback)) callback();
};
rafId = wrapperRaf(frameFunc);
return () => {
wrapperRaf.cancel(rafId);
};
});/**
* [tag-rec] 两 tab 共享会话态 (spec §3.6, 不落盘)
* 「显示已看」(bypassSeen) 已拆为各 tab 会话自有 flag (见 tagRecFavSession / tagRecCustomSession) ——
* 主题推荐默认滤掉已看、标签发掘默认显示已看 (调试便利); 见底横幅「临时放开已看」只写本 tab flag
*/
var tagRecSharedSession = exports("t",proxy({
/** 挑选观看范围期间,卡片不记已看。 */
scopeEditing: false,
/** 清空观察记录后刷新查询;也让在途刷新识别出条件已变。 */
recoveryRevision: 0,
/** 风控退避态镜像 (票 16 ③): 横幅槽位消费; BackoffGate.onStateChange 驱动 */
riskControl: false,
/**
* 票 18 (tag-share): 分享导入请求位 —— service/share-import 写, ui/share-import-modal 消费
* - origin 记触发源 ('deeplink'=深链走 tabbar 挂载点 / 'paste'=粘贴走 pane 挂载点),
* 双挂载点各自只渲自家 origin, 防 pane 开着时两处 Modal 叠开
* - draft 经 valtio ref() 脱壳 (plan 保持 raw —— 确认落表直推 store 不碰 frozen proxy)
*/
shareImport: {
loading: false,
origin: void 0,
draft: void 0
},
/**
* R7 (原票 18): 分享导入信息横幅位 (会话级一次性; 每次导入出) —— 携带包内一级组/主题:
* 文案按「已启用/未启用」分档; [只看这个包的主题] = 查看范围 onlyThemes; [去开启] 定位
*/
shareGuide: void 0,
/**
* R7 临时打开 (会话级): 未启用 L1 的临时入抽集 —— 刷新还原, 不影响持久 enabled。
* 主题下拉「临时打开」写, 首屏/deck 构造经 withTagRecTempEnables 合流 (deck.ts)
*/
tempEnabledL1Ids: [],
/** 精确到二级组的临时开启;不写入收藏表。 */
tempEnabledGroupIds: [],
/**
* 票 19 (tag-share): 体验后回赞横幅位 —— service/share-like 武装
* (pendingShareId 在 ∧ 未赞未拒 ∧ tab1 上屏 ≥20); banner 槽位消费:
* state==='none' 且 shareGuide 不在时出 (同槽互斥); busy = 点赞请求在飞
*/
shareLike: void 0
}));
tagRecBackoff.onStateChange = (engaged) => {
tagRecSharedSession.riskControl = engaged;
};
/** [tag-rec] 每轮滤除统计空值工厂 (spec §3.6 滤除计数: {dimension:count} 明细+合计) */
function emptyTagRecFilterStats() {
return {
dropped: 0,
byDim: {},
degraded: false
};
}
/** [tag-rec] tab1「主题推荐」会话态 (spec §3.6, 不落盘) */
var tagRecFavSession = exports("O",proxy({
/** 「显示已看」会话 flag: 开=去已看阶段直通; 主题推荐求新, 默认关=滤掉已看 (§3.4) */
bypassSeen: false,
partitionPending: 0,
partitionPaused: false,
/** 主题推荐临时过滤绑定当前查看范围,与标签发掘独立。 */
filterOverride: void 0,
tempWriteLog: [],
filterScopeSignature: void 0,
filterScopeRevision: 0,
/** 软见底标记 (连续 2 轮产出 <5, spec §4.4; 横幅消费归票 09) */
softBottom: false,
/** [tag-rec] 硬见底标记 (单轮产出 0, spec §4.4; service exhaustRound 置位, 横幅消费归票 09) */
hardBottom: false,
/** [tag-rec] 深挖待决态镜像 (票 13: 自动深挖 K 用尽但深挖流未尽 => 停自动加载待手动「继续深挖」, 非硬见底) */
digPending: false,
/** [tag-rec] 手动深挖在途标记 (横幅「继续深挖」按钮 loading/防重点) */
digRunning: false,
/** 最近一轮抽中的组名 (tabbar 状态文本 / 归因展示) */
drawnGroupNames: [],
/**
* 无可抽单位提示 (实机修复): 主题下 L1 未启用/麾下全挂起等「配置性空轮」落此而非 hardBottom ——
* 横幅出「启用一级组」直达动作, 不冒充「已到池底」; reason = in-feed separator 同文案
*/
noDrawableHint: void 0,
/** [tag-rec] 最近一轮滤除统计 (分维度计数+小池降级标记; 横幅消费归票 09) */
filterStats: emptyTagRecFilterStats()
}));
var tagRecCustomSession = exports("ad",proxy({
/** Temporary browsing pace; absent from persisted trials and saved recipes. */
supplyPaused: false,
/** 「显示已看」会话 flag: 开=去已看阶段直通; 发掘语境默认开=显示已看, 便于调试观察 (§3.4) */
bypassSeen: true,
workingGroup: void 0,
allowUnverified: false,
partitionPending: 0,
partitionPaused: false,
/** 票 005: 当前到上限停轮转的停牌条数 (每轮按账目重算; 上屏「放弃 N 条」) */
partitionAbandoned: 0,
/** 票 006: 本轮补查额度用尽标记 (停牌原因之一; 「重试核实」/语境切换归零) */
partitionBudgetExhausted: false,
/** 票 006: 本次访问本地回放交付的条数 (语境切换归零; 上屏「回放 N 条」) */
replayDelivered: 0,
/**
* 选条件加词后的本地先行闸 (Q5b): 加入条件先只吃本地可交付存量 (零请求), 存量耗尽或
* 显式查找 (beginTagRecCustomQuery) 才放行远程取数 —— 低频词点击不再立即引发请求风暴。
*/
localFirst: false,
/** 主查询换代编号;独立扩选解析绑定此编号,切走再切回也不接受旧结果。 */
queryRevision: 0,
/**
* [tag-rec] tab2 选中组 id 列表 (spec §3.6/§4.3): 会话内有效不持久; 正常+挂起可列, 停用不进
* 票 21 M3: 保数组但限长 1 (序列化 shape 不破, UI 层收单选); 与草稿组互斥, 写入一律走
* switchTagRecGroupContext / doResolveAnchor 清对侧
*/
selectedGroupIds: [],
/** 会话级草稿组: 首个临时输入自动成立 (票 14「临时输入即草稿组」) */
draftGroup: void 0,
/** 已提交的临时输入原文: 文本 = tag 名; 纯数字 = tag_id 直输 (spec §4.1 输入分流) */
inputText: "",
/** service 解析回填的锚点 (tag_id + name): 草稿组命名/深挖流 key/扩选去重/存成项首成员/弹层 isAnchor/深挖与存成项门禁 */
anchor: void 0,
/** 候选清单行: Phase 2 产出 ({tag_name,tag_id,use,共现频次?,来源徽章,checkbox}, spec §4.2) */
candidates: [],
/** [tag-rec] 候选清单收起态 (票 10): 默认收起为单行摘要不挡视频流; 展开/收起随会话, 新输入重置收起 */
candidatesCollapsed: true,
/** [tag-rec] 组内成员 chip 区收起态: 默认收起, 开关在候选清单动作行「存成项/并入当前组」右侧; 纯显示偏好随会话不重置 */
membersCollapsed: true,
/** [tag-rec] 「已扩选」chip 区收起态: 同上, 默认收起 */
expandedCollapsed: true,
/**
* 「扩进本次查询」并入的 tag_id (service 每轮开头并入查询单元; 提交新输入时清空)
* 票 22: 统一为「语境无关的会话级扩选暂存位」(试用层) —— ★「加入当前组」与下拉勾选同写此位,
* 两语境通用; 入表统一收口到「顺手存成项/并入当前组」(选定组语境并入该组, 草稿语境升级建组);
* 可见位 = 候选清单顶部「已扩选 N」chip 区, ✕ 即删 (下轮生效)
*/
expandedIds: [],
/** Phase 2/深挖流聚合状态 (UI 展示/「继续深挖」开关; 票 13 扩: 自动深挖计数+上轮净增) */
phase2: {
status: "idle",
pagesFetched: 0,
exhausted: false,
autoDigs: 0,
lastDigYield: 0
},
/** [tag-rec] 深挖待决态镜像 (票 13: 自动深挖 K 用尽但流未尽 => 停自动加载待手动; 非硬见底, 横幅新态) */
digPending: false,
/** [tag-rec] 手动深挖在途标记 (横幅「继续深挖」按钮 loading/防重点) */
digRunning: false,
/** 软见底标记 (连续 2 轮产出 <5, spec §4.4; 横幅消费归票 09) */
softBottom: false,
/** [tag-rec] 硬见底标记 (单轮产出 0 且给出见底 separator, spec §4.4; service exhaustRound 置位, 横幅消费归票 09) */
hardBottom: false,
/** [tag-rec] 最近一轮滤除统计 (分维度计数+小池降级标记; 横幅消费归票 09) */
filterStats: emptyTagRecFilterStats(),
/**
* 当前锚点池的临时过滤差分,不落盘;表单改动/右键临时操作创建。
* 换输入或标签组、手动清除、整页刷新时丢弃;同一锚点重查/深挖/扩选保留。
*/
filterOverride: void 0,
/** R8 临时账 (右键「临时 · 本会话」写入记录) —— 过滤弹层「临时名单」展示 + 固化/清空按账操作 */
tempWriteLog: []
}));
function tagRecFilterSession(scope = "custom") {
return scope === "fav" ? tagRecFavSession : tagRecCustomSession;
}
function resetTagRecFavFilter() {
tagRecFavSession.filterOverride = void 0;
tagRecFavSession.tempWriteLog = [];
}
/** 同范围重刷保留临时过滤;切换观看范围时解除,并关闭旧编辑面板。 */
function syncTagRecFavFilterScope(signature) {
const previous = tagRecFavSession.filterScopeSignature;
if (previous !== void 0 && signature !== previous) {
resetTagRecFavFilter();
tagRecFavSession.filterScopeRevision++;
}
tagRecFavSession.filterScopeSignature = signature;
}
/** 清除本锚点的全部临时过滤及右键账本,回到「标签过滤」。 */
function resetTagRecCustomFilter() {
tagRecCustomSession.filterOverride = void 0;
tagRecCustomSession.tempWriteLog = [];
}
/** 重置解析进度;过滤的去留由输入/选组入口按查询语境决定。 */
function resetTagRecCustomQueryState(inputText) {
tagRecCustomSession.queryRevision++;
tagRecCustomSession.inputText = inputText;
tagRecCustomSession.anchor = void 0;
tagRecCustomSession.candidates = [];
tagRecCustomSession.candidatesCollapsed = true;
tagRecCustomSession.expandedIds = [];
tagRecCustomSession.phase2 = {
status: "idle",
pagesFetched: 0,
exhausted: false,
autoDigs: 0,
lastDigYield: 0
};
tagRecCustomSession.digPending = false;
tagRecCustomSession.digRunning = false;
tagRecCustomSession.softBottom = false;
tagRecCustomSession.hardBottom = false;
tagRecCustomSession.filterStats = emptyTagRecFilterStats();
tagRecCustomSession.draftGroup = void 0;
}
/** 提交新输入;换锚点清除临时过滤,同一输入重查保留。 */
function beginTagRecCustomQuery(raw) {
const inputText = raw.trim();
tagRecCustomSession.localFirst = false;
if (inputText !== tagRecCustomSession.inputText || tagRecCustomSession.selectedGroupIds.length > 0) resetTagRecCustomFilter();
resetTagRecCustomQueryState(inputText);
}
/**
* 票 21 组语境互斥: tab2 同时只允许一个组语境 —— 选定组 × 草稿组双向互斥, 后动作者赢。
* 选择器选组/清选 => 弃临时输入侧: 整段查询态重置 (等价 beginTagRecCustomQuery(''),
* 含 inputText/anchor/draftGroup/candidates/expandedIds/phase2/见底镜像), 再立选定组 (限长 1)。
* 草稿组非空判定 + 「顺手存入」落表在 UI 侧先于本动作执行 (tabbar-custom GroupSelect);
* 反向 (输入解析成功 => 清选定组) 挂点在 service doResolveAnchor
*/
function switchTagRecGroupContext(groupId) {
const selectedIds = tagRecCustomSession.selectedGroupIds;
const sameGroup = groupId ? selectedIds.length === 1 && selectedIds[0] === groupId : selectedIds.length === 0;
if (tagRecCustomSession.inputText || !sameGroup) resetTagRecCustomFilter();
resetTagRecCustomQueryState("");
tagRecCustomSession.selectedGroupIds = groupId ? [groupId] : [];
}
/** 合并后跟随目标组重建查询语境;未固化的扩选保留,旧在途动作随候选引用失效。 */
function reconcileTagRecMergedGroupContext(sourceIds, targetId) {
const affected = /* @__PURE__ */ new Set([...sourceIds, targetId]);
if (tagRecCustomSession.selectedGroupIds.some((id) => affected.has(id))) {
const expanded = [...tagRecCustomSession.expandedIds];
resetTagRecCustomQueryState("");
tagRecCustomSession.selectedGroupIds = [targetId];
tagRecCustomSession.expandedIds = expanded;
} else if (tagRecCustomSession.draftGroup?.savedGroupId && affected.has(tagRecCustomSession.draftGroup.savedGroupId)) {
tagRecCustomSession.draftGroup.savedGroupId = targetId;
tagRecCustomSession.candidates = [...tagRecCustomSession.candidates];
}
}
/**
* 扩选暂存位写入 (★「加入当前组」/ 下拉「扩进本次查询」共用):
* 去重 = 已在暂存位 / 锚点自身 / 当前组真成员 (currentGroupTagIds, 选定组语境传入) 全跳过;
* 入参内部同 id 亦去重。新增成功的行顺手同步候选行 expanded (「已并入」徽标;
* checked 不伪造 —— 勾中=用户并入意图, 候选行是 saveAsFavoriteGroup 的并入来源,
* 伪造勾中会让 ✕ 取消后该 tag 仍经勾中行并入)。返回实际并入的条目
*/
function pushExpandedIds(entries, currentGroupTagIds = []) {
const have = new Set(tagRecCustomSession.expandedIds.map((e) => e.id));
if (tagRecCustomSession.anchor) have.add(tagRecCustomSession.anchor.id);
for (const e of currentGroupTagIds) have.add(e.id);
const adds = dedupeTagIdEntries(entries).filter((e) => !have.has(e.id));
if (!adds.length) return [];
tagRecCustomSession.expandedIds.push(...adds);
const ids = new Set(adds.map((e) => e.id));
for (const c of tagRecCustomSession.candidates) if (c.tag_id !== void 0 && ids.has(c.tag_id)) c.expanded = true;
return adds;
}
/**
* 扩选区 chip ✕ 取消 (票 22): 只删会话级暂存位 (下轮不再进查询单元), 不动已入表的组内容;
* 对应候选行 expanded+checked 一并清回 —— 实机修复: 仅清 expanded 留 checked, 勾中行仍是
* 「顺手存成项/并入当前组」的并入来源, 取消的 tag 会照旧入组
*/
function removeExpandedId(tagId) {
const i = tagRecCustomSession.expandedIds.findIndex((e) => e.id === tagId);
if (i === -1) return;
tagRecCustomSession.expandedIds.splice(i, 1);
for (const c of tagRecCustomSession.candidates) if (c.expanded && c.tag_id === tagId) {
c.expanded = false;
c.checked = false;
}
}var unionBy = (a = [], b = [], key) => {
const seen = /* @__PURE__ */ new Set();
return [...a, ...b].filter((v) => !seen.has(key(v)) && !!seen.add(key(v))).map((v) => cloneDeep(v));
};
function foldPartition(n = {}) {
const out = cloneDeep(n);
if (out.partition?.mode === "block") out.partitionBlock = [...out.partitionBlock ?? [], ...out.partition.list];
else if (out.partition?.mode === "allow" && out.partitionAllow === void 0) out.partitionAllow = {
enabled: true,
list: out.partition.list
};
delete out.partition;
for (const key of Object.keys(out)) if (out[key] === void 0) delete out[key];
return out;
}
function mergeNegative(dst, src) {
if (!dst && !src) return void 0;
const x = foldPartition(dst);
const y = foldPartition(src);
const out = {
...y,
...x
};
if (x.tagIds || y.tagIds) out.tagIds = unionBy(x.tagIds, y.tagIds, (e) => e.id);
if (x.up || y.up) out.up = unionBy(x.up, y.up, (e) => e.mid !== void 0 ? `mid:${e.mid}` : `name:${e.name.trim().toLowerCase()}`);
for (const key of ["title", "desc"]) if (x[key] || y[key]) out[key] = unionBy(x[key], y[key], (e) => e.trim().toLowerCase());
if (x.partitionBlock || y.partitionBlock) out.partitionBlock = unionBy(x.partitionBlock, y.partitionBlock, (e) => `${e.v1 ?? ""}|${e.v2 ?? ""}`);
if (x.tagBlock || y.tagBlock) out.tagBlock = {
enabled: x.tagBlock?.enabled ?? y.tagBlock?.enabled ?? false,
strict: x.tagBlock?.strict ?? y.tagBlock?.strict,
list: unionBy(x.tagBlock?.list, y.tagBlock?.list, (e) => e.id)
};
return out;
}
/** 各入口共用:目标显式值优先,按维度/字段补缺项,屏蔽名单并集、仅看整项保留目标。 */
function mergeTagRecGroupOverrides(dst, src) {
if (!dst && !src) return void 0;
const out = cloneDeep(dst ?? {});
if (!src) return out;
const neg = mergeNegative(dst?.negative, src.negative);
if (neg) out.negative = neg;
if (src.dims) out.dims = {
...cloneDeep(src.dims),
...out.dims
};
if (src.score) {
out.score ??= {};
if (src.score.weights) out.score.weights = {
...cloneDeep(src.score.weights),
...out.score.weights
};
if (src.score.anchors) out.score.anchors = {
...cloneDeep(src.score.anchors),
...out.score.anchors
};
}
out.smallPoolThreshold ??= src.smallPoolThreshold;
if (src.pools) {
out.pools ??= {};
for (const key of [
"tagTop",
"allV2",
"allV2Related",
"archiveRelated"
]) if (src.pools[key]) out.pools[key] = mergeTagRecGroupOverrides(out.pools[key], src.pools[key]);
}
if (src.poolsEnabled) out.poolsEnabled = {
...src.poolsEnabled,
...out.poolsEnabled
};
return out;
}
var compareMergeSources = (a, b) => a.addedAt - b.addedAt || a.id.localeCompare(b.id);
function overridesOf(g) {
const out = cloneDeep(g.overrides ?? {});
const ids = groupNegativeTagIds(g);
if (ids.length || g.negative_strict !== void 0) {
out.negative ??= {};
if (ids.length) out.negative.tagIds = ids;
out.negative.strict ??= g.negative_strict;
}
return out;
}
/** 先计算整批结果:成员合并、缺项补齐、冲突保留目标;不修改输入。 */
function mergeTagGroups(target, sources) {
const sorted = [...sources].sort(compareMergeSources);
const out = cloneDeep(target);
out.tag_ids = dedupeTagIdEntries([target, ...sorted].flatMap((g) => g.tag_ids)).map((e) => ({ ...e }));
out.overrides = overridesOf(target);
for (const source of sorted) out.overrides = mergeTagRecGroupOverrides(out.overrides, overridesOf(source));
const positives = new Set(out.tag_ids.map((e) => e.id));
setGroupNegativeTagIds(out, dedupeTagIdEntries([target, ...sorted].flatMap(groupNegativeTagIds)).filter((e) => !positives.has(e.id)));
out.negative_strict = out.overrides?.negative?.strict;
const notes = sorted.filter((g) => g.note?.trim() && g.note !== target.note);
if (notes.length) out.note = [target.note, ...notes.map((g) => `${g.name}: ${g.note}`)].filter(Boolean).join("\n");
return out;
}
/**
* 沉淀池共现聚合 (与收藏面板「共现变体表」同源, 零请求): 对给定 norm 集合的共现边累加
* (频次跨成员相加), 剔除排除集 (含种子自身), 按频次降序取前 limit;
* 展示名取对方节点 variants 首项 (原始拼写), 无则回落 norm。UI 侧经 useSnapshot 实时刷新。
*/
function aggregateTagRecCooccur(pool, norms, excludeNorms, limit = 50) {
const acc = /* @__PURE__ */ new Map();
for (const norm of norms) {
const node = pool.get(norm);
if (!node) continue;
for (const [other, edge] of Object.entries(node.cooccur)) {
if (excludeNorms.has(other)) continue;
acc.set(other, (acc.get(other) ?? 0) + edge.count);
}
}
return [...acc].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, limit).map(([norm, count]) => ({
norm,
name: pool.get(norm)?.variants?.[0] ?? norm,
count
}));
}
var SEDIMENT_NODE_CAP = 5e3;
var SEDIMENT_PRUNE_TO = 4800;
function ensureNode(pool, norm, now) {
let node = pool.get(norm);
if (!node) {
node = {
variants: [],
cooccur: {},
lastSeen: now
};
pool.set(norm, node);
}
node.lastSeen = now;
return node;
}
function pushUnique(arr, v, cap) {
if (arr.includes(v)) return;
if (arr.length >= cap) arr.shift();
arr.push(v);
}
/** 写一条有向共现边 a -> b */
function addEdge(pool, aNorm, bNorm, bvid, now) {
if (aNorm === bNorm) return;
const node = ensureNode(pool, aNorm, now);
let edge = node.cooccur[bNorm];
if (!edge) {
edge = {
count: 0,
exampleBvids: [],
lastSeen: now
};
node.cooccur[bNorm] = edge;
}
edge.count++;
edge.lastSeen = now;
if (bvid) pushUnique(edge.exampleBvids, bvid, 5);
}
/**
* sediment.merge(anchorNorm, items) 写路径:
* 每条 clique = 一个视频的 tag 团; 团内两两互边 (tag↔tag 同视频共现, 双向)
* anchorNorm 标识调用侧锚点 (变体写法聚合到锚点节点; 非锚点团也落 corpus)
*/
function sedimentMerge(pool, anchorNorm, items, now = Date.now()) {
ensureNode(pool, anchorNorm, now);
const edgeTouched = /* @__PURE__ */ new Set();
for (const item of items) {
const rawsByNorm = /* @__PURE__ */ new Map();
for (const raw of item.tags.slice(0, 40)) {
const norm = normalizeTagName(raw);
if (!norm) continue;
const raws = rawsByNorm.get(norm) ?? [];
if (!raws.includes(raw)) raws.push(raw);
rawsByNorm.set(norm, raws);
}
if (!rawsByNorm.size) continue;
for (const [norm, raws] of rawsByNorm) {
const node = ensureNode(pool, norm, now);
for (const raw of raws) pushUnique(node.variants, raw, 16);
}
const norms = [...rawsByNorm.keys()];
for (const n of norms) edgeTouched.add(n);
for (let i = 0; i < norms.length; i++) for (let j = i + 1; j < norms.length; j++) {
addEdge(pool, norms[i], norms[j], item.bvid, now);
addEdge(pool, norms[j], norms[i], item.bvid, now);
}
}
for (const norm of edgeTouched) {
const node = pool.get(norm);
if (node) trimSedimentNodeEdges(node);
}
pruneSediment(pool);
}
/** LRU/久未见淘汰: 节点数超上限时按 lastSeen 最旧删至水位 */
function pruneSediment(pool, cap = SEDIMENT_NODE_CAP, floor = SEDIMENT_PRUNE_TO) {
if (pool.size <= cap) return 0;
const byAge = [...pool.entries()].sort((a, b) => (a[1].lastSeen ?? 0) - (b[1].lastSeen ?? 0));
let removed = 0;
for (const [norm] of byAge) {
if (pool.size <= floor) break;
pool.delete(norm);
removed++;
}
return removed;
}
/**
* [tag-rec] 票 perf-11: 单节点共现边裁剪 —— 保「count 高 → lastSeen 新」前 cap 条, 未超 cap 早退零排序
* - cooccur 唯一消费 = dig.ts cooccurVocab 词表键并集 (只问「边存在」, 不读 count/证据/lastSeen);
* 候选视频自身 ~10-20 tag 去重命中计度, 128 词/锚点词表分辩力已富裕 => 裁剪对推荐近乎零损
* - count 优先于 lastSeen: 反复共现才是真邻域信号; 同 count 一次性弱边按 lastSeen 滚动出新;
* 饱和 hub (存量全 count≥2) 词表冻结在最强邻域 —— 可接受: 锚点语义近邻本就稳定
* - 原地 delete 保 edge 对象身份 (proxyMap 嵌套对象不重包); 返回裁掉边数
*/
function trimSedimentNodeEdges(node, cap = 128) {
const entries = Object.entries(node.cooccur);
if (entries.length <= cap) return 0;
entries.sort((a, b) => (a[1].count ?? 0) - (b[1].count ?? 0) || (a[1].lastSeen ?? 0) - (b[1].lastSeen ?? 0));
const drops = entries.slice(0, entries.length - cap);
for (const [k] of drops) delete node.cooccur[k];
return drops.length;
}
/**
* [tag-rec] 票 perf-11: 全池边裁剪 (存量大池一次性迁移, 挂 store 水合后) —— 收敛式幂等:
* 裁后无节点超 cap, 重跑返回 0, 无需 migrated 哨兵; 每次水合顺手跑兼自愈更老版本写入
*/
function trimSedimentPoolEdges(pool, cap = 128) {
let removed = 0;
for (const [, node] of pool.entries()) removed += trimSedimentNodeEdges(node, cap);
return removed;
}
/** confirmed 边标记: 候选清单勾选确认 => 节点记 confirmed 强信号 */
function sedimentConfirm(pool, norm, now = Date.now()) {
const node = ensureNode(pool, norm, now);
node.confirmed = true;
}
/**
* 票 36: 深挖游标写口 —— digPass 页游标成功推进后回写 (调用侧保证 allPagesFailed 整流跳过不写);
* 同实际 keyword 的正常页码只前进,复探记录独立保留;schema 2 区分 empty/cap 与复探冷却。
* 迁移前的 exhausted 不跨词残留,旧写口不得覆盖新版状态或修改其时间。
*/
function sedimentNoteDig(pool, norm, dig, now = Date.now()) {
const existing = pool.get(norm);
const previous = existing?.dig?.keyword === dig.keyword ? existing.dig : void 0;
if (previous?.schemaVersion === 2 && dig.schemaVersion !== 2) return;
if (dig.expectedRevision !== void 0 && dig.expectedRevision !== (previous?.revision ?? 0)) return;
const node = ensureNode(pool, norm, now);
trimSedimentNodeEdges(node);
const exhausted = dig.exhausted === true || dig.schemaVersion !== 2 && previous?.exhausted === true;
node.dig = {
keyword: dig.keyword,
nextPage: Math.max(dig.nextPage, previous?.nextPage ?? 1),
lastAt: now,
...previous?.probes ? { probes: previous.probes } : {},
...dig.schemaVersion === 2 ? {
schemaVersion: 2,
revision: (previous?.revision ?? 0) + 1,
stopReason: dig.stopReason,
capLifted: dig.capLifted,
retryAt: dig.retryAt
} : {}
};
if (exhausted) node.dig.exhausted = true;
}
/** 复探账独立于正常分页,按消费者×来源隔离;旧实例不得重新粘住已迁移状态。 */
function sedimentNoteProbe(pool, norm, keyword, scope, empty, ttlMs, expectedRevision, now = Date.now()) {
const existing = pool.get(norm);
if (existing?.dig && existing.dig.keyword !== keyword) return;
const dig = existing?.dig ?? {
keyword,
nextPage: 1,
lastAt: now
};
const previous = dig.probes?.[scope];
if (expectedRevision !== void 0 && expectedRevision !== (previous?.revision ?? 0)) return;
const node = ensureNode(pool, norm, now);
dig.schemaVersion = 2;
dig.probes ??= {};
dig.probes[scope] = {
at: now,
retryAt: now + ttlMs,
empty,
revision: (previous?.revision ?? 0) + 1
};
const oldest = Object.entries(dig.probes).sort((a, b) => a[1].at - b[1].at);
for (const [key] of oldest.slice(0, Math.max(0, oldest.length - 32))) delete dig.probes[key];
node.dig = dig;
}
/**
* 「重新发掘」出口 (票 36 深挖游标的定向复位): 只清某个锚点的深挖游标 + 复探冷却,
* 保留共现边与其他节点。与 sedimentClear 的区别: 后者清空整个沉淀池; 这里只把
* 「这个词挖到哪」复位到第 1 页 —— 已挖穿/冷却中的词重获一轮完整召回 (请求仍有界)。
*/
function sedimentClearDig(pool, norm) {
const node = pool.get(norm);
if (!node?.dig) return false;
delete node.dig;
return true;
}
/** 「清空沉淀」(危险区, 归票 08 UI 接线) */
function sedimentClear(pool) {
for (const [norm] of pool.entries()) pool.delete(norm);
}
/**
* [tag-rec] seen-set / bypassSeen 纯逻辑 (spec §3.4 / §4.5; 零依赖叶子模块, 可测)
* - seen-set = bvid 集合, 只计肉眼可见的展示 (≥50% 进视口), 被拉取/被滤除不计
* - 容量 FIFO 超限删最旧 (Set 迭代序 = 插入序); 两 tab 共享; 收藏表/黑名单变动不动 set
* - bypassSeen 开 => 去已看阶段直通 (不删 set); 关 => 滤掉 seen 命中条目
* - 黑名单条目渲染前已被过滤管线滤除 => 不计已看, 本文件不另处理 (§4.5)
*/
/**
* seen-set FIFO 容量默认值 (spec §3.3 seenCap=2000)
* 生效值 = settings.tagRec.seenCap, 由调用侧 (store/index.ts seen.add) 注入 cap 参数, 此处仅兜底
*/
var DEFAULT_SEEN_CAP = 2e3;
/**
* W3-2: seenCap 硬界 —— settings 可由 GM 直改/导入绕过 UI 校验:
* 超上限 => 每次 add(每卡 ≥50% 进视口)触发 ~15MB 级全量序列化 flush + 跨 tab 广播;
* 0/负 => while 把刚 add 的也删 = 去已看静默失效
*/
var SEEN_CAP_MAX = exports("S",1e5);
/** cap 入参钳制: 非有限数回落默认, 其余压进 [MIN, MAX] 取整 */
function clampSeenCap(cap) {
if (cap === void 0 || !Number.isFinite(cap)) return DEFAULT_SEEN_CAP;
return Math.min(SEEN_CAP_MAX, Math.max(1, Math.round(cap)));
}
/** 计已看生效的 tab (spec §4.5): 仅「收藏推荐 / 指定标签」两 tab */
function isTagRecSeenTab(tab) {
return tab === ETab.TagRecFav || tab === ETab.TagRecCustom;
}
/** 记入已看: 先查重 (重复 => false); 新入后超 cap => FIFO 删最旧; 返回是否新入 */
function addToSeenSet(seen, bvid, cap = DEFAULT_SEEN_CAP) {
if (!bvid || seen.has(bvid)) return false;
seen.add(bvid);
const effectiveCap = clampSeenCap(cap);
while (seen.size > effectiveCap) {
const oldest = seen.values().next().value;
if (oldest === void 0) break;
seen.delete(oldest);
}
return true;
}
/** 清空可重建的浏览去重记录,收藏与过滤配置不受影响。 */
function clearSeenSet(seen) {
seen.clear();
}
/**
* 轮次管线「去已看」纯判定 (spec §3.4):
* bypassSeen 开 => 原样直通 (不删 set); 关 => 滤掉 seen-set 命中条目 (已看不再入队)
*/
function filterTagRecSeenItems(items, isSeen, bypassSeen) {
if (bypassSeen) return items;
return items.filter((it) => !it.bvid || !isSeen(it.bvid));
}
/**
* [tag-rec] R7: 查看范围 (查看范围 = 「现在看哪些主题/一级组」的零写入视图筛)
* - 记录形: 桶键 (主题 id | 未归属哨兵) => 'all' | L1 id 数组; **缺键 = 'all' (默认全看)**
* 「全选」= 空记录; 取消某主题 = 该桶 []; 主题内细选 = 该桶 L1 id 数组
* - 未归属桶: 一级组无主题 (或悬挂已删主题) 落此桶 —— 主题删/并入时桶按并集吸收 (store 侧)
* - 纯函数, 不含 store/GM; deck 判定/指纹/计数共用展开口径 (tagRecViewScopeViewedIds)
*/
/** 未归属桶哨兵 (与 pane 的 THEME_FILTER_UNASSIGNED 同值; 防跨模块循环仅此处定义) */
var TAG_REC_VIEW_UNASSIGNED = exports("a9","__theme_unassigned__");
/** L1 的归属桶: 主题存在则用主题 id; 无主题/悬挂 (已删主题) 归未归属 */
function tagRecViewBucketOf(l1, themeIds) {
return l1.themeId && themeIds.has(l1.themeId) ? l1.themeId : TAG_REC_VIEW_UNASSIGNED;
}
/** 记录项 => 是否含该 L1 ('all'/缺键 = 全看; 数组 = 包含判定) */
function tagRecViewScopeCovers(scope, bucket, l1Id) {
const v = scope[bucket];
if (v === void 0 || v === "all") return true;
return v.includes(l1Id);
}
/** 展开查看范围为 L1 id 集 (deck 判定/指纹/计数用; 组序无关) */
function tagRecViewScopeViewedIds(scope, l1s, themeIds) {
const out = /* @__PURE__ */ new Set();
for (const l1 of l1s) if (tagRecViewScopeCovers(scope, tagRecViewBucketOf(l1, themeIds), l1.id)) out.add(l1.id);
return out;
}
/** 桶当前形 ('all' | string[]; 缺键 => 'all') —— UI 消费/写回用 */
function tagRecViewScopeEntry(scope, bucket) {
return scope[bucket] ?? "all";
}
/**
* 桶合并 (删主题/并入主题): from 的查看范围并入 to, 按**并集**语义 ('all' 吸收一切; 缺键 = 全看亦吸收);
* 就地改传入 scope (valtio proxy 可写)
*/
function tagRecViewScopeAbsorb(scope, fromId, toId) {
if (fromId === toId) return;
const from = scope[fromId];
delete scope[fromId];
if (from === void 0 || from === "all") {
scope[toId] = "all";
return;
}
const to = scope[toId];
if (to === void 0 || to === "all") return;
scope[toId] = [.../* @__PURE__ */ new Set([...to, ...from])];
}
/**
* 导出用 legacy 字段 (activeThemeId) 推导: 恰好「只看一个主题」(其余桶显式空) 时给该 id, 否则 null。
* 查看范围是本代模型, 导出 JSON 沿用旧键供旧端/旧解析读
*/
function tagRecViewScopeLegacyActiveTheme(scope, themeIds) {
const active = [...themeIds, TAG_REC_VIEW_UNASSIGNED].filter((b) => {
const v = scope[b];
if (v === void 0) return true;
return v === "all" || v.length > 0;
});
if (active.length === 1 && active[0] !== "__theme_unassigned__") return active[0];
return null;
}
/**
* 摘要文案 (tabbar 触发器/下拉脚注): 「主题:全选」/「主题:未选 (点此全选)」/「主题:A、B(部分)…」
* - 满 = 桶缺键/'all'/数组覆盖该桶全部 L1; 空 = 桶 [] 或数组为空; 空桶不列
*/
function tagRecViewScopeSummary(scope, buckets) {
const stateOf = (b) => {
const v = scope[b.id];
if (v === void 0 || v === "all") return "full";
if (!v.length) return "none";
return v.length >= b.l1Count ? "full" : "half";
};
const states = buckets.map((b) => ({
b,
s: stateOf(b)
}));
if (states.every((x) => x.s === "full")) return "主题:全选";
if (states.every((x) => x.s === "none")) return "主题:未选 (点此全选)";
const parts = states.filter((x) => x.s !== "none").map((x) => x.s === "full" ? x.b.name : `${x.b.name}(部分)`);
return "主题:" + (parts.length > 3 ? parts.slice(0, 3).join("、") + ` 等${parts.length}个` : parts.join("、"));
}
function tagRecViewedGroupIds(groups, scope) {
if (!Object.keys(scope).length) return void 0;
return new Set(groups.filter((g) => !g.l1Id || !scope[g.l1Id] || scope[g.l1Id].includes(g.id)).map((g) => g.id));
}
function createTagRecGroupScopeDraft(groups, l1s, themeIds, scope, groupScope, temporaryL1s, temporaryGroups) {
const parents = createTagRecThemeScopeDraft(l1s, themeIds, scope, temporaryL1s);
const viewed = tagRecViewedGroupIds(groups, groupScope);
const temporary = new Set(temporaryGroups);
return Object.fromEntries(groups.map((g) => [g.id, !!g.l1Id && !!parents[g.l1Id] && (g.tag_ids.length > 0 || hasTagRecCombination(g.combination)) && (!viewed || viewed.has(g.id)) && (g.enabled || temporary.has(g.id))]));
}
function materializeTagRecGroupScopeDraft(draft, groups, l1s, themeIds) {
const parentDraft = {};
const groupScope = {};
const temporaryGroups = [];
for (const l of l1s) {
const children = groups.filter((g) => g.l1Id === l.id);
const selected = children.filter((g) => (g.tag_ids.length > 0 || hasTagRecCombination(g.combination)) && (draft[g.id] ?? (l.enabled && g.enabled)));
parentDraft[l.id] = children.length ? selected.length > 0 : l.enabled;
if (children.some((g) => g.enabled && !selected.some((s) => s.id === g.id))) groupScope[l.id] = selected.map((g) => g.id);
temporaryGroups.push(...selected.filter((g) => !g.enabled).map((g) => g.id));
}
return {
...materializeTagRecThemeScopeDraft(parentDraft, l1s, themeIds),
groupScope,
temporaryGroups
};
}
/** 草稿只在面板内变化;未覆盖的一级组继续跟随收藏表启用状态。 */
function createTagRecThemeScopeDraft(l1s, themeIds, scope, temporary) {
const viewed = tagRecViewScopeViewedIds(scope, l1s, themeIds);
const temp = new Set(temporary);
return Object.fromEntries(l1s.map((l) => [l.id, viewed.has(l.id) && (l.enabled || temp.has(l.id))]));
}
/** 仅记录与收藏表不同的会话覆盖,不把关闭的一级组算成主题全选。 */
function materializeTagRecThemeScopeDraft(draft, l1s, themeIds) {
const scope = {};
const temporary = [];
for (const l of l1s) {
const on = draft[l.id] ?? l.enabled;
if (on && !l.enabled) temporary.push(l.id);
if (l.enabled && !on) {
const bucket = tagRecViewBucketOf(l, themeIds);
scope[bucket] ??= l1s.filter((x) => tagRecViewBucketOf(x, themeIds) === bucket && (draft[x.id] ?? x.enabled)).map((x) => x.id);
}
}
return {
scope,
temporary
};
}
var lazy$1 = proxyWithGmStorageLazy({
list: [],
invalid: false,
incompatibleData: void 0
}, "tag-rec:combination-blocks");
var tagRecCombinationBlocks = exports("E",lazy$1.proxy);
function preserveIncompatibleConditions() {
const combo = newTagRecCombination();
combo.exclude = lazy$1.proxy.list;
if (!validateTagRecCombination(combo)) {
lazy$1.proxy.incompatibleData = cloneDeep(lazy$1.proxy.list);
lazy$1.proxy.list = [];
lazy$1.proxy.invalid = true;
}
}
var tagRecCombinationBlocksHydrated = lazy$1.hydrated.then(preserveIncompatibleConditions);
subscribe(lazy$1.proxy, preserveIncompatibleConditions);
function blockTagRecConditionGlobally(atom) {
if (tagRecCombinationBlocks.invalid) return false;
if (tagRecCombinationBlocks.list.some((entry) => tagRecConditionKey(entry) === tagRecConditionKey(atom))) return false;
const combo = newTagRecCombination();
combo.exclude = [...tagRecCombinationBlocks.list, {
...cloneDeep(atom),
id: crypto.randomUUID()
}];
if (!validateTagRecCombination(combo)) return false;
tagRecCombinationBlocks.list = combo.exclude;
tagRecCustomSession.allowUnverified = false;
return true;
}
function unblockTagRecConditionGlobally(id) {
tagRecCombinationBlocks.list = tagRecCombinationBlocks.list.filter((atom) => atom.id !== id);
tagRecCustomSession.allowUnverified = false;
}
/**
* [tag-rec] GM 持久 store (spec §3.x)
* - favorites: 收藏表二级标签组, `tag-rec:favorites`, proxyWithGmStorage<{list: TagGroup[]}> (§3.1)
* - favorites-l1: 一级组(容器), `tag-rec:favorites-l1`, proxyWithGmStorage<{list, migrated}> (票 17)
* - themes: 主题 (L1 归属筛子, 票 26), `tag-rec:themes`, proxyWithGmStorage<{list: TagTheme[]}>
* - active-theme: tab1 当前主题 id, `tag-rec:active-theme`, proxyWithGmStorage<{id: string|null}> (null=全部)
* - blacklist: 标签黑名单, `tag-rec:blacklist`, proxyWithGmStorage<{list: TagBlacklistEntry[]}> (§3.2)
* - sediment: 沉淀池, `tag-rec:sediment`, proxyMapWithGmStorage (含跨 tab 同步, §3.5)
* - seen: 已看 bvid 集合, `tag-rec:seen`, proxySetWithGmStorage (FIFO 容量, 两 tab 共享, §3.4)
*
* 装载 (票 30 并行 => 票 perf-03 脱 TLA): 顶层不再 await —— 7 个持久位先立同步骨架 proxy,
* GM 读后台水合 (Object.assign/replaceAllWith 回填, valtio 响应式消费方自动接住);
* 模块图求值不再挂 GM 往返 —— 须读真值的链路 (service 构造期深链消费/语境签名/拉取)
* 经 `tagRecStoresHydrated` 闸控 (useRefresh 建 tag-rec service 前)。
*/
var favoritesStoreLazy = proxyWithGmStorageLazy({ list: [] }, "tag-rec:favorites");
var favoritesL1StoreLazy = proxyWithGmStorageLazy({
list: [],
migrated: false
}, "tag-rec:favorites-l1");
var themesStoreLazy = proxyWithGmStorageLazy({ list: [] }, "tag-rec:themes");
var activeThemeStoreLazy = proxyWithGmStorageLazy({ id: null }, "tag-rec:active-theme");
var viewScopeStore = exports("q",proxy({
scope: {},
groupScope: {}
}));
var blacklistStoreLazy = proxyWithGmStorageLazy({ list: [] }, "tag-rec:blacklist");
var sedimentPool = proxyMapWithGmStorageLazy("tag-rec:sediment");
var seenPool = proxySetWithGmStorageLazy("tag-rec:seen");
var favoritesStore = exports("n",favoritesStoreLazy.proxy);
var favoritesL1Store = exports("o",favoritesL1StoreLazy.proxy);
var themesStore = exports("p",themesStoreLazy.proxy);
var blacklistStore = exports("B",blacklistStoreLazy.proxy);
/**
* [tag-rec] perf 票 03: 7 持久位后台水合完成信号
* - fail-open: 单笔 GM 读失败记 warn 后照样放行 (该位退回空骨架初值), 不把模块图拖死
* - 须读真值链路一律 await: tag-rec service 构造 (useRefresh 闸控 —— 构造期消费深链/拍签名)
* - AUDIT-08: 逐项 settle 结果落 `storeHydrateOk` —— 迁移/收边这类「写型」收尾只允许在对应位
* 读成功时跑: 读败空骨架的 `migrated=false` 会骗过迁移哨兵, 伪迁移落盘覆盖真 L1 数据不可自愈
*/
var storeHydrationMap = {
favorites: favoritesStoreLazy,
favoritesL1: favoritesL1StoreLazy,
themes: themesStoreLazy,
activeTheme: activeThemeStoreLazy,
blacklist: blacklistStoreLazy,
sediment: sedimentPool,
seen: seenPool,
combinationBlocks: { hydrated: tagRecCombinationBlocksHydrated }
};
var storeHydrateOk = {};
var tagRecStoresHydrated = exports("ay",Promise.allSettled(Object.values(storeHydrationMap).map((s) => s.hydrated)).then((res) => {
Object.keys(storeHydrationMap).forEach((name, i) => storeHydrateOk[name] = res[i].status === "fulfilled");
const failed = res.filter((r) => r.status === "rejected");
if (failed.length) appWarn("tag-rec stores hydrate failed:", failed.map((r) => r.reason));
perfMark("tagrec-stores");
perfMarkBytes("tagrec-store-bytes", () => ({
favorites: JSON.stringify(favoritesStore),
favoritesL1: JSON.stringify(favoritesL1Store),
themes: JSON.stringify(themesStore),
blacklist: JSON.stringify(blacklistStore),
sediment: JSON.stringify(Object.fromEntries(sedimentPool.map)),
seen: JSON.stringify([...seenPool.set])
}));
}));
var favorites = exports("d",{
/** 实时引用 (mergeGroups 会整体换 list, 用 getter 防捕获旧数组) */
get list() {
return favoritesStore.list;
},
findGroupByTagId(tagId) {
return findGroupByTagId(favoritesStore.list, tagId);
},
/** 交集查重 (§3.1 不变式2): 任一 id 已入表 => 命中组 (excludeGroupId 排除自身组) */
intersectCheck(tagIds, excludeGroupId) {
return intersectCheck(favoritesStore.list, tagIds, excludeGroupId);
},
/**
* 入表建组: 空 ids 拒 (不变式1); 跨组 id 重复合法不拦截 (票 14), 命中仅信息提示
* 可携负向成员 (negative_tag_ids/negative_strict; 草稿组升级路径用)
* tag_type 缺省 = opportunistic 待回填 (§3.1)
* 票 17「无裸奔 L2」: input.l1Id 指认既有一级组 (须存在, 失效回落自动套); 缺省自动套同名新 L1
* (默认不启用 —— 新收藏的标签不进推荐流, 直到面板主动开); 返回 {group, l1=归属 L1}
*/
addGroup(input) {
const tag_ids = dedupeTagIdEntries(input.tag_ids);
if (!tag_ids.length && !input.name?.trim()) {
toast("请填写组名");
return;
}
const hits = findGroupsByTagIds(favoritesStore.list, tag_ids.map((e) => e.id));
const group = makeTagGroup({
...input,
tag_ids
});
if (!group) return void 0;
let l1 = input.l1Id ? favoritesL1Store.list.find((x) => x.id === input.l1Id) : void 0;
if (l1) group.l1Id = l1.id;
else l1 = ensureL1ForGroup(group);
favoritesStore.list.push(group);
if (hits.length) toast(`已入表 (提示: 其中标签也存在于组 ${summarizeGroupNames(hits.map((g) => g.name))})`);
return {
group,
l1
};
},
/**
* 批量入表 (CSV/JSON 导入): 绕过 addGroup 门面逐行 toast (批量场景会刷屏),
* 但同守「无裸奔 L2」不变式 —— 组携合法 l1Id 保留归属, 缺省/失效逐组自动套新 L1;
* 规范化 (空 ids 拒建/正负互斥) 已由 planCsvImport/planTagRecImport 同款执行
*/
addGroups(groups) {
for (const g of groups) {
if (g.l1Id && favoritesL1Store.list.some((l) => l.id === g.l1Id)) continue;
ensureL1ForGroup(g);
}
favoritesStore.list.push(...groups);
return groups.length;
},
/** 显示字段行内编辑 (name/weight/enabled/note/negative_strict/overrides; 资格类变更由消费侧感知, 见票 05 牌组指纹) */
updateGroup(id, patch) {
const g = favoritesStore.list.find((x) => x.id === id);
if (!g) return false;
if (patch.name !== void 0) {
const name = patch.name.trim();
if (!name) {
toast("组名不能为空");
return false;
}
g.name = name;
}
if (patch.weight !== void 0) g.weight = clampWeight(patch.weight);
if (patch.enabled !== void 0) {
g.enabled = patch.enabled && (g.tag_ids.length > 0 || hasTagRecCombination(g.combination));
tagRecSharedSession.tempEnabledGroupIds = tagRecSharedSession.tempEnabledGroupIds.filter((x) => x !== id);
if (g.l1Id) {
const entry = viewScopeStore.groupScope[g.l1Id];
if (entry) viewScopeStore.groupScope[g.l1Id] = g.enabled ? [.../* @__PURE__ */ new Set([...entry, id])] : entry.filter((x) => x !== id);
const parent = favoritesL1Store.list.find((l) => l.id === g.l1Id);
if (g.enabled && parent && (parent.enabled || tagRecSharedSession.tempEnabledL1Ids.includes(parent.id))) {
const bucket = tagRecViewBucketOf(parent, new Set(themesStore.list.map((t) => t.id)));
const parents = viewScopeStore.scope[bucket];
if (Array.isArray(parents)) viewScopeStore.scope[bucket] = [.../* @__PURE__ */ new Set([...parents, parent.id])];
}
}
}
if (patch.note !== void 0) g.note = patch.note;
if (patch.negative_strict !== void 0) g.negative_strict = patch.negative_strict;
if (patch.overrides !== void 0) g.overrides = patch.overrides;
return true;
},
removeGroup(id) {
const idx = favoritesStore.list.findIndex((x) => x.id === id);
if (idx === -1) return false;
favoritesStore.list.splice(idx, 1);
tagRecSharedSession.tempEnabledGroupIds = tagRecSharedSession.tempEnabledGroupIds.filter((x) => x !== id);
return true;
},
/** 批量删除 (票 44 吸顶条「删除」): 一遍 filter 等价逐 id removeGroup; 失效 id 静默跳过; 不 toast 调用侧汇总 */
removeGroups(ids) {
const set = new Set(ids);
const before = favoritesStore.list.length;
favoritesStore.list = favoritesStore.list.filter((g) => !set.has(g.id));
tagRecSharedSession.tempEnabledGroupIds = tagRecSharedSession.tempEnabledGroupIds.filter((x) => !set.has(x));
return before - favoritesStore.list.length;
},
/** 「清空收藏表」(票 44 危险区): L2 全清, 返回删除数; 一级组容器/主题/黑名单/沉淀/已看均不动 */
clearAll() {
const n = favoritesStore.list.length;
favoritesStore.list = [];
viewScopeStore.groupScope = {};
tagRecSharedSession.tempEnabledGroupIds = [];
return n;
},
/**
* 并入指定目标组:先计算整批成员和统一配置,再应用到目标并删除源组。
* target 自身在源列表内 => 跳过 (并入自己=其他源并入它); 返回并入的源组数 (undefined=目标失效)
*/
mergeGroupsInto(sourceIds, targetId) {
const target = favoritesStore.list.find((g) => g.id === targetId);
if (!target) {
toast("目标标签组不存在");
return;
}
const ids = new Set(sourceIds);
const sources = favoritesStore.list.filter((g) => g.id !== targetId && ids.has(g.id));
if (!sources.length) return 0;
if ([target, ...sources].some((g) => g.combination)) {
toast("完整组合请在标签发掘中选取条件,再存为二级组");
return;
}
const merged = mergeTagGroups(target, sources);
Object.assign(target, merged);
favoritesStore.list = favoritesStore.list.filter((g) => g.id === targetId || !ids.has(g.id));
reconcileTagRecMergedGroupContext(sources.map((g) => g.id), targetId);
return sources.length;
},
/** 新组以最早源组为基准,与并入既有组共用规则。 */
mergeGroupsToNew(sourceIds, name) {
const sources = favoritesStore.list.filter((g) => sourceIds.includes(g.id)).sort(compareMergeSources);
const base = sources[0];
if (!base || !name.trim()) return void 0;
if (sources.some((g) => g.combination)) {
toast("完整组合请在标签发掘中选取条件,再存为二级组");
return;
}
const merged = mergeTagGroups(base, sources.slice(1));
const created = favorites.addGroup({
name,
l1Id: merged.l1Id,
tag_ids: merged.tag_ids,
negative_tag_ids: merged.negative_tag_ids,
negative_strict: merged.negative_strict,
overrides: merged.overrides,
enabled: merged.enabled,
weight: merged.weight,
note: merged.note
})?.group;
if (!created) return void 0;
favorites.removeGroups(sources.map((g) => g.id));
reconcileTagRecMergedGroupContext(sources.map((g) => g.id), created.id);
return created;
},
/** 合并成组 (组手术只在面板): 并入最早 addedAt 组, id 并集去重, 其余组删除 */
mergeGroups(ids) {
const selected = favoritesStore.list.filter((g) => ids.includes(g.id));
const res = computeMergeGroups(selected);
if (!res) {
toast("至少勾选 2 组");
return false;
}
const keep = favoritesStore.list.find((g) => g.id === res.keepId);
if (favorites.mergeGroupsInto(res.absorbedIds, keep.id) === void 0) return false;
toast(`已合并 ${selected.length} 组为「${keep.name}」(${keep.tag_ids.length} 个 tag_id)`);
return true;
},
/**
* 向既有组加 id (面板「添加变体」勾选落地 / 票 20 弹层四档入组):
* 本组已有 => 跳过; 跨组重复合法照常加入, 命中组名进 alsoIn 信息上报 (票 14);
* 同组正负互斥: 撞负向的自动移出负向转正向 (moved);
* opts.silent: 抑制门面自带 toast, 调用侧按返回结果自报 (弹层「已并入组「A」,共 N 个标签」)
*/
addTagIdsToGroup(groupId, entries, opts) {
const res = {
added: 0,
skipped: 0,
moved: 0,
alsoIn: []
};
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return res;
const inGroup = new Set(g.tag_ids.map((e) => e.id));
const alsoIn = /* @__PURE__ */ new Set();
for (const e of dedupeTagIdEntries(entries)) {
if (g.combination) {
if (inGroup.has(e.id)) {
res.skipped++;
continue;
}
const copy = cloneDeep(g);
const negative = groupNegativeTagIds(copy);
const moved = negative.some((n) => n.id === e.id);
if (moved) setGroupNegativeTagIds(copy, negative.filter((n) => n.id !== e.id));
if (!addTagRecCondition(copy.combination, tagRecConditionAtom("tag", e)) || !validateTagRecCombination(copy.combination)) {
res.skipped++;
if (!opts?.silent) toast("组合条件已达上限,请先简化");
continue;
}
Object.assign(g, copy, { tag_ids: tagRecCombinationTagIds(copy.combination) });
inGroup.add(e.id);
res.added++;
if (moved) res.moved++;
const hit = findGroupByTagId(favoritesStore.list.filter((other) => other.id !== g.id), e.id);
if (hit) alsoIn.add(hit.name);
continue;
}
const negative = groupNegativeTagIds(g);
if (negative.some((n) => n.id === e.id)) {
setGroupNegativeTagIds(g, negative.filter((n) => n.id !== e.id));
res.moved++;
}
if (inGroup.has(e.id)) {
res.skipped++;
continue;
}
const hit = findGroupByTagId(favoritesStore.list, e.id);
if (hit) alsoIn.add(hit.name);
g.tag_ids.push({ ...e });
inGroup.add(e.id);
res.added++;
}
res.alsoIn = [...alsoIn];
if (!opts?.silent) {
const parts = [];
if (res.added) parts.push(`已加入 ${res.added} 个标签到「${g.name}」`);
if (res.moved) parts.push(`${res.moved} 个从负向移出转为正向`);
if (!res.added && !res.moved && res.skipped) parts.push("所选标签均已在组内");
if (res.alsoIn.length) parts.push(`提示: 其中标签也存在于组 ${summarizeGroupNames(res.alsoIn)}`);
if (parts.length) toast(parts.join(" · "), parts.length > 1 ? 3500 : void 0);
}
return res;
},
/**
* 加负向成员 (票 14; computeNegativeAdd 守互斥):
* 撞正向 => 自动移出正向转负向 (唯一正向拒移 => refusedSole); 已是负向 => skipped
*/
addNegativeToGroup(groupId, entries) {
const g = favoritesStore.list.find((x) => x.id === groupId);
const empty = {
added: 0,
moved: 0,
skipped: 0,
refusedSole: []
};
if (!g) return empty;
if (g.combination) {
const combo = cloneDeep(g.combination);
for (const entry of dedupeTagIdEntries(entries)) {
const matches = tagRecConditionAtoms(combo.include).filter((a) => a.field === "tag" && a.value.id === entry.id);
matches.forEach((a) => removeTagRecCondition(combo, a.id));
if (excludeTagRecCondition(combo, tagRecConditionAtom("tag", entry))) empty.added++;
else empty.skipped++;
if (matches.length) empty.moved++;
}
if (!validateTagRecCombination(combo)) {
toast("组合条件已达上限,请先简化");
return {
added: 0,
moved: 0,
skipped: entries.length,
refusedSole: []
};
}
g.combination = combo;
g.tag_ids = tagRecCombinationTagIds(g.combination);
return empty;
}
const res = computeNegativeAdd(g, entries);
g.tag_ids = res.tag_ids;
setGroupNegativeTagIds(g, res.negative_tag_ids);
return {
added: res.added,
moved: res.moved,
skipped: res.skipped,
refusedSole: res.refusedSole
};
},
removeNegativeFromGroup(groupId, tagId) {
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return false;
if (g.combination) {
const negative = groupNegativeTagIds(g);
const next = negative.filter((entry) => entry.id !== tagId);
if (next.length === negative.length) return false;
setGroupNegativeTagIds(g, next);
return true;
}
const negative = groupNegativeTagIds(g);
const next = negative.filter((e) => e.id !== tagId);
if (next.length === negative.length) return false;
setGroupNegativeTagIds(g, next);
return true;
},
/** id → 含其为负向成员的全部组 (弹层「负向@组名」素材) */
negativeGroupsOfTag(tagId) {
return findGroupsByNegativeTagId(favoritesStore.list, tagId);
},
/**
* 「组现状面板」直写条件树 (wayfinding 004): clone → mutate → validate → 落盘并重导 tag_ids 投影;
* 与 addNegativeToGroup 同款的收藏侧直写通道。校验失败整批丢弃并 toast, 不落半成品。
* 无组合的组首次走此通道时先把既有 tag_ids 播种成 tag 行原子 ——
* 否则新建空组合的投影重导会把扁平成员冲掉 (扁平名单 ≡ any-tag 行)。
*/
editGroupCombination(groupId, mutate) {
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return false;
const combo = g.combination ? cloneDeep(g.combination) : newTagRecCombination();
if (!g.combination && !seedTagRecCombination(combo, g.tag_ids)) {
toast("该组标签数超过组合节点上限,无法转为组合条件,未保存");
return false;
}
mutate(combo);
if (!validateTagRecCombination(combo)) {
toast("组合条件已达上限或形状不合法,未保存");
return false;
}
g.combination = combo;
g.tag_ids = tagRecCombinationTagIds(combo);
return true;
},
/** 移出最后一个标签保留空组,关闭保存开关及临时入抽资格。 */
removeTagIdFromGroup(groupId, tagId) {
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return false;
const res = removeTagIdFromGroup(g, tagId);
if (!res.ok) {
toast("标签不在组内");
return false;
}
if (g.combination) {
tagRecConditionAtoms(g.combination.include).filter((a) => a.field === "tag" && a.value.id === tagId).forEach((a) => removeTagRecCondition(g.combination, a.id));
g.tag_ids = tagRecCombinationTagIds(g.combination);
} else g.tag_ids = res.tag_ids.map((e) => ({ ...e }));
if (!g.tag_ids.length && !hasTagRecCombination(g.combination)) this.updateGroup(groupId, { enabled: false });
return true;
},
/** 「拆为新项」: id 移出源组并建同名新组 (源组须 ≥2 id; 新组过 addGroup 不变式) */
splitTagToNewGroup(groupId, tagId) {
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return void 0;
if (g.combination) {
toast("完整组合请在标签发掘中用分支试验拆选,原组会保留");
return;
}
if (!canSplitTagId(g)) {
toast("单标签组无需拆分");
return;
}
const entry = g.tag_ids.find((e) => e.id === tagId);
if (!entry) return void 0;
g.tag_ids = g.tag_ids.filter((e) => e.id !== tagId).map((e) => ({ ...e }));
return favorites.addGroup({
name: entry.name,
tag_ids: [{ ...entry }]
})?.group;
},
/**
* tag_type opportunistic 回填 (§3.1): view/detail/tag 免费给的枚举值,
* 表内已存在条目缺 tag_type 时顺手补上; 返回回填条数
*/
backfillTagTypes(items) {
let n = 0;
for (const { id, tag_type } of items) {
if (!tag_type) continue;
const e = findGroupByTagId(favoritesStore.list, id)?.tag_ids.find((x) => x.id === id);
if (e && !e.tag_type) {
e.tag_type = tag_type;
n++;
}
}
return n;
}
});
/** 无裸奔 L2 收口 (票 17): 组缺归属 => 自动套同名新 L1 (默认不启用, weight 继承组 weight); 返回所套 L1 */
function ensureL1ForGroup(group) {
const l1 = makeTagL1Group({
name: group.name,
weight: group.weight
});
favoritesL1Store.list.push(l1);
group.l1Id = l1.id;
return l1;
}
/**
* 一次性迁移 (票 17): favorites.list 内无合法 l1Id 的旧组各套一个自动 L1
* (同名/默认不启用/weight 继承旧组 weight —— 启用即恢复原曝光);
* migrated flag 持久在 favorites-l1 store 内防重跑 —— 幂等, 重复调用 no-op
*/
function migrateFavoritesToL1() {
if (favoritesL1Store.migrated) return;
favoritesL1Store.migrated = true;
for (const g of favoritesStore.list) {
if (g.l1Id && favoritesL1Store.list.some((l) => l.id === g.l1Id)) continue;
ensureL1ForGroup(g);
}
}
var favoritesL1 = exports("f",{
get list() {
return favoritesL1Store.list;
},
/** 面板新建 L1: 默认不启用 (麾下整棵入堆资格需显式开, spec); 空名拒建 */
addL1(input) {
const l1 = makeTagL1Group(input);
if (!l1) {
toast("一级组名不能为空");
return;
}
favoritesL1Store.list.push(l1);
return l1;
},
/**
* 批量入 L1 表 (JSON 导入): 规范化/id 撞车复用已由 planTagRecImport 完成;
* 须在 favorites.addGroups 之前落表 —— 组携 l1Id 的归属校验要能看到新 L1, 否则被 auto-wrap 截胡
*/
addL1s(items) {
favoritesL1Store.list.push(...items.map((l) => ({ ...l })));
return items.length;
},
/** 显示字段行内编辑 (name/weight/enabled/note/overrides; 资格类变更由外层牌堆指纹感知) */
updateL1(id, patch) {
const l1 = favoritesL1Store.list.find((x) => x.id === id);
if (!l1) return false;
if (patch.name !== void 0) {
const name = patch.name.trim();
if (!name) {
toast("一级组名不能为空");
return false;
}
l1.name = name;
}
if (patch.weight !== void 0) l1.weight = clampWeight(patch.weight);
if (patch.enabled !== void 0) {
l1.enabled = patch.enabled;
tagRecSharedSession.tempEnabledL1Ids = tagRecSharedSession.tempEnabledL1Ids.filter((x) => x !== id);
delete viewScopeStore.groupScope[id];
const children = new Set(favoritesStore.list.filter((g) => g.l1Id === id).map((g) => g.id));
tagRecSharedSession.tempEnabledGroupIds = tagRecSharedSession.tempEnabledGroupIds.filter((x) => !children.has(x));
const bucket = tagRecViewBucketOf(l1, new Set(themesStore.list.map((t) => t.id)));
const entry = viewScopeStore.scope[bucket];
if (Array.isArray(entry)) viewScopeStore.scope[bucket] = patch.enabled ? [.../* @__PURE__ */ new Set([...entry, id])] : entry.filter((x) => x !== id);
}
if (patch.note !== void 0) l1.note = patch.note;
if (patch.overrides !== void 0) l1.overrides = patch.overrides;
return true;
},
/** 非空阻塞 (spec: 不做级联删除防误伤): 麾下还有 L2 时拒删并提示; 空 L1 可删 */
removeL1(id) {
const idx = favoritesL1Store.list.findIndex((x) => x.id === id);
if (idx === -1) return false;
const l1 = favoritesL1Store.list[idx];
const children = favoritesStore.list.filter((g) => g.l1Id === id).length;
if (children) {
toast(`一级组「${l1.name}」下还有 ${children} 个标签组, 请先移走或删除`);
return false;
}
favoritesL1Store.list.splice(idx, 1);
return true;
},
/**
* 级联删除 (票 44 用户裁, 覆盖非空阻塞原裁): 麾下 L2 一并删 —— 「L1 勾选/删除 = 整棵子树」语义;
* 调用侧 confirm 明示麾下计数。批量吸顶条与行内删除共用此口 (删 L1 = 删子树, 一处语义)
*/
removeL1Tree(id) {
const idx = favoritesL1Store.list.findIndex((x) => x.id === id);
if (idx === -1) return false;
favorites.removeGroups(favoritesStore.list.filter((g) => g.l1Id === id).map((g) => g.id));
delete viewScopeStore.groupScope[id];
tagRecSharedSession.tempEnabledL1Ids = tagRecSharedSession.tempEnabledL1Ids.filter((x) => x !== id);
favoritesL1Store.list.splice(idx, 1);
return true;
},
/**
* 「清空收藏表」配套 (票 44 危险区): 一级组全清, 返回删除数 —— 绕过 removeL1 非空阻塞
* (调用侧语义本就是全表清空, 先清 L2 再清 L1 壳); 主题名册不动 (主题是无 L1 依附的独立实体)
*/
clearAll() {
const n = favoritesL1Store.list.length;
favoritesL1Store.list = [];
return n;
},
/**
* 指认 L2 所属一级组 (面板「所属一级组」下拉): l1Id 须存在, 否则拒;
* undefined => 自动套同名新 L1 (同 addGroup 缺省路径); 返回最终归属的 L1
*/
assignGroupToL1(groupId, l1Id) {
const g = favoritesStore.list.find((x) => x.id === groupId);
if (!g) return void 0;
if (l1Id === void 0) return ensureL1ForGroup(g);
const l1 = favoritesL1Store.list.find((x) => x.id === l1Id);
if (!l1) {
toast("目标一级组不存在");
return;
}
g.l1Id = l1.id;
return l1;
},
/**
* 批量改挂 (票 31 吸顶条「移到一级组…」): 逐组 assignGroupToL1 同款语义的薄封装;
* l1Id 缺省 => 首组 auto-wrap 套同名新 L1、余组同挂 (与行内「新建一级组…」入口同款);
* 改挂后清空的源一级组 = 空壳 => 删除并把名字汇总进返回值 (删空壳非数据, 调用侧 toast 不二次确认);
* 返回 {l1 最终归属, moved, removedShells 删壳名清单} | undefined (无有效组 / 目标失效)
*/
moveGroupsToL1(groupIds, l1Id) {
const groups = favoritesStore.list.filter((g) => groupIds.includes(g.id));
if (!groups.length) return void 0;
const sourceIds = new Set(groups.map((g) => g.l1Id).filter((x) => !!x));
let l1;
if (l1Id === void 0) {
l1 = favoritesL1.assignGroupToL1(groups[0].id);
if (!l1) return void 0;
for (const g of groups.slice(1)) favoritesL1.assignGroupToL1(g.id, l1.id);
} else {
l1 = favoritesL1Store.list.find((x) => x.id === l1Id);
if (!l1) {
toast("目标一级组不存在");
return;
}
for (const g of groups) favoritesL1.assignGroupToL1(g.id, l1.id);
}
const removedShells = [];
for (const sid of sourceIds) {
if (sid === l1.id) continue;
const shell = favoritesL1Store.list.find((x) => x.id === sid);
if (shell && !favoritesStore.list.some((g) => g.l1Id === sid)) {
removedShells.push(shell.name);
favoritesL1.removeL1(sid);
}
}
return {
l1,
moved: groups.length,
removedShells
};
},
/**
* 一级组并入 (票 29 面板 L1 行内「并入…」): 源麾下全部 L2 逐组改指目标 => 删空壳源;
* 单次动作替代「勾选+滚回顶部」合并链路; sourceId===targetId / 任一端失效 => 拒;
* 返回并入 L2 数 (undefined=拒; 0=源本就空, 等同删壳)
*/
mergeL1IntoL1(sourceId, targetId) {
if (sourceId === targetId) return void 0;
const source = favoritesL1Store.list.find((x) => x.id === sourceId);
const target = favoritesL1Store.list.find((x) => x.id === targetId);
if (!source || !target) {
toast("一级组不存在");
return;
}
const sourceName = source.name;
const targetName = target.name;
const children = favoritesStore.list.filter((g) => g.l1Id === sourceId);
for (const g of children) favoritesL1.assignGroupToL1(g.id, targetId);
favoritesL1.removeL1(sourceId);
toast(`已将一级组「${sourceName}」麾下 ${children.length} 个标签组并入「${targetName}」, 源一级组已删`);
return children.length;
}
});
tagRecStoresHydrated.then(() => setTimeout(() => {
if (storeHydrateOk.favorites && storeHydrateOk.favoritesL1) migrateFavoritesToL1();
if (storeHydrateOk.sediment) trimSedimentPoolEdges(sedimentPool.map);
}));
/**
* 当前主题持久位 (独立 key): null = 「全部」档无筛 (现状)
* 落盘用 null 而非 undefined —— null 哨兵在存储层显式往返 (键始终在、回读可判);
* undefined 依赖序列化丢键语义, 回读落骨架缺省值, 链路隐晦不可靠
*/
var themes = exports("w",{
get list() {
return themesStore.list;
},
/** 批量入主题表 (JSON 导入): 撞车复用/悬挂合成已由 planTagRecImport 完成, 与 favoritesL1.addL1s 同款静默批写 */
addThemes(items) {
themesStore.list.push(...items.map((t) => ({ ...t })));
return items.length;
},
/** 面板「新建主题」: 纯筛子实体 (无 weight/enabled/tag); 空名拒建 */
addTheme(input) {
const t = makeTagTheme(input);
if (!t) {
toast("主题名不能为空");
return;
}
themesStore.list.push(t);
return t;
},
/** 改名 (预留管理入口): 空名拒改 */
updateTheme(id, patch) {
const t = themesStore.list.find((x) => x.id === id);
if (!t) return false;
if (patch.name !== void 0) {
const name = patch.name.trim();
if (!name) {
toast("主题名不能为空");
return false;
}
t.name = name;
}
return true;
},
/**
* 删主题: 麾下 L1 的 themeId 清回 undefined (落回「未归属」桶); 零迁移对偶操作
* R7: 该主题的查看范围桶按并集并入「未归属」桶 (麾下 L1 的视图尽量保续)
*/
removeTheme(id) {
const idx = themesStore.list.findIndex((x) => x.id === id);
if (idx === -1) return false;
const t = themesStore.list[idx];
themesStore.list.splice(idx, 1);
for (const l1 of favoritesL1Store.list) if (l1.themeId === id) delete l1.themeId;
tagRecViewScopeAbsorb(viewScopeStore.scope, id, TAG_REC_VIEW_UNASSIGNED);
toast(`已删除主题「${t.name}」, 其麾下的一级组回落「全部」档`);
return true;
},
/**
* 批量删主题 (名册勾选「删除」): 一遍 filter 等价逐 id removeTheme 的存储面 ——
* 麾下 L1 themeId 清回落「未归属」; 查看范围桶逐个并入「未归属」; 失效 id 静默跳过;
* 不逐条 toast, 调用侧汇总。返回删除数
*/
removeThemes(ids) {
const dead = new Set(ids);
const before = themesStore.list.length;
for (const l1 of favoritesL1Store.list) if (l1.themeId && dead.has(l1.themeId)) delete l1.themeId;
themesStore.list = themesStore.list.filter((t) => !dead.has(t.id));
for (const id of dead) tagRecViewScopeAbsorb(viewScopeStore.scope, id, TAG_REC_VIEW_UNASSIGNED);
return before - themesStore.list.length;
},
/** 「清空收藏表」配套 (危险区): 名册全清 + 麾下 L1 themeId 清回 + 查看范围复位全看, 返回删除数 */
clearAll() {
const n = themesStore.list.length;
themesStore.list = [];
for (const l1 of favoritesL1Store.list) if (l1.themeId !== void 0) delete l1.themeId;
viewScope.selectAll();
return n;
},
/**
* 并入主题 (feat: 主题复选并入 —— 清 0-L1 空壳主路径): 源主题麾下 L1 的 themeId 全改挂 target,
* 源主题删; target 自身在源列表内 => 跳过 (并入自己=其余并入它); 目标失效 => undefined;
* R7: 源主题查看范围桶按并集并入 target 桶 (并入后看的是同一撮, 视图保续)
*/
mergeThemesInto(sourceIds, targetId) {
if (!themesStore.list.find((t) => t.id === targetId)) {
toast("目标主题不存在");
return;
}
const srcSet = new Set(sourceIds.filter((id) => id !== targetId));
if (!srcSet.size) return 0;
for (const l1 of favoritesL1Store.list) if (l1.themeId && srcSet.has(l1.themeId)) l1.themeId = targetId;
themesStore.list = themesStore.list.filter((t) => !srcSet.has(t.id));
for (const id of srcSet) tagRecViewScopeAbsorb(viewScopeStore.scope, id, targetId);
return srcSet.size;
},
/**
* 指认 L1 所属主题 (面板 L1 行「所属主题」下拉, 与 assignGroupToL1 同款单父语义):
* themeId 须存在否则拒; undefined => 清回未归属 (落「全部」档)
*/
assignL1ToTheme(l1Id, themeId) {
const l1 = favoritesL1Store.list.find((x) => x.id === l1Id);
if (!l1) return false;
if (themeId === void 0) {
delete l1.themeId;
return true;
}
const t = themesStore.list.find((x) => x.id === themeId);
if (!t) {
toast("目标主题不存在");
return false;
}
l1.themeId = t.id;
return true;
},
/**
* 查看范围切换 (tabbar 深链/旧入口): 只写查看范围 —— 零写入裁决, 不动任何 L1 enabled;
* undefined => 全看 (清空记录); id => 只看该主题 (其余桶清空); 失效 id 拒
*/
setActive(themeId) {
if (themeId !== void 0 && !themesStore.list.some((t) => t.id === themeId)) {
toast("目标主题不存在");
return false;
}
if (themeId === void 0) viewScope.selectAll();
else viewScope.onlyThemes([themeId]);
return true;
}
});
/**
* 查看范围门面: 记录形 {桶 => 'all'|L1 id 数组}, 缺键 = 全看 (model/view-scope 纯逻辑)
* - 写口只改会话范围与临时开启; 绝不碰 favoritesL1.enabled
* - 桶 = 主题 id | 未归属哨兵 (无主题/悬挂已删主题的 L1)
*/
var viewScope = exports("m",{
get scope() {
return viewScopeStore.scope;
},
/** 桶列表 (主题 + 未归属; 含麾下 L1 计数) —— 下拉左列/摘要/展开共用 */
buckets() {
const l1s = favoritesL1Store.list;
const out = themesStore.list.map((t) => ({
id: t.id,
name: t.name,
l1Count: l1s.filter((l) => l.themeId === t.id).length
}));
out.push({
id: TAG_REC_VIEW_UNASSIGNED,
name: "未归属",
l1Count: l1s.filter((l) => !l.themeId || !themesStore.list.some((t) => t.id === l.themeId)).length
});
return out;
},
/** 桶当前形 ('all' | string[]; 缺键 => 'all') */
entryOf(bucket) {
return tagRecViewScopeEntry(viewScopeStore.scope, bucket);
},
/** L1 是否在查看范围 */
isViewed(l1) {
const themeIds = new Set(themesStore.list.map((t) => t.id));
return tagRecViewScopeViewedIds(viewScopeStore.scope, [l1], themeIds).has(l1.id);
},
/** 展开 L1 id 集 (deck 判定/指纹/计数共用) */
viewedL1Ids() {
const themeIds = new Set(themesStore.list.map((t) => t.id));
return tagRecViewScopeViewedIds(viewScopeStore.scope, favoritesL1Store.list, themeIds);
},
viewedGroupIds() {
return tagRecViewedGroupIds(favoritesStore.list, viewScopeStore.groupScope);
},
/** 设桶 (undefined/'all' = 删键回缺省全看; [] = 整桶取消; 数组 = 主题内细选) */
setBucket(bucket, value) {
if (value === void 0 || value === "all") delete viewScopeStore.scope[bucket];
else viewScopeStore.scope[bucket] = value;
},
/** 全选 (清空记录 = 缺省全看) */
selectAll() {
viewScopeStore.scope = {};
viewScopeStore.groupScope = {};
tagRecSharedSession.tempEnabledGroupIds = [];
},
/** 收藏表「用此筛选看推荐」: 替换本次范围并临时开启所选主题的停用一级组; 未勾选则恢复长期启用集。 */
previewThemes(themeIds) {
viewScopeStore.groupScope = {};
tagRecSharedSession.tempEnabledGroupIds = [];
if (!themeIds.size) {
this.selectAll();
tagRecSharedSession.tempEnabledL1Ids = [];
return;
}
const scope = {};
for (const b of this.buckets()) scope[b.id] = themeIds.has(b.id) ? "all" : [];
const existingThemes = new Set(themesStore.list.map((t) => t.id));
const temporary = favoritesL1Store.list.filter((l) => !l.enabled && themeIds.has(tagRecViewBucketOf(l, existingThemes))).map((l) => l.id);
viewScopeStore.scope = scope;
tagRecSharedSession.tempEnabledL1Ids = temporary;
},
/** 只看这些主题 (其余桶与未归属清空) —— 横幅「只看这个包的主题」/ setActive 单主题 */
onlyThemes(themeIds) {
viewScopeStore.groupScope = {};
tagRecSharedSession.tempEnabledGroupIds = [];
const wanted = new Set(themeIds);
const scope = {};
for (const b of this.buckets()) scope[b.id] = b.id !== "__theme_unassigned__" && wanted.has(b.id) ? "all" : [];
viewScopeStore.scope = scope;
},
/** 摘要文案 (tabbar 触发器): 「主题:全选」/「主题:A、B(部分)…」 */
summaryText() {
return tagRecViewScopeSummary(viewScopeStore.scope, this.buckets());
},
/** 导出用 legacy activeThemeId (恰好只看单主题 => 该 id; 否则 null; 旧解析端兼容读) */
legacyActiveThemeId() {
return tagRecViewScopeLegacyActiveTheme(viewScopeStore.scope, themesStore.list.map((t) => t.id));
}
});
var blacklist = exports("z",{
get list() {
return blacklistStore.list;
}});
var sediment = exports("y",{
map: sedimentPool.map,
/** Phase 2 共现边写路径: anchorNorm = 调用侧锚点归一名; items = 视频 tag 团 */
merge(anchorNorm, items) {
if (!items.length) return;
sedimentMerge(sedimentPool.map, anchorNorm, items);
},
/** 候选清单勾选确认 => confirmed 强信号 */
confirm(normName) {
sedimentConfirm(sedimentPool.map, normName);
},
/**
* 票 36: 深挖游标回写 —— digPass 页游标成功推进后调用 (allPagesFailed 由调用侧整流跳过);
* 水合读端 = `sediment.map.get(key)?.dig` (建流点经 digResumeFromSediment 纯函数)
*/
noteDig(normName, dig) {
sedimentNoteDig(sedimentPool.map, normName, dig);
},
/** 清空已看后从头召回旧内容,保留标签关系与用户确认的变体。 */
resetDigProgress() {
for (const node of sedimentPool.map.values()) delete node.dig;
},
/** LRU 手动触发 (merge 内已自动 prune) */
prune() {
return pruneSediment(sedimentPool.map);
},
/** 清空可重采的标签关系与发掘进度,用户收藏保留。 */
clear() {
sedimentClear(sedimentPool.map);
}
});
var seen = exports("x",{
/** 原始集合 (读: has/遍历; 写一律走 add/clear 保 FIFO 不变式) */
set: seenPool.set,
has(bvid) {
return seenPool.set.has(bvid);
},
/** ≥50% 进视口 (useTagRecSeen) 即记: 先查重; 超容量 FIFO 删最旧 */
add(bvid) {
return addToSeenSet(seenPool.set, bvid, settings.tagRec.seenCap ?? 2e3);
},
/** 清空浏览去重记录,刷新与服务状态复位由恢复入口协调。 */
clear() {
clearSeenSet(seenPool.set);
}
});var import_react$9 = /* @__PURE__ */ __toESM(require_react());
var TabContext_default = /*#__PURE__*/ (0, import_react$9.createContext)(null);
var useIndicator = (options) => {
const { activeTabOffset, horizontal, rtl, indicator = {} } = options;
const { size, align = "center" } = indicator;
const [inkStyle, setInkStyle] = (0, import_react$9.useState)();
const inkBarRafRef = (0, import_react$9.useRef)(void 0);
const getLength = import_react$9.useCallback((origin) => {
if (typeof size === "function") return size(origin);
if (typeof size === "number") return size;
return origin;
}, [size]);
function cleanInkBarRaf() {
wrapperRaf.cancel(inkBarRafRef.current);
}
(0, import_react$9.useEffect)(() => {
const newInkStyle = {};
if (activeTabOffset) {
if (horizontal) {
newInkStyle.width = getLength(activeTabOffset.width);
const key = rtl ? "right" : "left";
if (align === "start") newInkStyle[key] = activeTabOffset[key];
if (align === "center") {
newInkStyle[key] = activeTabOffset[key] + activeTabOffset.width / 2;
newInkStyle.transform = rtl ? "translateX(50%)" : "translateX(-50%)";
}
if (align === "end") {
newInkStyle[key] = activeTabOffset[key] + activeTabOffset.width;
newInkStyle.transform = "translateX(-100%)";
}
} else {
newInkStyle.height = getLength(activeTabOffset.height);
if (align === "start") newInkStyle.top = activeTabOffset.top;
if (align === "center") {
newInkStyle.top = activeTabOffset.top + activeTabOffset.height / 2;
newInkStyle.transform = "translateY(-50%)";
}
if (align === "end") {
newInkStyle.top = activeTabOffset.top + activeTabOffset.height;
newInkStyle.transform = "translateY(-100%)";
}
}
}
cleanInkBarRaf();
inkBarRafRef.current = wrapperRaf(() => {
if (!(inkStyle && newInkStyle && Object.keys(newInkStyle).every((key) => {
const newValue = newInkStyle[key];
const oldValue = inkStyle[key];
return typeof newValue === "number" && typeof oldValue === "number" ? Math.round(newValue) === Math.round(oldValue) : newValue === oldValue;
}))) setInkStyle(newInkStyle);
});
return cleanInkBarRaf;
}, [
JSON.stringify(activeTabOffset),
horizontal,
rtl,
align,
getLength
]);
return { style: inkStyle };
};
var DEFAULT_SIZE$1 = {
width: 0,
height: 0,
left: 0,
top: 0
};
function useOffsets(tabs, tabSizes, holderScrollWidth) {
return (0, import_react$9.useMemo)(() => {
const map = /* @__PURE__ */ new Map();
const lastOffset = tabSizes.get(tabs[0]?.key) || DEFAULT_SIZE$1;
const rightOffset = lastOffset.left + lastOffset.width;
for (let i = 0; i < tabs.length; i += 1) {
const { key } = tabs[i];
let data = tabSizes.get(key);
if (!data) data = tabSizes.get(tabs[i - 1]?.key) || DEFAULT_SIZE$1;
const entity = map.get(key) || { ...data };
entity.right = rightOffset - entity.left - entity.width;
map.set(key, entity);
}
return map;
}, [
tabs.map((tab) => tab.key).join("_"),
tabSizes,
holderScrollWidth
]);
}
function useSyncState(defaultState, onChange) {
const stateRef = import_react$9.useRef(defaultState);
const [, forceUpdate] = import_react$9.useState({});
function setState(updater) {
const newValue = typeof updater === "function" ? updater(stateRef.current) : updater;
if (newValue !== stateRef.current) onChange(newValue, stateRef.current);
stateRef.current = newValue;
forceUpdate({});
}
return [stateRef.current, setState];
}
var MIN_SWIPE_DISTANCE = .1;
var STOP_SWIPE_DISTANCE = .01;
var REFRESH_INTERVAL = 20;
var SPEED_OFF_MULTIPLE = .995 ** REFRESH_INTERVAL;
function useTouchMove(ref, onOffset) {
const [touchPosition, setTouchPosition] = (0, import_react$9.useState)();
const [lastTimestamp, setLastTimestamp] = (0, import_react$9.useState)(0);
const [lastTimeDiff, setLastTimeDiff] = (0, import_react$9.useState)(0);
const [lastOffset, setLastOffset] = (0, import_react$9.useState)();
const motionRef = (0, import_react$9.useRef)(void 0);
function onTouchStart(e) {
const { screenX, screenY } = e.touches[0];
setTouchPosition({
x: screenX,
y: screenY
});
window.clearInterval(motionRef.current);
}
function onTouchMove(e) {
if (!touchPosition) return;
const { screenX, screenY } = e.touches[0];
setTouchPosition({
x: screenX,
y: screenY
});
const offsetX = screenX - touchPosition.x;
const offsetY = screenY - touchPosition.y;
onOffset(offsetX, offsetY);
const now = Date.now();
setLastTimestamp(now);
setLastTimeDiff(now - lastTimestamp);
setLastOffset({
x: offsetX,
y: offsetY
});
}
function onTouchEnd() {
if (!touchPosition) return;
setTouchPosition(null);
setLastOffset(null);
if (lastOffset) {
const distanceX = lastOffset.x / lastTimeDiff;
const distanceY = lastOffset.y / lastTimeDiff;
if (Math.max(Math.abs(distanceX), Math.abs(distanceY)) < MIN_SWIPE_DISTANCE) return;
let currentX = distanceX;
let currentY = distanceY;
motionRef.current = window.setInterval(() => {
if (Math.abs(currentX) < STOP_SWIPE_DISTANCE && Math.abs(currentY) < STOP_SWIPE_DISTANCE) {
window.clearInterval(motionRef.current);
return;
}
currentX *= SPEED_OFF_MULTIPLE;
currentY *= SPEED_OFF_MULTIPLE;
onOffset(currentX * REFRESH_INTERVAL, currentY * REFRESH_INTERVAL);
}, REFRESH_INTERVAL);
}
}
const lastWheelDirectionRef = (0, import_react$9.useRef)(void 0);
function onWheel(e) {
const { deltaX, deltaY } = e;
let mixed;
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
if (absX === absY) mixed = lastWheelDirectionRef.current === "x" ? deltaX : deltaY;
else if (absX > absY) {
mixed = deltaX;
lastWheelDirectionRef.current = "x";
} else {
mixed = deltaY;
lastWheelDirectionRef.current = "y";
}
if (onOffset(-mixed, -mixed)) e.preventDefault();
}
const touchEventsRef = (0, import_react$9.useRef)(null);
touchEventsRef.current = {
onTouchStart,
onTouchMove,
onTouchEnd,
onWheel
};
import_react$9.useEffect(() => {
function onProxyTouchStart(e) {
touchEventsRef.current.onTouchStart(e);
}
function onProxyTouchMove(e) {
touchEventsRef.current.onTouchMove(e);
}
function onProxyTouchEnd(e) {
touchEventsRef.current.onTouchEnd(e);
}
function onProxyWheel(e) {
touchEventsRef.current.onWheel(e);
}
document.addEventListener("touchmove", onProxyTouchMove, { passive: false });
document.addEventListener("touchend", onProxyTouchEnd, { passive: true });
ref.current.addEventListener("touchstart", onProxyTouchStart, { passive: true });
ref.current.addEventListener("wheel", onProxyWheel, { passive: false });
return () => {
document.removeEventListener("touchmove", onProxyTouchMove);
document.removeEventListener("touchend", onProxyTouchEnd);
};
}, []);
}
/**
* Help to merge callback with `useLayoutEffect`.
* One time will only trigger once.
*/
function useUpdate(callback) {
const [count, setCount] = (0, import_react$9.useState)(0);
const effectRef = (0, import_react$9.useRef)(0);
const callbackRef = (0, import_react$9.useRef)(void 0);
callbackRef.current = callback;
useLayoutUpdateEffect(() => {
callbackRef.current?.();
}, [count]);
return () => {
if (effectRef.current !== count) return;
effectRef.current += 1;
setCount(effectRef.current);
};
}
function useUpdateState(defaultState) {
const batchRef = (0, import_react$9.useRef)([]);
const [, forceUpdate] = (0, import_react$9.useState)({});
const state = (0, import_react$9.useRef)(typeof defaultState === "function" ? defaultState() : defaultState);
const flushUpdate = useUpdate(() => {
let current = state.current;
batchRef.current.forEach((callback) => {
current = callback(current);
});
batchRef.current = [];
state.current = current;
forceUpdate({});
});
function updater(callback) {
batchRef.current.push(callback);
flushUpdate();
}
return [state.current, updater];
}
var DEFAULT_SIZE$2 = {
width: 0,
height: 0,
left: 0,
top: 0,
right: 0
};
function useVisibleRange(tabOffsets, visibleTabContentValue, transform, tabContentSizeValue, addNodeSizeValue, operationNodeSizeValue, { tabs, tabPosition, rtl }) {
let charUnit;
let position;
let transformSize;
if (["top", "bottom"].includes(tabPosition)) {
charUnit = "width";
position = rtl ? "right" : "left";
transformSize = Math.abs(transform);
} else {
charUnit = "height";
position = "top";
transformSize = -transform;
}
return (0, import_react$9.useMemo)(() => {
if (!tabs.length) return [0, 0];
const len = tabs.length;
let endIndex = len;
for (let i = 0; i < len; i += 1) {
const offset = tabOffsets.get(tabs[i].key) || DEFAULT_SIZE$2;
if (Math.floor(offset[position] + offset[charUnit]) > Math.floor(transformSize + visibleTabContentValue)) {
endIndex = i - 1;
break;
}
}
let startIndex = 0;
for (let i = len - 1; i >= 0; i -= 1) if ((tabOffsets.get(tabs[i].key) || DEFAULT_SIZE$2)[position] < transformSize) {
startIndex = i + 1;
break;
}
return startIndex > endIndex ? [0, -1] : [startIndex, endIndex];
}, [
tabOffsets,
visibleTabContentValue,
tabContentSizeValue,
addNodeSizeValue,
operationNodeSizeValue,
transformSize,
tabPosition,
tabs.map((tab) => tab.key).join("_"),
rtl
]);
}
/**
* We trade Map as deps which may change with same value but different ref object.
* We should make it as hash for deps
* */
function stringify(obj) {
let tgt;
if (obj instanceof Map) {
tgt = {};
obj.forEach((v, k) => {
tgt[k] = v;
});
} else tgt = obj;
return JSON.stringify(tgt);
}
var RC_TABS_DOUBLE_QUOTE = "TABS_DQ";
function genDataNodeKey(key) {
return String(key).replace(/"/g, RC_TABS_DOUBLE_QUOTE);
}
function getRemovable(closable, closeIcon, editable, disabled) {
if (!editable || disabled || closable === false || closable === void 0 && (closeIcon === false || closeIcon === null)) return false;
return true;
}
var AddButton = /*#__PURE__*/ import_react$9.forwardRef((props, ref) => {
const { prefixCls, editable, locale, style } = props;
if (!editable || editable.showAdd === false) return null;
return /*#__PURE__*/ import_react$9.createElement("button", {
ref,
type: "button",
className: `${prefixCls}-nav-add`,
style,
"aria-label": locale?.addAriaLabel || "Add tab",
onClick: (event) => {
editable.onEdit("add", { event });
}
}, editable.addIcon || "+");
});
var ExtraContent = /*#__PURE__*/ import_react$9.forwardRef((props, ref) => {
const { position, prefixCls, extra } = props;
if (!extra) return null;
let content;
let assertExtra = {};
if (typeof extra === "object" && !/*#__PURE__*/ import_react$9.isValidElement(extra)) assertExtra = extra;
else assertExtra.right = extra;
if (position === "right") content = assertExtra.right;
if (position === "left") content = assertExtra.left;
return content ? /*#__PURE__*/ import_react$9.createElement("div", {
className: `${prefixCls}-extra-content`,
ref
}, content) : null;
});
function _extends$4() {
_extends$4 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$4.apply(this, arguments);
}
var OperationNode = /*#__PURE__*/ import_react$9.forwardRef((props, ref) => {
const { prefixCls, id, tabs, locale, mobile, more: moreProps = {}, style, className, editable, tabBarGutter, rtl, removeAriaLabel, onTabClick, getPopupContainer, popupClassName, popupStyle, classNames, styles } = props;
const [open, setOpen] = (0, import_react$9.useState)(false);
const [selectedKey, setSelectedKey] = (0, import_react$9.useState)(null);
const { icon: moreIcon = "More", popupRender } = moreProps;
const popupId = `${id}-more-popup`;
const dropdownPrefix = `${prefixCls}-dropdown`;
const selectedItemId = selectedKey !== null ? `${popupId}-${selectedKey}` : null;
const dropdownAriaLabel = locale?.dropdownAriaLabel;
function onRemoveTab(event, key) {
event.preventDefault();
event.stopPropagation();
editable.onEdit("remove", {
key,
event
});
}
const menu = /*#__PURE__*/ import_react$9.createElement(ExportMenu, {
onClick: ({ key, domEvent }) => {
onTabClick(key, domEvent);
setOpen(false);
},
prefixCls: `${dropdownPrefix}-menu`,
id: popupId,
tabIndex: -1,
role: "listbox",
"aria-activedescendant": selectedItemId,
selectedKeys: [selectedKey],
"aria-label": dropdownAriaLabel !== void 0 ? dropdownAriaLabel : "expanded dropdown"
}, tabs.map((tab) => {
const { closable, disabled, closeIcon, key, label } = tab;
const removable = getRemovable(closable, closeIcon, editable, disabled);
return /*#__PURE__*/ import_react$9.createElement(MenuItem_default, {
key,
id: `${popupId}-${key}`,
role: "option",
"aria-controls": id && `${id}-panel-${key}`,
disabled
}, /*#__PURE__*/ import_react$9.createElement("span", null, label), removable && /*#__PURE__*/ import_react$9.createElement("button", {
type: "button",
"aria-label": removeAriaLabel || "remove",
tabIndex: 0,
className: clsx(`${dropdownPrefix}-menu-item-remove`, classNames?.remove),
style: styles?.remove,
onClick: (e) => {
e.stopPropagation();
onRemoveTab(e, key);
}
}, closeIcon || editable.removeIcon || "×"));
}));
const overlay = popupRender ? popupRender(menu, {
restTabs: tabs,
onClose: () => setOpen(false)
}) : menu;
function selectOffset(offset) {
const enabledTabs = tabs.filter((tab) => !tab.disabled);
let selectedIndex = enabledTabs.findIndex((tab) => tab.key === selectedKey) || 0;
const len = enabledTabs.length;
for (let i = 0; i < len; i += 1) {
selectedIndex = (selectedIndex + offset + len) % len;
const tab = enabledTabs[selectedIndex];
if (!tab.disabled) {
setSelectedKey(tab.key);
return;
}
}
}
function onKeyDown(e) {
const { which } = e;
if (!open) {
if ([
KeyCode.DOWN,
KeyCode.SPACE,
KeyCode.ENTER
].includes(which)) {
setOpen(true);
e.preventDefault();
}
return;
}
switch (which) {
case KeyCode.UP:
selectOffset(-1);
e.preventDefault();
break;
case KeyCode.DOWN:
selectOffset(1);
e.preventDefault();
break;
case KeyCode.ESC:
setOpen(false);
break;
case KeyCode.SPACE:
case KeyCode.ENTER: if (selectedKey !== null) onTabClick(selectedKey, e);
}
}
(0, import_react$9.useEffect)(() => {
const ele = document.getElementById(selectedItemId);
if (ele?.scrollIntoView) ele.scrollIntoView(false);
}, [selectedItemId, selectedKey]);
(0, import_react$9.useEffect)(() => {
if (!open) setSelectedKey(null);
}, [open]);
const moreStyle = { marginInlineStart: tabBarGutter };
if (!tabs.length) {
moreStyle.visibility = "hidden";
moreStyle.order = 1;
}
const overlayClassName = clsx(popupClassName, { [`${dropdownPrefix}-rtl`]: rtl });
const moreNode = mobile ? null : /*#__PURE__*/ import_react$9.createElement(es_default$4, _extends$4({
prefixCls: dropdownPrefix,
overlay,
visible: tabs.length ? open : false,
onVisibleChange: setOpen,
overlayClassName,
overlayStyle: popupStyle,
mouseEnterDelay: .1,
mouseLeaveDelay: .1,
getPopupContainer
}, moreProps), /*#__PURE__*/ import_react$9.createElement("button", {
type: "button",
className: `${prefixCls}-nav-more`,
style: moreStyle,
"aria-haspopup": "listbox",
"aria-controls": popupId,
id: `${id}-more`,
"aria-expanded": open,
onKeyDown
}, moreIcon));
return /*#__PURE__*/ import_react$9.createElement("div", {
className: clsx(`${prefixCls}-nav-operations`, className),
style,
ref
}, moreNode, /*#__PURE__*/ import_react$9.createElement(AddButton, {
prefixCls,
locale,
editable
}));
});
var OperationNode_default = /*#__PURE__*/ import_react$9.memo(OperationNode, (_, next) => next.tabMoving);
var TabNode = (props) => {
const { prefixCls, id, active, focus, tab: { key, label, disabled, closeIcon, icon }, closable, renderWrapper, removeAriaLabel, editable, onClick, onFocus, onBlur, onKeyDown, onMouseDown, onMouseUp, styles, classNames: tabNodeClassNames, tabCount, currentPosition } = props;
const tabPrefix = `${prefixCls}-tab`;
const removable = getRemovable(closable, closeIcon, editable, disabled);
function onInternalClick(e) {
if (disabled) return;
onClick(e);
}
function onRemoveTab(event) {
event.preventDefault();
event.stopPropagation();
editable.onEdit("remove", {
key,
event
});
}
const labelNode = import_react$9.useMemo(() => icon && typeof label === "string" ? /*#__PURE__*/ import_react$9.createElement("span", null, label) : label, [label, icon]);
const btnRef = import_react$9.useRef(null);
import_react$9.useEffect(() => {
if (focus && btnRef.current) btnRef.current.focus();
}, [focus]);
const node = /*#__PURE__*/ import_react$9.createElement("div", {
key,
"data-node-key": genDataNodeKey(key),
className: clsx(tabPrefix, tabNodeClassNames?.item, {
[`${tabPrefix}-with-remove`]: removable,
[`${tabPrefix}-active`]: active,
[`${tabPrefix}-disabled`]: disabled,
[`${tabPrefix}-focus`]: focus
}),
style: styles?.item,
onClick: onInternalClick
}, /*#__PURE__*/ import_react$9.createElement("div", {
ref: btnRef,
role: "tab",
"aria-selected": active,
id: id && `${id}-tab-${key}`,
className: `${tabPrefix}-btn`,
"aria-controls": id && `${id}-panel-${key}`,
"aria-disabled": disabled,
tabIndex: disabled ? null : active ? 0 : -1,
onClick: (e) => {
e.stopPropagation();
onInternalClick(e);
},
onKeyDown,
onMouseDown,
onMouseUp,
onFocus,
onBlur
}, focus && /*#__PURE__*/ import_react$9.createElement("div", {
"aria-live": "polite",
style: {
width: 0,
height: 0,
position: "absolute",
overflow: "hidden",
opacity: 0
}
}, `Tab ${currentPosition} of ${tabCount}`), icon && /*#__PURE__*/ import_react$9.createElement("span", { className: `${tabPrefix}-icon` }, icon), label && labelNode), removable && /*#__PURE__*/ import_react$9.createElement("button", {
type: "button",
"aria-label": removeAriaLabel || "remove",
tabIndex: active ? 0 : -1,
className: clsx(`${tabPrefix}-remove`, tabNodeClassNames?.remove),
style: styles?.remove,
onClick: (e) => {
e.stopPropagation();
onRemoveTab(e);
}
}, closeIcon || editable.removeIcon || "×"));
return renderWrapper ? renderWrapper(node) : node;
};
function _extends$3$1() {
_extends$3$1 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$3$1.apply(this, arguments);
}
var getTabSize = (tab, containerRect) => {
const { offsetWidth, offsetHeight, offsetTop, offsetLeft } = tab;
const { width, height, left, top } = tab.getBoundingClientRect();
if (Math.abs(width - offsetWidth) < 1) return [
width,
height,
left - containerRect.left,
top - containerRect.top
];
return [
offsetWidth,
offsetHeight,
offsetLeft,
offsetTop
];
};
var getSize = (refObj) => {
const { offsetWidth = 0, offsetHeight = 0 } = refObj.current || {};
if (refObj.current) {
const { width, height } = refObj.current.getBoundingClientRect();
if (Math.abs(width - offsetWidth) < 1) return [width, height];
}
return [offsetWidth, offsetHeight];
};
/**
* Convert `SizeInfo` to unit value. Such as [123, 456] with `top` position get `123`
*/
var getUnitValue = (size, tabPositionTopOrBottom) => {
return size[tabPositionTopOrBottom ? 0 : 1];
};
var TabNavList = /*#__PURE__*/ import_react$9.forwardRef((props, ref) => {
const { className, style, id, animated, activeKey, rtl, extra, editable, locale, tabPosition, tabBarGutter, children, onTabClick, onTabScroll, indicator, classNames: tabsClassNames, styles } = props;
const { prefixCls, tabs } = import_react$9.useContext(TabContext_default);
const containerRef = (0, import_react$9.useRef)(null);
const extraLeftRef = (0, import_react$9.useRef)(null);
const extraRightRef = (0, import_react$9.useRef)(null);
const tabsWrapperRef = (0, import_react$9.useRef)(null);
const tabListRef = (0, import_react$9.useRef)(null);
const operationsRef = (0, import_react$9.useRef)(null);
const innerAddButtonRef = (0, import_react$9.useRef)(null);
const tabPositionTopOrBottom = tabPosition === "top" || tabPosition === "bottom";
const [transformLeft, setTransformLeft] = useSyncState(0, (next, prev) => {
if (tabPositionTopOrBottom && onTabScroll) onTabScroll({ direction: next > prev ? "left" : "right" });
});
const [transformTop, setTransformTop] = useSyncState(0, (next, prev) => {
if (!tabPositionTopOrBottom && onTabScroll) onTabScroll({ direction: next > prev ? "top" : "bottom" });
});
const [containerExcludeExtraSize, setContainerExcludeExtraSize] = (0, import_react$9.useState)([0, 0]);
const [tabContentSize, setTabContentSize] = (0, import_react$9.useState)([0, 0]);
const [addSize, setAddSize] = (0, import_react$9.useState)([0, 0]);
const [operationSize, setOperationSize] = (0, import_react$9.useState)([0, 0]);
const [tabSizes, setTabSizes] = useUpdateState(/* @__PURE__ */ new Map());
const tabOffsets = useOffsets(tabs, tabSizes, tabContentSize[0]);
const containerExcludeExtraSizeValue = getUnitValue(containerExcludeExtraSize, tabPositionTopOrBottom);
const tabContentSizeValue = getUnitValue(tabContentSize, tabPositionTopOrBottom);
const addSizeValue = getUnitValue(addSize, tabPositionTopOrBottom);
const operationSizeValue = getUnitValue(operationSize, tabPositionTopOrBottom);
const needScroll = Math.floor(containerExcludeExtraSizeValue) < Math.floor(tabContentSizeValue + addSizeValue);
const visibleTabContentValue = needScroll ? containerExcludeExtraSizeValue - operationSizeValue : containerExcludeExtraSizeValue - addSizeValue;
const operationsHiddenClassName = `${prefixCls}-nav-operations-hidden`;
let transformMin = 0;
let transformMax = 0;
if (!tabPositionTopOrBottom) {
transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue);
transformMax = 0;
} else if (rtl) {
transformMin = 0;
transformMax = Math.max(0, tabContentSizeValue - visibleTabContentValue);
} else {
transformMin = Math.min(0, visibleTabContentValue - tabContentSizeValue);
transformMax = 0;
}
function alignInRange(value) {
if (value < transformMin) return transformMin;
if (value > transformMax) return transformMax;
return value;
}
const touchMovingRef = (0, import_react$9.useRef)(null);
const [lockAnimation, setLockAnimation] = (0, import_react$9.useState)();
function doLockAnimation() {
setLockAnimation(Date.now());
}
function clearTouchMoving() {
if (touchMovingRef.current) clearTimeout(touchMovingRef.current);
}
useTouchMove(tabsWrapperRef, (offsetX, offsetY) => {
function doMove(setState, offset) {
setState((value) => {
return alignInRange(value + offset);
});
}
if (!needScroll) return false;
if (tabPositionTopOrBottom) doMove(setTransformLeft, offsetX);
else doMove(setTransformTop, offsetY);
clearTouchMoving();
doLockAnimation();
return true;
});
(0, import_react$9.useEffect)(() => {
clearTouchMoving();
if (lockAnimation) touchMovingRef.current = setTimeout(() => {
setLockAnimation(0);
}, 100);
return clearTouchMoving;
}, [lockAnimation]);
const [visibleStart, visibleEnd] = useVisibleRange(tabOffsets, visibleTabContentValue, tabPositionTopOrBottom ? transformLeft : transformTop, tabContentSizeValue, addSizeValue, operationSizeValue, {
...props,
tabs
});
const scrollToTab = useEvent$1((key = activeKey) => {
const tabOffset = tabOffsets.get(key) || {
width: 0,
height: 0,
left: 0,
right: 0,
top: 0
};
if (tabPositionTopOrBottom) {
let newTransform = transformLeft;
if (rtl) {
if (tabOffset.right < transformLeft) newTransform = tabOffset.right;
else if (tabOffset.right + tabOffset.width > transformLeft + visibleTabContentValue) newTransform = tabOffset.right + tabOffset.width - visibleTabContentValue;
} else if (tabOffset.left < -transformLeft) newTransform = -tabOffset.left;
else if (tabOffset.left + tabOffset.width > -transformLeft + visibleTabContentValue) newTransform = -(tabOffset.left + tabOffset.width - visibleTabContentValue);
setTransformTop(0);
setTransformLeft(alignInRange(newTransform));
} else {
let newTransform = transformTop;
if (tabOffset.top < -transformTop) newTransform = -tabOffset.top;
else if (tabOffset.top + tabOffset.height > -transformTop + visibleTabContentValue) newTransform = -(tabOffset.top + tabOffset.height - visibleTabContentValue);
setTransformLeft(0);
setTransformTop(alignInRange(newTransform));
}
});
const [focusKey, setFocusKey] = (0, import_react$9.useState)();
const [isMouse, setIsMouse] = (0, import_react$9.useState)(false);
const enabledTabs = tabs.filter((tab) => !tab.disabled).map((tab) => tab.key);
const onOffset = (offset) => {
const currentIndex = enabledTabs.indexOf(focusKey || activeKey);
const len = enabledTabs.length;
const nextIndex = (currentIndex + offset + len) % len;
const newKey = enabledTabs[nextIndex];
setFocusKey(newKey);
};
const handleRemoveTab = (removalTabKey, e) => {
const removeIndex = enabledTabs.indexOf(removalTabKey);
const removeTab = tabs.find((tab) => tab.key === removalTabKey);
if (getRemovable(removeTab?.closable, removeTab?.closeIcon, editable, removeTab?.disabled)) {
e.preventDefault();
e.stopPropagation();
editable.onEdit("remove", {
key: removalTabKey,
event: e
});
if (removeIndex === enabledTabs.length - 1) onOffset(-1);
else onOffset(1);
}
};
const handleMouseDown = (key, e) => {
setIsMouse(true);
if (e.button === 1) handleRemoveTab(key, e);
};
const handleKeyDown = (e) => {
const { code } = e;
const isRTL = rtl && tabPositionTopOrBottom;
const firstEnabledTab = enabledTabs[0];
const lastEnabledTab = enabledTabs[enabledTabs.length - 1];
switch (code) {
case "ArrowLeft":
if (tabPositionTopOrBottom) onOffset(isRTL ? 1 : -1);
break;
case "ArrowRight":
if (tabPositionTopOrBottom) onOffset(isRTL ? -1 : 1);
break;
case "ArrowUp":
e.preventDefault();
if (!tabPositionTopOrBottom) onOffset(-1);
break;
case "ArrowDown":
e.preventDefault();
if (!tabPositionTopOrBottom) onOffset(1);
break;
case "Home":
e.preventDefault();
setFocusKey(firstEnabledTab);
break;
case "End":
e.preventDefault();
setFocusKey(lastEnabledTab);
break;
case "Enter":
case "Space":
e.preventDefault();
onTabClick(focusKey ?? activeKey, e);
break;
case "Backspace":
case "Delete": handleRemoveTab(focusKey, e);
}
};
const tabNodeStyle = {};
if (tabPositionTopOrBottom) tabNodeStyle.marginInlineStart = tabBarGutter;
else tabNodeStyle.marginTop = tabBarGutter;
const tabNodes = tabs.map((tab, i) => {
const { key } = tab;
return /*#__PURE__*/ import_react$9.createElement(TabNode, {
id,
prefixCls,
key,
tab,
classNames: {
item: tabsClassNames?.item,
remove: tabsClassNames?.remove
},
styles: {
item: i === 0 ? styles?.item : {
...tabNodeStyle,
...styles?.item
},
remove: styles?.remove
},
closable: tab.closable,
editable,
active: key === activeKey,
focus: key === focusKey,
renderWrapper: children,
removeAriaLabel: locale?.removeAriaLabel,
tabCount: enabledTabs.length,
currentPosition: i + 1,
onClick: (e) => {
onTabClick(key, e);
},
onKeyDown: handleKeyDown,
onFocus: () => {
if (!isMouse) setFocusKey(key);
scrollToTab(key);
doLockAnimation();
if (!tabsWrapperRef.current) return;
if (!rtl) tabsWrapperRef.current.scrollLeft = 0;
tabsWrapperRef.current.scrollTop = 0;
},
onBlur: () => {
setFocusKey(void 0);
},
onMouseDown: (e) => handleMouseDown(key, e),
onMouseUp: () => {
setIsMouse(false);
}
});
});
const updateTabSizes = () => setTabSizes(() => {
const newSizes = /* @__PURE__ */ new Map();
const listRect = tabListRef.current?.getBoundingClientRect();
tabs.forEach(({ key }) => {
const btnNode = tabListRef.current?.querySelector(`[data-node-key="${genDataNodeKey(key)}"]`);
if (btnNode) {
const [width, height, left, top] = getTabSize(btnNode, listRect);
newSizes.set(key, {
width,
height,
left,
top
});
}
});
return newSizes;
});
(0, import_react$9.useEffect)(() => {
updateTabSizes();
}, [tabs.map((tab) => tab.key).join("_")]);
const onListHolderResize = useUpdate(() => {
const containerSize = getSize(containerRef);
const extraLeftSize = getSize(extraLeftRef);
const extraRightSize = getSize(extraRightRef);
setContainerExcludeExtraSize([containerSize[0] - extraLeftSize[0] - extraRightSize[0], containerSize[1] - extraLeftSize[1] - extraRightSize[1]]);
const newAddSize = getSize(innerAddButtonRef);
setAddSize(newAddSize);
const newOperationSize = getSize(operationsRef);
setOperationSize(newOperationSize);
const tabContentFullSize = getSize(tabListRef);
setTabContentSize([tabContentFullSize[0] - newAddSize[0], tabContentFullSize[1] - newAddSize[1]]);
updateTabSizes();
});
const startHiddenTabs = tabs.slice(0, visibleStart);
const endHiddenTabs = tabs.slice(visibleEnd + 1);
const hiddenTabs = [...startHiddenTabs, ...endHiddenTabs];
const activeTabOffset = tabOffsets.get(activeKey);
const { style: indicatorStyle } = useIndicator({
activeTabOffset,
horizontal: tabPositionTopOrBottom,
indicator,
rtl
});
(0, import_react$9.useEffect)(() => {
scrollToTab();
}, [
activeKey,
transformMin,
transformMax,
stringify(activeTabOffset),
stringify(tabOffsets),
tabPositionTopOrBottom
]);
(0, import_react$9.useEffect)(() => {
onListHolderResize();
}, [rtl]);
const hasDropdown = !!hiddenTabs.length;
const wrapPrefix = `${prefixCls}-nav-wrap`;
let pingLeft;
let pingRight;
let pingTop;
let pingBottom;
if (tabPositionTopOrBottom) {
if (rtl) {
pingRight = transformLeft > 0;
pingLeft = transformLeft !== transformMax;
} else {
pingLeft = transformLeft < 0;
pingRight = transformLeft !== transformMin;
}
} else {
pingTop = transformTop < 0;
pingBottom = transformTop !== transformMin;
}
return /*#__PURE__*/ import_react$9.createElement(RefResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/ import_react$9.createElement("div", {
ref: useComposeRef(ref, containerRef),
role: "tablist",
"aria-orientation": tabPositionTopOrBottom ? "horizontal" : "vertical",
className: clsx(`${prefixCls}-nav`, className, tabsClassNames?.header),
style: {
...styles?.header,
...style
},
onKeyDown: () => {
doLockAnimation();
}
}, /*#__PURE__*/ import_react$9.createElement(ExtraContent, {
ref: extraLeftRef,
position: "left",
extra,
prefixCls
}), /*#__PURE__*/ import_react$9.createElement(RefResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/ import_react$9.createElement("div", {
className: clsx(wrapPrefix, {
[`${wrapPrefix}-ping-left`]: pingLeft,
[`${wrapPrefix}-ping-right`]: pingRight,
[`${wrapPrefix}-ping-top`]: pingTop,
[`${wrapPrefix}-ping-bottom`]: pingBottom
}),
ref: tabsWrapperRef
}, /*#__PURE__*/ import_react$9.createElement(RefResizeObserver, { onResize: onListHolderResize }, /*#__PURE__*/ import_react$9.createElement("div", {
ref: tabListRef,
className: `${prefixCls}-nav-list`,
style: {
transform: `translate(${transformLeft}px, ${transformTop}px)`,
transition: lockAnimation ? "none" : void 0
}
}, tabNodes, /*#__PURE__*/ import_react$9.createElement(AddButton, {
ref: innerAddButtonRef,
prefixCls,
locale,
editable,
style: {
...tabNodes.length === 0 ? void 0 : tabNodeStyle,
visibility: hasDropdown ? "hidden" : null
}
}), /*#__PURE__*/ import_react$9.createElement("div", {
className: clsx(`${prefixCls}-ink-bar`, tabsClassNames?.indicator, { [`${prefixCls}-ink-bar-animated`]: animated.inkBar }),
style: {
...indicatorStyle,
...styles?.indicator
}
}))))), /*#__PURE__*/ import_react$9.createElement(OperationNode_default, _extends$3$1({}, props, {
removeAriaLabel: locale?.removeAriaLabel,
ref: operationsRef,
prefixCls,
tabs: hiddenTabs,
className: !hasDropdown && operationsHiddenClassName,
popupStyle: styles?.popup,
tabMoving: !!lockAnimation
})), /*#__PURE__*/ import_react$9.createElement(ExtraContent, {
ref: extraRightRef,
position: "right",
extra,
prefixCls
})));
});
var TabNavListWrapper = ({ renderTabBar, ...restProps }) => {
if (renderTabBar) return renderTabBar(restProps, TabNavList);
return /*#__PURE__*/ import_react$9.createElement(TabNavList, restProps);
};
var TabPane$1 = /*#__PURE__*/ import_react$9.forwardRef((props, ref) => {
const { prefixCls, className, style, id, active, tabKey, children } = props;
const hasContent = import_react$9.Children.count(children) > 0;
return /*#__PURE__*/ import_react$9.createElement("div", {
id: id && `${id}-panel-${tabKey}`,
role: "tabpanel",
tabIndex: active && hasContent ? 0 : -1,
"aria-labelledby": id && `${id}-tab-${tabKey}`,
"aria-hidden": !active,
style,
className: clsx(prefixCls, active && `${prefixCls}-active`, className),
ref
}, children);
});
function _extends$2$1() {
_extends$2$1 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$2$1.apply(this, arguments);
}
var TabPanelList = (props) => {
const { id, activeKey, animated, tabPosition, destroyOnHidden, bodyStyle, bodyClassName, contentStyle, contentClassName } = props;
const { prefixCls, tabs } = import_react$9.useContext(TabContext_default);
const tabPaneAnimated = animated.tabPane;
const bodyPrefixCls = `${prefixCls}-body`;
const contentPrefixCls = `${prefixCls}-content`;
return /*#__PURE__*/ import_react$9.createElement("div", { className: clsx(`${bodyPrefixCls}-holder`) }, /*#__PURE__*/ import_react$9.createElement("div", {
className: clsx(bodyPrefixCls, `${bodyPrefixCls}-${tabPosition}`, { [`${bodyPrefixCls}-animated`]: tabPaneAnimated }, bodyClassName),
style: bodyStyle
}, tabs.map((item) => {
const { key, forceRender, style: paneStyle, className: paneClassName, destroyOnHidden: itemDestroyOnHidden, ...restTabProps } = item;
const active = key === activeKey;
return /*#__PURE__*/ import_react$9.createElement(es_default$5, _extends$2$1({
key,
visible: active,
forceRender,
removeOnLeave: !!(destroyOnHidden ?? itemDestroyOnHidden),
leavedClassName: `${contentPrefixCls}-hidden`
}, animated.tabPaneMotion), ({ style: motionStyle, className: motionClassName }, ref) => /*#__PURE__*/ import_react$9.createElement(TabPane$1, _extends$2$1({}, restTabProps, {
prefixCls: contentPrefixCls,
id,
tabKey: key,
animated: tabPaneAnimated,
active,
style: {
...contentStyle,
...paneStyle,
...motionStyle
},
className: clsx(contentClassName, paneClassName, motionClassName),
ref
})));
})));
};
function useAnimateConfig$1(animated = {
inkBar: true,
tabPane: false
}) {
let mergedAnimated;
if (animated === false) mergedAnimated = {
inkBar: false,
tabPane: false
};
else if (animated === true) mergedAnimated = {
inkBar: true,
tabPane: false
};
else mergedAnimated = {
inkBar: true,
...typeof animated === "object" ? animated : {}
};
if (mergedAnimated.tabPaneMotion && mergedAnimated.tabPane === void 0) mergedAnimated.tabPane = true;
if (!mergedAnimated.tabPaneMotion && mergedAnimated.tabPane) mergedAnimated.tabPane = false;
return mergedAnimated;
}
function _extends$1$2() {
_extends$1$2 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$1$2.apply(this, arguments);
}
/**
* Should added antd:
* - type
*
* Removed:
* - onNextClick
* - onPrevClick
* - keyboard
*/
var uuid = 0;
var es_default$1 = /* @__PURE__ */ import_react$9.forwardRef((props, ref) => {
const { id, prefixCls = "rc-tabs", className, items, direction, activeKey, defaultActiveKey, editable, animated, tabPosition = "top", tabBarGutter, tabBarStyle, tabBarExtraContent, locale, more, destroyOnHidden, renderTabBar, onChange, onTabClick, onTabScroll, getPopupContainer, popupClassName, indicator, classNames: tabsClassNames, styles, ...restProps } = props;
const tabs = import_react$9.useMemo(() => (items || []).filter((item) => item && typeof item === "object" && "key" in item), [items]);
const rtl = direction === "rtl";
const mergedAnimated = useAnimateConfig$1(animated);
const [mobile, setMobile] = (0, import_react$9.useState)(false);
(0, import_react$9.useEffect)(() => {
setMobile(getIsMobile());
}, []);
const [mergedActiveKey, setMergedActiveKey] = useControlledState(defaultActiveKey ?? tabs[0]?.key, activeKey);
const [activeIndex, setActiveIndex] = (0, import_react$9.useState)(() => tabs.findIndex((tab) => tab.key === mergedActiveKey));
(0, import_react$9.useEffect)(() => {
let newActiveIndex = tabs.findIndex((tab) => tab.key === mergedActiveKey);
if (newActiveIndex === -1) {
newActiveIndex = Math.max(0, Math.min(activeIndex, tabs.length - 1));
setMergedActiveKey(tabs[newActiveIndex]?.key);
}
setActiveIndex(newActiveIndex);
}, [
tabs.map((tab) => tab.key).join("_"),
mergedActiveKey,
activeIndex
]);
const [mergedId, setMergedId] = useControlledState(null, id);
(0, import_react$9.useEffect)(() => {
if (!id) {
setMergedId(`rc-tabs-${uuid}`);
uuid += 1;
}
}, []);
function onInternalTabClick(key, e) {
onTabClick?.(key, e);
const isActiveChanged = key !== mergedActiveKey;
setMergedActiveKey(key);
if (isActiveChanged) onChange?.(key);
}
const sharedProps = {
id: mergedId,
activeKey: mergedActiveKey,
animated: mergedAnimated,
tabPosition,
rtl,
mobile
};
const tabNavBarProps = {
...sharedProps,
editable,
locale,
more,
tabBarGutter,
onTabClick: onInternalTabClick,
onTabScroll,
extra: tabBarExtraContent,
style: tabBarStyle,
getPopupContainer,
popupClassName: clsx(popupClassName, tabsClassNames?.popup),
indicator,
styles,
classNames: tabsClassNames
};
const memoizedValue = import_react$9.useMemo(() => {
return {
tabs,
prefixCls
};
}, [tabs, prefixCls]);
return /*#__PURE__*/ import_react$9.createElement(TabContext_default.Provider, { value: memoizedValue }, /*#__PURE__*/ import_react$9.createElement("div", _extends$1$2({
ref,
id,
className: clsx(prefixCls, `${prefixCls}-${tabPosition}`, {
[`${prefixCls}-mobile`]: mobile,
[`${prefixCls}-editable`]: editable,
[`${prefixCls}-rtl`]: rtl
}, className)
}, restProps), /*#__PURE__*/ import_react$9.createElement(TabNavListWrapper, _extends$1$2({}, tabNavBarProps, { renderTabBar })), /*#__PURE__*/ import_react$9.createElement(TabPanelList, _extends$1$2({ destroyOnHidden }, sharedProps, {
bodyStyle: styles?.body,
bodyClassName: tabsClassNames?.body,
contentStyle: styles?.content,
contentClassName: tabsClassNames?.content,
animated: mergedAnimated
}))));
});
var motion = {
motionAppear: false,
motionEnter: true,
motionLeave: true
};
function useAnimateConfig(prefixCls, animated = {
inkBar: true,
tabPane: false
}) {
let mergedAnimated;
if (animated === false) mergedAnimated = {
inkBar: false,
tabPane: false
};
else if (animated === true) mergedAnimated = {
inkBar: true,
tabPane: true
};
else mergedAnimated = {
inkBar: true,
...isPlainObject(animated) ? animated : {}
};
if (mergedAnimated.tabPane) mergedAnimated.tabPaneMotion = {
...motion,
motionName: getTransitionName(prefixCls, "switch")
};
return mergedAnimated;
}
function filter(items) {
return items.filter((item) => item);
}
function useLegacyItems(items, children) {
if (items) return items.map((item) => ({
...item,
destroyOnHidden: item.destroyOnHidden ?? item.destroyInactiveTabPane
}));
return filter(toArray$2(children).map((node) => {
if (/*#__PURE__*/ import_react$9.isValidElement(node)) {
const { key, props } = node;
const { tab, ...restProps } = props || {};
return {
key: String(key),
...restProps,
label: tab
};
}
return null;
}));
}
var genMotionStyle$1 = (token) => {
const { componentCls, motionDurationSlow } = token;
return [{ [componentCls]: { [`${componentCls}-switch`]: {
"&-appear, &-enter": {
transition: "none",
"&-start": { opacity: 0 },
"&-active": {
opacity: 1,
transition: `opacity ${motionDurationSlow}`
}
},
"&-leave": {
position: "absolute",
transition: "none",
inset: 0,
"&-start": { opacity: 1 },
"&-active": {
opacity: 0,
transition: `opacity ${motionDurationSlow}`
}
}
} } }, [initSlideMotion(token, "slide-up"), initSlideMotion(token, "slide-down")]];
};
var genCardStyle = (token) => {
const { componentCls, tabsCardPadding, cardBg, cardGutter, colorBorderSecondary, itemSelectedColor } = token;
return { [`${componentCls}-card`]: {
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
[`${componentCls}-tab`]: {
margin: 0,
padding: tabsCardPadding,
background: cardBg,
border: `${unit$1(token.lineWidth)} ${token.lineType} ${colorBorderSecondary}`,
transition: `all ${token.motionDurationSlow} ${token.motionEaseInOut}`
},
[`${componentCls}-tab-active`]: {
color: itemSelectedColor,
background: token.colorBgContainer
},
[`${componentCls}-tab-focus:has(${componentCls}-tab-btn:focus-visible)`]: genFocusOutline(token, -3),
[`& ${componentCls}-tab${componentCls}-tab-focus ${componentCls}-tab-btn:focus-visible`]: { outline: "none" },
[`${componentCls}-ink-bar`]: { visibility: "hidden" }
},
[`&${componentCls}-top, &${componentCls}-bottom`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: { [`${componentCls}-tab + ${componentCls}-tab`]: { marginLeft: {
_skip_check_: true,
value: unit$1(cardGutter)
} } } },
[`&${componentCls}-top`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { borderRadius: `${unit$1(token.borderRadiusLG)} ${unit$1(token.borderRadiusLG)} 0 0` },
[`${componentCls}-tab-active`]: { borderBottomColor: token.colorBgContainer }
} },
[`&${componentCls}-bottom`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { borderRadius: `0 0 ${unit$1(token.borderRadiusLG)} ${unit$1(token.borderRadiusLG)}` },
[`${componentCls}-tab-active`]: { borderTopColor: token.colorBgContainer }
} },
[`&${componentCls}-left, &${componentCls}-right`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: { [`${componentCls}-tab + ${componentCls}-tab`]: { marginTop: unit$1(cardGutter) } } },
[`&${componentCls}-left`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { borderRadius: {
_skip_check_: true,
value: `${unit$1(token.borderRadiusLG)} 0 0 ${unit$1(token.borderRadiusLG)}`
} },
[`${componentCls}-tab-active`]: { borderRightColor: {
_skip_check_: true,
value: token.colorBgContainer
} }
} },
[`&${componentCls}-right`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { borderRadius: {
_skip_check_: true,
value: `0 ${unit$1(token.borderRadiusLG)} ${unit$1(token.borderRadiusLG)} 0`
} },
[`${componentCls}-tab-active`]: { borderLeftColor: {
_skip_check_: true,
value: token.colorBgContainer
} }
} }
} };
};
var genDropdownStyle = (token) => {
const { antCls, componentCls, itemHoverColor, dropdownEdgeChildVerticalPadding } = token;
return { [`${componentCls}-dropdown`]: {
...resetComponent(token),
position: "absolute",
top: -9999,
left: {
_skip_check_: true,
value: -9999
},
zIndex: token.zIndexPopup,
display: "block",
"&-hidden": { display: "none" },
[`&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-dropdown-placement-bottomLeft,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-dropdown-placement-bottomLeft,
&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-dropdown-placement-bottom,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-dropdown-placement-bottom,
&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-dropdown-placement-bottomRight,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-dropdown-placement-bottomRight`]: { animationName: slideUpIn },
[`&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-topLeft,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-topLeft,
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-top,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-top,
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-dropdown-placement-topRight,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-dropdown-placement-topRight`]: { animationName: slideDownIn },
[`&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-dropdown-placement-bottomLeft,
&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-dropdown-placement-bottom,
&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-dropdown-placement-bottomRight`]: { animationName: slideUpOut },
[`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-topLeft,
&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-top,
&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-dropdown-placement-topRight`]: { animationName: slideDownOut },
[`${componentCls}-dropdown-menu`]: {
maxHeight: token.tabsDropdownHeight,
margin: 0,
padding: `${unit$1(dropdownEdgeChildVerticalPadding)} 0`,
overflowX: "hidden",
overflowY: "auto",
textAlign: {
_skip_check_: true,
value: "left"
},
listStyleType: "none",
backgroundColor: token.colorBgContainer,
backgroundClip: "padding-box",
borderRadius: token.borderRadiusLG,
outline: "none",
boxShadow: token.boxShadowSecondary,
"&-item": {
...textEllipsis,
display: "flex",
alignItems: "center",
minWidth: token.tabsDropdownWidth,
margin: 0,
padding: `${unit$1(token.paddingXXS)} ${unit$1(token.paddingSM)}`,
color: token.colorText,
fontWeight: "normal",
fontSize: token.fontSize,
lineHeight: token.lineHeight,
cursor: "pointer",
transition: `all ${token.motionDurationSlow}`,
"> span": {
flex: 1,
whiteSpace: "nowrap"
},
"&-remove": {
flex: "none",
marginLeft: {
_skip_check_: true,
value: token.marginSM
},
color: token.colorIcon,
fontSize: token.fontSizeSM,
background: "transparent",
border: 0,
cursor: "pointer",
"&:hover": { color: itemHoverColor }
},
"&:hover": { background: token.controlItemBgHover },
"&-disabled": { "&, &:hover": {
color: token.colorTextDisabled,
background: "transparent",
cursor: "not-allowed"
} }
}
}
} };
};
var genPositionStyle = (token) => {
const { componentCls, margin, colorBorderSecondary, horizontalMargin, verticalItemPadding, verticalItemMargin, motionDurationSlow, calc } = token;
return {
[`${componentCls}-top, ${componentCls}-bottom`]: {
flexDirection: "column",
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
margin: horizontalMargin,
"&::before": {
position: "absolute",
right: {
_skip_check_: true,
value: 0
},
left: {
_skip_check_: true,
value: 0
},
borderBottom: `${unit$1(token.lineWidth)} ${token.lineType} ${colorBorderSecondary}`,
content: "''"
},
[`${componentCls}-ink-bar`]: {
height: token.lineWidthBold,
"&-animated": { transition: [
"width",
"left",
"right"
].map((prop) => `${prop} ${motionDurationSlow}`).join(", ") }
},
[`${componentCls}-nav-wrap`]: {
"&::before, &::after": {
top: 0,
bottom: 0,
width: token.controlHeight
},
"&::before": {
left: {
_skip_check_: true,
value: 0
},
boxShadow: token.boxShadowTabsOverflowLeft
},
"&::after": {
right: {
_skip_check_: true,
value: 0
},
boxShadow: token.boxShadowTabsOverflowRight
},
[`&${componentCls}-nav-wrap-ping-left::before`]: { opacity: 1 },
[`&${componentCls}-nav-wrap-ping-right::after`]: { opacity: 1 }
}
}
},
[`${componentCls}-top`]: { [`> ${componentCls}-nav,
> div > ${componentCls}-nav`]: {
"&::before": { bottom: 0 },
[`${componentCls}-ink-bar`]: { bottom: 0 }
} },
[`${componentCls}-bottom`]: {
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
order: 1,
marginTop: margin,
marginBottom: 0,
"&::before": { top: 0 },
[`${componentCls}-ink-bar`]: { top: 0 }
},
[`> ${componentCls}-body-holder, > div > ${componentCls}-body-holder`]: { order: 0 }
},
[`${componentCls}-left, ${componentCls}-right`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
flexDirection: "column",
minWidth: calc(token.controlHeight).mul(1.25).equal(),
[`${componentCls}-tab`]: {
padding: verticalItemPadding,
textAlign: "center"
},
[`${componentCls}-tab + ${componentCls}-tab`]: { margin: verticalItemMargin },
[`${componentCls}-nav-wrap`]: {
flexDirection: "column",
"&::before, &::after": {
right: {
_skip_check_: true,
value: 0
},
left: {
_skip_check_: true,
value: 0
},
height: token.controlHeight
},
"&::before": {
top: 0,
boxShadow: token.boxShadowTabsOverflowTop
},
"&::after": {
bottom: 0,
boxShadow: token.boxShadowTabsOverflowBottom
},
[`&${componentCls}-nav-wrap-ping-top::before`]: { opacity: 1 },
[`&${componentCls}-nav-wrap-ping-bottom::after`]: { opacity: 1 }
},
[`${componentCls}-ink-bar`]: {
width: token.lineWidthBold,
"&-animated": { transition: ["height", "top"].map((prop) => `${prop} ${motionDurationSlow}`).join(", ") }
},
[`${componentCls}-nav-list, ${componentCls}-nav-operations`]: {
flex: "1 0 auto",
flexDirection: "column"
}
} },
[`${componentCls}-left`]: {
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: { [`${componentCls}-ink-bar`]: { right: {
_skip_check_: true,
value: 0
} } },
[`> ${componentCls}-body-holder, > div > ${componentCls}-body-holder`]: {
marginLeft: {
_skip_check_: true,
value: unit$1(calc(token.lineWidth).mul(-1).equal())
},
borderLeft: {
_skip_check_: true,
value: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorBorder}`
},
[`> ${componentCls}-body > ${componentCls}-content`]: { paddingLeft: {
_skip_check_: true,
value: token.paddingLG
} }
}
},
[`${componentCls}-right`]: {
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
order: 1,
[`${componentCls}-ink-bar`]: { left: {
_skip_check_: true,
value: 0
} }
},
[`> ${componentCls}-body-holder, > div > ${componentCls}-body-holder`]: {
order: 0,
marginRight: {
_skip_check_: true,
value: calc(token.lineWidth).mul(-1).equal()
},
borderRight: {
_skip_check_: true,
value: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorBorder}`
},
[`> ${componentCls}-body > ${componentCls}-content`]: { paddingRight: {
_skip_check_: true,
value: token.paddingLG
} }
}
}
};
};
var genSizeStyle = (token) => {
const { componentCls, cardPaddingSM, cardPaddingLG, cardHeightSM, cardHeightLG, horizontalItemPaddingSM, horizontalItemPaddingLG } = token;
return {
[componentCls]: {
"&-small": { [`> ${componentCls}-nav`]: { [`${componentCls}-tab`]: {
padding: horizontalItemPaddingSM,
fontSize: token.titleFontSizeSM
} } },
"&-large": { [`> ${componentCls}-nav`]: { [`${componentCls}-tab`]: {
padding: horizontalItemPaddingLG,
fontSize: token.titleFontSizeLG,
lineHeight: token.lineHeightLG
} } }
},
[`${componentCls}-card`]: {
[`&${componentCls}-small`]: {
[`> ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { padding: cardPaddingSM },
[`${componentCls}-nav-add`]: {
minWidth: cardHeightSM,
minHeight: cardHeightSM
}
},
[`&${componentCls}-bottom`]: { [`> ${componentCls}-nav ${componentCls}-tab`]: { borderRadius: `0 0 ${unit$1(token.borderRadius)} ${unit$1(token.borderRadius)}` } },
[`&${componentCls}-top`]: { [`> ${componentCls}-nav ${componentCls}-tab`]: { borderRadius: `${unit$1(token.borderRadius)} ${unit$1(token.borderRadius)} 0 0` } },
[`&${componentCls}-right`]: { [`> ${componentCls}-nav ${componentCls}-tab`]: { borderRadius: {
_skip_check_: true,
value: `0 ${unit$1(token.borderRadius)} ${unit$1(token.borderRadius)} 0`
} } },
[`&${componentCls}-left`]: { [`> ${componentCls}-nav ${componentCls}-tab`]: { borderRadius: {
_skip_check_: true,
value: `${unit$1(token.borderRadius)} 0 0 ${unit$1(token.borderRadius)}`
} } }
},
[`&${componentCls}-large`]: { [`> ${componentCls}-nav`]: {
[`${componentCls}-tab`]: { padding: cardPaddingLG },
[`${componentCls}-nav-add`]: {
minWidth: cardHeightLG,
minHeight: cardHeightLG
}
} }
}
};
};
var genTabStyle = (token) => {
const { componentCls, itemActiveColor, itemHoverColor, iconCls, tabsHorizontalItemMargin, horizontalItemPadding, itemSelectedColor, itemColor } = token;
const tabCls = `${componentCls}-tab`;
return {
[tabCls]: {
position: "relative",
WebkitTouchCallout: "none",
WebkitTapHighlightColor: "transparent",
display: "inline-flex",
alignItems: "center",
padding: horizontalItemPadding,
fontSize: token.titleFontSize,
background: "transparent",
border: 0,
outline: "none",
cursor: "pointer",
color: itemColor,
"&-btn, &-remove": { "&:focus:not(:focus-visible), &:active": { color: itemActiveColor } },
"&-btn": {
outline: "none",
transition: `all ${token.motionDurationSlow}`,
[`${tabCls}-icon:not(:last-child)`]: { marginInlineEnd: token.marginSM },
[`${tabCls}-icon > svg`]: {
display: "inline-block",
verticalAlign: "middle",
marginBlockEnd: "0.2em"
}
},
"&-remove": {
flex: "none",
lineHeight: 1,
marginRight: {
_skip_check_: true,
value: token.calc(token.marginXXS).mul(-1).equal()
},
marginLeft: {
_skip_check_: true,
value: token.marginXS
},
color: token.colorIcon,
fontSize: token.fontSizeSM,
background: "transparent",
border: "none",
outline: "none",
cursor: "pointer",
transition: `all ${token.motionDurationSlow}`,
"&:hover": { color: token.colorTextHeading },
...genFocusStyle(token)
},
"&:hover": { color: itemHoverColor },
[`&${tabCls}-active ${tabCls}-btn`]: { color: itemSelectedColor },
[`&${tabCls}-focus ${tabCls}-btn:focus-visible`]: genFocusOutline(token),
[`&${tabCls}-disabled`]: {
color: token.colorTextDisabled,
cursor: "not-allowed"
},
[`&${tabCls}-disabled ${tabCls}-btn, &${tabCls}-disabled ${componentCls}-remove`]: { "&:focus, &:active": { color: token.colorTextDisabled } },
[`& ${tabCls}-remove ${iconCls}`]: {
margin: 0,
verticalAlign: "middle"
},
[`${iconCls}:not(:last-child)`]: { marginRight: {
_skip_check_: true,
value: token.marginSM
} }
},
[`${tabCls} + ${tabCls}`]: { margin: {
_skip_check_: true,
value: tabsHorizontalItemMargin
} }
};
};
var genRtlStyle = (token) => {
const { componentCls, tabsHorizontalItemMarginRTL, iconCls, cardGutter, calc } = token;
return {
[`${componentCls}-rtl`]: {
direction: "rtl",
[`${componentCls}-nav`]: { [`${componentCls}-tab`]: {
margin: {
_skip_check_: true,
value: tabsHorizontalItemMarginRTL
},
[`${componentCls}-tab:last-of-type`]: { marginLeft: {
_skip_check_: true,
value: 0
} },
[iconCls]: {
marginRight: {
_skip_check_: true,
value: 0
},
marginLeft: {
_skip_check_: true,
value: token.marginSM
}
},
[`${componentCls}-tab-remove`]: {
marginRight: {
_skip_check_: true,
value: token.marginXS
},
marginLeft: {
_skip_check_: true,
value: calc(token.marginXXS).mul(-1).equal()
},
[iconCls]: { margin: 0 }
}
} },
[`&${componentCls}-left`]: {
[`> ${componentCls}-nav`]: { order: 1 },
[`> ${componentCls}-body-holder`]: { order: 0 }
},
[`&${componentCls}-right`]: {
[`> ${componentCls}-nav`]: { order: 0 },
[`> ${componentCls}-body-holder`]: { order: 1 }
},
[`&${componentCls}-card${componentCls}-top, &${componentCls}-card${componentCls}-bottom`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: { [`${componentCls}-tab + ${componentCls}-tab`]: {
marginRight: {
_skip_check_: true,
value: cardGutter
},
marginLeft: {
_skip_check_: true,
value: 0
}
} } }
},
[`${componentCls}-dropdown-rtl`]: { direction: "rtl" },
[`${componentCls}-menu-item`]: { [`${componentCls}-dropdown-rtl`]: { textAlign: {
_skip_check_: true,
value: "right"
} } }
};
};
var genTabsStyle = (token) => {
const { componentCls, tabsCardPadding, cardHeight, cardGutter, itemHoverColor, itemActiveColor, colorBorderSecondary } = token;
return {
[componentCls]: {
...resetComponent(token),
display: "flex",
[`> ${componentCls}-nav, > div > ${componentCls}-nav`]: {
position: "relative",
display: "flex",
flex: "none",
alignItems: "center",
[`${componentCls}-nav-wrap`]: {
position: "relative",
display: "flex",
flex: "auto",
alignSelf: "stretch",
overflow: "hidden",
whiteSpace: "nowrap",
transform: "translate(0)",
"&::before, &::after": {
position: "absolute",
zIndex: 1,
opacity: 0,
transition: `opacity ${token.motionDurationSlow}`,
content: "''",
pointerEvents: "none"
}
},
[`${componentCls}-nav-list`]: {
position: "relative",
display: "flex",
transition: `opacity ${token.motionDurationSlow}`
},
[`${componentCls}-nav-operations`]: {
display: "flex",
alignSelf: "stretch"
},
[`${componentCls}-nav-operations-hidden`]: {
position: "absolute",
visibility: "hidden",
pointerEvents: "none"
},
[`${componentCls}-nav-more`]: {
position: "relative",
padding: tabsCardPadding,
background: "transparent",
border: 0,
color: token.colorText,
"&::after": {
position: "absolute",
right: {
_skip_check_: true,
value: 0
},
bottom: 0,
left: {
_skip_check_: true,
value: 0
},
height: token.calc(token.controlHeightLG).div(8).equal(),
transform: "translateY(100%)",
content: "''"
}
},
[`${componentCls}-nav-add`]: {
minWidth: cardHeight,
minHeight: cardHeight,
marginLeft: {
_skip_check_: true,
value: cardGutter
},
background: "transparent",
border: `${unit$1(token.lineWidth)} ${token.lineType} ${colorBorderSecondary}`,
borderRadius: `${unit$1(token.borderRadiusLG)} ${unit$1(token.borderRadiusLG)} 0 0`,
outline: "none",
cursor: "pointer",
color: token.colorText,
transition: `all ${token.motionDurationSlow} ${token.motionEaseInOut}`,
"&:hover": { color: itemHoverColor },
"&:active, &:focus:not(:focus-visible)": { color: itemActiveColor },
...genFocusStyle(token, -3)
}
},
[`${componentCls}-extra-content`]: { flex: "none" },
[`${componentCls}-ink-bar`]: {
position: "absolute",
background: token.inkBarColor,
pointerEvents: "none"
},
...genTabStyle(token),
[`${componentCls}-body`]: {
position: "relative",
width: "100%"
},
[`${componentCls}-body-holder`]: {
flex: "auto",
minWidth: 0,
minHeight: 0
},
[`${componentCls}-content`]: {
...genFocusStyle(token),
"&-hidden": { display: "none" }
}
},
[`${componentCls}-centered`]: { [`> ${componentCls}-nav, > div > ${componentCls}-nav`]: { [`${componentCls}-nav-wrap`]: { [`&:not([class*='${componentCls}-nav-wrap-ping']) > ${componentCls}-nav-list`]: { margin: "auto" } } } }
};
};
var prepareComponentToken$1$1 = (token) => {
const { cardHeight, cardHeightSM, cardHeightLG, controlHeight, controlHeightLG } = token;
const mergedCardHeight = cardHeight || controlHeightLG;
const mergedCardHeightSM = cardHeightSM || controlHeight;
const mergedCardHeightLG = cardHeightLG || controlHeightLG + 8;
return {
zIndexPopup: token.zIndexPopupBase + 50,
cardBg: token.colorFillAlter,
cardHeight: mergedCardHeight,
cardHeightSM: mergedCardHeightSM,
cardHeightLG: mergedCardHeightLG,
cardPadding: `${(mergedCardHeight - token.fontHeight) / 2 - token.lineWidth}px ${token.padding}px`,
cardPaddingSM: `${(mergedCardHeightSM - token.fontHeight) / 2 - token.lineWidth}px ${token.paddingXS}px`,
cardPaddingLG: `${(mergedCardHeightLG - token.fontHeightLG) / 2 - token.lineWidth}px ${token.padding}px`,
titleFontSize: token.fontSize,
titleFontSizeLG: token.fontSizeLG,
titleFontSizeSM: token.fontSize,
inkBarColor: token.colorPrimary,
horizontalMargin: `0 0 ${token.margin}px 0`,
horizontalItemGutter: 32,
horizontalItemMargin: ``,
horizontalItemMarginRTL: ``,
horizontalItemPadding: `${token.paddingSM}px 0`,
horizontalItemPaddingSM: `${token.paddingXS}px 0`,
horizontalItemPaddingLG: `${token.padding}px 0`,
verticalItemPadding: `${token.paddingXS}px ${token.paddingLG}px`,
verticalItemMargin: `${token.margin}px 0 0 0`,
itemColor: token.colorText,
itemSelectedColor: token.colorPrimary,
itemHoverColor: token.colorPrimaryHover,
itemActiveColor: token.colorPrimaryActive,
cardGutter: token.marginXXS / 2
};
};
var style_default$1$1 = genStyleHooks("Tabs", (token) => {
const tabsToken = merge(token, {
tabsCardPadding: token.cardPadding,
dropdownEdgeChildVerticalPadding: token.paddingXXS,
tabsDropdownHeight: 200,
tabsDropdownWidth: 120,
tabsHorizontalItemMargin: `0 0 0 ${unit$1(token.horizontalItemGutter)}`,
tabsHorizontalItemMarginRTL: `0 0 0 ${unit$1(token.horizontalItemGutter)}`
});
return [
genSizeStyle(tabsToken),
genRtlStyle(tabsToken),
genPositionStyle(tabsToken),
genDropdownStyle(tabsToken),
genCardStyle(tabsToken),
genTabsStyle(tabsToken),
genMotionStyle$1(tabsToken)
];
}, prepareComponentToken$1$1);
var TabPane = () => null;
var Tabs = exports("T",/* @__PURE__ */ import_react$9.forwardRef((props, ref) => {
const { type, className, rootClassName, size: customSize, onEdit, hideAdd, centered, addIcon, removeIcon, moreIcon, more, popupClassName, children, items, animated, style, indicatorSize, indicator, classNames, styles, destroyInactiveTabPane, destroyOnHidden, tabPlacement, tabPosition, ...restProps } = props;
const { prefixCls: customizePrefixCls } = restProps;
const { getPrefixCls, direction, getPopupContainer, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("tabs");
const { tabs } = import_react$9.useContext(ConfigContext);
const prefixCls = getPrefixCls("tabs", customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = style_default$1$1(prefixCls, rootCls);
const tabsRef = import_react$9.useRef(null);
import_react$9.useImperativeHandle(ref, () => ({ nativeElement: tabsRef.current }));
let editable;
if (type === "editable-card") editable = {
onEdit: (editType, { key, event }) => {
onEdit?.(editType === "add" ? event : key, editType);
},
removeIcon: removeIcon ?? tabs?.removeIcon ?? /*#__PURE__*/ import_react$9.createElement(RefIcon$4, null),
addIcon: (addIcon ?? tabs?.addIcon) || /*#__PURE__*/ import_react$9.createElement(RefIcon$7, null),
showAdd: hideAdd !== true
};
const rootPrefixCls = getPrefixCls();
const size = useSize(customSize);
const mergedItems = useLegacyItems(items, children);
const mergedAnimated = useAnimateConfig(prefixCls, animated);
const mergedIndicator = {
align: indicator?.align ?? tabs?.indicator?.align,
size: indicator?.size ?? indicatorSize ?? tabs?.indicator?.size ?? tabs?.indicatorSize
};
const mergedPlacement = import_react$9.useMemo(() => {
const placement = tabPlacement ?? tabPosition ?? void 0;
const isRTL = direction === "rtl";
switch (placement) {
case "start": return isRTL ? "right" : "left";
case "end": return isRTL ? "left" : "right";
default: return placement;
}
}, [
tabPlacement,
tabPosition,
direction
]);
const mergedProps = {
...props,
size,
tabPlacement: mergedPlacement,
items: mergedItems
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps }, { popup: { _default: "root" } });
return /*#__PURE__*/ import_react$9.createElement(es_default$1, {
ref: tabsRef,
direction,
getPopupContainer,
...restProps,
items: mergedItems,
className: clsx({
[`${prefixCls}-large`]: size === "large",
[`${prefixCls}-small`]: size === "small",
[`${prefixCls}-card`]: ["card", "editable-card"].includes(type),
[`${prefixCls}-editable-card`]: type === "editable-card",
[`${prefixCls}-centered`]: centered
}, contextClassName, className, rootClassName, mergedClassNames.root, hashId, cssVarCls, rootCls),
classNames: {
...mergedClassNames,
popup: clsx(popupClassName, hashId, cssVarCls, rootCls, mergedClassNames.popup?.root)
},
styles: {
...mergedStyles,
popup: mergedStyles.popup?.root
},
style: mergedStyles.root,
editable,
more: {
icon: tabs?.more?.icon ?? tabs?.moreIcon ?? moreIcon ?? /*#__PURE__*/ import_react$9.createElement(RefIcon$8, null),
transitionName: `${rootPrefixCls}-slide-up`,
...more
},
prefixCls,
animated: mergedAnimated,
indicator: mergedIndicator,
destroyOnHidden: destroyOnHidden ?? destroyInactiveTabPane,
tabPosition: mergedPlacement
});
}));
Tabs.TabPane = TabPane;
var calcThumbStyle = (targetElement, vertical) => {
if (!targetElement) return null;
const style = {
left: targetElement.offsetLeft,
right: targetElement.parentElement.clientWidth - targetElement.clientWidth - targetElement.offsetLeft,
width: targetElement.clientWidth,
top: targetElement.offsetTop,
bottom: targetElement.parentElement.clientHeight - targetElement.clientHeight - targetElement.offsetTop,
height: targetElement.clientHeight
};
if (vertical) return {
left: 0,
right: 0,
width: 0,
top: style.top,
bottom: style.bottom,
height: style.height
};
return {
left: style.left,
right: style.right,
width: style.width,
top: 0,
bottom: 0,
height: 0
};
};
var toPX = (value) => value !== void 0 ? `${value}px` : void 0;
function MotionThumb(props) {
const { prefixCls, containerRef, value, getValueIndex, motionName, onMotionStart, onMotionEnd, direction, vertical = false } = props;
const thumbRef = import_react$9.useRef(null);
const [prevValue, setPrevValue] = import_react$9.useState(value);
const findValueElement = (val) => {
const index = getValueIndex(val);
const ele = containerRef.current?.querySelectorAll(`.${prefixCls}-item`)[index];
return ele?.offsetParent && ele;
};
const [prevStyle, setPrevStyle] = import_react$9.useState(null);
const [nextStyle, setNextStyle] = import_react$9.useState(null);
useLayoutEffect$2(() => {
if (prevValue !== value) {
const prev = findValueElement(prevValue);
const next = findValueElement(value);
const calcPrevStyle = calcThumbStyle(prev, vertical);
const calcNextStyle = calcThumbStyle(next, vertical);
setPrevValue(value);
setPrevStyle(calcPrevStyle);
setNextStyle(calcNextStyle);
if (prev && next) onMotionStart();
else onMotionEnd();
}
}, [value]);
const thumbStart = import_react$9.useMemo(() => {
if (vertical) return toPX(prevStyle?.top ?? 0);
if (direction === "rtl") return toPX(-prevStyle?.right);
return toPX(prevStyle?.left);
}, [
vertical,
direction,
prevStyle
]);
const thumbActive = import_react$9.useMemo(() => {
if (vertical) return toPX(nextStyle?.top ?? 0);
if (direction === "rtl") return toPX(-nextStyle?.right);
return toPX(nextStyle?.left);
}, [
vertical,
direction,
nextStyle
]);
const onAppearStart = () => {
if (vertical) return {
transform: "translateY(var(--thumb-start-top))",
height: "var(--thumb-start-height)"
};
return {
transform: "translateX(var(--thumb-start-left))",
width: "var(--thumb-start-width)"
};
};
const onAppearActive = () => {
if (vertical) return {
transform: "translateY(var(--thumb-active-top))",
height: "var(--thumb-active-height)"
};
return {
transform: "translateX(var(--thumb-active-left))",
width: "var(--thumb-active-width)"
};
};
const onVisibleChanged = () => {
setPrevStyle(null);
setNextStyle(null);
onMotionEnd();
};
if (!prevStyle || !nextStyle) return null;
return /*#__PURE__*/ import_react$9.createElement(es_default$5, {
visible: true,
motionName,
motionAppear: true,
onAppearStart,
onAppearActive,
onVisibleChanged
}, ({ className: motionClassName, style: motionStyle }, ref) => {
const mergedStyle = {
...motionStyle,
"--thumb-start-left": thumbStart,
"--thumb-start-width": toPX(prevStyle?.width),
"--thumb-active-left": thumbActive,
"--thumb-active-width": toPX(nextStyle?.width),
"--thumb-start-top": thumbStart,
"--thumb-start-height": toPX(prevStyle?.height),
"--thumb-active-top": thumbActive,
"--thumb-active-height": toPX(nextStyle?.height)
};
const motionProps = {
ref: composeRef(thumbRef, ref),
style: mergedStyle,
className: clsx(`${prefixCls}-thumb`, motionClassName)
};
return /*#__PURE__*/ import_react$9.createElement("div", motionProps);
});
}
function _extends$5() {
_extends$5 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$5.apply(this, arguments);
}
function getValidTitle(option) {
if (typeof option.title !== "undefined") return option.title;
if (typeof option.label !== "object") return option.label?.toString();
}
function normalizeOptions(options) {
return options.map((option) => {
if (typeof option === "object" && option !== null) {
const validTitle = getValidTitle(option);
return {
...option,
value: option.value,
title: validTitle
};
}
return {
label: option?.toString(),
title: option?.toString(),
value: option
};
});
}
var InternalSegmentedOption = ({ prefixCls, className, style, styles, classNames: segmentedClassNames, data, disabled, checked, label, title, value, name, onChange, onFocus, onBlur, onKeyDown, onKeyUp, onMouseDown, itemRender = (node) => node }) => {
const handleChange = (event) => {
if (disabled) return;
onChange(event, value);
};
return itemRender(/* @__PURE__ */ import_react$9.createElement("label", {
className: clsx(className, { [`${prefixCls}-item-disabled`]: disabled }),
style,
onMouseDown
}, /*#__PURE__*/ import_react$9.createElement("input", {
name,
className: `${prefixCls}-item-input`,
type: "radio",
disabled,
checked,
onChange: handleChange,
onFocus,
onBlur,
onKeyDown,
onKeyUp
}), /*#__PURE__*/ import_react$9.createElement("div", {
className: clsx(`${prefixCls}-item-label`, segmentedClassNames?.label),
title,
style: styles?.label
}, label)), { item: data });
};
var TypedSegmented = /* @__PURE__ */ import_react$9.forwardRef((props, ref) => {
const { prefixCls = "rc-segmented", direction, vertical, options = [], disabled, defaultValue, value, name, onChange, className = "", style, styles, classNames: segmentedClassNames, motionName = "thumb-motion", itemRender, ...restProps } = props;
const containerRef = import_react$9.useRef(null);
const mergedRef = import_react$9.useMemo(() => composeRef(containerRef, ref), [containerRef, ref]);
const segmentedOptions = import_react$9.useMemo(() => {
return normalizeOptions(options);
}, [options]);
const [rawValue, setRawValue] = useControlledState(defaultValue ?? segmentedOptions[0]?.value, value);
const [thumbShow, setThumbShow] = import_react$9.useState(false);
const handleChange = (event, val) => {
setRawValue(val);
onChange?.(val);
};
const divProps = omit(restProps, ["children"]);
const [isKeyboard, setIsKeyboard] = import_react$9.useState(false);
const [isFocused, setIsFocused] = import_react$9.useState(false);
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
};
const handleMouseDown = () => {
setIsKeyboard(false);
};
const handleKeyUp = (event) => {
if (event.key === "Tab") setIsKeyboard(true);
};
const onOffset = (offset) => {
const validOptions = segmentedOptions.filter((option) => option.value === rawValue || !option.disabled);
const currentIndex = validOptions.findIndex((option) => option.value === rawValue);
const total = validOptions.length;
const nextOption = validOptions[(currentIndex + offset + total) % total];
if (nextOption && nextOption.value !== rawValue) {
setRawValue(nextOption.value);
onChange?.(nextOption.value);
}
};
const handleKeyDown = (event) => {
switch (event.key) {
case "ArrowLeft":
case "ArrowUp":
onOffset(-1);
break;
case "ArrowRight":
case "ArrowDown": onOffset(1);
}
};
const renderOption = (segmentedOption) => {
const { value: optionValue, disabled: optionDisabled } = segmentedOption;
return /*#__PURE__*/ import_react$9.createElement(InternalSegmentedOption, _extends$5({}, segmentedOption, {
name,
data: segmentedOption,
itemRender,
key: optionValue,
prefixCls,
className: clsx(segmentedOption.className, `${prefixCls}-item`, segmentedClassNames?.item, {
[`${prefixCls}-item-selected`]: optionValue === rawValue && !thumbShow,
[`${prefixCls}-item-selected-text`]: optionValue === rawValue,
[`${prefixCls}-item-focused`]: isFocused && isKeyboard && optionValue === rawValue
}),
style: styles?.item,
classNames: segmentedClassNames,
styles,
checked: optionValue === rawValue,
onChange: handleChange,
onFocus: handleFocus,
onBlur: handleBlur,
onKeyDown: handleKeyDown,
onKeyUp: handleKeyUp,
onMouseDown: handleMouseDown,
disabled: !!disabled || !!optionDisabled
}));
};
return /*#__PURE__*/ import_react$9.createElement("div", _extends$5({
role: "radiogroup",
"aria-label": "segmented control",
tabIndex: disabled ? void 0 : 0,
"aria-orientation": vertical ? "vertical" : "horizontal",
style
}, divProps, {
className: clsx(prefixCls, {
[`${prefixCls}-rtl`]: direction === "rtl",
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-vertical`]: vertical
}, className),
ref: mergedRef
}), /*#__PURE__*/ import_react$9.createElement("div", { className: `${prefixCls}-group` }, /*#__PURE__*/ import_react$9.createElement(MotionThumb, {
vertical,
prefixCls,
value: rawValue,
containerRef,
motionName: `${prefixCls}-${motionName}`,
direction,
getValueIndex: (val) => segmentedOptions.findIndex((n) => n.value === val),
onMotionStart: () => {
setThumbShow(true);
},
onMotionEnd: () => {
setThumbShow(false);
}
}), segmentedOptions.map(renderOption)));
});
function getItemDisabledStyle(cls, token) {
return { [`${cls}, ${cls}:hover, ${cls}:focus`]: {
color: token.colorTextDisabled,
cursor: "not-allowed"
} };
}
var getItemSelectedStyle = (token) => {
return {
background: token.itemSelectedBg,
boxShadow: token.boxShadowTertiary
};
};
var segmentedTextEllipsisCss = {
overflow: "hidden",
...textEllipsis
};
var genSegmentedStyle = (token) => {
const { componentCls, motionDurationSlow, motionEaseInOut, motionDurationMid } = token;
const labelHeight = token.calc(token.controlHeight).sub(token.calc(token.trackPadding).mul(2)).equal();
const labelHeightLG = token.calc(token.controlHeightLG).sub(token.calc(token.trackPadding).mul(2)).equal();
const labelHeightSM = token.calc(token.controlHeightSM).sub(token.calc(token.trackPadding).mul(2)).equal();
return { [componentCls]: {
...resetComponent(token),
display: "inline-block",
padding: token.trackPadding,
color: token.itemColor,
background: token.trackBg,
borderRadius: token.borderRadius,
transition: `all ${motionDurationMid}`,
...genFocusStyle(token),
[`${componentCls}-group`]: {
position: "relative",
display: "flex",
alignItems: "stretch",
justifyItems: "flex-start",
flexDirection: "row",
width: "100%"
},
[`&${componentCls}-rtl`]: { direction: "rtl" },
[`&${componentCls}-vertical`]: {
[`${componentCls}-group`]: { flexDirection: "column" },
[`${componentCls}-thumb`]: {
width: "100%",
height: 0,
padding: `0 ${unit$1(token.paddingXXS)}`
}
},
[`&${componentCls}-block`]: { display: "flex" },
[`&${componentCls}-block ${componentCls}-item`]: {
flex: 1,
minWidth: 0
},
[`${componentCls}-item`]: {
position: "relative",
textAlign: "center",
cursor: "pointer",
transition: `color ${motionDurationMid}`,
borderRadius: token.borderRadiusSM,
transform: "translateZ(0)",
"&-selected": {
...getItemSelectedStyle(token),
color: token.itemSelectedColor
},
"&-selected-text": {
color: token.itemSelectedColor,
transition: `color ${motionDurationMid}`
},
"&-focused": genFocusOutline(token),
"&::after": {
content: "\"\"",
position: "absolute",
zIndex: -1,
width: "100%",
height: "100%",
top: 0,
insetInlineStart: 0,
borderRadius: "inherit",
opacity: 0,
pointerEvents: "none",
transition: ["opacity", "background-color"].map((prop) => `${prop} ${motionDurationMid}`).join(", ")
},
[`&:not(${componentCls}-item-selected):not(${componentCls}-item-selected-text):not(${componentCls}-item-disabled)`]: {
"&:hover, &:active": { color: token.itemHoverColor },
"&:hover::after": {
opacity: 1,
backgroundColor: token.itemHoverBg
},
"&:active::after": {
opacity: 1,
backgroundColor: token.itemActiveBg
}
},
"&-label": {
minHeight: labelHeight,
lineHeight: unit$1(labelHeight),
padding: `0 ${unit$1(token.segmentedPaddingHorizontal)}`,
...segmentedTextEllipsisCss
},
"&-icon + *": { marginInlineStart: token.calc(token.marginSM).div(2).equal() },
"&-icon > svg": {
display: "inline-block",
verticalAlign: "middle",
marginBlockEnd: "0.2em"
},
"&-input": {
position: "absolute",
insetBlockStart: 0,
insetInlineStart: 0,
width: 0,
height: 0,
opacity: 0,
pointerEvents: "none"
}
},
[`${componentCls}-thumb`]: {
...getItemSelectedStyle(token),
position: "absolute",
insetBlockStart: 0,
insetInlineStart: 0,
width: 0,
height: "100%",
padding: `${unit$1(token.paddingXXS)} 0`,
borderRadius: token.borderRadiusSM,
[`& ~ ${componentCls}-item:not(${componentCls}-item-selected):not(${componentCls}-item-disabled)::after`]: { backgroundColor: "transparent" }
},
[`&${componentCls}-lg`]: {
borderRadius: token.borderRadiusLG,
[`${componentCls}-item-label`]: {
minHeight: labelHeightLG,
lineHeight: unit$1(labelHeightLG),
padding: `0 ${unit$1(token.segmentedPaddingHorizontal)}`,
fontSize: token.fontSizeLG
},
[`${componentCls}-item, ${componentCls}-thumb`]: { borderRadius: token.borderRadius }
},
[`&${componentCls}-sm`]: {
borderRadius: token.borderRadiusSM,
[`${componentCls}-item-label`]: {
minHeight: labelHeightSM,
lineHeight: unit$1(labelHeightSM),
padding: `0 ${unit$1(token.segmentedPaddingHorizontalSM)}`
},
[`${componentCls}-item, ${componentCls}-thumb`]: { borderRadius: token.borderRadiusXS }
},
...getItemDisabledStyle(`&-disabled ${componentCls}-item`, token),
...getItemDisabledStyle(`${componentCls}-item-disabled`, token),
[`${componentCls}-thumb-motion-appear-active`]: {
willChange: "transform, width",
transition: [`transform`, `width`].map((prop) => `${prop} ${motionDurationSlow} ${motionEaseInOut}`).join(", ")
},
[`&${componentCls}-shape-round`]: {
borderRadius: 9999,
[`${componentCls}-item, ${componentCls}-thumb`]: { borderRadius: 9999 }
}
} };
};
var prepareComponentToken$4 = (token) => {
const { colorTextLabel, colorText, colorFillSecondary, colorBgElevated, colorFill, lineWidthBold, colorBgLayout } = token;
return {
trackPadding: lineWidthBold,
trackBg: colorBgLayout,
itemColor: colorTextLabel,
itemHoverColor: colorText,
itemHoverBg: colorFillSecondary,
itemSelectedBg: colorBgElevated,
itemActiveBg: colorFill,
itemSelectedColor: colorText
};
};
var style_default$4 = genStyleHooks("Segmented", (token) => {
const { lineWidth, calc } = token;
return genSegmentedStyle(merge(token, {
segmentedPaddingHorizontal: calc(token.controlPaddingHorizontal).sub(lineWidth).equal(),
segmentedPaddingHorizontalSM: calc(token.controlPaddingHorizontalSM).sub(lineWidth).equal()
}));
}, prepareComponentToken$4);
function isSegmentedLabeledOptionWithIcon(option) {
return isPlainObject(option) && !!option?.icon;
}
var Segmented = exports("N",/* @__PURE__ */ import_react$9.forwardRef((props, ref) => {
const defaultName = useId_default();
const { prefixCls: customizePrefixCls, className, rootClassName, block, options = [], size: customSize, style, vertical, orientation, shape = "default", name = defaultName, styles, classNames, ...restProps } = props;
const { getPrefixCls, direction, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("segmented");
const mergedProps = {
...props,
options,
size: customSize,
shape
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps });
const prefixCls = getPrefixCls("segmented", customizePrefixCls);
const [hashId, cssVarCls] = style_default$4(prefixCls);
const mergedSize = useSize(customSize);
const extendedOptions = import_react$9.useMemo(() => options.map((option) => {
if (isSegmentedLabeledOptionWithIcon(option)) {
const { icon, label, ...restOption } = option;
return {
...restOption,
label: /*#__PURE__*/ import_react$9.createElement(import_react$9.Fragment, null, /*#__PURE__*/ import_react$9.createElement("span", {
className: clsx(`${prefixCls}-item-icon`, mergedClassNames.icon),
style: mergedStyles.icon
}, icon), isReactRenderable(label) && /*#__PURE__*/ import_react$9.createElement("span", null, label))
};
}
return option;
}), [
options,
prefixCls,
mergedClassNames.icon,
mergedStyles.icon
]);
const [, mergedVertical] = useOrientation(orientation, vertical);
const cls = clsx(className, rootClassName, contextClassName, mergedClassNames.root, {
[`${prefixCls}-block`]: block,
[`${prefixCls}-sm`]: mergedSize === "small",
[`${prefixCls}-lg`]: mergedSize === "large",
[`${prefixCls}-vertical`]: mergedVertical,
[`${prefixCls}-shape-${shape}`]: shape === "round"
}, hashId, cssVarCls);
const itemRender = (node, { item }) => {
if (!item.tooltip) return node;
const tooltipProps = isPlainObject(item.tooltip) ? item.tooltip : { title: item.tooltip };
return /*#__PURE__*/ import_react$9.createElement(Tooltip, { ...tooltipProps }, node);
};
return /*#__PURE__*/ import_react$9.createElement(TypedSegmented, {
...restProps,
name,
className: cls,
style: mergedStyles.root,
classNames: mergedClassNames,
styles: mergedStyles,
itemRender,
options: extendedOptions,
ref,
prefixCls,
direction,
vertical: mergedVertical
});
}));
var defaultSvgProps = {
xmlns: "http://www.w3.org/2000/svg",
width: "1em",
height: "1em"
};
var defaultSvgFillProps = { fill: "currentColor" };
/**
* [tag-rec] tab 图标 (~16-18px, 随 className 控制尺寸)
* 基形 = 标签 (Material sell 轮廓, evenodd 打孔)
*/
var tagBasePath = "M21.41 11.58l-9-9A2 2 0 0 0 11 2H4a2 2 0 0 0-2 2v7a2 2 0 0 0 .59 1.42l9 9a2 2 0 0 0 2.83 0l7-7a2 2 0 0 0 0-2.83zM5.5 7A1.5 1.5 0 1 0 7 5.5 1.5 1.5 0 0 0 5.5 7z";
var starBadgePath = "M17.5 13l1.12 2.96 3.16.15-2.47 1.98.74 3.11-2.55-1.71-2.55 1.71.74-3.11-2.47-1.98 3.16-.15z";
/** 「主题推荐」: 标签 + 星 */
function IconForTagRecFav(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
...defaultSvgFillProps,
fillRule: "evenodd",
...props,
children: [/* @__PURE__ */ jsx("path", { d: tagBasePath }), /* @__PURE__ */ jsx("path", { d: starBadgePath })]
});
}
/** 🏷 标签弹层入口: 纯标签 (卡片按钮 + 右键菜单共用) */
function IconForTagManage(props) {
return /* @__PURE__ */ jsx("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
...defaultSvgFillProps,
fillRule: "evenodd",
...props,
children: /* @__PURE__ */ jsx("path", { d: tagBasePath })
});
}
/** ⛔ 拉黑/已黑: 禁止圈 (弹层行动作列) */
function IconForTagBlock(props) {
return /* @__PURE__ */ jsx("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
...defaultSvgFillProps,
...props,
children: /* @__PURE__ */ jsx("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20zm0 2a8 8 0 0 1 6.32 12.9L7.1 5.68A7.96 7.96 0 0 1 12 4zm-6.32 3.1L16.9 18.32A7.96 7.96 0 0 1 12 20a8 8 0 0 1-6.32-12.9z" })
});
}
/** 筛选入口用对象图标,避免标题/简介/分区都显示禁止圈。 */
function IconForFilterAuthor(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
strokeLinecap: "round",
...props,
children: [/* @__PURE__ */ jsx("circle", {
cx: "12",
cy: "7",
r: "4"
}), /* @__PURE__ */ jsx("path", { d: "M4 21v-2a8 8 0 0 1 16 0v2" })]
});
}
function IconForFilterTitle(props) {
return /* @__PURE__ */ jsx("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
strokeLinecap: "round",
...props,
children: /* @__PURE__ */ jsx("path", { d: "M4 6V3h16v3M12 3v18M8 21h8" })
});
}
function IconForFilterDescription(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
strokeLinecap: "round",
strokeLinejoin: "round",
...props,
children: [/* @__PURE__ */ jsx("rect", {
x: "4",
y: "2",
width: "16",
height: "20",
rx: "2"
}), /* @__PURE__ */ jsx("path", { d: "M8 7h8M8 12h8M8 17h5" })]
});
}
function IconForFilterPartition(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
...props,
children: [
/* @__PURE__ */ jsx("rect", {
x: "3",
y: "3",
width: "7",
height: "7",
rx: "1.5"
}),
/* @__PURE__ */ jsx("rect", {
x: "14",
y: "3",
width: "7",
height: "7",
rx: "1.5"
}),
/* @__PURE__ */ jsx("rect", {
x: "3",
y: "14",
width: "7",
height: "7",
rx: "1.5"
}),
/* @__PURE__ */ jsx("rect", {
x: "14",
y: "14",
width: "7",
height: "7",
rx: "1.5"
})
]
});
}
function IconForTagAllow(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
strokeLinejoin: "round",
...props,
children: [/* @__PURE__ */ jsx("path", { d: "M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" }), /* @__PURE__ */ jsx("circle", {
cx: "12",
cy: "12",
r: "3"
})]
});
}
/** 「标签发掘」: 标签 + 放大镜 */
function IconForTagRecCustom(props) {
return /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
...defaultSvgProps,
...defaultSvgFillProps,
fillRule: "evenodd",
...props,
children: [
/* @__PURE__ */ jsx("path", { d: tagBasePath }),
/* @__PURE__ */ jsx("circle", {
cx: "16.6",
cy: "16.6",
r: "3.6",
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8"
}),
/* @__PURE__ */ jsx("path", {
d: "M19.5 19.5 22.3 22.3",
fill: "none",
stroke: "currentColor",
strokeWidth: "1.8",
strokeLinecap: "round"
})
]
});
}
function resolveTagRecBannerState(s) {
if (s.noDrawableHint) return "no-drawable";
if (s.partitionPaused) return "partition-pending";
if (s.hardBottom) return "hard-bottom";
if (s.digPending) return "dig-pending";
if (s.softBottom) return "soft-bottom";
if (s.filterStats.degraded) return "degraded";
return "none";
}
/**
* 票 006: 停牌原因文案 —— "待核实 N 条"旁边必须说清卡在哪个闸、该点哪里。
* 判据全部来自会话镜像位 (无新增取数); 返回 undefined = 无闸无放弃, 不编造原因。
* 优先级: 风控退避 (等) > 供给暂停「先看缓存」(取消勾选) > 额度用尽 (重试/下轮) > 放弃计数 (重试核实)
*/
function tagRecHoldReasonText(s, riskControl) {
if (riskControl) return "风控退避中,分区核验已暂停 · 恢复后自动继续";
if (s.supplyPaused) return "「先看缓存」开着,已暂停取新与补查 · 取消勾选或点「重试核实」";
if (s.partitionBudgetExhausted) return "本轮补查额度用尽 · 点「重试核实」或下轮自动继续";
if ((s.partitionAbandoned ?? 0) > 0) return `其中 ${s.partitionAbandoned} 条多轮未核实已暂停自动重试 · 可点「重试核实」再来一轮`;
}
/** 滤除维度键 => 中文名 (spec §5.4「按维度分解」; 穷尽映射, 漏键编译错) */
var TAG_REC_DROP_DIM_LABELS = exports("Q",{
combination: "组合条件",
blacklist: "负向",
viewMin: "播放下限",
viewMax: "播放上限",
likeMin: "点赞下限",
likeMax: "点赞上限",
favMin: "收藏下限",
favMax: "收藏上限",
coinMin: "投币下限",
coinMax: "投币上限",
danmakuMin: "弹幕下限",
danmakuMax: "弹幕上限",
replyMin: "评论下限",
replyMax: "评论上限",
shareMin: "分享下限",
shareMax: "分享上限",
likeRate: "点赞率",
favRate: "收藏率",
coinRate: "投币率",
interactRate: "互动率",
shareRate: "分享率",
duration: "时长",
pubdate: "发布时间",
upBlacklist: "UP主",
descKeywords: "简介屏蔽词",
partitionBlock: "分区屏蔽",
partitionAllow: "分区仅看",
titleKeywords: "标题关键词",
upAllow: "UP仅看",
titleAllow: "标题仅看",
descAllow: "简介仅看",
tagBlock: "标签屏蔽",
tagAllow: "标签仅看",
poolFilter: "池筛选",
minScore: "综合分"
});
/** 名单来源 scope 豁免名单 (尾注只列组级来源; 全局/会话是默认层不赘述) */
var SCOPE_TAIL_SKIP = /* @__PURE__ */ new Set(["全局", "会话"]);
/**
* 滤除计数文案 (spec §5.4): 「本轮滤除 N 条(负向 x·阈值 y)」按 byDim 分维度分解
* dropped=0 时更简洁; byDim 明细顺序 = filter-engine 首命中归因评估序 (黑名单维在最前);
* stats.unchecked>0 => 尾附「未校验 N」 (严格校验未核查放行条数, 票 14);
* 票12 尾注: byScope 双键聚合出组级/L1 来源 => 尾附「组「X」滤 n · 组「Y」滤 m」(top2, 余等)
*/
function formatTagRecDroppedText(stats, short = false) {
const { dropped, byDim, unchecked, byScope } = stats;
const uncheckedText = unchecked ? ` · 未校验 ${unchecked}` : "";
const seenText = stats.seenDropped ? ` · 已看跳过 ${stats.seenDropped} 条` : "";
const lead = short ? "滤除" : "本轮滤除";
if (!dropped) return `${lead} 0 条${uncheckedText}${seenText}`;
const detail = Object.entries(byDim).filter(([, n]) => n > 0).map(([dim, n]) => `${TAG_REC_DROP_DIM_LABELS[dim]} ${n}`).join("·");
const perScope = /* @__PURE__ */ new Map();
for (const [k, n] of Object.entries(byScope ?? {})) {
const scope = k.slice(k.lastIndexOf("@") + 1);
if (SCOPE_TAIL_SKIP.has(scope) || !n) continue;
perScope.set(scope, (perScope.get(scope) ?? 0) + n);
}
const scopeParts = [...perScope].sort((a, b) => b[1] - a[1]);
const scopeTail = scopeParts.length ? ` · ${scopeParts.slice(0, 2).map(([s, n]) => `组「${s}」滤 ${n}`).join("·")}${scopeParts.length > 2 ? " 等" : ""}` : "";
return `${lead} ${dropped} 条${detail ? `(${detail})` : ""}${uncheckedText}${scopeTail}${seenText}`;
}
/**
* [tag-rec] 四池中文标 (R8 中立模块 —— filter-form 与 scope-config-editor 双向引用会成环, 标签放这)
* 与 filtercfg.js / 站页 POOL_LABELS 同口径
*/
var TAG_REC_POOL_LABELS = exports("M",{
tagTop: "tag/top 池",
allV2: "搜索池 (all/v2·精确)",
allV2Related: "搜索相关池 (all/v2·相关)",
archiveRelated: "推荐相关池 (archive/related)"
});
var TAG_REC_POOL_KEYS = exports("L",Object.keys(TAG_REC_POOL_LABELS));
/**
* [tag-rec] 票 33 右键屏蔽「双写」规划层 —— 纯函数可单测 (spec §4.7 备忘)
* - 「屏蔽此 UP」= filter.byAuthor.keywords (推荐类 tab) + tagRec.filter.dims.upBlacklist (标签两 tab)
* - 「按标题关键词屏蔽…」= filter.byTitle.keywords + tagRec.filter.dims.titleKeywords
* 单写任一边另一面漏放; 落定由调用侧做: 全局侧走 updateSettingsInnerArray 门面 (多 tab 安全),
* dims 侧照 filter-form 先例直写 settings proxy + 开维
* - W1-3: 归一化后 <2 字符的词不产出 (spec ≥2 字符; 单字符词 includes 几乎必中 = 静默灭池)
*/
/** 屏蔽词是否达判定下限 (归一化后 ≥2 字符 —— 与 scopedSubstringHit 消费口径一致) */
var isKeywordUsable = (w) => normalizeTagName(w).length >= 2;
/**
* 「屏蔽此 UP」双写规划:
* - 全局侧一项: 有 mid 写 mid 串 (parseFilterByAuthor 口径: 数字=mid, 非数字=name), 无 mid 退名
* - tagRec 侧一项 {name, mid?}; 去重并入既有逻辑 (filter-form 同口径: 有 mid 按 mid, 无则按 name)
* @returns 两侧各自的待写项 (undefined = 该侧已存在, 跳过)
*/
function planUpBlockWrite(up, globalKeywords, tagRecList) {
const globalContent = up.mid !== void 0 ? `${up.mid}` : up.name?.trim() || void 0;
const globalAdd = globalContent && !globalKeywords.includes(globalContent) ? globalContent : void 0;
const tagRecAdd = {
name: up.name?.trim() ?? "",
mid: up.mid
};
return {
globalAdd,
tagRecAdd: tagRecList.some((e) => up.mid !== void 0 ? e.mid === up.mid : e.name === tagRecAdd.name) ? void 0 : tagRecAdd
};
}
/**
* 「按标题关键词屏蔽」双写规划: 逐词两表各查 (表内 dedupe 含输入自身重复)
* - 两表皆已有 => skipped (toast 注明跳过数)
* - 单边缺 => 补缺侧 (计入对应 adds); 全空/全重 => adds 皆空
*/
function planTitleKeywordWrites(words, globalKeywords, tagRecList) {
const g = new Set(globalKeywords);
const t = new Set(tagRecList);
const seen = /* @__PURE__ */ new Set();
const globalAdds = [];
const tagRecAdds = [];
const skipped = [];
for (const raw of words) {
const w = raw.trim();
if (!w || !isKeywordUsable(w) || seen.has(w)) continue;
seen.add(w);
if (g.has(w) && t.has(w)) {
skipped.push(w);
continue;
}
if (!g.has(w)) globalAdds.push(w);
if (!t.has(w)) tagRecAdds.push(w);
}
return {
globalAdds,
tagRecAdds,
skipped
};
}
/**
* 标题片段切段 (票 33 裁量: 仓库无现成分词, 按标点/空白/符号切)
* trim 去空 + 大小写不敏感去重 (dims.titleKeywords 命中本就是小写子串比较), 保留先现形
*/
var TITLE_SEGMENT_SEP = /[\s\p{P}\p{S}]+/u;
function splitTitleToSegments(title) {
const seen = /* @__PURE__ */ new Set();
const out = [];
for (const raw of title.split(TITLE_SEGMENT_SEP)) {
const seg = raw.trim();
if (!isKeywordUsable(seg)) continue;
const k = seg.toLowerCase();
if (seen.has(k)) continue;
seen.add(k);
out.push(seg);
}
return out;
}
/** 完整对象仅供表单和需要 core 的边界使用;取数/过滤始终传稀疏差分。 */
function materializeTagRecSessionFilter(core, patch) {
const out = cloneTagRecFilterCore(core);
if (!patch) return out;
for (const key of [
"enabled",
"strictMode",
"smallPoolThreshold"
]) if (patch[key] !== void 0) Object.assign(out, { [key]: patch[key] });
Object.assign(out.dims, cloneDeep(patch.dims));
Object.assign(out.score.weights, patch.score?.weights);
Object.assign(out.score.anchors, patch.score?.anchors);
if (patch.pools) out.pools = {
...out.pools,
...cloneDeep(patch.pools)
};
if (patch.poolsEnabled) out.poolsEnabled = {
...out.poolsEnabled,
...patch.poolsEnabled
};
return out;
}
/** 表单基线展示选定组的实际配置;主题推荐的多组场景展示全局基线。 */
function createTagRecSessionFilterBase(core, group, l1) {
const eff = resolveTagRecEffective(core, "tagTop", group, l1);
const out = cloneTagRecFilterCore(eff.cfg);
for (const key of [
"upAllow",
"titleAllow",
"descAllow",
"partitionAllow",
"tagAllow"
]) {
const rule = eff.neg[key];
if (rule) Object.assign(out.dims, { [key]: {
enabled: true,
list: cloneDeep(rule.list),
..."strict" in rule ? { strict: rule.strict } : {}
} });
}
out.pools = void 0;
out.poolsEnabled = { ...eff.poolsEnabled };
return out;
}
/** 表单实际改过的维度、评分字段与来源开关;回到基线即恢复继承。 */
function diffTagRecSessionFilter(next, base, previous) {
const patch = { dims: {} };
for (const key of [
"enabled",
"strictMode",
"smallPoolThreshold"
]) if (next[key] !== base[key]) Object.assign(patch, { [key]: next[key] });
for (const key of Object.keys(base.dims)) if (!isEqual$1(next.dims[key], base.dims[key])) Object.assign(patch.dims, { [key]: cloneDeep(next.dims[key]) });
const score = {};
for (const half of ["weights", "anchors"]) {
const fields = {};
for (const [key, value] of Object.entries(next.score[half])) if (value !== base.score[half][key]) fields[key] = value;
if (Object.keys(fields).length) score[half] = fields;
}
if (Object.keys(score).length) patch.score = score;
for (const pool of TAG_REC_ALL_POOL_KEYS) {
if (!isEqual$1(next.pools?.[pool], base.pools?.[pool]) && next.pools?.[pool]) (patch.pools ??= {})[pool] = cloneDeep(next.pools[pool]);
const enabled = next.poolsEnabled?.[pool] ?? true;
if (enabled !== (base.poolsEnabled?.[pool] ?? true) || previous?.poolsEnabled?.[pool] === enabled) (patch.poolsEnabled ??= {})[pool] = enabled;
}
return Object.keys(patch.dims).length || Object.keys(patch).length > 1 ? patch : void 0;
}
/**
* [tag-rec] R8 临时写位 + 固化引擎 (右键「临时 · 本会话」与过滤弹层「临时名单」共用)
* - 两页分别写自己的 filterOverride 稀疏差分 + tempWriteLog,不写全局或组配置。
* - 账只记「右键临时档」写入 (弹层手改不入账) —— [清空临时]/固化 按账操作, 不误伤手调
* - 固化到本组: 按写入时记录的稳定组 ID 写持久;目标已删/已存在跳过计数
* - 固化到「标签过滤」(全局): 屏蔽类镜像菜单全局档 (UP/标题连带主 feed 双写);
* 仅看类只有「分区仅看」有全局档 (窄口径全局仅看无意义, Q4)
*/
/** 不复制未修改的字段,避免一次右键操作遮蔽整份组配置。 */
function ensureTagRecSessionOverride(scope = "custom") {
const session = tagRecFilterSession(scope);
return session.filterOverride ??= { dims: {} };
}
var dimOfKind = {
upBlock: "upBlacklist",
upAllow: "upAllow",
titleBlock: "titleKeywords",
titleAllow: "titleAllow",
descBlock: "descKeywords",
descAllow: "descAllow",
partitionBlock: "partitionBlock",
partitionAllow: "partitionAllow"
};
function prepareTempDim(patch, kind) {
const key = dimOfKind[kind];
if (patch.dims[key]) return;
const inherited = settings.tagRec.filter.dims[key];
Object.assign(patch.dims, { [key]: kind.endsWith("Allow") || !inherited.enabled ? {
enabled: false,
list: []
} : cloneDeep(inherited) });
}
/** 单条写进 dims (覆盖层): 幂等去重 + 开闸; 只认账内 kind */
function writeEntryToDims(dims, kind, value) {
switch (kind) {
case "upBlock": {
const entry = value;
dims.upBlacklist ??= {
enabled: false,
list: []
};
if (dims.upBlacklist.list.some((e) => entry.mid !== void 0 ? e.mid === entry.mid : e.name === entry.name)) return "dup";
dims.upBlacklist.list.push(entry);
dims.upBlacklist.enabled = true;
return "ok";
}
case "upAllow": {
const entry = value;
dims.upAllow ??= {
enabled: false,
list: []
};
if (dims.upAllow.list.some((e) => entry.mid !== void 0 ? e.mid === entry.mid : e.name === entry.name)) return "dup";
dims.upAllow.list.push(entry);
dims.upAllow.enabled = true;
return "ok";
}
case "titleBlock":
case "titleAllow":
case "descBlock":
case "descAllow": {
const word = String(value);
const key = kind === "titleBlock" ? "titleKeywords" : kind === "titleAllow" ? "titleAllow" : kind === "descBlock" ? "descKeywords" : "descAllow";
const dim = dims[key] ??= {
enabled: false,
list: []
};
if (dim.list.includes(word)) return "dup";
dim.list.push(word);
dim.enabled = true;
return "ok";
}
case "partitionBlock": {
dims.partitionBlock ??= {
enabled: false,
list: []
};
const entry = value;
const key = `${entry.v1 ?? ""}|${entry.v2 ?? ""}`;
if (dims.partitionBlock.list.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) return "dup";
dims.partitionBlock.list.push(entry);
dims.partitionBlock.enabled = true;
return "ok";
}
case "partitionAllow": {
const entry = value;
const key = `${entry.v1 ?? ""}|${entry.v2 ?? ""}`;
dims.partitionAllow ??= {
enabled: false,
list: []
};
if (dims.partitionAllow.list.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) return "dup";
dims.partitionAllow.list.push(entry);
dims.partitionAllow.enabled = true;
return "ok";
}
}
}
/** 单条写进 negative 容器 (组/L1 持久层; 固化到本组用) */
function writeEntryToNegative(neg, entry) {
const { kind, value } = entry;
switch (kind) {
case "upBlock": {
const v = value;
neg.up ??= [];
if (neg.up.some((e) => v.mid !== void 0 ? e.mid === v.mid : e.name === v.name)) return "dup";
neg.up.push(v);
return "ok";
}
case "upAllow": {
const v = value;
neg.upAllow ??= {
enabled: true,
list: []
};
if (neg.upAllow.list.some((e) => v.mid !== void 0 ? e.mid === v.mid : e.name === v.name)) return "dup";
neg.upAllow.list.push(v);
neg.upAllow.enabled = true;
return "ok";
}
case "titleBlock":
case "titleAllow":
case "descBlock":
case "descAllow": {
const word = String(value);
const key = kind === "titleBlock" ? "title" : kind === "titleAllow" ? "titleAllow" : kind === "descBlock" ? "desc" : "descAllow";
if (key === "title" || key === "desc") {
neg[key] ??= [];
if (neg[key].includes(word)) return "dup";
neg[key].push(word);
return "ok";
}
const allowKey = key;
neg[allowKey] ??= {
enabled: true,
list: []
};
if (neg[allowKey].list.includes(word)) return "dup";
neg[allowKey].list.push(word);
neg[allowKey].enabled = true;
return "ok";
}
case "partitionBlock": {
const v = value;
neg.partitionBlock ??= [];
const key = `${v.v1 ?? ""}|${v.v2 ?? ""}`;
if (neg.partitionBlock.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) return "dup";
neg.partitionBlock.push(v);
return "ok";
}
case "partitionAllow": {
const v = value;
neg.partitionAllow ??= {
enabled: true,
list: []
};
const key = `${v.v1 ?? ""}|${v.v2 ?? ""}`;
if (neg.partitionAllow.list.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) return "dup";
neg.partitionAllow.list.push(v);
neg.partitionAllow.enabled = true;
return "ok";
}
}
}
/** 从覆盖里摘除一条 (清空/固化后; 与写入同身份键) */
function removeEntryFromDims(dims, kind, value) {
switch (kind) {
case "upBlock":
case "upAllow": {
const entry = value;
const dim = kind === "upBlock" ? dims.upBlacklist : dims.upAllow;
if (!dim) return;
const idx = dim.list.findIndex((e) => entry.mid !== void 0 ? e.mid === entry.mid : e.name === entry.name);
if (idx !== -1) dim.list.splice(idx, 1);
return;
}
case "titleBlock":
case "titleAllow":
case "descBlock":
case "descAllow": {
const word = String(value);
const dim = dims[kind === "titleBlock" ? "titleKeywords" : kind === "titleAllow" ? "titleAllow" : kind === "descBlock" ? "descKeywords" : "descAllow"];
if (!dim) return;
const idx = dim.list.indexOf(word);
if (idx !== -1) dim.list.splice(idx, 1);
return;
}
case "partitionBlock":
case "partitionAllow": {
const entry = value;
const key = `${entry.v1 ?? ""}|${entry.v2 ?? ""}`;
if (kind === "partitionBlock") {
if (!dims.partitionBlock) return;
const idx = dims.partitionBlock.list.findIndex((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key);
if (idx !== -1) dims.partitionBlock.list.splice(idx, 1);
} else if (dims.partitionAllow) {
const idx = dims.partitionAllow.list.findIndex((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key);
if (idx !== -1) dims.partitionAllow.list.splice(idx, 1);
}
return;
}
}
}
/**
* 右键临时写位:只修改所在页的本次差分并记账。
* @returns 'ok' | 'dup' (已存在于临时名单)
*/
function writeTagRecTempEntry(input) {
const session = tagRecFilterSession(input.scope);
const override = ensureTagRecSessionOverride(input.scope);
const key = dimOfKind[input.kind];
const inherited = override.dims[key] === void 0;
prepareTempDim(override, input.kind);
if (writeEntryToDims(override.dims, input.kind, input.value) === "dup") {
if (inherited) delete override.dims[key];
if (!Object.keys(override.dims).length && Object.keys(override).length === 1) session.filterOverride = void 0;
return "dup";
}
session.tempWriteLog.push({
kind: input.kind,
value: input.value,
label: input.label,
owningGroupNames: [...input.owningGroupNames],
owningGroupIds: input.owningGroupIds ? [...input.owningGroupIds] : input.owningGroupNames.flatMap((name) => {
const matches = favorites.list.filter((g) => g.name === name);
return matches.length === 1 ? [matches[0].id] : [];
})
});
return "ok";
}
/** 仅看家族的全局档只对分区有意义 (窄口径全局仅看无意义, Q4) —— 固化到全局按钮的可用性判据 */
function canKeepTempEntryToGlobal(kind) {
return kind !== "upAllow" && kind !== "titleAllow" && kind !== "descAllow";
}
/**
* 固化到本组: 逐「记账认领组」写持久 (overrides.negative);
* **永不走草稿组**; 目标组已删/值已存在 => 跳过计数; 全跳 (有认领组但一个都没写成) => 保留临时条不消费
*/
function keepTagRecTempEntryToGroup(index, scope = "custom") {
const session = tagRecFilterSession(scope);
const entry = session.tempWriteLog[index];
if (!entry) return;
const ids = entry.owningGroupIds ?? entry.owningGroupNames.flatMap((name) => {
const matches = favorites.list.filter((g) => g.name === name);
return matches.length === 1 ? [matches[0].id] : [];
});
if (!ids.length) {
if (entry.owningGroupNames.length) toast("固化未生效: 目标组已删除或名称不明确, 临时规则已保留");
return;
}
let written = 0;
let skipped = Math.max(0, entry.owningGroupNames.length - ids.length);
for (const id of new Set(ids)) {
const g = favorites.list.find((x) => x.id === id);
if (!g) {
skipped++;
continue;
}
g.overrides ??= {};
g.overrides.negative ??= {};
if (writeEntryToNegative(g.overrides.negative, entry) === "ok") written++;
else skipped++;
}
if (!written) {
toast(`固化未生效: 目标组已删除或值已存在 (跳过 ${skipped} 个) —— 可改「固化到「标签过滤」」`);
return;
}
removeTempEntryFromOverride(entry, scope);
session.tempWriteLog.splice(index, 1);
toast(`已固化到 ${written} 个组${skipped ? ` (跳过 ${skipped} 个: 已删/已存在)` : ""}`);
}
/** 固化到「标签过滤」(全局): 屏蔽类镜像菜单全局档 (UP/标题连带主 feed 双写); 仅看类仅 partitionAllow */
async function keepTagRecTempEntryToGlobal(index, scope = "custom") {
const session = tagRecFilterSession(scope);
const entry = session.tempWriteLog[index];
if (!entry || !canKeepTempEntryToGlobal(entry.kind)) return;
const dims = settings.tagRec.filter.dims;
switch (entry.kind) {
case "upBlock": {
const v = entry.value;
const plan = planUpBlockWrite(v, settings.filter.byAuthor.keywords, dims.upBlacklist.list);
if (plan.globalAdd) await updateSettingsInnerArray("filter.byAuthor.keywords", { add: [plan.globalAdd] });
updateSettings({ filter: { byAuthor: { enabled: true } } });
if (plan.tagRecAdd) dims.upBlacklist.list.push(plan.tagRecAdd);
dims.upBlacklist.enabled = true;
if (v.mid !== void 0 && v.name) setNicknameCache(v.mid, v.name);
break;
}
case "titleBlock": {
const word = String(entry.value);
const globalKw = await getNewestValueOfSettingsInnerArray("filter.byTitle.keywords");
const plan = planTitleKeywordWrites([word], globalKw, dims.titleKeywords.list);
if (plan.globalAdds.length) await updateSettingsInnerArray("filter.byTitle.keywords", { add: plan.globalAdds });
updateSettings({ filter: { byTitle: { enabled: true } } });
if (plan.tagRecAdds.length) dims.titleKeywords.list.push(...plan.tagRecAdds);
dims.titleKeywords.enabled = true;
break;
}
case "descBlock": {
const word = String(entry.value);
if (!dims.descKeywords.list.includes(word)) dims.descKeywords.list.push(word);
dims.descKeywords.enabled = true;
break;
}
case "partitionBlock": {
const v = entry.value;
const key = `${v.v1 ?? ""}|${v.v2 ?? ""}`;
if (!dims.partitionBlock.list.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) dims.partitionBlock.list.push(v);
dims.partitionBlock.enabled = true;
break;
}
case "partitionAllow": {
const v = entry.value;
dims.partitionAllow ??= {
enabled: false,
list: []
};
const key = `${v.v1 ?? ""}|${v.v2 ?? ""}`;
if (!dims.partitionAllow.list.some((e) => `${e.v1 ?? ""}|${e.v2 ?? ""}` === key)) dims.partitionAllow.list.push(v);
dims.partitionAllow.enabled = true;
break;
}
default: return;
}
const currentIndex = session.tempWriteLog.indexOf(entry);
if (currentIndex !== -1) {
removeTempEntryFromOverride(entry, scope);
session.tempWriteLog.splice(currentIndex, 1);
}
toast("已固化到「标签过滤」(长期生效)");
}
/** 从覆盖摘除一条账 (清空/固化共用) */
function removeTempEntryFromOverride(entry, scope = "custom") {
const session = tagRecFilterSession(scope);
const override = session.filterOverride;
if (!override) return;
removeEntryFromDims(override.dims, entry.kind, entry.value);
const key = dimOfKind[entry.kind];
const dim = override.dims[key];
if (dim && (!dim.list.length && dim.enabled || isEqual$1(dim, settings.tagRec.filter.dims[key]))) delete override.dims[key];
if (!Object.keys(override.dims).length && Object.keys(override).length === 1) session.filterOverride = void 0;
}
/** 单条移除 (临时名单 [×]): 摘覆盖条目 + 销账 */
function removeTagRecTempWriteAt(index, scope = "custom") {
const session = tagRecFilterSession(scope);
const entry = session.tempWriteLog[index];
if (!entry) return;
removeTempEntryFromOverride(entry, scope);
session.tempWriteLog.splice(index, 1);
}
/** [清空临时]: 按账摘除覆盖条目 + 清账 (弹层手改不动 —— 那是你自己的编辑, 用「重置为「标签过滤」」清) */
function clearTagRecTempWrites(scope = "custom") {
const session = tagRecFilterSession(scope);
for (const entry of session.tempWriteLog) removeTempEntryFromOverride(entry, scope);
session.tempWriteLog.length = 0;
}function computed(obj) {
const computedState = proxy({});
for (const key in obj) watch(() => {
computedState[key] = obj[key]();
});
return computedState;
}
var import_react$8 = /* @__PURE__ */ __toESM(require_react());
var iconParkOutlineDrag = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "currentColor",
fillRule: "evenodd",
d: "M19 10a4 4 0 1 1-8 0a4 4 0 0 1 8 0m-4 18a4 4 0 1 0 0-8a4 4 0 0 0 0 8m0 14a4 4 0 1 0 0-8a4 4 0 0 0 0 8m22-32a4 4 0 1 1-8 0a4 4 0 0 1 8 0m-4 18a4 4 0 1 0 0-8a4 4 0 0 0 0 8m0 14a4 4 0 1 0 0-8a4 4 0 0 0 0 8",
clipRule: "evenodd"
})]
});
var ForwardRef$b = exports("I",(0, import_react$8.forwardRef)(iconParkOutlineDrag));
function useHotkeyForConfig(hotkey, configPath, label) {
return useHotkey(hotkey, (e) => {
const newValue = !Boolean(get$2(settings, configPath));
set$2(settings, configPath, newValue);
antMessage.success({
content: `已${newValue ? "启用" : "禁用"}「${label}」`,
key: `hotkey-${hotkey}`
});
});
}
var borderCycleList = [
{ useBorder: false },
{
useBorder: true,
useBorderOnlyOnHover: true
},
{
useBorder: true,
useBorderOnlyOnHover: false
}
];
var borderCycleListLabels = exports("b",[
"「卡片边框」: 禁用",
"「卡片边框」: 仅在悬浮时显示",
"「卡片边框」: 总是显示"
]);
function useVideoCardBorderHotkeySetup() {
return useHotkey("Shift+B", (e) => {
const curState = pick(settings.style.videoCard, ["useBorder", "useBorderOnlyOnHover"]);
const curIndex = borderCycleList.findIndex((state) => {
return isEqual$1(state, pick(curState, Object.keys(state)));
});
if (curIndex === -1) throw new Error("unexpected curIndex = -1");
const nextIndex = (curIndex + 1) % borderCycleList.length;
const nextState = borderCycleList[nextIndex];
Object.assign(settings.style.videoCard, nextState);
const nextLabel = borderCycleListLabels[nextIndex];
antMessage.success({
key: "hotkey-border",
content: nextLabel
});
});
}
function ModalSettingsHotkey() {
useHotkeyForConfig("Shift+P", "videoCard.imgPreview.autoPreviewWhenHover", "自动预览: 鼠标悬浮");
useVideoCardBorderHotkeySetup();
return null;
}
var import_ms$2 = /* @__PURE__ */ __toESM(require_ms(), 1);
var debug$4 = baseDebug.extend("main:homepage:init-relations");
var relationsStore = await proxyWithGmStorage({
blacklistUpdatedAt: 0,
followingsUpdatedAt: 0
}, "relations-info");
var cacheStore = computed({
blacklistCacheValid() {
return !!blacklistMidSet.size && !!relationsStore.blacklistUpdatedAt && Date.now() - relationsStore.blacklistUpdatedAt <= (0, import_ms$2.default)("1days");
},
followingsCacheValid() {
return !!followedMidSet.size && !!relationsStore.followingsUpdatedAt && Date.now() - relationsStore.followingsUpdatedAt <= (0, import_ms$2.default)("3days");
}
});
async function initMyRelations() {
if (!IN_BILIBILI_HOMEPAGE) return;
await initLoginStorePromise;
await whenIdle();
await initMyBlacklist();
await initMyFollowings();
}
async function handleManualRefreshRelationsCache() {
{
const result = await initMyBlacklist(false);
if (result.isErr()) {
const err = result.error;
const messageContent = err instanceof WebApiError ? err.formatAsReactNode() : err.message;
antMessage.error(messageContent, 8);
return;
} else antMessage.success(`已更新黑名单: ${blacklistMidSet.size}条数据`);
}
{
const result = await initMyFollowings(false);
if (result.isErr()) {
const err = result.error;
const messageContent = err instanceof WebApiError ? err.formatAsReactNode() : err.message;
antMessage.error(messageContent, 8);
return;
} else antMessage.success(`已更新关注列表: ${followedMidSet.size}条数据`);
}
}
async function initMyBlacklist(useCache = true) {
if (useCache && cacheStore.blacklistCacheValid) return ae.ok({ skip: true });
return (await fetchFullBlacklist()).tapError((err) => appError("initMyBlacklist error:", err)).tap((mids) => {
debug$4("my blocklist fetched: %o", mids);
if (mids.length) {
blacklistMidSetReplaceAllWith(mids.map((x) => x.toString()));
relationsStore.blacklistUpdatedAt = Date.now();
}
});
}
async function initMyFollowings(useCache = true) {
if (useCache && cacheStore.followingsCacheValid) return ae.ok({ skip: true });
return (await fetchFullFollowings()).tapError((err) => appError("initMyFollowings error:", err)).tap((mids) => {
debug$4("my followings fetched: %o", mids);
if (mids.length) {
followedMidSetReplaceAllWith(mids.map((x) => x.toString()));
relationsStore.followingsUpdatedAt = Date.now();
}
});
}var import_react$7 = /* @__PURE__ */ __toESM(require_react());
var defaultShouldUpdate = (a, b) => !Object.is(a, b);
function usePrevious$1(state, shouldUpdate = defaultShouldUpdate) {
const prevRef = (0, import_react$7.useRef)(void 0);
const curRef = (0, import_react$7.useRef)(void 0);
if (shouldUpdate(curRef.current, state)) {
prevRef.current = curRef.current;
curRef.current = state;
}
return prevRef.current;
}
var TabConfig = exports("F",{
[ETab.AppRecommend]: {
icon: /* @__PURE__ */ jsx(IconForPhone, { className: "size-18px" }),
label: "推荐",
desc: "使用 Bilibili App 端推荐 API",
anonymousUsage: true
},
[ETab.PcRecommend]: {
icon: /* @__PURE__ */ jsx(IconForPc, { className: "size-18px" }),
label: "推荐",
desc: "使用 PC 端首页推荐 API",
anonymousUsage: true
},
[ETab.KeepFollowOnly]: {
icon: /* @__PURE__ */ jsx(IconForFollowedOnly, { className: "size-18px" }),
label: "已关注",
desc: "从 PC 端推荐中筛选出「已关注」,可能比较慢;关注的UP更新在动态"
},
[ETab.DynamicFeed]: {
icon: /* @__PURE__ */ jsx(IconForDynamicFeed, { className: "size-18px" }),
label: "动态",
desc: "关注的 UP 动态更新",
swr: true
},
[ETab.Watchlater]: {
icon: /* @__PURE__ */ jsx(IconForWatchlater, { className: "size-17px [&_.circle]:stroke-current" }),
label: "稍后再看",
desc: "我的稍后再看",
swr: true
},
[ETab.Fav]: {
icon: /* @__PURE__ */ jsx(IconForFav, { className: "mt--1px size-16px" }),
label: "收藏",
desc: "我的收藏",
get swr() {
return !favStore.usingShuffle;
}
},
[ETab.Hot]: {
icon: /* @__PURE__ */ jsx(IconForHot, { className: "size-16px" }),
label: "热门",
desc: "各个领域中新奇好玩的优质内容都在这里~",
anonymousUsage: true,
get swr() {
return !isHotTabUsingShuffle();
}
},
[ETab.Live]: {
icon: /* @__PURE__ */ jsx(IconForLive, { className: "size-16px" }),
label: "直播",
desc: "关注的直播",
swr: true
},
[ETab.SpaceUpload]: {
icon: /* @__PURE__ */ jsx(IconForSpaceUpload, { className: "size-16px" }),
label: "投稿",
desc: "UP 视频投稿",
anonymousUsage: true,
extraHelpInfo: /* @__PURE__ */ jsxs("div", {
className: "ml-20px",
children: [
"搜索词:搜索相关作品 ",
/* @__PURE__ */ jsx("br", {}),
"本地过滤词:本地过滤搜索结果;本地过滤词支持高级规则:",
/* @__PURE__ */ jsx("div", {
className: "ml-20px",
children: advancedFilterHelpInfo
})
]
})
},
[ETab.Liked]: {
icon: /* @__PURE__ */ jsx(IconForLike, { className: "size-16px" }),
label: "赞",
desc: "我的点赞:使用 App 端个人空间 API,需要 access_key",
swr: true
},
[ETab.History]: {
icon: /* @__PURE__ */ jsx(IconForHistory, { className: "size-16px" }),
label: "历史",
desc: "我看过的内容",
swr: true
},
[ETab.TagRecFav]: {
icon: /* @__PURE__ */ jsx(IconForTagRecFav, { className: "size-16px" }),
label: "主题推荐",
desc: "从标签收藏表加权抽样",
anonymousUsage: true
},
[ETab.TagRecCustom]: {
icon: /* @__PURE__ */ jsx(IconForTagRecCustom, { className: "size-16px" }),
label: "标签发掘",
desc: "标签发掘组拉取 tag/top 池",
anonymousUsage: true
}
});
function TabIcon({ tabKey, active, className }) {
const { icon } = TabConfig[tabKey];
const iconProps = icon.props;
const newProps = {
...iconProps,
className: r(iconProps.className, className),
active: tabKey === ETab.Live ? active : void 0
};
return (0, import_react$7.cloneElement)(icon, newProps);
}
var ALL_TAB_KEYS = Object.values(ETab);
var NONE_CONFIGURABLE_TAB_KEYS = [ETab.SpaceUpload];
var CONFIGURABLE_TAB_KEYS = exports("C",difference(ALL_TAB_KEYS, NONE_CONFIGURABLE_TAB_KEYS));
/**
* initial tab
*/
var videoSourceTabState = exports("s",await proxyWithGmStorage({ value: ETab.AppRecommend }, `video-source-tab`));
{
const onlyTab = getOnlyTab();
const entryTab = getEntryTab();
if (onlyTab) videoSourceTabState.value = onlyTab;
else if (entryTab) videoSourceTabState.value = entryTab;
}
function getSortedTabKeys(customTabKeysOrder) {
return CONFIGURABLE_TAB_KEYS.slice().sort((a, b) => {
let aIndex = customTabKeysOrder.indexOf(a);
let bIndex = customTabKeysOrder.indexOf(b);
if (aIndex === -1) aIndex = ALL_TAB_KEYS.indexOf(a);
if (bIndex === -1) bIndex = ALL_TAB_KEYS.indexOf(b);
return aIndex - bIndex;
});
}
function useSortedTabKeys(options) {
const { customTabKeysOrder } = useSettingsSnapshot(options);
return (0, import_react$7.useMemo)(() => getSortedTabKeys(customTabKeysOrder), [customTabKeysOrder]);
}
function useCurrentDisplayingTabKeys() {
const { hidingTabKeys, customTabKeysOrder } = useSettingsSnapshot();
const logined = useLoginStatus();
const onlyTab = (0, import_react$7.useMemo)(getOnlyTab, []);
const keys = (0, import_react$7.useMemo)(() => {
return getSortedTabKeys(customTabKeysOrder).filter((key) => {
if (key === ETab.AppRecommend && !logined) return true;
return !hidingTabKeys.includes(key);
});
}, [
hidingTabKeys,
customTabKeysOrder,
logined
]);
if (onlyTab) return [onlyTab];
return keys;
}
function useCurrentDisplayingTabConfigList() {
const keys = useCurrentDisplayingTabKeys();
return (0, import_react$7.useMemo)(() => keys.map((key) => ({
key,
...TabConfig[key]
})), [keys]);
}
function useCurrentUsingTab() {
const tab = useSnapshot(videoSourceTabState).value;
const displayingTabKeys = useCurrentDisplayingTabKeys();
const logined = useLoginStatus();
const fallbackTab = ETab.AppRecommend;
if (displayingTabKeys.length === 1) return displayingTabKeys[0];
if (!displayingTabKeys.includes(tab)) return fallbackTab;
if (!logined && !TabConfig[tab].anonymousUsage) return fallbackTab;
return tab;
}
function getCurrentUsingTab() {
const { hidingTabKeys, customTabKeysOrder } = getSettingsSnapshot();
const logined = getLoginStatus();
const fallbackTab = ETab.AppRecommend;
const onlyTab = getOnlyTab();
if (onlyTab) return onlyTab;
const displayingTabKeys = getSortedTabKeys([...customTabKeysOrder]).filter((key) => {
if (key === ETab.AppRecommend && !logined) return true;
return !hidingTabKeys.includes(key);
});
if (displayingTabKeys.length === 1) return displayingTabKeys[0];
const tab = videoSourceTabState.value;
if (!displayingTabKeys.includes(tab)) return fallbackTab;
if (!logined && !TabConfig[tab].anonymousUsage) return fallbackTab;
return tab;
}
function useDeferredTab() {
const tab = (0, import_react$7.useDeferredValue)(useCurrentUsingTab());
const prevTab = usePrevious$1(tab);
const tabOrders = useSortedTabKeys();
return {
tab,
prevTab,
direction: (0, import_react$7.useMemo)(() => {
return prevTab ? tabOrders.indexOf(tab) > tabOrders.indexOf(prevTab) ? "right" : "left" : void 0;
}, [tabOrders, tab])
};
}
var radioBtnCss = /* @__PURE__ */ css("vertical-align:top;height:32px;line-height:0;--ant-border-radius:16px;&:has(:focus-visible){outline:none;outline-offset:unset;}.ant-radio-button-label{display:inline-block;height:100%;}");
var tagRecCustomDotEl = /* @__PURE__ */ jsx("span", { className: "ml-4px inline-block size-6px rounded-full bg-red-500 align-middle" });
function VideoSourceTab({ className }) {
const tab = useCurrentUsingTab();
const currentTabConfigList = useCurrentDisplayingTabConfigList();
const { __internalRecTabRenderAsSegments, tagRec } = useSettingsSnapshot();
const { list: tagFavGroups } = useSnapshot(favoritesStore);
const showTagRecCustomDot = shouldShowTagRecCustomDot(tagRec.customTabSeen, tagFavGroups.length);
(0, import_react$7.useEffect)(() => {
if (tab === ETab.TagRecCustom && !settings.tagRec.customTabSeen) settings.tagRec.customTabSeen = true;
}, [tab]);
const onChangeTab = useMemoizedFn((newTab) => {
if (!TabConfig[newTab].anonymousUsage && !getLoginStatus()) return toastNeedLogin();
videoSourceTabState.value = newTab;
});
const renderAsRadio = /* @__PURE__ */ jsx(Radio.Group, {
optionType: "button",
buttonStyle: "solid",
size: "middle",
value: tab,
className,
onFocus: (e) => {
e.target.blur();
},
onChange: (e) => {
const newValue = e.target.value;
onChangeTab(newValue);
},
children: currentTabConfigList.map(({ key, label }) => /* @__PURE__ */ jsx(Radio.Button, {
css: radioBtnCss,
className: "video-source-tab",
tabIndex: -1,
value: key,
children: /* @__PURE__ */ jsxs("span", {
className: "h-full inline-flex-center align-top",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: key,
className: "mr-4px",
active: key === tab
}),
label,
key === ETab.TagRecCustom && showTagRecCustomDot && tagRecCustomDotEl,
" "
]
})
}, key))
});
let renderAsSegment;
{
const options = (0, import_react$7.useMemo)(() => {
return currentTabConfigList.map(({ key, label }) => {
return {
value: key,
label: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: key,
active: key === tab
}),
label,
key === ETab.TagRecCustom && showTagRecCustomDot && tagRecCustomDotEl,
" "
]
})
};
});
}, [
currentTabConfigList,
tab,
showTagRecCustomDot
]);
renderAsSegment = /* @__PURE__ */ jsx(Segmented, {
size: "middle",
options,
value: tab,
onChange: onChangeTab
});
}
return /* @__PURE__ */ jsxs("div", {
className: "flex-v-center",
children: [__internalRecTabRenderAsSegments ? renderAsSegment : renderAsRadio, /* @__PURE__ */ jsx(HelpInfo, {
className: "ml-6px size-16px",
children: /* @__PURE__ */ jsx(Fragment, { children: currentTabConfigList.map(({ key, label, desc, extraHelpInfo }) => /* @__PURE__ */ jsxs(import_react$7.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
className: "h-22px flex items-center",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: key,
className: "mr-4px",
active: true
}),
label,
": ",
desc
]
}), !!extraHelpInfo && extraHelpInfo] }, key)) })
})]
});
}/**
* [tag-rec] tab2「标签发掘」深链二级参数 (票 24; 纯逻辑叶子模块 —— 不 import $routes:
* 其模块级 `new URL(location.href)` 快照会把浏览器 location 传递依赖进 node 测试)
* - `?gate-tab=tag-rec-custom` 一级激活是 Gate 既有机制 (routes.tsx GateQueryKey.Tab →
* getEntryTab: tagrec 族只激活不收栏,tab 栏全量可见;非 tagrec 值仍走 getOnlyTab 单栏),
* 参数名字面值在此镜像), 本文件补两个二级参数, 由 TagRecCustomService 构造函数消费后清参
* (实机修复: 曾挂 tabbar useMount+emit refresh, 撞 mount 'reuse' 在途的「同 tab 刷新中」守卫被丢)
* - `tag-rec-input=` (收藏表点名等入口): 新 tab 以标签名直输查询 —— 走文本解析全管线
* (锚点+Phase2 候选+变体俱全; 实机裁决: 传名不传 id —— 数字 id 逃生口零解析但也跳过 Phase 2,
* 新 tab 会是"退化"体验; 同名解析到不同 id 的碎片化边角按规范名语义接受)
* - `tag-rec-new=&tag-rec-id=` (弹层点 `#id`): 新 tab 落成「含该标签条件的新试验组合」
* —— 弹层已知 (id,name) 无需解析, 绕开输入管线直接成条件; id 缺/坏时回落 input 语义
* - `tag-rec-group=` (★「新建收藏项」toast 动作, 票 20「选中并重开」改道): 新 tab 选定组语境
* - 两参同现 => group 优先 (id 已在表内, 确定性强); 空值视同无参
*/
/** Gate 一级深链参数名 —— 同 routes.tsx GateQueryKey.Tab 字面值 (镜像, 见头注) */
var GATE_TAB_PARAM = "gate-tab";
var TagRecDeepLinkParam = exports("bb",/* @__PURE__ */ function(TagRecDeepLinkParam) {
/** 预填临时输入 (tag_name 直输 —— 走文本解析全管线) */
TagRecDeepLinkParam["Input"] = "tag-rec-input";
/** 选定组语境 (L2 组 id) */
TagRecDeepLinkParam["Group"] = "tag-rec-group";
/** 新组合种子 (tag_name —— 试验显示名 + 条件显示名): 新 tab 落成含该标签的试验, 不走输入解析管线 */
TagRecDeepLinkParam["New"] = "tag-rec-new";
/** New 配套 tag_id —— 已知 id 零解析直接成条件原子; 缺/坏 => 回落 input 语义 */
TagRecDeepLinkParam["NewId"] = "tag-rec-id";
/** [tag-rec] 票 18 (tag-share): 分享导入 —— `?gate-tab=tag-rec-fav&tag-rec-share=` (tab1 语境) */
TagRecDeepLinkParam["Share"] = "tag-rec-share";
/** 分享组子集: 逗号分隔 L2 组 id; 缺省 = 整包 (spec §6: >40 组网站侧不传此参) */
TagRecDeepLinkParam["Groups"] = "tag-rec-groups";
/** [tag-rec] 票 27 (tag-share): 试改版包 —— 走 `#tag-rec-pkg=<导入码>` fragment (不进 B 站服务端);
* 编解码见 model/share-pkg.ts; 消费与清 hash 在 tag-rec-fav service 构造期 */
TagRecDeepLinkParam["Pkg"] = "tag-rec-pkg";
return TagRecDeepLinkParam;
}({}));
/** `${origin}/?gate-tab=tag-rec-custom&…` 构造 (input 数/字符串皆可; group = L2 组 id) */
function buildTagRecCustomDeepLink(origin, params) {
const u = new URL("/", origin);
u.searchParams.set(GATE_TAB_PARAM, ETab.TagRecCustom);
if (params.input !== void 0 && `${params.input}`.trim()) u.searchParams.set("tag-rec-input", `${params.input}`);
if (params.group?.trim()) u.searchParams.set("tag-rec-group", params.group);
if (params.seed && params.seed.name.trim() && Number.isFinite(params.seed.id)) {
u.searchParams.set("tag-rec-new", params.seed.name.trim());
u.searchParams.set("tag-rec-id", `${params.seed.id}`);
}
return u.href;
}
/** `${origin}/?gate-tab=tag-rec-fav` —— tab1「主题推荐」深链 (主题跳转: 调用侧先 themes.setActive 再开新 tab) */
function buildTagRecFavDeepLink(origin) {
const u = new URL("/", origin);
u.searchParams.set(GATE_TAB_PARAM, ETab.TagRecFav);
return u.href;
}
/**
* 解析 location.search => 消费意图 + 待清参数名:
* 任一二级参存在 => 四参都进 paramsToClear (残留参数一并清, 防刷新重放歧义);
* 皆无 => target=undefined 且 paramsToClear=[]; 空值视同无参 (仍算「存在」进清参列表);
* 优先级 group > new > input; new 缺/坏 id => 回落 input 语义 (名仍走解析管线)
*/
function parseTagRecDeepLink(search) {
const sp = new URLSearchParams(search);
const hasGroup = sp.has("tag-rec-group");
const hasInput = sp.has("tag-rec-input");
const hasNew = sp.has("tag-rec-new");
if (!hasGroup && !hasInput && !hasNew) return {
target: void 0,
paramsToClear: []
};
const groupId = sp.get("tag-rec-group")?.trim();
const input = sp.get("tag-rec-input")?.trim();
const newName = sp.get("tag-rec-new")?.trim();
const newId = Number(sp.get("tag-rec-id"));
return {
target: groupId ? {
kind: "group",
groupId
} : newName && Number.isInteger(newId) && newId > 0 ? {
kind: "new",
id: newId,
name: newName
} : newName ?? input ? {
kind: "input",
input: newName ?? input
} : void 0,
paramsToClear: [
"tag-rec-group",
"tag-rec-input",
"tag-rec-new",
"tag-rec-id"
]
};
}
/** groups 参逗号串解析: 去空白/丢空 token/保序去重; 缺席或全空 => undefined (整包语义) */
function parseTagRecGroupIds(raw) {
if (!raw) return void 0;
const out = [];
const seen = /* @__PURE__ */ new Set();
for (const token of raw.split(",")) {
const id = token.trim();
if (!id || seen.has(id)) continue;
seen.add(id);
out.push(id);
}
return out.length ? out : void 0;
}
/**
* tab1 分享参解析 (与 parseTagRecDeepLink 的 tab2 语境独立 —— 各清各家参数):
* - share/groups 任一存在 => 两参一并进 paramsToClear (残留一并清, 防刷新重放歧义)
* - 空值视同无参但仍算「存在」进清参列表; shareId 空 => undefined (孤 groups 不触发导入)
*/
function parseTagRecShareDeepLink(search) {
const sp = new URLSearchParams(search);
const hasShare = sp.has("tag-rec-share");
const hasGroups = sp.has("tag-rec-groups");
if (!hasShare && !hasGroups) return {
shareId: void 0,
groupIds: void 0,
paramsToClear: []
};
const rawShareId = sp.get("tag-rec-share")?.trim();
return {
shareId: rawShareId && /^[\w-]{10}$/.test(rawShareId) ? rawShareId : void 0,
groupIds: parseTagRecGroupIds(sp.get("tag-rec-groups")),
paramsToClear: ["tag-rec-share", "tag-rec-groups"]
};
}
/**
* 票 27 (tag-share): location.hash 里的试改版导入码 (`#tag-rec-pkg=`)。
* hash 与上方 query 参独立各清各家 —— 网站 fork 链 = `?gate-tab=tag-rec-fav#tag-rec-pkg=`;
* 返回原始码串 (百分号转义还原; 解码/校验在 model/share-pkg.ts 与 service 层)。
*/
function parseTagRecPkgHash(hash) {
const m = new RegExp(`(?:^|#|&)tag-rec-pkg=([^\\s]*)`).exec(hash);
if (!m) return {
code: void 0,
hasPkg: false
};
let code = m[1];
try {
code = decodeURIComponent(code);
} catch {}
return {
code: code || void 0,
hasPkg: true
};
}var require_duration = exports("bd",/* @__PURE__ */ __commonJSMin(((exports, module) => {
(function(t, s) {
"object" == typeof exports && "undefined" != typeof module ? module.exports = s() : "function" == typeof define && define.amd ? define(s) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs_plugin_duration = s();
})(exports, (function() {
var t, s, n = 1e3, i = 6e4, e = 36e5, r = 864e5, o = 31536e6, u = 2628e6, d = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/, a = /\[([^\]]+)]|YYYY|YY|Y|M{1,2}|D{1,2}|H{1,2}|m{1,2}|s{1,2}|SSS/g, h = {
years: o,
months: u,
days: r,
hours: e,
minutes: i,
seconds: n,
milliseconds: 1,
weeks: 6048e5
}, c = function(t) {
return t instanceof g;
}, f = function(t, s, n) {
return new g(t, n, s.$l);
}, m = function(t) {
return s.p(t) + "s";
}, l = function(t) {
return t < 0;
}, $ = function(t) {
return l(t) ? Math.ceil(t) : Math.floor(t);
}, y = function(t) {
return Math.abs(t);
}, v = function(t, s) {
return t ? l(t) ? {
negative: true,
format: "" + y(t) + s
} : {
negative: false,
format: "" + t + s
} : {
negative: false,
format: ""
};
}, g = function() {
function l(t, s, n) {
var i = this;
if (this.$d = {}, this.$l = n, void 0 === t && (this.$ms = 0, this.parseFromMilliseconds()), s) return f(t * h[m(s)], this);
if ("number" == typeof t) return this.$ms = t, this.parseFromMilliseconds(), this;
if ("object" == typeof t) return Object.keys(t).forEach((function(s) {
i.$d[m(s)] = t[s];
})), this.calMilliseconds(), this;
if ("string" == typeof t) {
var e = t.match(d);
if (e) {
var r = e.slice(2).map((function(t) {
return null != t ? Number(t) : 0;
}));
return this.$d.years = r[0], this.$d.months = r[1], this.$d.weeks = r[2], this.$d.days = r[3], this.$d.hours = r[4], this.$d.minutes = r[5], this.$d.seconds = r[6], this.calMilliseconds(), this;
}
}
return this;
}
var y = l.prototype;
return y.calMilliseconds = function() {
var t = this;
this.$ms = Object.keys(this.$d).reduce((function(s, n) {
return s + (t.$d[n] || 0) * h[n];
}), 0);
}, y.parseFromMilliseconds = function() {
var t = this.$ms;
this.$d.years = $(t / o), t %= o, this.$d.months = $(t / u), t %= u, this.$d.days = $(t / r), t %= r, this.$d.hours = $(t / e), t %= e, this.$d.minutes = $(t / i), t %= i, this.$d.seconds = $(t / n), t %= n, this.$d.milliseconds = t;
}, y.toISOString = function() {
var t = v(this.$d.years, "Y"), s = v(this.$d.months, "M"), n = +this.$d.days || 0;
this.$d.weeks && (n += 7 * this.$d.weeks);
var i = v(n, "D"), e = v(this.$d.hours, "H"), r = v(this.$d.minutes, "M"), o = this.$d.seconds || 0;
this.$d.milliseconds && (o += this.$d.milliseconds / 1e3, o = Math.round(1e3 * o) / 1e3);
var u = v(o, "S"), d = t.negative || s.negative || i.negative || e.negative || r.negative || u.negative, a = e.format || r.format || u.format ? "T" : "", h = (d ? "-" : "") + "P" + t.format + s.format + i.format + a + e.format + r.format + u.format;
return "P" === h || "-P" === h ? "P0D" : h;
}, y.toJSON = function() {
return this.toISOString();
}, y.format = function(t) {
var n = t || "YYYY-MM-DDTHH:mm:ss", i = {
Y: this.$d.years,
YY: s.s(this.$d.years, 2, "0"),
YYYY: s.s(this.$d.years, 4, "0"),
M: this.$d.months,
MM: s.s(this.$d.months, 2, "0"),
D: this.$d.days,
DD: s.s(this.$d.days, 2, "0"),
H: this.$d.hours,
HH: s.s(this.$d.hours, 2, "0"),
m: this.$d.minutes,
mm: s.s(this.$d.minutes, 2, "0"),
s: this.$d.seconds,
ss: s.s(this.$d.seconds, 2, "0"),
SSS: s.s(this.$d.milliseconds, 3, "0")
};
return n.replace(a, (function(t, s) {
return s || String(i[t]);
}));
}, y.as = function(t) {
return this.$ms / h[m(t)];
}, y.get = function(t) {
var s = this.$ms, n = m(t);
return "milliseconds" === n ? s %= 1e3 : s = "weeks" === n ? $(s / h[n]) : this.$d[n], s || 0;
}, y.add = function(t, s, n) {
var i = s ? t * h[m(s)] : c(t) ? t.$ms : f(t, this).$ms;
return f(this.$ms + i * (n ? -1 : 1), this);
}, y.subtract = function(t, s) {
return this.add(t, s, true);
}, y.locale = function(t) {
var s = this.clone();
return s.$l = t, s;
}, y.clone = function() {
return f(this.$ms, this);
}, y.humanize = function(s) {
return t().add(this.$ms, "ms").locale(this.$l).fromNow(!s);
}, y.valueOf = function() {
return this.asMilliseconds();
}, y.milliseconds = function() {
return this.get("milliseconds");
}, y.asMilliseconds = function() {
return this.as("milliseconds");
}, y.seconds = function() {
return this.get("seconds");
}, y.asSeconds = function() {
return this.as("seconds");
}, y.minutes = function() {
return this.get("minutes");
}, y.asMinutes = function() {
return this.as("minutes");
}, y.hours = function() {
return this.get("hours");
}, y.asHours = function() {
return this.as("hours");
}, y.days = function() {
return this.get("days");
}, y.asDays = function() {
return this.as("days");
}, y.weeks = function() {
return this.get("weeks");
}, y.asWeeks = function() {
return this.as("weeks");
}, y.months = function() {
return this.get("months");
}, y.asMonths = function() {
return this.as("months");
}, y.years = function() {
return this.get("years");
}, y.asYears = function() {
return this.as("years");
}, l;
}(), p = function(t, s, n) {
return t.add(s.years() * n, "y").add(s.months() * n, "M").add(s.days() * n, "d").add(s.hours() * n, "h").add(s.minutes() * n, "m").add(s.seconds() * n, "s").add(s.milliseconds() * n, "ms");
};
return function(n, i, e) {
t = e, s = e().$utils(), e.duration = function(t, s) {
return f(t, { $l: e.locale() }, s);
}, e.isDuration = c;
var r = i.prototype.add, o = i.prototype.subtract;
i.prototype.add = function(t, s) {
return c(t) ? p(this, t, 1) : r.bind(this)(t, s);
}, i.prototype.subtract = function(t, s) {
return c(t) ? p(this, t, -1) : o.bind(this)(t, s);
};
};
}));
})));var cache = new SessionFetchCache({
dedupeMs: 15e3,
maxAgeMs: 0,
maxEntries: 2e3,
isCacheable: () => false
});
/** A single bounded detail request supplies every missing content field; an absent description is still unknown. */
function fetchTagRecVideoEvidence(bvid, signal) {
if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
const cached = getCachedTagRecVideoEvidence(bvid);
if (cached) return Promise.resolve(cached);
return cache.fetch(bvid, async (entrySignal) => {
const data = await fetchVideoDetail(bvid, entrySignal);
if (!data) return;
return rememberTagRecVideoEvidence(bvid, data);
}, signal);
}
function mergeTagRecVideoEvidence(item, fields) {
if (!fields) return item;
const present = Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== void 0));
const evidence = { ...item.fieldEvidence };
for (const field of ["title", "desc"]) {
const state = fields.fieldEvidence?.[field];
if (state && (state !== "missing" || evidence[field] === void 0)) evidence[field] = state;
}
return {
...item,
...present,
fieldEvidence: evidence
};
}
var import_react$6 = /* @__PURE__ */ __toESM(require_react(), 1);
function TagRecRecheckButton({ service, onDone }) {
const [open, setOpen] = (0, import_react$6.useState)(false);
const [value, setValue] = (0, import_react$6.useState)();
const [running, setRunning] = (0, import_react$6.useState)(false);
const controller = (0, import_react$6.useRef)(void 0);
(0, import_react$6.useEffect)(() => () => controller.current?.abort(), [service]);
const options = open ? service.getRecheckOptions() : [];
const selected = options.find((option) => option.value === value);
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
setValue(void 0);
setOpen(true);
},
children: "定向重查"
}), /* @__PURE__ */ jsxs(Modal, {
title: "选择重查范围",
open,
confirmLoading: running,
okButtonProps: { disabled: !selected },
onCancel: () => {
controller.current?.abort();
setOpen(false);
},
okText: "重查首屏",
onOk: async () => {
if (!selected || running) return;
setRunning(true);
controller.current = new AbortController();
try {
const result = await service.recheck(selected, controller.current.signal);
toast(result.stale ? "范围已改变或正在退避,本次结果未写入" : !result.requests ? "此范围暂无可执行路径;推荐相关需要已有种子" : `重查完成:${result.requests} 个请求,新增 ${result.added} 条${result.failed ? ",部分请求失败" : ""}`);
setOpen(false);
if (!result.stale) onDone();
} finally {
controller.current = void 0;
setRunning(false);
}
},
children: [/* @__PURE__ */ jsx(Select, {
className: "w-full",
disabled: running,
showSearch: true,
options,
value,
onChange: setValue,
optionFilterProp: "label",
placeholder: "选择组、来源和检索词"
}), /* @__PURE__ */ jsx(Typography.Paragraph, {
type: "secondary",
className: "mt-12px",
children: "每词只探测第一页,每次最多 4 个请求。保留已看、已交付、其他组库存和正常分页进度。"
})]
})] });
}var lazy = proxyWithGmStorageLazy({
version: 1,
activeId: "",
trials: [],
recent: []
}, "tag-rec:workbench");
var tagRecWorkbench = exports("af",lazy.proxy);
var tagRecWorkbenchStateHydrated = lazy.hydrated;
function currentTagRecTrial() {
return Array.isArray(tagRecWorkbench.trials) ? tagRecWorkbench.trials.find((trial) => trial?.id === tagRecWorkbench.activeId) : void 0;
}
/** Display preferences belong to the workbench, never to a saved recipe. */
var tagRecDiscoveryPreferences = exports("ai",proxyWithGmStorageLazy({
collapsed: false,
topCollapsed: false,
size: "medium"
}, "tag-rec:discovery-display").proxy);
/** Transient panel state only. Conditions and pending picks live in the workbench. */
var tagRecDiscoveryUi = exports("aj",proxy({
video: void 0,
videoTrialId: "",
borrowId: "",
/** One-shot request for the workbench to run a material search (consumed by TagRecWorkbenchView). */
materialQuery: void 0
}));
function tagRecMaterialKey(material) {
return JSON.stringify([
material.atom ? tagRecConditionKey(material.atom) : material.tagId ?? material.name,
!!material.exclude,
material.filter
]);
}
function stageTagRecMaterial(trialId, material) {
const trial = tagRecWorkbench.trials.find((entry) => entry.id === trialId);
if (!trial) return false;
const list = trial.pending ?? [];
const key = tagRecMaterialKey(material);
const at = list.findIndex((entry) => tagRecMaterialKey(entry) === key);
if (at === -1 && list.length >= 64) {
toast("已暂选 64 项,请先加入组合或移除一些;已有暂选已保留");
return false;
}
trial.pending = at === -1 ? [...list, {
...material,
pickId: crypto.randomUUID()
}] : list.filter((_, i) => i !== at);
return true;
}
function openTagRecVideoMaterials(item, trialId) {
tagRecDiscoveryUi.video = ref(item);
tagRecDiscoveryUi.videoTrialId = trialId;
}
function requestTagRecMaterialSearch(text, field = "tag") {
tagRecDiscoveryUi.materialQuery = {
text,
field
};
}
var tagRecWorkbenchHydrated = Promise.all([tagRecWorkbenchStateHydrated, tagRecStoresHydrated]);
var undo = /* @__PURE__ */ new Map();
var activated = false;
var writing = false;
var lastPatch = "";
var lastSynchronizedTrial = "";
var TAG_REC_WORKING_GROUP_ID = "__tagrec_working__";
function workingGroup(source) {
const usable = source && tagRecCombinationOverflows(source) ? void 0 : source;
if (source && !usable) toast(`「${source.name}」标签数超过组合节点上限,无法载入条件编辑器;已开新试验`);
const group = usable ? prepareTagRecWorkingGroup(usable) : {
id: TAG_REC_WORKING_GROUP_ID,
name: "新试验",
tag_ids: [],
enabled: true,
weight: 1,
addedAt: Date.now()
};
group.id = TAG_REC_WORKING_GROUP_ID;
group.enabled = true;
group.combination ??= newTagRecCombination();
return group;
}
function remember(trial) {
if (!validateTagRecCombination(trial.group?.combination)) return;
if (!hasTagRecCombination(trial.group.combination) && !trial.pending?.length) return;
const signature = (entry) => JSON.stringify([tagRecWorkingGroupSignature(entry.group), entry.pending ?? []]);
const sig = signature(trial);
tagRecWorkbench.recent = [{
...cloneDeep(trial),
id: crypto.randomUUID()
}, ...tagRecWorkbench.recent.filter((entry) => !entry.group?.combination || signature(entry) !== sig)];
let completed = 0;
tagRecWorkbench.recent = tagRecWorkbench.recent.filter((entry) => entry.pending?.length || ++completed <= 30);
}
function synchronize() {
const trial = currentTagRecTrial();
if (!trial) return;
writing = true;
try {
const tags = tagRecCombinationTagIds(trial.group.combination);
if (JSON.stringify(trial.group.tag_ids) !== JSON.stringify(tags)) trial.group.tag_ids = tags;
if (trial.group.name !== trial.name) trial.group.name = trial.name;
tagRecCustomSession.inputText = "";
tagRecCustomSession.anchor = void 0;
tagRecCustomSession.draftGroup = void 0;
tagRecCustomSession.selectedGroupIds = [];
tagRecCustomSession.expandedIds = [];
tagRecCustomSession.workingGroup = trial.group;
tagRecCustomSession.filterOverride = cloneDeep(trial.group.combination?.filters);
tagRecCustomSession.allowUnverified = false;
tagRecCustomSession.queryRevision++;
lastPatch = JSON.stringify(tagRecCustomSession.filterOverride);
lastSynchronizedTrial = JSON.stringify([trial.id, trial.group]);
} finally {
writing = false;
}
}
async function activateTagRecWorkbench() {
if (activated) return;
await tagRecWorkbenchHydrated;
if (activated) return;
activated = true;
if (!Array.isArray(tagRecWorkbench.trials) || !Array.isArray(tagRecWorkbench.recent) || tagRecWorkbench.version !== 1) {
toast("本地组合格式不兼容,原数据已保留,请升级插件");
return;
}
const validTrials = tagRecWorkbench.trials.filter((trial) => trial?.group?.combination && validateTagRecCombination(trial.group.combination));
if (validTrials.length !== tagRecWorkbench.trials.length) toast("部分试验条件不兼容,原数据已保留;请升级后再打开");
if (!validTrials.length) {
const selected = favorites.list.find((group) => group.id === tagRecCustomSession.selectedGroupIds[0]);
const group = workingGroup(selected);
if (!selected && tagRecCustomSession.anchor) addTagRecCondition(group.combination, {
kind: "atom",
id: crypto.randomUUID(),
field: "tag",
value: cloneDeep(tagRecCustomSession.anchor)
});
if (!selected && tagRecCustomSession.draftGroup) {
const draft = tagRecCustomSession.draftGroup;
group.combination = prepareTagRecWorkingGroup({
...group,
negative_tag_ids: draft.negative_tag_ids,
overrides: { negative: draft.negative }
}).combination;
}
if (!selected && tagRecCustomSession.filterOverride) group.combination.filters = cloneDeep(tagRecCustomSession.filterOverride);
for (const tag of tagRecCustomSession.expandedIds) addTagRecCondition(group.combination, {
kind: "atom",
id: crypto.randomUUID(),
field: "tag",
value: cloneDeep(tag)
});
const trial = {
id: crypto.randomUUID(),
name: selected?.name ?? "新试验",
originId: selected?.id,
group
};
tagRecWorkbench.trials.push(trial);
tagRecWorkbench.activeId = trial.id;
}
if (!currentTagRecTrial()?.group.combination || !validateTagRecCombination(currentTagRecTrial().group.combination)) tagRecWorkbench.activeId = validTrials[0]?.id ?? tagRecWorkbench.trials.at(-1).id;
synchronize();
}
function editTagRecWorkingGroup(change) {
const trial = currentTagRecTrial();
if (!trial) return false;
const previous = cloneDeep(trial.group);
const next = cloneDeep(previous);
change(next);
if (!next.combination || !validateTagRecCombination(next.combination)) {
toast("条件过多、嵌套过深或条件不完整,请先简化组合");
return false;
}
if (JSON.stringify(next) === JSON.stringify(previous)) return false;
if (tagRecConditionAtoms(next.combination.include).length > tagRecConditionAtoms(previous.combination.include).length) tagRecCustomSession.localFirst = true;
remember({
id: trial.id,
name: trial.name,
group: cloneDeep(previous)
});
const stack = undo.get(trial.id) ?? [];
stack.push(previous);
undo.set(trial.id, stack.slice(-30));
trial.group = next;
synchronize();
return true;
}
function addTagRecWorkingCondition(atom, target) {
if (tagRecCombinationBlocks.list.some((entry) => tagRecConditionKey(entry) === tagRecConditionKey(atom))) {
toast("此条件已被全局排除,请先在「更多」中解除");
return false;
}
if (currentTagRecTrial()?.group.combination?.exclude.some((entry) => tagRecConditionKey(entry) === tagRecConditionKey(atom))) {
toast("此条件已被本组合排除,请先移除排除条件");
return false;
}
return editTagRecWorkingGroup((group) => {
addTagRecCondition(group.combination, atom, target);
});
}
function excludeTagRecWorkingCondition(atom) {
return editTagRecWorkingGroup((group) => {
tagRecConditionAtoms(group.combination.include).filter((a) => tagRecConditionKey(a) === tagRecConditionKey(atom)).forEach((a) => removeTagRecCondition(group.combination, a.id));
excludeTagRecCondition(group.combination, atom);
});
}
function removeTagRecWorkingCondition(id) {
return editTagRecWorkingGroup((group) => {
removeTagRecCondition(group.combination, id);
});
}
/** 药丸菜单「移到…」: 把条件挪进指定组; 'new' = 新建顶层子组收编。单次编辑 => 只占一条撤销/最近尝试记录 */
function moveTagRecWorkingCondition(atomId, target) {
const include = currentTagRecTrial()?.group.combination?.include;
const atom = include ? findTagRecCondition(include, atomId) : void 0;
if (atom?.kind !== "atom") return false;
return editTagRecWorkingGroup((group) => {
const combination = group.combination;
if (target === "new") {
removeTagRecCondition(combination, atomId);
combination.include.children.push({
kind: "group",
id: crypto.randomUUID(),
mode: "any",
name: "条件组",
children: [{
...cloneDeep(atom),
id: crypto.randomUUID()
}]
});
return;
}
const destination = findTagRecCondition(combination.include, target);
if (destination?.kind !== "group") return;
if (destination.children.some((child) => child.id === atomId)) return;
if (destination.children.some((child) => child.kind === "atom" && tagRecConditionKey(child) === tagRecConditionKey(atom))) {
toast("该组已有相同条件");
return;
}
removeTagRecCondition(combination, atomId);
destination.children.push({
...cloneDeep(atom),
id: crypto.randomUUID()
});
});
}
function undoTagRecWorkingGroup() {
const trial = currentTagRecTrial();
const previous = trial && undo.get(trial.id)?.pop();
if (!trial || !previous) return false;
trial.group = previous;
synchronize();
return true;
}
function newTagRecTrial(source, originId, label, seed) {
const current = currentTagRecTrial();
if (current) remember(current);
const group = workingGroup(source);
if (seed) addTagRecCondition(group.combination, seed);
if (!validateTagRecCombination(group.combination)) {
toast("组合条件不兼容,原数据已保留");
return;
}
const trial = {
id: crypto.randomUUID(),
name: label || source?.name || "新试验",
originId,
group
};
tagRecWorkbench.trials.push(trial);
if (tagRecWorkbench.trials.length > 8) {
const oldest = tagRecWorkbench.trials[0];
remember(oldest);
undo.delete(oldest.id);
tagRecWorkbench.trials = tagRecWorkbench.trials.slice(1);
toast("最早的试验已移到最近尝试,条件和暂选均已保留");
}
tagRecWorkbench.activeId = trial.id;
synchronize();
return trial;
}
function openTagRecFavorite(group) {
if (group.combination && !validateTagRecCombination(group.combination)) {
toast("这份组合条件不兼容,请升级插件后重试");
return;
}
return newTagRecTrial(group, group.id, group.name);
}
/**
* 「仅看这个标签」: 当前组合记入最近尝试 (未提交暂选随之挂到该条, openTagRecRecent 可取回),
* 原位换成只含该标签的新试验 —— 深挖/共现管线随 synchronize 的生效签名自动重启。
* 全局排除/未解析直接拒绝, 当前组合不动。
*/
function onlyTagRecTag(entry) {
const trial = currentTagRecTrial();
if (!trial || !entry?.id || !entry.name) return false;
const index = tagRecWorkbench.trials.findIndex((t) => t.id === trial.id);
if (index === -1) return false;
const atom = tagRecConditionAtom("tag", {
id: entry.id,
name: entry.name,
tag_type: entry.tag_type
});
if (tagRecCombinationBlocks.list.some((a) => tagRecConditionKey(a) === tagRecConditionKey(atom))) {
toast(`「${entry.name}」在「以后都不看」里,先在「更多」里解除全局排除`);
return false;
}
const group = workingGroup();
addTagRecCondition(group.combination, atom);
if (!validateTagRecCombination(group.combination)) return false;
if (tagRecDiscoveryUi.videoTrialId === trial.id) {
tagRecDiscoveryUi.video = void 0;
tagRecDiscoveryUi.videoTrialId = "";
}
remember(trial);
const bankedIsOurs = hasTagRecCombination(trial.group.combination) || !!trial.pending?.length;
const next = {
id: crypto.randomUUID(),
name: entry.name,
group
};
tagRecWorkbench.trials[index] = next;
undo.delete(trial.id);
tagRecWorkbench.activeId = next.id;
synchronize();
toast(bankedIsOurs ? `仅看「${entry.name}」· 原组合记入最近尝试` : `仅看「${entry.name}」`);
return true;
}
/** 仅看 + 打开该标签的素材解析 (共现/同形/视频字段) —— UI 各入口共用的完整动作 */
function exploreTagRecTagOnly(entry) {
if (!onlyTagRecTag(entry)) return false;
requestTagRecMaterialSearch(entry.name);
return true;
}
/**
* 弹层 #tag_id 深链落地: 在既有试验之外「开新组合」—— 新试验追加进书架并激活
* (原试验照常记入最近尝试, 不像「仅看」那样原位替换), 条件即该标签原子, 顺带触发素材解析。
*/
function seedTagRecNewTrialWithTag(entry) {
if (!entry?.id || !entry.name) return false;
const atom = tagRecConditionAtom("tag", {
id: entry.id,
name: entry.name,
tag_type: entry.tag_type
});
if (tagRecCombinationBlocks.list.some((a) => tagRecConditionKey(a) === tagRecConditionKey(atom))) {
toast(`「${entry.name}」在「以后都不看」里,先在「更多」里解除全局排除`);
return false;
}
if (!newTagRecTrial(void 0, void 0, entry.name, atom)) return false;
requestTagRecMaterialSearch(entry.name);
return true;
}
/** 素材行入口: 未解析素材先按 tagId/名称补 tag 实体再仅看 (与 select 的惰性解析同口径) */
async function exploreTagRecMaterialOnly(material) {
const trialId = tagRecWorkbench.activeId;
const revision = tagRecCustomSession.queryRevision;
const signature = currentTagRecTrial() && tagRecWorkingGroupSignature(currentTagRecTrial().group);
let entry = material.atom?.field === "tag" ? cloneDeep(material.atom.value) : void 0;
if (!entry && material.filter) return false;
if (!entry) {
const info = await (material.tagId ? fetchTagInfoById(material.tagId) : fetchTagInfoByName(material.name)).catch(() => void 0);
if (!info) {
toast("暂时未解析到这个标签,可稍后重试");
return false;
}
entry = {
id: info.id,
name: info.name,
tag_type: info.tag_type
};
}
if (trialId !== tagRecWorkbench.activeId || revision !== tagRecCustomSession.queryRevision || signature !== tagRecWorkingGroupSignature(currentTagRecTrial().group)) return false;
return exploreTagRecTagOnly(entry);
}
/** 最近尝试 => 开新试验; 「仅看」记账时挂上的暂选一并对号取回 */
function openTagRecRecent(entry) {
const trial = newTagRecTrial(entry.group, entry.originId, entry.name);
if (!trial) return;
trial.pending = cloneDeep(entry.pending ?? []);
return trial;
}
function switchTagRecTrial(id) {
if (id === tagRecWorkbench.activeId) return true;
const current = currentTagRecTrial();
if (current) remember(current);
const target = tagRecWorkbench.trials.find((trial) => trial.id === id);
if (!target) return false;
if (!validateTagRecCombination(target.group?.combination)) {
toast("组合条件不兼容,原数据已保留");
return false;
}
tagRecWorkbench.activeId = id;
synchronize();
return true;
}
function closeTagRecTrial(id) {
const trial = tagRecWorkbench.trials.find((entry) => entry.id === id);
if (trial) remember(trial);
tagRecWorkbench.trials = tagRecWorkbench.trials.filter((entry) => entry.id !== id);
undo.delete(id);
if (!tagRecWorkbench.trials.length) newTagRecTrial();
else if (tagRecWorkbench.activeId === id) {
tagRecWorkbench.activeId = tagRecWorkbench.trials[0].id;
synchronize();
}
}
function saveTagRecWorkingGroup(name, updateOriginal = false) {
const trial = currentTagRecTrial();
if (!trial?.group.combination || !hasTagRecCombination(trial.group.combination)) {
toast("请先选入内容条件或调整过滤");
return;
}
const group = cloneDeep(trial.group);
group.name = name?.trim() || nameTagRecCombination(group.combination);
group.tag_ids = tagRecCombinationTagIds(group.combination);
if (updateOriginal && trial.originId) {
const original = favorites.list.find((entry) => entry.id === trial.originId);
if (!original) {
toast("原二级组已移除,请另存");
return;
}
Object.assign(original, {
name: group.name,
combination: group.combination,
tag_ids: group.tag_ids,
overrides: group.overrides
});
delete original.negative_tag_ids;
delete original.negative_strict;
if (group.l1Id !== original.l1Id) {
const parent = favoritesL1.assignGroupToL1(original.id, group.l1Id);
if (parent) trial.group.l1Id = parent.id;
}
toast("已更新原二级组");
return original;
}
const existing = favorites.list.find((entry) => entry.combination && entry.l1Id === group.l1Id && tagRecWorkingGroupSignature(entry) === tagRecWorkingGroupSignature(group));
if (existing) {
trial.originId = existing.id;
toast("这份完整条件已收藏");
return existing;
}
const result = favorites.addGroup({
name: group.name,
l1Id: group.l1Id,
tag_ids: group.tag_ids,
combination: group.combination,
overrides: group.overrides,
enabled: true
});
if (!result) return;
trial.originId = result.group.id;
trial.group.l1Id = result.group.l1Id;
trial.name = result.group.name;
remember(trial);
toast(result.l1.enabled ? "已存为二级组,继续试改保留原收藏" : "已存为二级组;主题推荐中需开启所属一级组,这里可直接试看");
return result.group;
}
subscribe(tagRecCustomSession, () => {
if (!activated || writing) return;
const json = JSON.stringify(tagRecCustomSession.filterOverride);
if (json === lastPatch) return;
lastPatch = json;
const trial = currentTagRecTrial();
if (!trial?.group.combination) return;
const next = {
...cloneDeep(trial.group.combination),
filters: cloneDeep(tagRecCustomSession.filterOverride)
};
if (!validateTagRecCombination(next)) {
toast("本组合的过滤条件已达上限或格式不完整,本次调整未保存");
writing = true;
tagRecCustomSession.filterOverride = cloneDeep(trial.group.combination.filters);
lastPatch = JSON.stringify(tagRecCustomSession.filterOverride);
writing = false;
return;
}
const stack = undo.get(trial.id) ?? [];
stack.push(cloneDeep(trial.group));
undo.set(trial.id, stack.slice(-30));
remember({
id: trial.id,
name: trial.name,
group: cloneDeep(trial.group)
});
trial.group.combination = next;
tagRecCustomSession.allowUnverified = false;
lastSynchronizedTrial = JSON.stringify([trial.id, trial.group]);
}, true);
subscribe(tagRecWorkbench, () => {
if (!activated || writing) return;
const trial = currentTagRecTrial();
if (!trial || !validateTagRecCombination(trial.group?.combination)) return;
if (JSON.stringify([trial.id, trial.group]) !== lastSynchronizedTrial) synchronize();
});var import_react$5 = /* @__PURE__ */ __toESM(require_react());
var DrawerContext = /*#__PURE__*/ import_react$5.createContext(null);
var RefContext = /*#__PURE__*/ import_react$5.createContext({});
function _extends$1$1() {
_extends$1$1 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$1$1.apply(this, arguments);
}
var DrawerPanel$1 = (props) => {
const { prefixCls, className, containerRef, ...restProps } = props;
const { panel: panelRef } = import_react$5.useContext(RefContext);
const mergedRef = useComposeRef(panelRef, containerRef);
return /*#__PURE__*/ import_react$5.createElement("div", _extends$1$1({
className: clsx(`${prefixCls}-section`, className),
role: "dialog",
ref: mergedRef
}, pickAttrs(props, { aria: true }), { "aria-modal": "true" }, restProps));
};
function useDrag(options) {
const { prefixCls, direction, className, style, maxSize, containerRef, currentSize, onResize, onResizeEnd, onResizeStart } = options;
const [isDragging, setIsDragging] = import_react$5.useState(false);
const [startPos, setStartPos] = import_react$5.useState(0);
const [startSize, setStartSize] = import_react$5.useState(0);
const isHorizontal = direction === "left" || direction === "right";
const handleMouseDown = useEvent$1((e) => {
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
if (isHorizontal) setStartPos(e.clientX);
else setStartPos(e.clientY);
let startSize;
if (typeof currentSize === "number") startSize = currentSize;
else if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
startSize = isHorizontal ? rect.width : rect.height;
}
setStartSize(startSize);
onResizeStart?.(startSize);
});
const handleMouseMove = useEvent$1((e) => {
if (!isDragging) return;
let delta = (isHorizontal ? e.clientX : e.clientY) - startPos;
if (direction === "right" || direction === "bottom") delta = -delta;
let newSize = startSize + delta;
if (newSize < 0) newSize = 0;
if (maxSize && newSize > maxSize) newSize = maxSize;
onResize?.(newSize);
});
const handleMouseUp = import_react$5.useCallback(() => {
if (isDragging) {
setIsDragging(false);
if (containerRef?.current) {
const rect = containerRef.current.getBoundingClientRect();
const finalSize = isHorizontal ? rect.width : rect.height;
onResizeEnd?.(finalSize);
}
}
}, [
isDragging,
containerRef,
onResizeEnd,
isHorizontal
]);
import_react$5.useEffect(() => {
if (isDragging) {
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}
}, [
isDragging,
handleMouseMove,
handleMouseUp
]);
return {
dragElementProps: {
className: clsx(`${prefixCls}-dragger`, `${prefixCls}-dragger-${direction}`, {
[`${prefixCls}-dragger-dragging`]: isDragging,
[`${prefixCls}-dragger-horizontal`]: isHorizontal,
[`${prefixCls}-dragger-vertical`]: !isHorizontal
}, className),
style,
onMouseDown: handleMouseDown
},
isDragging
};
}
function parseWidthHeight(value) {
if (typeof value === "string") {
const num = Number(value.replace(/px$/i, ""));
if (parseFloat(value) === num) warningOnce$1(false, "Invalid value type of `width` or `height` which should be number type instead.");
if (!Number.isNaN(num)) return num;
}
return value;
}
function useFocusable(getContainer, open, autoFocus, focusTrap, mask) {
const [ignoreElement] = useLockFocus(open && (focusTrap ?? mask !== false), getContainer);
import_react$5.useEffect(() => {
if (open && autoFocus === true) getContainer()?.focus({ preventScroll: true });
}, [open]);
return ignoreElement;
}
function _extends$3() {
_extends$3 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$3.apply(this, arguments);
}
var DrawerPopup = (props, ref) => {
const { prefixCls, open, placement, inline, push, forceRender, autoFocus, focusTrap, classNames: drawerClassNames, rootClassName, rootStyle, zIndex, className, id, style, motion, width, height, size, maxSize, children, mask, maskClosable, maskMotion, maskClassName, maskStyle, afterOpenChange, onClose, onMouseEnter, onMouseOver, onMouseLeave, onClick, onKeyDown, onKeyUp, styles, drawerRender, resizable, defaultSize } = props;
const panelRef = import_react$5.useRef(null);
import_react$5.useImperativeHandle(ref, () => panelRef.current);
const ignoreElement = useFocusable(() => panelRef.current, open, autoFocus, focusTrap, mask);
const [pushed, setPushed] = import_react$5.useState(false);
const parentContext = import_react$5.useContext(DrawerContext);
let pushConfig;
if (typeof push === "boolean") pushConfig = push ? {} : { distance: 0 };
else pushConfig = push || {};
const pushDistance = pushConfig?.distance ?? parentContext?.pushDistance ?? 180;
const mergedContext = import_react$5.useMemo(() => ({
pushDistance,
push: () => {
setPushed(true);
},
pull: () => {
setPushed(false);
}
}), [pushDistance]);
import_react$5.useEffect(() => {
if (open) parentContext?.push?.();
else parentContext?.pull?.();
}, [open]);
import_react$5.useEffect(() => () => {
parentContext?.pull?.();
}, []);
const maskNode = /*#__PURE__*/ import_react$5.createElement(es_default$5, _extends$3({ key: "mask" }, maskMotion, { visible: mask && open }), ({ className: motionMaskClassName, style: motionMaskStyle }, maskRef) => /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(`${prefixCls}-mask`, motionMaskClassName, drawerClassNames?.mask, maskClassName),
style: {
...motionMaskStyle,
...maskStyle,
...styles?.mask
},
onClick: maskClosable && open ? onClose : void 0,
ref: maskRef
}));
const motionProps = typeof motion === "function" ? motion(placement) : motion;
const [currentSize, setCurrentSize] = import_react$5.useState();
const isHorizontal = placement === "left" || placement === "right";
const mergedSize = import_react$5.useMemo(() => {
return parseWidthHeight(size ?? (isHorizontal ? width : height) ?? currentSize ?? defaultSize ?? (isHorizontal ? 378 : void 0));
}, [
size,
width,
height,
defaultSize,
isHorizontal,
currentSize
]);
const wrapperStyle = import_react$5.useMemo(() => {
const nextWrapperStyle = {};
if (pushed && pushDistance) switch (placement) {
case "top":
nextWrapperStyle.transform = `translateY(${pushDistance}px)`;
break;
case "bottom":
nextWrapperStyle.transform = `translateY(${-pushDistance}px)`;
break;
case "left":
nextWrapperStyle.transform = `translateX(${pushDistance}px)`;
break;
default: nextWrapperStyle.transform = `translateX(${-pushDistance}px)`;
}
if (isHorizontal) nextWrapperStyle.width = parseWidthHeight(mergedSize);
else nextWrapperStyle.height = parseWidthHeight(mergedSize);
return nextWrapperStyle;
}, [
pushed,
pushDistance,
placement,
isHorizontal,
mergedSize
]);
const wrapperRef = import_react$5.useRef(null);
const isResizable = !!resizable;
const resizeConfig = typeof resizable === "object" && resizable || {};
const onInternalResize = useEvent$1((size) => {
setCurrentSize(size);
resizeConfig.onResize?.(size);
});
const { dragElementProps, isDragging } = useDrag({
prefixCls: `${prefixCls}-resizable`,
direction: placement,
className: drawerClassNames?.dragger,
style: styles?.dragger,
maxSize,
containerRef: wrapperRef,
currentSize: mergedSize,
onResize: onInternalResize,
onResizeStart: resizeConfig.onResizeStart,
onResizeEnd: resizeConfig.onResizeEnd
});
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp,
onFocus: (e) => {
ignoreElement(e.target);
}
};
const panelNode = /*#__PURE__*/ import_react$5.createElement(es_default$5, _extends$3({ key: "panel" }, motionProps, {
visible: open,
forceRender,
onVisibleChanged: afterOpenChange,
removeOnLeave: false,
leavedClassName: `${prefixCls}-content-wrapper-hidden`
}), ({ className: motionClassName, style: motionStyle }, motionRef) => {
const content = /*#__PURE__*/ import_react$5.createElement(DrawerPanel$1, _extends$3({
id,
containerRef: motionRef,
prefixCls,
className: clsx(className, drawerClassNames?.section),
style: {
...style,
...styles?.section
}
}, pickAttrs(props, { aria: true }), eventHandlers), children);
return /*#__PURE__*/ import_react$5.createElement("div", _extends$3({
ref: wrapperRef,
className: clsx(`${prefixCls}-content-wrapper`, isDragging && `${prefixCls}-content-wrapper-dragging`, drawerClassNames?.wrapper, !isDragging && motionClassName),
style: {
...motionStyle,
...wrapperStyle,
...styles?.wrapper
}
}, pickAttrs(props, { data: true })), isResizable && /*#__PURE__*/ import_react$5.createElement("div", dragElementProps), drawerRender ? drawerRender(content) : content);
});
const containerStyle = { ...rootStyle };
if (zIndex) containerStyle.zIndex = zIndex;
return /*#__PURE__*/ import_react$5.createElement(DrawerContext.Provider, { value: mergedContext }, /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(prefixCls, `${prefixCls}-${placement}`, rootClassName, {
[`${prefixCls}-open`]: open,
[`${prefixCls}-inline`]: inline
}),
style: containerStyle,
tabIndex: -1,
ref: panelRef
}, maskNode, panelNode));
};
var RefDrawerPopup = /*#__PURE__*/ import_react$5.forwardRef(DrawerPopup);
var Drawer$1 = (props) => {
const { open = false, prefixCls = "rc-drawer", placement = "right", autoFocus = true, keyboard = true, width, height, size, maxSize, mask = true, maskClosable = true, getContainer, forceRender, afterOpenChange, destroyOnHidden, onMouseEnter, onMouseOver, onMouseLeave, onClick, onKeyDown, onKeyUp, onClose, resizable, defaultSize, focusTriggerAfterClose, panelRef } = props;
const [animatedVisible, setAnimatedVisible] = import_react$5.useState(false);
const [mounted, setMounted] = import_react$5.useState(false);
useLayoutEffect$2(() => {
setMounted(true);
}, []);
const mergedOpen = mounted ? open : false;
const popupRef = import_react$5.useRef(null);
const lastActiveRef = import_react$5.useRef(null);
useLayoutEffect$2(() => {
if (mergedOpen) lastActiveRef.current = document.activeElement;
}, [mergedOpen]);
const internalAfterOpenChange = (nextVisible) => {
setAnimatedVisible(nextVisible);
afterOpenChange?.(nextVisible);
if (!nextVisible && focusTriggerAfterClose !== false && lastActiveRef.current && !popupRef.current?.contains(lastActiveRef.current)) lastActiveRef.current?.focus({ preventScroll: true });
};
const refContext = import_react$5.useMemo(() => ({ panel: panelRef }), [panelRef]);
if (!forceRender && !animatedVisible && !mergedOpen && destroyOnHidden) return null;
const eventHandlers = {
onMouseEnter,
onMouseOver,
onMouseLeave,
onClick,
onKeyDown,
onKeyUp
};
const drawerPopupProps = {
...props,
open: mergedOpen,
prefixCls,
placement,
autoFocus,
keyboard,
width,
height,
size,
maxSize,
defaultSize,
mask,
maskClosable,
inline: getContainer === false,
afterOpenChange: internalAfterOpenChange,
ref: popupRef,
resizable,
...eventHandlers
};
const onEsc = ({ top, event }) => {
if (top && keyboard) {
event.stopPropagation();
onClose?.(event);
}
};
return /*#__PURE__*/ import_react$5.createElement(RefContext.Provider, { value: refContext }, /*#__PURE__*/ import_react$5.createElement(es_default$4$1, {
open: mergedOpen || forceRender || animatedVisible,
autoDestroy: false,
getContainer,
autoLock: mask && (mergedOpen || animatedVisible),
onEsc
}, /*#__PURE__*/ import_react$5.createElement(RefDrawerPopup, drawerPopupProps)));
};
var es_default = Drawer$1;
var DrawerPanel = (props) => {
const { prefixCls, ariaId, title, footer, extra, closable, loading, onClose, headerStyle, bodyStyle, footerStyle, children, classNames: drawerClassNames, styles: drawerStyles } = props;
const drawerContext = useComponentConfig("drawer");
const { classNames: contextClassNames, styles: contextStyles, closable: contextClosable } = drawerContext;
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, drawerClassNames], [contextStyles, drawerStyles], { props: {
...props,
closable: closable ?? contextClosable
} });
const closablePlacement = import_react$5.useMemo(() => {
const merged = closable ?? contextClosable;
if (merged === false) return;
if (isPlainObject(merged) && merged?.placement === "end") return "end";
return "start";
}, [closable, contextClosable]);
const customCloseIconRender = import_react$5.useCallback((icon) => /*#__PURE__*/ import_react$5.createElement("button", {
type: "button",
onClick: onClose,
className: clsx(`${prefixCls}-close`, { [`${prefixCls}-close-${closablePlacement}`]: closablePlacement === "end" }, mergedClassNames.close),
style: mergedStyles.close
}, icon), [
onClose,
prefixCls,
closablePlacement,
mergedClassNames.close,
mergedStyles.close
]);
const [mergedClosable, mergedCloseIcon, closeBtnIsDisabled] = useClosable(pickClosable(props), pickClosable(drawerContext), {
closable: true,
closeIconRender: customCloseIconRender
});
const mergedCloseButton = cloneElement(mergedCloseIcon, { disabled: closeBtnIsDisabled });
const hasTitle = isReactRenderable(title);
const hasExtra = isReactRenderable(extra);
const renderHeader = () => {
if (!hasTitle && !mergedClosable && !hasExtra) return null;
return /*#__PURE__*/ import_react$5.createElement("div", {
style: {
...mergedStyles.header,
...headerStyle
},
className: clsx(`${prefixCls}-header`, mergedClassNames.header, { [`${prefixCls}-header-close-only`]: mergedClosable && !hasTitle && !hasExtra })
}, /*#__PURE__*/ import_react$5.createElement("div", { className: `${prefixCls}-header-title` }, closablePlacement === "start" && mergedCloseButton, hasTitle && /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(`${prefixCls}-title`, mergedClassNames.title),
style: mergedStyles.title,
id: ariaId
}, title)), hasExtra && /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(`${prefixCls}-extra`, mergedClassNames.extra),
style: mergedStyles.extra
}, extra), closablePlacement === "end" && mergedCloseButton);
};
const renderFooter = () => {
if (!footer) return null;
return /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(`${prefixCls}-footer`, mergedClassNames.footer),
style: {
...mergedStyles.footer,
...footerStyle
}
}, footer);
};
return /*#__PURE__*/ import_react$5.createElement(import_react$5.Fragment, null, renderHeader(), /*#__PURE__*/ import_react$5.createElement("div", {
className: clsx(`${prefixCls}-body`, mergedClassNames.body),
style: {
...mergedStyles.body,
...bodyStyle
}
}, loading ? /*#__PURE__*/ import_react$5.createElement(skeleton_default, {
active: true,
title: false,
paragraph: { rows: 5 },
className: `${prefixCls}-body-skeleton`
}) : children), renderFooter());
};
var getMoveTranslate = (direction) => {
const value = "100%";
return {
left: `translateX(-${value})`,
right: `translateX(${value})`,
top: `translateY(-${value})`,
bottom: `translateY(${value})`
}[direction];
};
var getEnterLeaveStyle = (startStyle, endStyle) => ({
"&-enter, &-appear": {
...startStyle,
"&-active": endStyle
},
"&-leave": {
...endStyle,
"&-active": startStyle
}
});
var getFadeStyle = (from, duration) => ({
"&-enter, &-appear, &-leave": {
"&-start": { transition: "none" },
"&-active": { transition: `all ${duration}` }
},
...getEnterLeaveStyle({ opacity: from }, { opacity: 1 })
});
var getPanelMotionStyles = (direction, duration) => [getFadeStyle(.7, duration), getEnterLeaveStyle({ transform: getMoveTranslate(direction) }, { transform: "none" })];
var genMotionStyle = (token) => {
const { componentCls, motionDurationSlow } = token;
return { [componentCls]: {
[`${componentCls}-mask-motion`]: getFadeStyle(0, motionDurationSlow),
[`${componentCls}-panel-motion`]: [
"left",
"right",
"top",
"bottom"
].reduce((obj, direction) => {
return {
...obj,
[`&-${direction}`]: getPanelMotionStyles(direction, motionDurationSlow)
};
}, {})
} };
};
var genDrawerStyle = (token) => {
const { borderRadiusSM, componentCls, zIndexPopup, colorBgMask, colorBgElevated, motionDurationSlow, motionDurationMid, paddingXS, padding, paddingLG, fontSizeLG, lineHeightLG, lineWidth, lineType, colorSplit, marginXS, colorIcon, colorIconHover, colorBgTextHover, colorBgTextActive, colorText, fontWeightStrong, footerPaddingBlock, footerPaddingInline, draggerSize, calc } = token;
const wrapperCls = `${componentCls}-content-wrapper`;
const draggerCls = `${componentCls}-resizable-dragger`;
return { [componentCls]: {
position: "fixed",
inset: 0,
zIndex: zIndexPopup,
pointerEvents: "none",
color: colorText,
"&-pure": {
position: "relative",
background: colorBgElevated,
display: "flex",
flexDirection: "column",
pointerEvents: "auto",
[`&${componentCls}-left`]: { boxShadow: token.boxShadowDrawerLeft },
[`&${componentCls}-right`]: { boxShadow: token.boxShadowDrawerRight },
[`&${componentCls}-top`]: { boxShadow: token.boxShadowDrawerUp },
[`&${componentCls}-bottom`]: { boxShadow: token.boxShadowDrawerDown }
},
"&-inline": { position: "absolute" },
[`${componentCls}-mask`]: {
position: "absolute",
inset: 0,
zIndex: zIndexPopup,
background: colorBgMask,
pointerEvents: "auto",
[`&${componentCls}-mask-blur`]: { backdropFilter: "blur(4px)" }
},
[wrapperCls]: {
position: "absolute",
zIndex: zIndexPopup,
maxWidth: "100vw",
transition: `all ${motionDurationSlow}`,
"&-hidden": { display: "none" }
},
[`&-left > ${wrapperCls}`]: {
top: 0,
bottom: 0,
left: {
_skip_check_: true,
value: 0
},
boxShadow: token.boxShadowDrawerLeft
},
[`&-right > ${wrapperCls}`]: {
top: 0,
right: {
_skip_check_: true,
value: 0
},
bottom: 0,
boxShadow: token.boxShadowDrawerRight
},
[`&-top > ${wrapperCls}`]: {
top: 0,
insetInline: 0,
boxShadow: token.boxShadowDrawerUp
},
[`&-bottom > ${wrapperCls}`]: {
bottom: 0,
insetInline: 0,
boxShadow: token.boxShadowDrawerDown
},
[`${componentCls}-section`]: {
display: "flex",
flexDirection: "column",
width: "100%",
height: "100%",
overflow: "auto",
background: colorBgElevated,
pointerEvents: "auto"
},
[`${componentCls}-header`]: {
display: "flex",
flex: 0,
alignItems: "center",
padding: `${unit$1(padding)} ${unit$1(paddingLG)}`,
fontSize: fontSizeLG,
lineHeight: lineHeightLG,
borderBottom: `${unit$1(lineWidth)} ${lineType} ${colorSplit}`,
"&-title": {
display: "flex",
flex: 1,
alignItems: "center",
minWidth: 0,
minHeight: 0
}
},
[`${componentCls}-extra`]: { flex: "none" },
[`${componentCls}-close`]: {
display: "inline-flex",
width: calc(fontSizeLG).add(paddingXS).equal(),
height: calc(fontSizeLG).add(paddingXS).equal(),
borderRadius: borderRadiusSM,
justifyContent: "center",
alignItems: "center",
color: colorIcon,
fontWeight: fontWeightStrong,
fontSize: fontSizeLG,
fontStyle: "normal",
lineHeight: 1,
textAlign: "center",
textTransform: "none",
textDecoration: "none",
background: "transparent",
border: 0,
cursor: "pointer",
transition: `all ${motionDurationMid}`,
textRendering: "auto",
[`&${componentCls}-close-end`]: { marginInlineStart: marginXS },
[`&:not(${componentCls}-close-end)`]: { marginInlineEnd: marginXS },
"&:disabled": { pointerEvents: "none" },
"&:hover": {
color: colorIconHover,
backgroundColor: colorBgTextHover,
textDecoration: "none"
},
"&:active": { backgroundColor: colorBgTextActive },
...genFocusStyle(token)
},
[`${componentCls}-title`]: {
flex: 1,
margin: 0,
fontWeight: token.fontWeightStrong,
fontSize: fontSizeLG,
lineHeight: lineHeightLG
},
[`${componentCls}-body`]: {
flex: 1,
minWidth: 0,
minHeight: 0,
padding: paddingLG,
overflow: "auto",
[`${componentCls}-body-skeleton`]: {
width: "100%",
height: "100%",
display: "flex",
justifyContent: "center"
}
},
[`${componentCls}-footer`]: {
flexShrink: 0,
padding: `${unit$1(footerPaddingBlock)} ${unit$1(footerPaddingInline)}`,
borderTop: `${unit$1(lineWidth)} ${lineType} ${colorSplit}`
},
[draggerCls]: {
position: "absolute",
zIndex: 1,
backgroundColor: "transparent",
userSelect: "none",
pointerEvents: "auto",
"&:hover": {
backgroundColor: token.colorPrimary,
opacity: .2
},
"&-dragging": {
backgroundColor: token.colorPrimary,
opacity: .3
}
},
[`${draggerCls}-left`]: {
top: 0,
bottom: 0,
right: {
_skip_check_: true,
value: 0
},
width: draggerSize,
cursor: "col-resize"
},
[`${draggerCls}-right`]: {
top: 0,
bottom: 0,
left: {
_skip_check_: true,
value: 0
},
width: draggerSize,
cursor: "col-resize"
},
[`${draggerCls}-top`]: {
insetInline: 0,
bottom: 0,
height: draggerSize,
cursor: "row-resize"
},
[`${draggerCls}-bottom`]: {
insetInline: 0,
top: 0,
height: draggerSize,
cursor: "row-resize"
},
[`${wrapperCls}-dragging`]: {
userSelect: "none",
transition: "none",
willChange: "width, height",
[`${componentCls}-content`]: { pointerEvents: "none" },
[`${componentCls}-section`]: { pointerEvents: "none" }
},
"&-rtl": { direction: "rtl" }
} };
};
var prepareComponentToken$3 = (token) => ({
zIndexPopup: token.zIndexPopupBase,
footerPaddingBlock: token.paddingXS,
footerPaddingInline: token.padding,
draggerSize: 4
});
var style_default$3 = genStyleHooks("Drawer", (token) => {
const drawerToken = merge(token, {});
return [genDrawerStyle(drawerToken), genMotionStyle(drawerToken)];
}, prepareComponentToken$3);
var DEFAULT_PUSH_STATE = { distance: 180 };
var DEFAULT_SIZE = 378;
var MOTION_CONFIG = {
motionAppear: true,
motionEnter: true,
motionLeave: true,
motionDeadline: 500
};
var Drawer = (props) => {
const { rootClassName, size, defaultSize = DEFAULT_SIZE, height, width, mask: drawerMask, push = DEFAULT_PUSH_STATE, open, afterOpenChange, onClose, prefixCls: customizePrefixCls, getContainer: customizeGetContainer, panelRef = null, style, className, resizable, "aria-labelledby": ariaLabelledby, focusable, maskClosable, maskStyle, drawerStyle, contentWrapperStyle, destroyOnClose, destroyOnHidden, ...rest } = props;
const { placement } = rest;
const id = useId_default();
const ariaId = isReactRenderable(rest.title) ? id : void 0;
const { getPopupContainer, getPrefixCls, direction, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles, mask: contextMask, focusable: contextFocusable } = useComponentConfig("drawer");
const prefixCls = getPrefixCls("drawer", customizePrefixCls);
const [hashId, cssVarCls] = style_default$3(prefixCls);
const getContainer = customizeGetContainer === void 0 && getPopupContainer ? () => getPopupContainer(document.body) : customizeGetContainer;
const drawerSize = import_react$5.useMemo(() => {
if (isNumber$1(size)) return size;
if (size === "large") return 736;
if (size === "default") return DEFAULT_SIZE;
if (typeof size === "string") {
if (/^\d+(\.\d+)?$/.test(size)) return Number(size);
return size;
}
if (!placement || placement === "left" || placement === "right") return width;
return height;
}, [
size,
placement,
width,
height
]);
const maskMotion = {
motionName: getTransitionName(prefixCls, "mask-motion"),
...MOTION_CONFIG
};
const panelMotion = (motionPlacement) => ({
motionName: getTransitionName(prefixCls, `panel-motion-${motionPlacement}`),
...MOTION_CONFIG
});
const innerPanelRef = usePanelRef();
const mergedPanelRef = composeRef(panelRef, innerPanelRef);
const [zIndex, contextZIndex] = useZIndex("Drawer", rest.zIndex);
const [mergedMask, maskBlurClassName, mergedMaskClosable] = useMergedMask(drawerMask, contextMask, prefixCls, maskClosable);
const mergedFocusable = useFocusable$1({
...contextFocusable,
...focusable
}, getContainer !== false && mergedMask);
const { classNames, styles, rootStyle } = rest;
const mergedProps = {
...props,
zIndex,
panelRef,
mask: mergedMask,
maskClosable: mergedMaskClosable,
defaultSize,
push,
focusable: mergedFocusable
};
const contextSectionStyle = useSemanticRootStyle(contextStyle, "section");
const drawerSectionStyle = useSemanticRootStyle(drawerStyle, "section");
const sectionStyle = useSemanticRootStyle(style, "section");
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextSectionStyle,
styles,
drawerSectionStyle,
sectionStyle
], { props: mergedProps });
const drawerClassName = clsx({
"no-mask": !mergedMask,
[`${prefixCls}-rtl`]: direction === "rtl"
}, rootClassName, hashId, cssVarCls, mergedClassNames.root);
return /*#__PURE__*/ import_react$5.createElement(ContextIsolator, {
form: true,
space: true
}, /*#__PURE__*/ import_react$5.createElement(ZIndexContext.Provider, { value: contextZIndex }, /*#__PURE__*/ import_react$5.createElement(es_default, {
prefixCls,
onClose,
maskMotion,
motion: panelMotion,
...rest,
classNames: {
mask: clsx(mergedClassNames.mask, maskBlurClassName.mask),
section: mergedClassNames.section,
wrapper: mergedClassNames.wrapper,
dragger: mergedClassNames.dragger
},
styles: {
mask: {
...mergedStyles.mask,
...maskStyle
},
section: mergedStyles.section,
wrapper: {
...mergedStyles.wrapper,
...contentWrapperStyle
},
dragger: mergedStyles.dragger
},
open,
mask: mergedMask,
maskClosable: mergedMaskClosable,
push,
size: drawerSize,
defaultSize,
rootStyle: {
...mergedStyles.root,
...rootStyle
},
className: clsx(contextClassName, className),
rootClassName: drawerClassName,
getContainer,
afterOpenChange,
panelRef: mergedPanelRef,
zIndex,
...resizable ? { resizable } : {},
"aria-labelledby": ariaLabelledby ?? ariaId,
destroyOnHidden: destroyOnHidden ?? destroyOnClose,
focusTriggerAfterClose: mergedFocusable.focusTriggerAfterClose,
focusTrap: mergedFocusable.trap
}, /*#__PURE__*/ import_react$5.createElement(DrawerPanel, {
prefixCls,
size,
...rest,
ariaId,
onClose
}))));
};
/** @private Internal Component. Do not use in your production. */
var PurePanel$1 = (props) => {
const { prefixCls: customizePrefixCls, style, className, placement = "right", ...restProps } = props;
const { getPrefixCls } = import_react$5.useContext(ConfigContext);
const prefixCls = getPrefixCls("drawer", customizePrefixCls);
const [hashId, cssVarCls] = style_default$3(prefixCls);
const cls = clsx(prefixCls, `${prefixCls}-pure`, `${prefixCls}-${placement}`, hashId, cssVarCls, className);
return /*#__PURE__*/ import_react$5.createElement("div", {
className: cls,
style
}, /*#__PURE__*/ import_react$5.createElement(DrawerPanel, {
prefixCls,
...restProps
}));
};
Drawer._InternalPanelDoNotUseOrYouWillBeFired = PurePanel$1;
var drawer_default = exports("aI",Drawer);
/** Suggest literal phrases, not dictionary tokens. Exact names inside brackets stay intact. */
function tagRecMaterialWords(text) {
const clean = text.replaceAll(/(?:https?:\/\/|www\.)[^\s<>,。;]+/gi, "\n").replaceAll(/(?:提取码|提取碼|访问码|访问碼|密码|密碼)[\s::]*[\da-z]+/gi, "\n");
const names = [...clean.matchAll(/[《「『【(([]([^《》「」『』【】()()[\]\n]{2,32})[》」』】))\]]/g)].map((match) => match[1]);
const parts = clean.split(/[\n\r\t,,。.!!??;;::、/|《》「」『』【】()()[\]]+/);
const noise = /^(?:链接|連結|提取码|提取碼|本人|简介|视频|标题|我的|之一|通过网盘分享的文件|pdf|https?|www|com|\d+)$/i;
return [...new Set([
...names,
...parts,
...tagRecSegmentWords(clean)
].map((part) => part.trim()))].filter((part) => part.length >= 2 && part.length <= 32 && /[\p{L}\p{N}]/u.test(part) && !noise.test(part)).slice(0, 24);
}
/** 浏览器内建 ICU 切词 (零依赖, Chromium 直用): 无标点标题也能给出词级建议; 老环境返回空回退现状。 */
function getSegmenter() {
const ctor = Intl.Segmenter;
if (!ctor) return void 0;
try {
return new ctor("zh", { granularity: "word" });
} catch {
return;
}
}
/** 词级切分结果 (过噪声过滤前的原文词, ≥1 字符); 无 Segmenter 环境为空数组。 */
function tagRecSegmentWords(text) {
const segmenter = getSegmenter();
if (!segmenter) return [];
const words = [];
for (const part of segmenter.segment(text)) if (part.isWordLike) words.push(part.segment);
return words;
}
/**
* 原文渲染用: 词段 (可点, ≥2 字符) 与标点/空白 (不可点) 交替切分, 相邻同类合并;
* 无 Segmenter 环境返回单段纯文本 (保留原有拖选取词行为)。
*/
function tagRecMaterialSegments(text) {
const segmenter = getSegmenter();
if (!segmenter) return [{
text,
word: false
}];
const out = [];
for (const part of segmenter.segment(text)) {
const word = !!part.isWordLike && part.segment.trim().length >= 2;
const last = out.at(-1);
if (last && last.word === word) last.text += part.segment;
else out.push({
text: part.segment,
word
});
}
return out;
}
/**
* @param tag 视频 tag
* @param tag.id tag_id (宿主判定主键)
* @param tag.name tag 名 (变体归一化判定原料)
* @param scope 本卡落点集 = 判定集 = 动作集 (票 19): 归因名解析出的来源组 + 未归属名的草稿组;
* 空集 (非 tag-rec 卡) => 全不激活, 落点集外宿主全进 *Outside 只读位
* @param universe 宿主枚举全集 (收藏表组 + 草稿组伪组); 缺省 = scope (单集语义同票 14 旧行为)
*/
function classifyVideoTagStatus(tag, scope, universe = scope) {
const scopeIds = new Set(scope.map((g) => g.id));
const inScope = (g) => scopeIds.has(g.id);
const favedAll = findGroupsByTagId(universe, tag.id);
const negAll = findGroupsByNegativeTagId(universe, tag.id);
const norm = normalizeTagName(tag.name);
let variant;
for (const g of scope) {
const entry = g.tag_ids.find((e) => e.id !== tag.id && normalizeTagName(e.name) === norm);
if (entry) {
variant = {
group: g,
entry
};
break;
}
}
return {
faved: favedAll.filter(inScope),
favedOutside: favedAll.filter((g) => !inScope(g)),
negative: negAll.filter(inScope),
negativeOutside: negAll.filter((g) => !inScope(g)),
variant
};
}
/** 草稿组在弹层展示用的占位 id (非收藏表真组; statusGroups 伪组与本卡落点集共用) */
var DRAFT_GROUP_ID = "__draft__";
/**
* 本卡落点集解析 (票 19: 状态判定集与动作集同收窄到此; 复用票 14 Q2 写入侧语义)
* 优先使用卡片的稳定组 ID;旧名称只接受唯一匹配,未归属输入词可落草稿组。
* tab2 组语境恒 1 组 (票 21), 多落点只剩 tab1 多归因卡; 空 => ⛔ 点击走 Modal 兜底
* 票 20 顺带修: 归因名可带 via 后缀 (兜底流旁路归因 `${组名}·相关`, fallback-ladder.ts) ——
* 剥掉后缀再按名查组, 对齐 service 侧 negEntriesOf 的 attributionKey (stripRelatedSuffix) 语义;
* statusGroups 含草稿组伪组 iff 会话草稿组存在, 故不再回读 session (保持纯函数可测)
*/
function resolveLandingGroups(groups, statusGroups, sourceGroupNames, sourceGroupIds) {
if (sourceGroupIds?.length) {
const ids = new Set(sourceGroupIds);
return groups.filter((g) => ids.has(g.id));
}
const names = sourceGroupNames.map(stripRelatedSuffix);
const counts = /* @__PURE__ */ new Map();
for (const g of groups) counts.set(g.name, (counts.get(g.name) ?? 0) + 1);
const set = new Set(names);
const named = groups.filter((g) => set.has(g.name) && counts.get(g.name) === 1);
const hasUnowned = names.some((n) => !groups.some((g) => g.name === n));
const draftAsGroup = statusGroups.find((g) => g.id === DRAFT_GROUP_ID);
const targets = [...named];
if (hasUnowned && draftAsGroup) targets.push(draftAsGroup);
return targets;
}
/** view/detail/tag 的 tag_type => TagIdEntry.tag_type; 枚举外取值 (如数值 type) 不落 */
function parseTagType(raw) {
return raw === "topic" || raw === "old_channel" || raw === "bgm" ? raw : void 0;
}
/** view/detail/tag 行 => 收藏表条目 (id+name 快照 + opportunistic tag_type, spec §3.1) */
function videoTagToTagIdEntry(tag) {
const tag_type = parseTagType(tag.tag_type);
return {
id: tag.tag_id,
name: tag.tag_name,
...tag_type && { tag_type }
};
}
async function resolve(material) {
if (material.atom || material.filter) return material;
const info = await (material.tagId ? fetchTagInfoById(material.tagId) : fetchTagInfoByName(material.name)).catch(() => void 0);
return info ? {
...material,
atom: tagRecConditionAtom("tag", {
id: info.id,
name: info.name,
tag_type: info.tag_type
})
} : void 0;
}
/** Apply accepted items together. Conflicts and over-limit items stay pending. */
function apply(materials, target) {
const accepted = /* @__PURE__ */ new Set();
editTagRecWorkingGroup((group) => {
if (target && findTagRecCondition(group.combination.include, target)?.kind !== "group") return;
for (const material of materials) {
const next = cloneDeep(group.combination);
const atom = material.atom;
if (atom) {
const key = tagRecConditionKey(atom);
if (!material.exclude && (tagRecCombinationBlocks.list.some((entry) => tagRecConditionKey(entry) === key) || next.exclude.some((entry) => tagRecConditionKey(entry) === key))) continue;
if (material.exclude) {
for (const entry of tagRecConditionAtoms(next.include)) if (tagRecConditionKey(entry) === key) removeTagRecCondition(next, entry.id);
excludeTagRecCondition(next, atom);
} else {
const destination = target ? findTagRecCondition(next.include, target) : next.include;
if (!tagRecConditionAtoms(destination).some((entry) => tagRecConditionKey(entry) === key) && !addTagRecCondition(next, atom, target)) continue;
}
}
if (material.filter) next.filters = mergeTagRecCombinationFilters(next.filters, material.filter);
if (!validateTagRecCombination(next)) continue;
group.combination = next;
accepted.add(material);
}
});
return accepted;
}
async function selectTagRecMaterial(material, target) {
const trialId = tagRecWorkbench.activeId;
const revision = tagRecCustomSession.queryRevision;
const trial = currentTagRecTrial();
if (!trial) return false;
const signature = tagRecWorkingGroupSignature(trial.group);
const resolved = await resolve(material);
if (trialId !== tagRecWorkbench.activeId || revision !== tagRecCustomSession.queryRevision || signature !== tagRecWorkingGroupSignature(currentTagRecTrial().group)) return false;
if (!resolved) {
toast("暂未解析到这个标签,请稍后重试");
return false;
}
const accepted = apply([resolved], target).size > 0;
if (!accepted) toast("未加入:请检查排除条件、目标组或条件数量");
return accepted;
}
/**
* 收藏组「整组导入」: 每组 include 整棵嫁接为目标组的子组 (保留原 any/all 嵌套结构),
* exclude 并入前先拔掉工作区里的同键正原子 (与单选排除同语义), filters 并入;
* 单次工作区提交。与 apply() 同轨的安全闸: 全局拉黑词、工作区既有排除命中的原子
* 从源子树剪掉 (剪空的嵌套组随剪随去), 校验不过或零变化的组不计数。
* 返回实际并入的组数。
*/
function importTagRecFavoriteCombinations(sources, target) {
const prepared = sources.filter((source) => tagRecConditionAtoms(source.include).length > 0 || source.exclude.length > 0 || source.filters);
if (!prepared.length) return 0;
let imported = 0;
editTagRecWorkingGroup((group) => {
group.combination ??= newTagRecCombination();
if (target && findTagRecCondition(group.combination.include, target)?.kind !== "group") return;
for (const source of prepared) {
const src = cloneDeep(source);
const next = cloneDeep(group.combination);
const blocked = /* @__PURE__ */ new Set([...tagRecCombinationBlocks.list.map(tagRecConditionKey), ...next.exclude.map(tagRecConditionKey)]);
for (const atom of tagRecConditionAtoms(src.include)) if (blocked.has(tagRecConditionKey(atom))) removeTagRecCondition(src, atom.id);
if (tagRecConditionAtoms(src.include).length) graftTagRecConditionTree(next, src.include, target);
for (const atom of src.exclude) {
const key = tagRecConditionKey(atom);
for (const entry of tagRecConditionAtoms(next.include)) if (tagRecConditionKey(entry) === key) removeTagRecCondition(next, entry.id);
excludeTagRecCondition(next, atom);
}
next.filters = mergeTagRecCombinationFilters(next.filters, src.filters);
if (!validateTagRecCombination(next) || JSON.stringify(next) === JSON.stringify(group.combination)) continue;
group.combination = next;
imported++;
}
});
return imported;
}
async function commitTagRecMaterialDraft(target) {
const trial = currentTagRecTrial();
if (!trial) return false;
const revision = tagRecCustomSession.queryRevision;
const signature = tagRecWorkingGroupSignature(trial.group);
const current = () => trial.id === tagRecWorkbench.activeId && revision === tagRecCustomSession.queryRevision && signature === tagRecWorkingGroupSignature(currentTagRecTrial().group);
const original = cloneDeep(trial.pending ?? []);
const resolved = [];
let requests = 0;
for (const material of original) {
if (!current()) return false;
if (!material.atom && !material.filter && requests++ >= 20) continue;
const value = await resolve(material);
if (value) resolved.push(value);
}
if (!current()) return false;
const stillPending = new Set(trial.pending?.map((entry) => entry.pickId));
const accepted = apply(resolved.filter((entry) => stillPending.has(entry.pickId)), target);
const completed = new Set([...accepted].map((entry) => entry.pickId));
trial.pending = (trial.pending ?? []).filter((entry) => !completed.has(entry.pickId));
toast(trial.pending.length ? "未解析、冲突或超出上限的暂选已保留,请检查后重试" : "已加入当前组合,可继续试改或保存");
return accepted.size > 0;
}
var TAG_REC_POOL_NAMES = {
tagTop: "标签视频",
allV2: "精确搜索",
allV2Related: "搜索相关",
archiveRelated: "推荐相关"
};
function setTagRecWorkingPool(pool, value) {
return editTagRecWorkingGroup((group) => {
const filters = group.combination.filters ??= { dims: {} };
if (value === void 0) {
delete filters.poolsEnabled?.[pool];
if (!Object.keys(filters.poolsEnabled ?? {}).length) delete filters.poolsEnabled;
} else (filters.poolsEnabled ??= {})[pool] = value;
});
}
function TagRecDiscoveryPools({ service, onRefresh }) {
const [busyPool, setBusyPool] = (0, import_react$5.useState)();
const controller = (0, import_react$5.useRef)(void 0);
(0, import_react$5.useEffect)(() => () => controller.current?.abort(), [service]);
const session = useSnapshot(tagRecCustomSession);
const { riskControl } = useSnapshot(tagRecSharedSession);
const holdReason = tagRecHoldReasonText(session, riskControl);
const parents = useSnapshot(favoritesL1Store);
useSnapshot(settings.tagRec);
const group = session.workingGroup;
const overrides = group?.combination?.filters?.poolsEnabled;
const l1 = parents.list.find((entry) => entry.id === group?.l1Id);
const effective = (pool) => settings.tagRec.filter ? resolveTagRecEffective(settings.tagRec.filter, pool, group, l1, group?.combination?.filters).poolsEnabled[pool] : overrides?.[pool] ?? true;
const related = effective("allV2Related") || effective("archiveRelated");
const counts = service?.getDiscoveryStatus?.() ?? [];
const changeRelated = () => editTagRecWorkingGroup((current) => {
const filters = current.combination.filters ??= { dims: {} };
filters.poolsEnabled = {
...filters.poolsEnabled,
allV2Related: !related,
archiveRelated: !related
};
});
const content = /* @__PURE__ */ jsxs("div", {
style: { maxWidth: 470 },
className: "flex flex-col gap-12px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
children: "内容来源 · 本组合"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "只保存明确改过的池选择,其余继续继承。关池后停止该池取新;同一视频仍可由其他已开启的来源出现。"
}),
TAG_REC_POOL_KEYS.map((pool) => /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-8px",
children: [
/* @__PURE__ */ jsx(Checkbox, {
checked: effective(pool),
onChange: (event) => setTagRecWorkingPool(pool, event.target.checked),
children: TAG_REC_POOL_NAMES[pool]
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: overrides?.[pool] === void 0 ? "继承" : "本组合"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: group?.combination ? `候选 ${counts.find((entry) => entry.pool === pool)?.cached ?? 0} · 可交付 ${counts.find((entry) => entry.pool === pool)?.deliverable ?? 0} · 待核实 ${counts.find((entry) => entry.pool === pool)?.held ?? 0} · 标签待补 ${counts.find((entry) => entry.pool === pool)?.tagsPending ?? 0}` : `本地候选 ${counts.find((entry) => entry.pool === pool)?.cached ?? 0} · 标签未核实 ${counts.find((entry) => entry.pool === pool)?.tagsPending ?? 0}`
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setTagRecWorkingPool(pool),
children: "恢复继承"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
loading: busyPool === pool,
disabled: !!busyPool || !counts.find((entry) => entry.pool === pool)?.tagsPending,
onClick: async () => {
setBusyPool(pool);
controller.current = new AbortController();
try {
const result = await service.completeDiscoveryTags(pool, controller.current.signal);
toast(result.stale ? "当前条件已改变或正在退避,缓存已保留" : `本批补齐 ${result.updated} / ${result.attempted} 条,余下可稍后继续`);
if (!result.stale) {
service.retryPartitions();
onRefresh();
}
} catch {
toast("这批标签暂未取到,已有缓存仍保留");
} finally {
controller.current = void 0;
setBusyPool(void 0);
}
},
children: "批量补齐标签"
})
]
}, pool)),
!group?.tag_ids.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "标签池需要已解析的正向标签;当前可使用其他来源。"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "按池限量补查,每批最多 20 条;复用缓存并遵守并发、时间与退避限额。"
}),
!!holdReason && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: holdReason
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => editTagRecWorkingGroup((current) => {
(current.combination.filters ??= { dims: {} }).poolsEnabled = {
tagTop: false,
allV2: true,
allV2Related: false,
archiveRelated: false
};
}),
children: "只用精确搜索"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => editTagRecWorkingGroup((current) => {
if (current.combination.filters) delete current.combination.filters.poolsEnabled;
}),
children: "池选择全部恢复继承"
}),
/* @__PURE__ */ jsx(TagRecRecheckButton, {
service,
onDone: onRefresh
}),
/* @__PURE__ */ jsx(collapse_default, {
ghost: true,
size: "small",
items: [{
key: "execution",
label: "取新与必要补查",
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Checkbox, {
checked: session.supplyPaused,
onChange: (event) => {
tagRecCustomSession.supplyPaused = event.target.checked;
service.retryPartitions();
onRefresh();
},
children: "先看缓存,暂缓自动取新和补查"
}), /* @__PURE__ */ jsx("p", { children: "这是临时浏览节奏,不随二级组保存。明确排除仍会生效,无法核实的内容继续暂存;手动取词或重查按你的点击执行。" })] })
}]
})
]
});
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px gap-y-2px",
style: { padding: "1px 0" },
children: [
/* @__PURE__ */ jsx(Popover, {
trigger: "click",
placement: "bottomLeft",
content,
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
children: [
"内容来源 · ",
TAG_REC_POOL_KEYS.filter(effective).length,
" 项 ▾"
]
})
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 11 },
children: TAG_REC_POOL_KEYS.filter(effective).map((pool) => TAG_REC_POOL_NAMES[pool]).join(" / ")
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: changeRelated,
children: related ? "不看相关" : "启用相关"
}),
TAG_REC_POOL_KEYS.every((pool) => !effective(pool)) && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "四个池都已关闭,可在这里重新开启。"
}),
session.supplyPaused && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
children: [
"缓存浏览中",
" ",
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
onClick: () => {
tagRecCustomSession.supplyPaused = false;
service.retryPartitions();
onRefresh();
},
children: "继续取新与补查"
})
]
})
]
});
}
function tagRecFieldTones(token) {
const fieldTones = {
tag: {
fg: token.blue7,
bg: token.blue1,
bd: token.blue3
},
up: {
fg: token.green7,
bg: token.green1,
bd: token.green3
},
title: {
fg: token.gold7,
bg: token.gold1,
bd: token.gold3
},
desc: {
fg: token.purple7,
bg: token.purple1,
bd: token.purple3
},
partition: {
fg: token.cyan7,
bg: token.cyan1,
bd: token.cyan3
}
};
const excludeTone = {
fg: token.red7,
bg: token.red1,
bd: token.red3
};
const neutralTone = {
fg: token.colorTextSecondary,
bg: token.colorFillQuaternary,
bd: token.colorBorderSecondary
};
const materialTone = (material) => material.exclude ? excludeTone : material.atom ? fieldTones[material.atom.field] ?? neutralTone : material.filter ? neutralTone : fieldTones.tag;
return {
fieldTones,
excludeTone,
neutralTone,
materialTone
};
}
function TagRecDiscoveryCardActions({ item, visible }) {
return /* @__PURE__ */ jsx(VideoCardActionButton, {
visible,
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(IconForTagManage, { className: "size-16px" }),
tooltip: "选条件",
role: "button",
"aria-label": "从视频选条件",
tabIndex: visible ? 0 : -1,
onKeyDown: (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
event.stopPropagation();
openTagRecVideoMaterials(item, tagRecWorkbench.activeId);
}
},
onClick: (event) => {
event.preventDefault();
event.stopPropagation();
openTagRecVideoMaterials(item, tagRecWorkbench.activeId);
}
});
}
function TagRecVideoMaterials() {
const ui = useSnapshot(tagRecDiscoveryUi);
const memory = useSnapshot(tagRecWorkbench);
const { activeId } = memory;
const { supplyPaused, workingGroup } = useSnapshot(tagRecCustomSession);
const workingCombination = workingGroup?.combination;
const globalBlocks = useSnapshot(tagRecCombinationBlocks);
const favList = useSnapshot(favoritesStore).list;
const { token } = theme.useToken();
const { excludeTone, materialTone } = tagRecFieldTones(token);
const [item, setItem] = (0, import_react$5.useState)();
const [busy, setBusy] = (0, import_react$5.useState)(false);
const [error, setError] = (0, import_react$5.useState)(false);
const [tags, setTags] = (0, import_react$5.useState)([]);
const [infos, setInfos] = (0, import_react$5.useState)({});
const [phrase, setPhrase] = (0, import_react$5.useState)("");
const [tab, setTab] = (0, import_react$5.useState)("tag");
const [committing, setCommitting] = (0, import_react$5.useState)(false);
const [batch, setBatch] = (0, import_react$5.useState)(false);
/** Q9: 点击加入后的一行非阻断成本提示 (关系/本地预计命中), 随视频切换清空。 */
const [costHint, setCostHint] = (0, import_react$5.useState)("");
const [details, setDetails] = (0, import_react$5.useState)(false);
const [morePhrases, setMorePhrases] = (0, import_react$5.useState)(false);
const request = (0, import_react$5.useRef)(void 0);
/** 本 BV 详情证据已尝试补查的标记 —— 防「补后仍不完整」自触发循环; 换视频复位 */
const evidenceTried = (0, import_react$5.useRef)(void 0);
const open = !!ui.video && ui.videoTrialId === activeId;
const load = async (video) => {
if (!open || video.bvid !== ui.video?.bvid) return;
request.current?.abort();
const controller = new AbortController();
request.current = controller;
const trialId = ui.videoTrialId;
const isCurrent = () => !controller.signal.aborted && request.current === controller && tagRecDiscoveryUi.video?.bvid === video.bvid && tagRecDiscoveryUi.videoTrialId === trialId && tagRecWorkbench.activeId === trialId;
setBusy(true);
setError(false);
try {
const fields = await fetchTagRecVideoEvidence(video.bvid, controller.signal);
const paired = await getPersistedVideoTags(video.bvid).catch(() => void 0);
if (isCurrent()) {
setItem(mergeTagRecVideoEvidence(video, fields));
setTags(paired ?? []);
setError(!fields);
}
} catch {
if (isCurrent()) setError(true);
} finally {
if (isCurrent()) setBusy(false);
}
};
(0, import_react$5.useEffect)(() => {
request.current?.abort();
setBusy(false);
setError(false);
setPhrase("");
setTags([]);
setInfos({});
setTab("tag");
setDetails(false);
setMorePhrases(false);
evidenceTried.current = void 0;
if (!open || !ui.video) {
setItem(void 0);
return;
}
const video = mergeTagRecVideoEvidence(cloneDeep(ui.video), getCachedTagRecVideoEvidence(ui.video.bvid));
setItem(video);
let active = true;
getPersistedVideoTags(video.bvid).then((paired) => {
if (active) setTags(paired ?? []);
}).catch(() => void 0);
return () => {
active = false;
request.current?.abort();
};
}, [
ui.video,
open,
supplyPaused
]);
(0, import_react$5.useEffect)(() => setCostHint(""), [ui.video, open]);
(0, import_react$5.useEffect)(() => {
if (!open || !item || item.bvid !== ui.video?.bvid || supplyPaused || busy) return;
const needsTags = item.ownTagIds === void 0;
const needsDesc = tab === "desc" && item.fieldEvidence?.desc !== "complete";
if (!needsTags && !needsDesc || evidenceTried.current === item.bvid) return;
evidenceTried.current = item.bvid;
load(item);
}, [
item,
tab,
open,
ui.video?.bvid,
supplyPaused,
busy
]);
const ownTagNames = item?.ownTagNames;
(0, import_react$5.useEffect)(() => {
if (!open || !details) return;
const names = [.../* @__PURE__ */ new Set([...ownTagNames ?? [], ...tags.flatMap((tag) => tag.tag_name ? [tag.tag_name] : [])])];
if (!names.length) return;
const ctrl = new AbortController();
Promise.allSettled(names.slice(0, 12).map((name) => fetchTagInfoByName(name, ctrl.signal).then((info) => [name, info]))).then((results) => {
if (ctrl.signal.aborted) return;
setInfos((previous) => {
const next = { ...previous };
for (const result of results) if (result.status === "fulfilled" && result.value[1]) next[result.value[0]] = result.value[1];
return next;
});
});
return () => ctrl.abort();
}, [
ownTagNames,
tags,
open,
details
]);
const staged = memory.trials.find((entry) => entry.id === activeId)?.pending ?? [];
const pick = (material) => stageTagRecMaterial(activeId, material);
const isStaged = (material) => staged.some((entry) => tagRecMaterialKey(entry) === tagRecMaterialKey(material));
/**
* Q9: 点击即加入 (多选友好, 无弹窗) —— 成本已在取数链封顶 (本地先行零请求 / 文本词 ≤2 页 /
* 长词不开流 / 零进展护栏), 预览改为面板内一行非阻断提示; 想先攒一批可勾「批量暂选」。
*/
const addMaterial = (material) => {
const atom = material.atom;
if (atom) {
const impact = estimateTagRecConditionImpact(atom);
setCostHint(`已加入「${material.name}」· 关系「${impact.join === "any" ? "或" : "且"}」· 加入前本地预计命中 ${impact.hits} 条 · 存量耗尽后自动检索`);
}
selectTagRecMaterial(material);
};
const isTagMaterial = (material) => material.atom ? material.atom.field === "tag" : true;
const includeAtoms = workingCombination ? tagRecConditionAtoms(workingCombination.include) : [];
const same = (atom, material) => material.atom ? tagRecConditionKey(atom) === tagRecConditionKey(material.atom) : atom.field === "tag" && normalizeTagName(atom.value.name) === normalizeTagName(material.name);
const positive = (material) => includeAtoms.find((atom) => same(atom, material));
const negative = (material) => workingCombination?.exclude.find((atom) => same(atom, material));
const globalBlocked = (material) => globalBlocks.list.find((atom) => same(atom, material));
const favTagSets = (0, import_react$5.useMemo)(() => favList.map((group) => {
const names = /* @__PURE__ */ new Set();
const ids = /* @__PURE__ */ new Set();
for (const atom of tagRecConditionAtoms(tagRecCombinationOf(group).include)) if (atom.field === "tag") {
names.add(normalizeTagName(atom.value.name));
ids.add(atom.value.id);
}
return {
names,
ids
};
}), [favList]);
const favCount = (name, id) => favTagSets.filter((entry) => id !== void 0 && entry.ids.has(id) || entry.names.has(normalizeTagName(name))).length;
const materialId = (material) => isTagMaterial(material) ? material.atom?.field === "tag" ? material.atom.value.id : material.tagId ?? infos[material.name]?.id : void 0;
const resolveTagEntry = async (material) => {
const known = materialId(material);
const info = infos[material.name] ?? await (known !== void 0 ? fetchTagInfoById(known) : fetchTagInfoByName(material.name)).catch(() => void 0);
if (info && !infos[material.name]) setInfos((previous) => ({
...previous,
[material.name]: info
}));
const id = known ?? info?.id;
if (id === void 0) return void 0;
return {
id,
name: info?.name ?? material.name,
...info?.tag_type ? { tag_type: info.tag_type } : {}
};
};
const favAct = async (material, action) => {
const entry = await resolveTagEntry(material);
if (!entry) {
toast("这条标签还没解析到,稍后再试");
return;
}
if (action === "new") {
const res = favorites.addGroup({
name: entry.name,
tag_ids: [entry]
});
if (res) toast(`已收藏到「${res.group.name}」`);
return;
}
if (action.remove) {
if (favorites.removeTagIdFromGroup(action.group.id, entry.id)) toast(`已移出「${action.group.name}」`);
return;
}
const res = favorites.addTagIdsToGroup(action.group.id, [entry]);
toast(res.added || res.moved ? `已加入「${action.group.name}」` : res.skipped ? `「${action.group.name}」已有此标签` : "未加入");
};
const row = (material, meta) => {
const pos = positive(material);
const neg = negative(material);
const glob = globalBlocked(material);
const picked = isStaged(material);
const info = isTagMaterial(material) ? infos[material.name] : void 0;
const id = materialId(material);
const resolved = material.atom || !id ? material : {
...material,
atom: tagRecConditionAtom("tag", {
id,
name: material.name
})
};
const resolvedAtom = resolved.atom;
const favs = isTagMaterial(material) ? favCount(info?.name ?? material.name, id) : 0;
const metaParts = [
...meta,
info?.use !== void 0 ? `使用 ${info.use}` : "",
!resolvedAtom ? "待解析" : "",
favs ? `已在 ${favs} 个收藏` : ""
].filter(Boolean);
const menuItems = [
isTagMaterial(material) ? {
key: "only",
label: "仅看这个标签 · 原组合记入最近尝试",
onClick: () => void exploreTagRecMaterialOnly(resolved)
} : void 0,
{
key: "excludeStage",
label: "暂选为「这个组合不看」",
onClick: () => pick({
...resolved,
exclude: true
})
},
resolvedAtom ? {
key: "exclude",
label: "本组合不看",
onClick: () => excludeTagRecWorkingCondition(cloneDeep(resolvedAtom))
} : void 0,
resolvedAtom ? {
key: "global",
label: "以后都不看",
onClick: () => blockTagRecConditionGlobally(cloneDeep(resolvedAtom))
} : void 0
].filter((entry) => !!entry);
const [primaryLabel, primaryAction] = pos ? ["已选 · 移出", () => removeTagRecWorkingCondition(pos.id)] : neg ? ["已不看 · 恢复", () => removeTagRecWorkingCondition(neg.id)] : picked ? ["已暂选 · 取消", () => pick(material)] : [batch ? "暂选" : "选入", () => batch ? pick(material) : addMaterial(resolved)];
const favEntry = id !== void 0 ? {
id,
name: info?.name ?? material.name,
...info?.tag_type ? { tag_type: info.tag_type } : {}
} : void 0;
const favHosts = favEntry ? findGroupsByTagIds(favorites.list, [favEntry.id]) : [];
const favItems = [
{
key: "fav-new",
label: "新建收藏项",
onClick: () => void favAct(material, "new")
},
...favorites.list.filter((group) => group.enabled && !favHosts.some((host) => host.id === group.id)).map((group) => ({
key: `fav-${group.id}`,
label: `加入「${group.name}」`,
onClick: () => void favAct(material, { group })
})),
...favHosts.map((group) => ({
key: `unfav-${group.id}`,
label: `移出「${group.name}」`,
danger: true,
onClick: () => void favAct(material, {
group,
remove: true
})
}))
];
if (!details || !isTagMaterial(material)) {
const tone = neg || glob ? excludeTone : materialTone(material);
const solid = !!pos;
const fg = solid ? "#fff" : tone.fg;
return /* @__PURE__ */ jsxs("div", {
style: {
display: "inline-flex",
maxWidth: "100%",
border: `1px ${picked && !pos ? "dashed" : "solid"} ${pos || picked ? tone.fg : tone.bd}`,
borderRadius: 8,
overflow: "hidden",
background: solid ? tone.fg : tone.bg
},
children: [/* @__PURE__ */ jsxs(button_default, {
type: "text",
disabled: !!glob,
onClick: primaryAction,
title: glob ? "已全局排除" : primaryLabel,
"aria-label": `${primaryLabel}:${material.name}`,
style: {
height: "auto",
minHeight: 34,
padding: "5px 10px",
whiteSpace: "normal",
textAlign: "left",
color: fg,
fontWeight: pos || picked ? 600 : void 0
},
children: [pos || picked ? "✓ " : neg || glob ? "− " : "+ ", material.name]
}), /* @__PURE__ */ jsx(Dropdown, {
menu: { items: [...menuItems, ...isTagMaterial(material) ? [{
key: "favorites",
label: favs ? `收藏管理 · 已在 ${favs} 个组` : "收藏到…",
children: favItems
}] : []] },
trigger: ["click", "contextMenu"],
children: /* @__PURE__ */ jsx(button_default, {
type: "text",
"aria-label": `${material.name} 的更多操作`,
style: {
height: "auto",
paddingInline: 6,
color: fg,
borderRadius: 0,
borderLeft: `1px ${picked && !pos ? "dashed" : "solid"} ${solid ? "rgba(255,255,255,.45)" : tone.bd}`
},
children: "⋯"
})
})]
}, tagRecMaterialKey(material));
}
return /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
style: {
padding: "9px 0",
borderBottom: `1px solid ${token.colorBorderSecondary}`
},
children: [
/* @__PURE__ */ jsxs("div", {
style: {
flex: 1,
minWidth: 0
},
children: [/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
style: { fontSize: 13 },
children: material.name
}),
id !== void 0 && /* @__PURE__ */ jsxs(button_default, {
type: "link",
size: "small",
title: "仅看这个标签,原组合记入最近尝试",
onClick: () => void exploreTagRecMaterialOnly(resolved),
style: {
fontSize: 12,
padding: 0
},
children: ["#", id]
}),
glob && /* @__PURE__ */ jsx(Tag, {
color: "error",
children: "全局排除"
}),
!glob && neg && /* @__PURE__ */ jsx(Tag, {
color: "warning",
children: "本组合不看"
}),
!glob && !neg && pos && /* @__PURE__ */ jsx(Tag, {
color: "success",
children: "已生效"
}),
picked && /* @__PURE__ */ jsx(Tag, {
color: "purple",
children: "已暂选"
})
]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: metaParts.join(" · ")
})]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !!glob,
onClick: primaryAction,
children: primaryLabel
}),
isTagMaterial(material) && /* @__PURE__ */ jsx(Dropdown, {
menu: { items: favItems },
trigger: ["click"],
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
"aria-label": favs ? `已收藏于 ${favs} 个组,点开管理` : "收藏此标签",
title: favs ? `已收藏于 ${favs} 个组` : "收藏此标签",
children: favs ? /* @__PURE__ */ jsx(IconForFaved, { className: "size-14px color-gate-primary" }) : /* @__PURE__ */ jsx(IconForFav, { className: "size-14px" })
})
}),
/* @__PURE__ */ jsx(Dropdown, {
menu: { items: menuItems },
trigger: ["click", "contextMenu"],
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
"aria-label": `${material.name} 的更多操作`,
children: "⋯"
})
})
]
}, tagRecMaterialKey(material));
};
const retryEl = /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
style: { padding: "10px 0" },
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
loading: busy,
onClick: () => {
if (item) load(item);
},
children: "补齐这条视频的标签与简介"
}), error && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "暂未取到,可以稍后重试。"
})]
});
const textTab = (field, text, needsRetry) => /* @__PURE__ */ jsxs("section", {
className: "flex flex-col gap-10px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
children: "从原文选一段"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "点词段直接取词(先看成本预览),或拖选/输入完整词组;加入后按这段文字匹配。"
}),
/* @__PURE__ */ jsx("div", {
style: {
padding: "10px 12px",
borderRadius: 8,
background: token.colorFillQuaternary,
whiteSpace: "pre-wrap",
userSelect: "text",
maxHeight: field === "desc" ? 170 : 130,
overflowY: "auto",
fontSize: 13
},
onMouseUp: () => {
const selection = window.getSelection();
if (selection?.toString().trim()) setPhrase(selection.toString().trim().slice(0, 200));
},
children: text ? tagRecMaterialSegments(text).map((segment, index) => segment.word ? /* @__PURE__ */ jsx("span", {
role: "button",
title: `加入「${segment.text}」为${TAG_REC_CONDITION_FIELDS[field]}条件`,
style: {
cursor: "pointer",
borderBottom: "1px dashed currentColor"
},
onClick: () => {
if (window.getSelection()?.toString().trim()) return;
addMaterial({
atom: tagRecConditionAtom(field, segment.text),
name: segment.text,
source: "视频"
});
},
children: segment.text
}, index) : /* @__PURE__ */ jsx("span", { children: segment.text }, index)) : "暂无可用文本"
}),
/* @__PURE__ */ jsxs("div", {
className: "flex gap-6px",
children: [/* @__PURE__ */ jsx(Input, {
size: "small",
style: { flex: "1 1 170px" },
value: phrase,
maxLength: 200,
onChange: (event) => setPhrase(event.target.value),
placeholder: `拖选${TAG_REC_CONDITION_FIELDS[field]}原文取词,或直接输入`
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !phrase.trim(),
onClick: () => batch ? pick({
atom: tagRecConditionAtom(field, phrase.trim()),
name: phrase.trim(),
source: "视频"
}) : addMaterial({
atom: tagRecConditionAtom(field, phrase.trim()),
name: phrase.trim(),
source: "视频"
}),
children: batch ? "暂选这段" : "选入这段"
})]
}),
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
style: { fontSize: 13 },
children: "短语建议"
}),
/* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-8px",
children: tagRecMaterialWords(text).slice(0, morePhrases ? 24 : 8).map((word) => row({
atom: tagRecConditionAtom(field, word),
name: word,
source: "视频"
}, ["视频", TAG_REC_CONDITION_FIELDS[field]]))
}),
tagRecMaterialWords(text).length > 8 && /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
style: {
alignSelf: "flex-start",
padding: 0
},
onClick: () => setMorePhrases(!morePhrases),
children: morePhrases ? "收起短语" : `展开其余 ${tagRecMaterialWords(text).length - 8} 个短语`
}),
!tagRecMaterialWords(text).length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "没有合适的短语建议,可直接从原文拖选。"
}),
needsRetry && retryEl
]
});
const tagMaterials = [...tags.filter((tag) => !!(tag.tag_id && tag.tag_name)).map((tag) => {
const tag_type = parseTagType(tag.tag_type);
return {
atom: tagRecConditionAtom("tag", {
id: tag.tag_id,
name: tag.tag_name,
...tag_type ? { tag_type } : {}
}),
tagId: tag.tag_id,
name: tag.tag_name,
source: "视频"
};
}), ...(ownTagNames ?? []).filter((name) => !tags.some((tag) => normalizeTagName(tag.tag_name ?? "") === normalizeTagName(name))).map((name) => {
const id = infos[name]?.id;
return {
...id ? { atom: tagRecConditionAtom("tag", {
id,
name
}) } : {},
...id ? { tagId: id } : {},
name,
source: "视频"
};
})];
const tabItems = [
{
key: "tag",
label: `标签${tagMaterials.length ? ` ${tagMaterials.length}` : ""}`,
children: /* @__PURE__ */ jsxs("section", { children: [
[
{
label: "可选标签",
materials: tagMaterials.filter((m) => !positive(m) && !isStaged(m) && !negative(m) && !globalBlocked(m))
},
{
label: "已选条件 · 再点可移出",
materials: tagMaterials.filter((m) => !negative(m) && !globalBlocked(m) && (positive(m) || isStaged(m)))
},
{
label: "已设为不看",
materials: tagMaterials.filter((m) => negative(m) || globalBlocked(m))
}
].filter((group) => group.materials.length).map((group) => /* @__PURE__ */ jsxs("section", {
style: { marginBlock: 16 },
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: {
display: "block",
fontSize: 12,
marginBottom: 8
},
children: group.label
}), /* @__PURE__ */ jsx("div", {
className: details ? "" : "flex flex-wrap gap-8px",
children: group.materials.map((material) => row(material, []))
})]
}, group.label)),
!!tagMaterials.length && /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
style: { padding: 0 },
onClick: () => setDetails(!details),
children: details ? "收起标签详情" : "查看标签编号、使用数与收藏"
}),
item?.ownTagIds === void 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "完整标签待核实,其他字段可以先取。"
}),
(error || supplyPaused || item?.ownTagIds === void 0) && retryEl,
!tagMaterials.length && item?.ownTagIds !== void 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "这条视频没有公开标签。"
})
] })
},
...item?.owner?.name ? [{
key: "up",
label: "UP 主",
children: /* @__PURE__ */ jsx("section", { children: row({
atom: tagRecConditionAtom("up", {
name: item.owner.name,
mid: item.owner.mid
}),
name: item.owner.name,
source: "视频"
}, [
"视频",
"UP 主",
...item.owner.mid ? [`UID ${item.owner.mid}`] : []
]) })
}] : [],
...item?.tidv2 || item?.tid ? [{
key: "partition",
label: "分区",
children: /* @__PURE__ */ jsx("section", { children: row({
atom: tagRecConditionAtom("partition", {
name: item.tnamev2 || `分区 ${item.tidv2 || item.tid}`,
...item.tidv2 ? { v2: item.tidv2 } : { v1: item.tid },
level: "leaf"
}),
name: item.tnamev2 || `分区 ${item.tidv2 || item.tid}`,
source: "视频"
}, ["视频", "子分区"]) })
}] : [],
{
key: "title",
label: "标题",
children: textTab("title", item?.title ?? "", false)
},
{
key: "desc",
label: "简介",
children: textTab("desc", item?.desc ?? "", (error || supplyPaused) && item?.fieldEvidence?.desc !== "complete")
}
];
return /* @__PURE__ */ jsxs(drawer_default, {
title: "选条件",
open,
onClose: () => {
tagRecDiscoveryUi.video = void 0;
},
size: 520,
mask: { closable: true },
styles: {
mask: { background: "rgba(0,0,0,.12)" },
body: { padding: "0 20px 16px" }
},
children: [
/* @__PURE__ */ jsxs("div", {
style: {
position: "sticky",
top: 0,
zIndex: 5,
background: token.colorBgElevated,
paddingTop: 12,
borderBottom: `1px solid ${token.colorBorderSecondary}`
},
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
ellipsis: true,
title: item?.title,
style: {
fontSize: 12,
display: "block"
},
children: item?.title
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "点击条件,立即加入当前组合。点 ⋯ 可仅看此标签、收藏或排除。"
}),
costHint && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: {
fontSize: 12,
color: token.colorPrimary
},
children: costHint
}),
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Checkbox, {
checked: batch,
onChange: (event) => setBatch(event.target.checked),
children: "批量暂选,最后一起加入"
}) }),
/* @__PURE__ */ jsx(Tabs, {
size: "small",
activeKey: tab,
onChange: (key) => {
setTab(key);
setPhrase("");
setMorePhrases(false);
},
items: tabItems.map(({ key, label }) => ({
key,
label
}))
})
]
}),
tabItems.find((entry) => entry.key === tab)?.children,
item && /* @__PURE__ */ jsx(collapse_default, {
ghost: true,
size: "small",
items: [{
key: "reason",
label: "为什么出现这条视频",
children: /* @__PURE__ */ jsxs(Fragment, { children: [[...new Set((item.activeSourceClaims ?? item.source.claims ?? [item.source]).map((claim) => `${TAG_REC_POOL_NAMES[poolOfVia(claim.via)]}${claim.queryKeyword ? ` · ${claim.queryKeyword}` : ""}`))].map((label) => /* @__PURE__ */ jsx("div", { children: label }, label)), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
children: ["相关性优先,同档内容再参考质量。", item.relevance ? `已确认 ${item.relevance.hits} 个相关命中。` : ""]
})] })
}]
}),
(batch || staged.length > 0) && /* @__PURE__ */ jsx("div", {
style: {
position: "sticky",
bottom: 0,
background: token.colorBgElevated,
paddingTop: 10,
marginTop: 12,
borderTop: `1px solid ${token.colorBorderSecondary}`
},
children: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
ellipsis: true,
style: {
flex: 1,
minWidth: 0,
fontSize: 12
},
children: [
"已暂选 ",
staged.length,
" 项",
staged.length ? ` · ${staged.map((entry) => entry.atom ? tagRecConditionLabel(entry.atom) : entry.name).join("、")}` : ""
]
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
disabled: !staged.length,
loading: committing,
onClick: async () => {
setCommitting(true);
try {
await commitTagRecMaterialDraft();
} finally {
setCommitting(false);
}
},
children: "加入当前组合"
})]
})
})
]
});
}
var DiscoveryShelfContext = (0, import_react$5.createContext)(null);
var useTagRecDiscoveryShelf = exports("al",() => (0, import_react$5.useContext)(DiscoveryShelfContext));
var DiscoveryToolbarContext = (0, import_react$5.createContext)(null);
var useTagRecDiscoveryToolbar = exports("am",() => (0, import_react$5.useContext)(DiscoveryToolbarContext));
var DiscoveryHeaderContext = (0, import_react$5.createContext)(null);
/** 当前组合卡槽位: 与工具栏同处一个吸顶栈, 展开时随页面滚动一起吸住。 */
var useTagRecDiscoveryHeader = exports("an",() => (0, import_react$5.useContext)(DiscoveryHeaderContext));
var DiscoveryMetaContext = (0, import_react$5.createContext)(null);
/** 元信息行槽位 (来源池开关/待核实/视频大小): 展开时挂进吸顶栈底, 收起时留在正文流里。 */
var useTagRecDiscoveryMeta = exports("ao",() => (0, import_react$5.useContext)(DiscoveryMetaContext));
/** Both page and modal keep the shelf beside the actual video grid. */
function TagRecDiscoveryLayout({ controls, children, stickyTop = 0 }) {
const { collapsed } = useSnapshot(tagRecDiscoveryPreferences);
const { token } = theme.useToken();
const stackBackground = `var(--bilibili-gate--bg, ${token.colorBgContainer})`;
const [shelf, setShelf] = (0, import_react$5.useState)(null);
const [toolbar, setToolbar] = (0, import_react$5.useState)(null);
const [header, setHeader] = (0, import_react$5.useState)(null);
const [meta, setMeta] = (0, import_react$5.useState)(null);
const shelfWidth = collapsed ? 48 : 216;
const aside = /* @__PURE__ */ jsx("aside", {
ref: setShelf,
"aria-label": "我的组合书架",
css: {
"position": "sticky",
"top": stickyTop,
"display": "flex",
"minWidth": 0,
"maxHeight": `calc(100dvh - ${stickyTop + 16}px)`,
"overflow": "hidden",
"boxSizing": "border-box",
"border": `1px solid ${token.colorBorderSecondary}`,
"borderRadius": 12,
"background": stackBackground,
"@media (max-width: 1100px)": {
position: "relative",
top: 0,
maxHeight: collapsed ? 56 : 380
}
}
});
return /* @__PURE__ */ jsx(DiscoveryShelfContext.Provider, {
value: shelf,
children: /* @__PURE__ */ jsx(DiscoveryToolbarContext.Provider, {
value: toolbar,
children: /* @__PURE__ */ jsx(DiscoveryHeaderContext.Provider, {
value: header,
children: /* @__PURE__ */ jsx(DiscoveryMetaContext.Provider, {
value: meta,
children: /* @__PURE__ */ jsxs("div", {
"data-discovery-layout": true,
"data-collapsed": collapsed,
css: {
"display": "grid",
"gridTemplateColumns": `${shelfWidth}px minmax(0, 1fr)`,
"gap": 16,
"width": "100%",
"alignItems": "start",
"@media (max-width: 1100px)": {
gridTemplateColumns: "minmax(0, 1fr)",
gap: 12
}
},
children: [aside, /* @__PURE__ */ jsxs("main", {
style: { minWidth: 0 },
children: [
/* @__PURE__ */ jsxs("div", {
"data-discovery-sticky-stack": true,
css: {
"position": "sticky",
"top": stickyTop,
"zIndex": 100,
"paddingBottom": 4,
"background": stackBackground,
"&::before": {
content: "\"\"",
position: "absolute",
left: 0,
right: 0,
top: -6,
height: 6,
background: stackBackground
}
},
children: [
/* @__PURE__ */ jsx("div", {
ref: setToolbar,
"data-discovery-toolbar": true
}),
/* @__PURE__ */ jsx("div", {
ref: setHeader,
"data-discovery-header": true
}),
/* @__PURE__ */ jsx("div", {
ref: setMeta,
"data-discovery-meta": true
})
]
}),
controls,
children
]
})]
})
})
})
})
});
}var video_grid_module_exports = /* @__PURE__ */ __exportAll({
default: () => video_grid_module_default,
gridTemplateColumnsUsingCardMinWidth: () => gridTemplateColumnsUsingCardMinWidth,
gridTemplateColumnsUsingVarCol: () => gridTemplateColumnsUsingVarCol,
narrowMode: () => narrowMode,
videoGrid: () => videoGrid,
videoGridAddonCenterEmpty: () => videoGridAddonCenterEmpty,
videoGridBiliFeed4: () => videoGridBiliFeed4,
videoGridCustom: () => videoGridCustom
});
var videoGrid = "_video-grid_qhuuo_1";
var gridTemplateColumnsUsingVarCol = "_grid-template-columns-using-var-col_qhuuo_9";
var gridTemplateColumnsUsingCardMinWidth = "_grid-template-columns-using-card-min-width_qhuuo_14";
var videoGridAddonCenterEmpty = "_video-grid-addon-center-empty_qhuuo_19";
var videoGridBiliFeed4 = "_video-grid-bili-feed4_qhuuo_26";
var videoGridCustom = "_video-grid-custom_qhuuo_47";
var narrowMode = "_narrow-mode_qhuuo_63";
var video_grid_module_default = {
videoGrid,
gridTemplateColumnsUsingVarCol,
gridTemplateColumnsUsingCardMinWidth,
videoGridAddonCenterEmpty,
videoGridBiliFeed4,
videoGridCustom,
narrowMode
};
var import_react$4 = /* @__PURE__ */ __toESM(require_react());
function appendBits(val, len, bb) {
if (len < 0 || len > 31 || val >>> len != 0) throw new RangeError("Value out of range");
for (let i = len - 1; i >= 0; i--) bb.push(val >>> i & 1);
}
function getBit(x, i) {
return (x >>> i & 1) != 0;
}
function assert(cond) {
if (!cond) throw new Error("Assertion error");
}
var Mode = class Mode {
static #_ = (() => {
_defineProperty(this, "NUMERIC", new Mode(1, [
10,
12,
14
]));
_defineProperty(this, "ALPHANUMERIC", new Mode(2, [
9,
11,
13
]));
_defineProperty(this, "BYTE", new Mode(4, [
8,
16,
16
]));
_defineProperty(this, "KANJI", new Mode(8, [
8,
10,
12
]));
_defineProperty(this, "ECI", new Mode(7, [
0,
0,
0
]));
})();
modeBits;
numBitsCharCount;
constructor(modeBits, numBitsCharCount) {
this.modeBits = modeBits;
this.numBitsCharCount = numBitsCharCount;
}
numCharCountBits(ver) {
return this.numBitsCharCount[Math.floor((ver + 7) / 17)];
}
};
var Ecc = class Ecc {
static #_ = (() => {
_defineProperty(this, "LOW", new Ecc(0, 1));
_defineProperty(this, "MEDIUM", new Ecc(1, 0));
_defineProperty(this, "QUARTILE", new Ecc(2, 3));
_defineProperty(this, "HIGH", new Ecc(3, 2));
})();
ordinal;
formatBits;
constructor(ordinal, formatBits) {
this.ordinal = ordinal;
this.formatBits = formatBits;
}
};
var QrSegment = class QrSegment {
static makeBytes(data) {
const bb = [];
for (const b of data) appendBits(b, 8, bb);
return new QrSegment(Mode.BYTE, data.length, bb);
}
static makeNumeric(digits) {
if (!QrSegment.isNumeric(digits)) throw new RangeError("String contains non-numeric characters");
const bb = [];
for (let i = 0; i < digits.length;) {
const n = Math.min(digits.length - i, 3);
appendBits(parseInt(digits.substring(i, i + n), 10), n * 3 + 1, bb);
i += n;
}
return new QrSegment(Mode.NUMERIC, digits.length, bb);
}
static makeAlphanumeric(text) {
if (!QrSegment.isAlphanumeric(text)) throw new RangeError("String contains unencodable characters in alphanumeric mode");
const bb = [];
let i = 0;
for (; i + 2 <= text.length; i += 2) {
let temp = QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)) * 45;
temp += QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i + 1));
appendBits(temp, 11, bb);
}
if (i < text.length) appendBits(QrSegment.ALPHANUMERIC_CHARSET.indexOf(text.charAt(i)), 6, bb);
return new QrSegment(Mode.ALPHANUMERIC, text.length, bb);
}
static makeSegments(text) {
if (text == "") return [];
else if (QrSegment.isNumeric(text)) return [QrSegment.makeNumeric(text)];
else if (QrSegment.isAlphanumeric(text)) return [QrSegment.makeAlphanumeric(text)];
else return [QrSegment.makeBytes(QrSegment.toUtf8ByteArray(text))];
}
static makeEci(assignVal) {
const bb = [];
if (assignVal < 0) throw new RangeError("ECI assignment value out of range");
else if (assignVal < 128) appendBits(assignVal, 8, bb);
else if (assignVal < 16384) {
appendBits(2, 2, bb);
appendBits(assignVal, 14, bb);
} else if (assignVal < 1e6) {
appendBits(6, 3, bb);
appendBits(assignVal, 21, bb);
} else throw new RangeError("ECI assignment value out of range");
return new QrSegment(Mode.ECI, 0, bb);
}
static isNumeric(text) {
return QrSegment.NUMERIC_REGEX.test(text);
}
static isAlphanumeric(text) {
return QrSegment.ALPHANUMERIC_REGEX.test(text);
}
mode;
numChars;
bitData;
constructor(mode, numChars, bitData) {
this.mode = mode;
this.numChars = numChars;
this.bitData = bitData;
if (numChars < 0) throw new RangeError("Invalid argument");
this.bitData = bitData.slice();
}
getData() {
return this.bitData.slice();
}
static getTotalBits(segs, version) {
let result = 0;
for (const seg of segs) {
const ccbits = seg.mode.numCharCountBits(version);
if (seg.numChars >= 1 << ccbits) return Infinity;
result += 4 + ccbits + seg.bitData.length;
}
return result;
}
static toUtf8ByteArray(input) {
const str = encodeURI(input);
const result = [];
for (let i = 0; i < str.length; i++) if (str.charAt(i) != "%") result.push(str.charCodeAt(i));
else {
result.push(parseInt(str.substring(i + 1, i + 3), 16));
i += 2;
}
return result;
}
static NUMERIC_REGEX = /^[0-9]*$/;
static ALPHANUMERIC_REGEX = /^[A-Z0-9 $%*+.\/:-]*$/;
static ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
};
var QrCode = class QrCode {
static encodeText(text, ecl) {
const segs = QrSegment.makeSegments(text);
return QrCode.encodeSegments(segs, ecl);
}
static encodeBinary(data, ecl) {
const seg = QrSegment.makeBytes(data);
return QrCode.encodeSegments([seg], ecl);
}
static encodeSegments(segs, oriEcl, minVersion = 1, maxVersion = 40, mask = -1, boostEcl = true) {
if (!(QrCode.MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= QrCode.MAX_VERSION) || mask < -1 || mask > 7) throw new RangeError("Invalid value");
let version;
let dataUsedBits;
for (version = minVersion;; version++) {
const dataCapacityBits = QrCode.getNumDataCodewords(version, oriEcl) * 8;
const usedBits = QrSegment.getTotalBits(segs, version);
if (usedBits <= dataCapacityBits) {
dataUsedBits = usedBits;
break;
}
if (version >= maxVersion) throw new RangeError("Data too long");
}
let ecl = oriEcl;
for (const newEcl of [
Ecc.MEDIUM,
Ecc.QUARTILE,
Ecc.HIGH
]) if (boostEcl && dataUsedBits <= QrCode.getNumDataCodewords(version, newEcl) * 8) ecl = newEcl;
const bb = [];
for (const seg of segs) {
appendBits(seg.mode.modeBits, 4, bb);
appendBits(seg.numChars, seg.mode.numCharCountBits(version), bb);
for (const b of seg.getData()) bb.push(b);
}
assert(bb.length == dataUsedBits);
const dataCapacityBits = QrCode.getNumDataCodewords(version, ecl) * 8;
assert(bb.length <= dataCapacityBits);
appendBits(0, Math.min(4, dataCapacityBits - bb.length), bb);
appendBits(0, (8 - bb.length % 8) % 8, bb);
assert(bb.length % 8 == 0);
for (let padByte = 236; bb.length < dataCapacityBits; padByte ^= 253) appendBits(padByte, 8, bb);
const dataCodewords = [];
while (dataCodewords.length * 8 < bb.length) dataCodewords.push(0);
bb.forEach((b, i) => {
dataCodewords[i >>> 3] |= b << 7 - (i & 7);
});
return new QrCode(version, ecl, dataCodewords, mask);
}
size;
mask;
modules = [];
isFunction = [];
version;
errorCorrectionLevel;
constructor(version, errorCorrectionLevel, dataCodewords, oriMsk) {
let msk = oriMsk;
this.version = version;
this.errorCorrectionLevel = errorCorrectionLevel;
if (version < QrCode.MIN_VERSION || version > QrCode.MAX_VERSION) throw new RangeError("Version value out of range");
if (msk < -1 || msk > 7) throw new RangeError("Mask value out of range");
this.size = version * 4 + 17;
const row = [];
for (let i = 0; i < this.size; i++) row.push(false);
for (let i = 0; i < this.size; i++) {
this.modules.push(row.slice());
this.isFunction.push(row.slice());
}
this.drawFunctionPatterns();
const allCodewords = this.addEccAndInterleave(dataCodewords);
this.drawCodewords(allCodewords);
if (msk == -1) {
let minPenalty = 1e9;
for (let i = 0; i < 8; i++) {
this.applyMask(i);
this.drawFormatBits(i);
const penalty = this.getPenaltyScore();
if (penalty < minPenalty) {
msk = i;
minPenalty = penalty;
}
this.applyMask(i);
}
}
assert(0 <= msk && msk <= 7);
this.mask = msk;
this.applyMask(msk);
this.drawFormatBits(msk);
this.isFunction = [];
}
getModule(x, y) {
return 0 <= x && x < this.size && 0 <= y && y < this.size && this.modules[y][x];
}
getModules() {
return this.modules;
}
drawFunctionPatterns() {
for (let i = 0; i < this.size; i++) {
this.setFunctionModule(6, i, i % 2 == 0);
this.setFunctionModule(i, 6, i % 2 == 0);
}
this.drawFinderPattern(3, 3);
this.drawFinderPattern(this.size - 4, 3);
this.drawFinderPattern(3, this.size - 4);
const alignPatPos = this.getAlignmentPatternPositions();
const numAlign = alignPatPos.length;
for (let i = 0; i < numAlign; i++) for (let j = 0; j < numAlign; j++) if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0)) this.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
this.drawFormatBits(0);
this.drawVersion();
}
drawFormatBits(mask) {
const data = this.errorCorrectionLevel.formatBits << 3 | mask;
let rem = data;
for (let i = 0; i < 10; i++) rem = rem << 1 ^ (rem >>> 9) * 1335;
const bits = (data << 10 | rem) ^ 21522;
assert(bits >>> 15 == 0);
for (let i = 0; i <= 5; i++) this.setFunctionModule(8, i, getBit(bits, i));
this.setFunctionModule(8, 7, getBit(bits, 6));
this.setFunctionModule(8, 8, getBit(bits, 7));
this.setFunctionModule(7, 8, getBit(bits, 8));
for (let i = 9; i < 15; i++) this.setFunctionModule(14 - i, 8, getBit(bits, i));
for (let i = 0; i < 8; i++) this.setFunctionModule(this.size - 1 - i, 8, getBit(bits, i));
for (let i = 8; i < 15; i++) this.setFunctionModule(8, this.size - 15 + i, getBit(bits, i));
this.setFunctionModule(8, this.size - 8, true);
}
drawVersion() {
if (this.version < 7) return;
let rem = this.version;
for (let i = 0; i < 12; i++) rem = rem << 1 ^ (rem >>> 11) * 7973;
const bits = this.version << 12 | rem;
assert(bits >>> 18 == 0);
for (let i = 0; i < 18; i++) {
const color = getBit(bits, i);
const a = this.size - 11 + i % 3;
const b = Math.floor(i / 3);
this.setFunctionModule(a, b, color);
this.setFunctionModule(b, a, color);
}
}
drawFinderPattern(x, y) {
for (let dy = -4; dy <= 4; dy++) for (let dx = -4; dx <= 4; dx++) {
const dist = Math.max(Math.abs(dx), Math.abs(dy));
const xx = x + dx;
const yy = y + dy;
if (0 <= xx && xx < this.size && 0 <= yy && yy < this.size) this.setFunctionModule(xx, yy, dist != 2 && dist != 4);
}
}
drawAlignmentPattern(x, y) {
for (let dy = -2; dy <= 2; dy++) for (let dx = -2; dx <= 2; dx++) this.setFunctionModule(x + dx, y + dy, Math.max(Math.abs(dx), Math.abs(dy)) != 1);
}
setFunctionModule(x, y, isDark) {
this.modules[y][x] = isDark;
this.isFunction[y][x] = true;
}
addEccAndInterleave(data) {
const ver = this.version;
const ecl = this.errorCorrectionLevel;
if (data.length != QrCode.getNumDataCodewords(ver, ecl)) throw new RangeError("Invalid argument");
const numBlocks = QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
const blockEccLen = QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver];
const rawCodewords = Math.floor(QrCode.getNumRawDataModules(ver) / 8);
const numShortBlocks = numBlocks - rawCodewords % numBlocks;
const shortBlockLen = Math.floor(rawCodewords / numBlocks);
const blocks = [];
const rsDiv = QrCode.reedSolomonComputeDivisor(blockEccLen);
for (let i = 0, k = 0; i < numBlocks; i++) {
const dat = data.slice(k, k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1));
k += dat.length;
const ecc = QrCode.reedSolomonComputeRemainder(dat, rsDiv);
if (i < numShortBlocks) dat.push(0);
blocks.push(dat.concat(ecc));
}
const result = [];
for (let i = 0; i < blocks[0].length; i++) blocks.forEach((block, j) => {
if (i != shortBlockLen - blockEccLen || j >= numShortBlocks) result.push(block[i]);
});
assert(result.length == rawCodewords);
return result;
}
drawCodewords(data) {
if (data.length != Math.floor(QrCode.getNumRawDataModules(this.version) / 8)) throw new RangeError("Invalid argument");
let i = 0;
for (let right = this.size - 1; right >= 1; right -= 2) {
if (right == 6) right = 5;
for (let vert = 0; vert < this.size; vert++) for (let j = 0; j < 2; j++) {
const x = right - j;
const y = (right + 1 & 2) == 0 ? this.size - 1 - vert : vert;
if (!this.isFunction[y][x] && i < data.length * 8) {
this.modules[y][x] = getBit(data[i >>> 3], 7 - (i & 7));
i++;
}
}
}
assert(i == data.length * 8);
}
applyMask(mask) {
if (mask < 0 || mask > 7) throw new RangeError("Mask value out of range");
for (let y = 0; y < this.size; y++) for (let x = 0; x < this.size; x++) {
let invert;
switch (mask) {
case 0:
invert = (x + y) % 2 == 0;
break;
case 1:
invert = y % 2 == 0;
break;
case 2:
invert = x % 3 == 0;
break;
case 3:
invert = (x + y) % 3 == 0;
break;
case 4:
invert = (Math.floor(x / 3) + Math.floor(y / 2)) % 2 == 0;
break;
case 5:
invert = x * y % 2 + x * y % 3 == 0;
break;
case 6:
invert = (x * y % 2 + x * y % 3) % 2 == 0;
break;
case 7:
invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
break;
default: throw new Error("Unreachable");
}
if (!this.isFunction[y][x] && invert) this.modules[y][x] = !this.modules[y][x];
}
}
getPenaltyScore() {
let result = 0;
for (let y = 0; y < this.size; y++) {
let runColor = false;
let runX = 0;
const runHistory = [
0,
0,
0,
0,
0,
0,
0
];
for (let x = 0; x < this.size; x++) if (this.modules[y][x] == runColor) {
runX++;
if (runX == 5) result += QrCode.PENALTY_N1;
else if (runX > 5) result++;
} else {
this.finderPenaltyAddHistory(runX, runHistory);
if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runX = 1;
}
result += this.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * QrCode.PENALTY_N3;
}
for (let x = 0; x < this.size; x++) {
let runColor = false;
let runY = 0;
const runHistory = [
0,
0,
0,
0,
0,
0,
0
];
for (let y = 0; y < this.size; y++) if (this.modules[y][x] == runColor) {
runY++;
if (runY == 5) result += QrCode.PENALTY_N1;
else if (runY > 5) result++;
} else {
this.finderPenaltyAddHistory(runY, runHistory);
if (!runColor) result += this.finderPenaltyCountPatterns(runHistory) * QrCode.PENALTY_N3;
runColor = this.modules[y][x];
runY = 1;
}
result += this.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * QrCode.PENALTY_N3;
}
for (let y = 0; y < this.size - 1; y++) for (let x = 0; x < this.size - 1; x++) {
const color = this.modules[y][x];
if (color == this.modules[y][x + 1] && color == this.modules[y + 1][x] && color == this.modules[y + 1][x + 1]) result += QrCode.PENALTY_N2;
}
let dark = 0;
for (const row of this.modules) dark = row.reduce((sum, color) => sum + (color ? 1 : 0), dark);
const total = this.size * this.size;
const k = Math.ceil(Math.abs(dark * 20 - total * 10) / total) - 1;
assert(0 <= k && k <= 9);
result += k * QrCode.PENALTY_N4;
assert(0 <= result && result <= 2568888);
return result;
}
getAlignmentPatternPositions() {
if (this.version == 1) return [];
else {
const numAlign = Math.floor(this.version / 7) + 2;
const step = this.version == 32 ? 26 : Math.ceil((this.version * 4 + 4) / (numAlign * 2 - 2)) * 2;
const result = [6];
for (let pos = this.size - 7; result.length < numAlign; pos -= step) result.splice(1, 0, pos);
return result;
}
}
static getNumRawDataModules(ver) {
if (ver < QrCode.MIN_VERSION || ver > QrCode.MAX_VERSION) throw new RangeError("Version number out of range");
let result = (16 * ver + 128) * ver + 64;
if (ver >= 2) {
const numAlign = Math.floor(ver / 7) + 2;
result -= (25 * numAlign - 10) * numAlign - 55;
if (ver >= 7) result -= 36;
}
assert(208 <= result && result <= 29648);
return result;
}
static getNumDataCodewords(ver, ecl) {
return Math.floor(QrCode.getNumRawDataModules(ver) / 8) - QrCode.ECC_CODEWORDS_PER_BLOCK[ecl.ordinal][ver] * QrCode.NUM_ERROR_CORRECTION_BLOCKS[ecl.ordinal][ver];
}
static reedSolomonComputeDivisor(degree) {
if (degree < 1 || degree > 255) throw new RangeError("Degree out of range");
const result = [];
for (let i = 0; i < degree - 1; i++) result.push(0);
result.push(1);
let root = 1;
for (let i = 0; i < degree; i++) {
for (let j = 0; j < result.length; j++) {
result[j] = QrCode.reedSolomonMultiply(result[j], root);
if (j + 1 < result.length) result[j] ^= result[j + 1];
}
root = QrCode.reedSolomonMultiply(root, 2);
}
return result;
}
static reedSolomonComputeRemainder(data, divisor) {
const result = divisor.map(() => 0);
for (const b of data) {
const factor = b ^ result.shift();
result.push(0);
divisor.forEach((coef, i) => {
result[i] ^= QrCode.reedSolomonMultiply(coef, factor);
});
}
return result;
}
static reedSolomonMultiply(x, y) {
if (x >>> 8 != 0 || y >>> 8 != 0) throw new RangeError("Byte out of range");
let z = 0;
for (let i = 7; i >= 0; i--) {
z = z << 1 ^ (z >>> 7) * 285;
z ^= (y >>> i & 1) * x;
}
assert(z >>> 8 == 0);
return z;
}
finderPenaltyCountPatterns(runHistory) {
const n = runHistory[1];
assert(n <= this.size * 3);
const core = n > 0 && runHistory[2] == n && runHistory[3] == n * 3 && runHistory[4] == n && runHistory[5] == n;
return (core && runHistory[0] >= n * 4 && runHistory[6] >= n ? 1 : 0) + (core && runHistory[6] >= n * 4 && runHistory[0] >= n ? 1 : 0);
}
finderPenaltyTerminateAndCount(currentRunColor, oriCurrentRunLength, runHistory) {
let currentRunLength = oriCurrentRunLength;
if (currentRunColor) {
this.finderPenaltyAddHistory(currentRunLength, runHistory);
currentRunLength = 0;
}
currentRunLength += this.size;
this.finderPenaltyAddHistory(currentRunLength, runHistory);
return this.finderPenaltyCountPatterns(runHistory);
}
finderPenaltyAddHistory(oriCurrentRunLength, runHistory) {
let currentRunLength = oriCurrentRunLength;
if (runHistory[0] == 0) currentRunLength += this.size;
runHistory.pop();
runHistory.unshift(currentRunLength);
}
static MIN_VERSION = 1;
static MAX_VERSION = 40;
static PENALTY_N1 = 3;
static PENALTY_N2 = 3;
static PENALTY_N3 = 40;
static PENALTY_N4 = 10;
static ECC_CODEWORDS_PER_BLOCK = [
[
-1,
7,
10,
15,
20,
26,
18,
20,
24,
30,
18,
20,
24,
26,
30,
22,
24,
28,
30,
28,
28,
28,
28,
30,
30,
26,
28,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30
],
[
-1,
10,
16,
26,
18,
24,
16,
18,
22,
22,
26,
30,
22,
22,
24,
24,
28,
28,
26,
26,
26,
26,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28,
28
],
[
-1,
13,
22,
18,
26,
18,
24,
18,
22,
20,
24,
28,
26,
24,
20,
30,
24,
28,
28,
26,
30,
28,
30,
30,
30,
30,
28,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30
],
[
-1,
17,
28,
22,
16,
22,
28,
26,
26,
24,
28,
24,
28,
22,
24,
24,
30,
28,
28,
26,
28,
30,
24,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30,
30
]
];
static NUM_ERROR_CORRECTION_BLOCKS = [
[
-1,
1,
1,
1,
1,
1,
2,
2,
2,
2,
4,
4,
4,
4,
4,
6,
6,
6,
6,
7,
8,
8,
9,
9,
10,
12,
12,
12,
13,
14,
15,
16,
17,
18,
19,
19,
20,
21,
22,
24,
25
],
[
-1,
1,
1,
1,
2,
2,
4,
4,
4,
5,
5,
5,
8,
9,
9,
10,
10,
11,
13,
14,
16,
17,
17,
18,
20,
21,
23,
25,
26,
28,
29,
31,
33,
35,
37,
38,
40,
43,
45,
47,
49
],
[
-1,
1,
1,
2,
2,
4,
4,
6,
6,
8,
8,
8,
10,
12,
16,
12,
17,
16,
18,
21,
20,
23,
23,
25,
27,
29,
34,
34,
35,
38,
40,
43,
45,
48,
51,
53,
56,
59,
62,
65,
68
],
[
-1,
1,
1,
2,
4,
4,
4,
5,
6,
8,
8,
11,
11,
16,
16,
18,
16,
19,
21,
25,
25,
25,
34,
30,
32,
35,
37,
40,
42,
45,
48,
51,
54,
57,
60,
63,
66,
70,
74,
77,
81
]
];
};
var ERROR_LEVEL_MAP = {
L: Ecc.LOW,
M: Ecc.MEDIUM,
Q: Ecc.QUARTILE,
H: Ecc.HIGH
};
var DEFAULT_BACKGROUND_COLOR = "#FFFFFF";
var DEFAULT_FRONT_COLOR = "#000000";
var DEFAULT_IMG_SCALE = .1;
/**
* Generate a path string from modules
* @param modules
* @param margin
* @returns
*/
var generatePath = (modules, margin = 0) => {
const ops = [];
modules.forEach((row, y) => {
let start = null;
row.forEach((cell, x) => {
if (!cell && start !== null) {
ops.push(`M${start + margin} ${y + margin}h${x - start}v1H${start + margin}z`);
start = null;
return;
}
if (x === row.length - 1) {
if (!cell) return;
if (start === null) ops.push(`M${x + margin},${y + margin} h1v1H${x + margin}z`);
else ops.push(`M${start + margin},${y + margin} h${x + 1 - start}v1H${start + margin}z`);
return;
}
if (cell && start === null) start = x;
});
});
return ops.join("");
};
/**
* Excavate modules
* @param modules
* @param excavation
* @returns
*/
var excavateModules = (modules, excavation) => {
return modules.slice().map((row, y) => {
if (y < excavation.y || y >= excavation.y + excavation.h) return row;
return row.map((cell, x) => {
if (x < excavation.x || x >= excavation.x + excavation.w) return cell;
return false;
});
});
};
/**
* Get image settings
* @param cells The modules of the QR code
* @param size The size of the QR code
* @param margin
* @param imageSettings
* @returns
*/
var getImageSettings = (cells, size, margin, imageSettings) => {
if (imageSettings == null) return null;
const numCells = cells.length + margin * 2;
const defaultSize = Math.floor(size * DEFAULT_IMG_SCALE);
const scale = numCells / size;
const w = (imageSettings.width || defaultSize) * scale;
const h = (imageSettings.height || defaultSize) * scale;
const x = imageSettings.x == null ? cells.length / 2 - w / 2 : imageSettings.x * scale;
const y = imageSettings.y == null ? cells.length / 2 - h / 2 : imageSettings.y * scale;
const opacity = imageSettings.opacity == null ? 1 : imageSettings.opacity;
let excavation = null;
if (imageSettings.excavate) {
const floorX = Math.floor(x);
const floorY = Math.floor(y);
excavation = {
x: floorX,
y: floorY,
w: Math.ceil(w + x - floorX),
h: Math.ceil(h + y - floorY)
};
}
const crossOrigin = imageSettings.crossOrigin;
return {
x,
y,
h,
w,
excavation,
opacity,
crossOrigin
};
};
/**
* Get margin size
* @param needMargin Whether need margin
* @param marginSize Custom margin size
* @returns
*/
var getMarginSize = (needMargin, marginSize) => {
if (marginSize != null) return Math.max(Math.floor(marginSize), 0);
return needMargin ? 4 : 0;
};
/**
* Check if Path2D is supported
*/
var isSupportPath2d = (() => {
try {
new Path2D().addPath(new Path2D());
} catch {
return false;
}
return true;
})();
var useQRCode = (opt) => {
const { value, level, minVersion, includeMargin, marginSize, imageSettings, size, boostLevel } = opt;
const memoizedQrcode = import_react$4.useMemo(() => {
const segments = (Array.isArray(value) ? value : [value]).reduce((acc, val) => {
acc.push(...QrSegment.makeSegments(val));
return acc;
}, []);
return QrCode.encodeSegments(segments, ERROR_LEVEL_MAP[level], minVersion, void 0, void 0, boostLevel);
}, [
value,
level,
minVersion,
boostLevel
]);
return import_react$4.useMemo(() => {
const cs = memoizedQrcode.getModules();
const mg = getMarginSize(includeMargin, marginSize);
return {
cells: cs,
margin: mg,
numCells: cs.length + mg * 2,
calculatedImageSettings: getImageSettings(cs, size, mg, imageSettings),
qrcode: memoizedQrcode
};
}, [
memoizedQrcode,
size,
imageSettings,
includeMargin,
marginSize
]);
};
var QRCodeCanvas = /*#__PURE__*/ import_react$4.forwardRef((props, ref) => {
const { value, size = 128, level = "L", bgColor = DEFAULT_BACKGROUND_COLOR, fgColor = DEFAULT_FRONT_COLOR, includeMargin = false, minVersion = 1, marginSize, style, imageSettings, boostLevel, ...otherProps } = props;
const imgSrc = imageSettings?.src;
const _canvas = import_react$4.useRef(null);
const _image = import_react$4.useRef(null);
const setCanvasRef = import_react$4.useCallback((node) => {
_canvas.current = node;
if (typeof ref === "function") ref(node);
else if (ref) ref.current = node;
}, [ref]);
const [, setIsImageLoaded] = import_react$4.useState(false);
const { margin, cells, numCells, calculatedImageSettings } = useQRCode({
value,
level,
minVersion,
includeMargin,
marginSize,
imageSettings,
size,
boostLevel
});
import_react$4.useEffect(() => {
if (_canvas.current) {
const canvas = _canvas.current;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let cellsToDraw = cells;
const image = _image.current;
const haveImageToRender = calculatedImageSettings != null && image !== null && image.complete && image.naturalHeight !== 0 && image.naturalWidth !== 0;
if (haveImageToRender) {
if (calculatedImageSettings.excavation != null) cellsToDraw = excavateModules(cells, calculatedImageSettings.excavation);
}
const pixelRatio = window.devicePixelRatio || 1;
canvas.height = canvas.width = size * pixelRatio;
const scale = size / numCells * pixelRatio;
ctx.scale(scale, scale);
ctx.fillStyle = bgColor;
ctx.fillRect(0, 0, numCells, numCells);
ctx.fillStyle = fgColor;
if (isSupportPath2d) ctx.fill(new Path2D(generatePath(cellsToDraw, margin)));
else cells.forEach((row, rdx) => {
row.forEach((cell, cdx) => {
if (cell) ctx.fillRect(cdx + margin, rdx + margin, 1, 1);
});
});
if (calculatedImageSettings) ctx.globalAlpha = calculatedImageSettings.opacity;
if (haveImageToRender) ctx.drawImage(image, calculatedImageSettings.x + margin, calculatedImageSettings.y + margin, calculatedImageSettings.w, calculatedImageSettings.h);
}
});
import_react$4.useEffect(() => {
setIsImageLoaded(false);
}, [imgSrc]);
const canvasStyle = {
height: size,
width: size,
...style
};
let img = null;
if (imgSrc != null) img = /*#__PURE__*/ import_react$4.createElement("img", {
alt: "QR-Code",
src: imgSrc,
key: imgSrc,
style: { display: "none" },
onLoad: () => {
setIsImageLoaded(true);
},
ref: _image,
crossOrigin: calculatedImageSettings?.crossOrigin
});
return /*#__PURE__*/ import_react$4.createElement(import_react$4.Fragment, null, /*#__PURE__*/ import_react$4.createElement("canvas", _extends$24({
style: canvasStyle,
height: size,
width: size,
ref: setCanvasRef,
role: "img"
}, otherProps)), img);
});
var QRCodeSVG = /*#__PURE__*/ import_react$4.forwardRef((props, ref) => {
const { value, size = 128, level = "L", bgColor = DEFAULT_BACKGROUND_COLOR, fgColor = DEFAULT_FRONT_COLOR, includeMargin = false, minVersion = 1, title, marginSize, imageSettings, boostLevel, ...otherProps } = props;
const { margin, cells, numCells, calculatedImageSettings } = useQRCode({
value,
level,
minVersion,
includeMargin,
marginSize,
imageSettings,
size,
boostLevel
});
let cellsToDraw = cells;
let image = null;
if (imageSettings != null && calculatedImageSettings != null) {
if (calculatedImageSettings.excavation != null) cellsToDraw = excavateModules(cells, calculatedImageSettings.excavation);
image = /*#__PURE__*/ import_react$4.createElement("image", {
href: imageSettings.src,
height: calculatedImageSettings.h,
width: calculatedImageSettings.w,
x: calculatedImageSettings.x + margin,
y: calculatedImageSettings.y + margin,
preserveAspectRatio: "none",
opacity: calculatedImageSettings.opacity,
crossOrigin: calculatedImageSettings.crossOrigin
});
}
const fgPath = generatePath(cellsToDraw, margin);
return /*#__PURE__*/ import_react$4.createElement("svg", _extends$24({
height: size,
width: size,
viewBox: `0 0 ${numCells} ${numCells}`,
ref,
role: "img"
}, otherProps), !!title && /*#__PURE__*/ import_react$4.createElement("title", null, title), /*#__PURE__*/ import_react$4.createElement("path", {
fill: bgColor,
d: `M0,0 h${numCells}v${numCells}H0z`,
shapeRendering: "crispEdges"
}), /*#__PURE__*/ import_react$4.createElement("path", {
fill: fgColor,
d: fgPath,
shapeRendering: "crispEdges"
}), image);
});
var ReloadOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z" }
}]
},
"name": "reload",
"theme": "outlined"
};
function _extends$2() {
_extends$2 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$2.apply(this, arguments);
}
var ReloadOutlined = (props, ref) => /*#__PURE__*/ import_react$4.createElement(Icon, _extends$2({}, props, {
ref,
icon: ReloadOutlined$1
}));
/** */
var RefIcon$2 = /*#__PURE__*/ import_react$4.forwardRef(ReloadOutlined);
var defaultSpin = /*#__PURE__*/ import_react$4.createElement(Spin, null);
function QRcodeStatus({ prefixCls, locale, onRefresh, statusRender, status }) {
const defaultNodes = {
expired: /* @__PURE__ */ import_react$4.createElement(import_react$4.Fragment, null, /*#__PURE__*/ import_react$4.createElement("p", { className: `${prefixCls}-expired` }, locale?.expired), onRefresh && /*#__PURE__*/ import_react$4.createElement(Button, {
type: "link",
icon: /*#__PURE__*/ import_react$4.createElement(RefIcon$2, null),
onClick: onRefresh
}, locale?.refresh)),
loading: defaultSpin,
scanned: /* @__PURE__ */ import_react$4.createElement("p", { className: `${prefixCls}-scanned` }, locale?.scanned)
};
const defaultStatusRender = (info) => defaultNodes[info.status];
return (statusRender ?? defaultStatusRender)({
status,
locale,
onRefresh
});
}
var genQRCodeStyle = (token) => {
const { componentCls, lineWidth, lineType, colorSplit } = token;
return {
[componentCls]: {
...resetComponent(token),
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: token.paddingSM,
backgroundColor: token.colorWhite,
borderRadius: token.borderRadiusLG,
border: `${unit$1(lineWidth)} ${lineType} ${colorSplit}`,
position: "relative",
overflow: "hidden",
[`& > ${componentCls}-cover`]: {
position: "absolute",
insetBlockStart: 0,
insetInlineStart: 0,
zIndex: 10,
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100%",
color: token.colorText,
lineHeight: token.lineHeight,
background: token.QRCodeCoverBackgroundColor,
textAlign: "center",
[`& > ${componentCls}-expired, & > ${componentCls}-scanned`]: { color: token.QRCodeTextColor }
},
"> canvas": {
alignSelf: "stretch",
flex: "auto",
minWidth: 0
},
"&-icon": {
marginBlockEnd: token.marginXS,
fontSize: token.controlHeight
}
},
[`${componentCls}-borderless`]: {
borderColor: "transparent",
padding: 0,
borderRadius: 0
}
};
};
var prepareComponentToken$2 = (token) => ({ QRCodeCoverBackgroundColor: new FastColor(token.colorBgContainer).setA(.96).toRgbString() });
var style_default$2 = genStyleHooks("QRCode", (token) => {
return genQRCodeStyle(merge(token, { QRCodeTextColor: token.colorText }));
}, prepareComponentToken$2);
var QRCode = /*#__PURE__*/ import_react$4.forwardRef((props, ref) => {
const [, token] = useToken();
const { value, type = "canvas", icon = "", size = 160, iconSize, color = token.colorText, errorLevel = "M", status = "active", bordered = true, onRefresh, style, className, rootClassName, prefixCls: customizePrefixCls, bgColor = "transparent", marginSize, statusRender, classNames, styles, boostLevel, ...rest } = props;
const { getPrefixCls, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("qrcode");
const mergedProps = {
...props,
bgColor,
type,
size,
status,
bordered,
errorLevel
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps });
const prefixCls = getPrefixCls("qrcode", customizePrefixCls);
const [hashId, cssVarCls] = style_default$2(prefixCls);
const imageSettings = {
src: icon,
x: void 0,
y: void 0,
height: isNumber$1(iconSize) ? iconSize : iconSize?.height ?? 40,
width: isNumber$1(iconSize) ? iconSize : iconSize?.width ?? 40,
excavate: true,
crossOrigin: "anonymous"
};
const a11yProps = pickAttrs(rest, true);
const restProps = omit(rest, Object.keys(a11yProps));
const qrCodeProps = {
value,
size,
level: errorLevel,
bgColor,
fgColor: color,
style: {
width: style?.width,
height: style?.height
},
imageSettings: icon ? imageSettings : void 0,
marginSize,
boostLevel,
...a11yProps
};
const [locale] = useLocale("QRCode");
const nativeElementRef = import_react$4.useRef(null);
import_react$4.useImperativeHandle(ref, () => ({ nativeElement: nativeElementRef.current }));
if (!value) return null;
const rootClassNames = clsx(prefixCls, className, rootClassName, hashId, cssVarCls, contextClassName, mergedClassNames.root, { [`${prefixCls}-borderless`]: !bordered });
const rootStyle = {
backgroundColor: bgColor,
...mergedStyles.root,
width: style?.width ?? size,
height: style?.height ?? size
};
return /*#__PURE__*/ import_react$4.createElement("div", {
ref: nativeElementRef,
...restProps,
className: rootClassNames,
style: rootStyle
}, status !== "active" && /*#__PURE__*/ import_react$4.createElement("div", {
className: clsx(`${prefixCls}-cover`, mergedClassNames.cover),
style: mergedStyles.cover
}, /*#__PURE__*/ import_react$4.createElement(QRcodeStatus, {
prefixCls,
locale,
status,
onRefresh,
statusRender
})), type === "canvas" ? /*#__PURE__*/ import_react$4.createElement(QRCodeCanvas, { ...qrCodeProps }) : /*#__PURE__*/ import_react$4.createElement(QRCodeSVG, { ...qrCodeProps }));
});
/**
* React hook for registering multiple keyboard hotkeys at once.
*
* Uses the singleton HotkeyManager for efficient event handling.
* Accepts a dynamic array of hotkey definitions, making it safe to use
* with variable-length lists without violating the rules of hooks.
*
* Options are merged in this order:
* HotkeysProvider defaults < commonOptions < per-definition options
*
* Callbacks and options are synced on every render to avoid stale closures.
*
* @param hotkeys - Array of hotkey definitions to register
* @param commonOptions - Shared options applied to all hotkeys (overridden by per-definition options).
* Per-row `enabled: false` still registers that hotkey: `HotkeyManager` suppresses execution only (the row
* stays in the store and appears in TanStack Hotkeys devtools). Toggling `enabled` updates the existing handle
* via `setOptions` (no unregister/re-register churn).
*
* @example
* ```tsx
* function Editor() {
* useHotkeys([
* { hotkey: 'Mod+S', callback: () => save() },
* { hotkey: 'Mod+Z', callback: () => undo() },
* { hotkey: 'Escape', callback: () => close() },
* ])
* }
* ```
*
* @example
* ```tsx
* function MenuShortcuts({ items }) {
* // Dynamic hotkeys from data -- safe because it's a single hook call
* useHotkeys(
* items.map((item) => ({
* hotkey: item.shortcut,
* callback: item.action,
* options: { enabled: item.enabled },
* })),
* { preventDefault: true },
* )
* }
* ```
*/
function useHotkeys(hotkeys, commonOptions = {}) {
const defaultOptions = useDefaultHotkeysOptions().hotkey;
const manager = getHotkeyManager();
const platform = commonOptions.platform ?? defaultOptions?.platform ?? detectPlatform();
const registrationsRef = (0, import_react$4.useRef)(/* @__PURE__ */ new Map());
const hotkeysRef = (0, import_react$4.useRef)(hotkeys);
const hotkeyStringsRef = (0, import_react$4.useRef)([]);
const commonOptionsRef = (0, import_react$4.useRef)(commonOptions);
const defaultOptionsRef = (0, import_react$4.useRef)(defaultOptions);
const managerRef = (0, import_react$4.useRef)(manager);
const hotkeyStrings = hotkeys.map((def) => normalizeRegisterableHotkey(def.hotkey, platform));
hotkeysRef.current = hotkeys;
hotkeyStringsRef.current = hotkeyStrings;
commonOptionsRef.current = commonOptions;
defaultOptionsRef.current = defaultOptions;
managerRef.current = manager;
(0, import_react$4.useEffect)(() => {
const prevRegistrations = registrationsRef.current;
const nextRegistrations = /* @__PURE__ */ new Map();
const rows = [];
for (let i = 0; i < hotkeysRef.current.length; i++) {
const def = hotkeysRef.current[i];
const hotkeyStr = hotkeyStringsRef.current[i];
const mergedOptions = {
...defaultOptionsRef.current,
...commonOptionsRef.current,
...def.options
};
const resolvedTarget = isRef(mergedOptions.target) ? mergedOptions.target.current : mergedOptions.target ?? (typeof document !== "undefined" ? document : null);
if (!resolvedTarget) continue;
const registrationKey = `${i}:${hotkeyStr}`;
rows.push({
registrationKey,
def,
hotkeyStr,
mergedOptions,
resolvedTarget
});
}
const nextKeys = new Set(rows.map((r) => r.registrationKey));
for (const [key, record] of prevRegistrations) if (!nextKeys.has(key) && record.handle.isActive) record.handle.unregister();
for (const row of rows) {
const { registrationKey, def, hotkeyStr, mergedOptions, resolvedTarget } = row;
const existing = prevRegistrations.get(registrationKey);
if (existing?.handle.isActive && existing.target === resolvedTarget) {
nextRegistrations.set(registrationKey, existing);
continue;
}
if (existing?.handle.isActive) existing.handle.unregister();
const handle = managerRef.current.register(hotkeyStr, def.callback, {
...mergedOptions,
target: resolvedTarget
});
nextRegistrations.set(registrationKey, {
handle,
target: resolvedTarget
});
}
registrationsRef.current = nextRegistrations;
});
(0, import_react$4.useEffect)(() => {
return () => {
for (const { handle } of registrationsRef.current.values()) if (handle.isActive) handle.unregister();
registrationsRef.current = /* @__PURE__ */ new Map();
};
}, []);
for (let i = 0; i < hotkeys.length; i++) {
const def = hotkeys[i];
const hotkeyStr = hotkeyStrings[i];
const registrationKey = `${i}:${hotkeyStr}`;
const handle = registrationsRef.current.get(registrationKey)?.handle;
if (handle?.isActive) {
handle.callback = def.callback;
const { target: _target, ...optionsWithoutTarget } = {
...defaultOptions,
...commonOptions,
...def.options
};
handle.setOptions(optionsWithoutTarget);
}
}
}
var import_react_dom$1 = /* @__PURE__ */ __toESM(require_react_dom());
function useCombinedRefs() {
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) refs[_key] = arguments[_key];
return (0, import_react$4.useMemo)(() => (node) => {
refs.forEach((ref) => ref(node));
}, refs);
}
var canUseDOM = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
function isWindow(element) {
const elementString = Object.prototype.toString.call(element);
return elementString === "[object Window]" || elementString === "[object global]";
}
function isNode(node) {
return "nodeType" in node;
}
function getWindow(target) {
var _target$ownerDocument, _target$ownerDocument2;
if (!target) return window;
if (isWindow(target)) return target;
if (!isNode(target)) return window;
return (_target$ownerDocument = (_target$ownerDocument2 = target.ownerDocument) == null ? void 0 : _target$ownerDocument2.defaultView) != null ? _target$ownerDocument : window;
}
function isDocument(node) {
const { Document } = getWindow(node);
return node instanceof Document;
}
function isHTMLElement(node) {
if (isWindow(node)) return false;
return node instanceof getWindow(node).HTMLElement;
}
function isSVGElement(node) {
return node instanceof getWindow(node).SVGElement;
}
function getOwnerDocument(target) {
if (!target) return document;
if (isWindow(target)) return target.document;
if (!isNode(target)) return document;
if (isDocument(target)) return target;
if (isHTMLElement(target) || isSVGElement(target)) return target.ownerDocument;
return document;
}
/**
* A hook that resolves to useEffect on the server and useLayoutEffect on the client
* @param callback {function} Callback function that is invoked when the dependencies of the hook change
*/
var useIsomorphicLayoutEffect$1 = canUseDOM ? import_react$4.useLayoutEffect : import_react$4.useEffect;
function useEvent(handler) {
const handlerRef = (0, import_react$4.useRef)(handler);
useIsomorphicLayoutEffect$1(() => {
handlerRef.current = handler;
});
return (0, import_react$4.useCallback)(function() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) args[_key] = arguments[_key];
return handlerRef.current == null ? void 0 : handlerRef.current(...args);
}, []);
}
function useInterval() {
const intervalRef = (0, import_react$4.useRef)(null);
return [(0, import_react$4.useCallback)((listener, duration) => {
intervalRef.current = setInterval(listener, duration);
}, []), (0, import_react$4.useCallback)(() => {
if (intervalRef.current !== null) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, [])];
}
function useLatestValue(value, dependencies) {
if (dependencies === void 0) dependencies = [value];
const valueRef = (0, import_react$4.useRef)(value);
useIsomorphicLayoutEffect$1(() => {
if (valueRef.current !== value) valueRef.current = value;
}, dependencies);
return valueRef;
}
function useLazyMemo(callback, dependencies) {
const valueRef = (0, import_react$4.useRef)();
return (0, import_react$4.useMemo)(() => {
const newValue = callback(valueRef.current);
valueRef.current = newValue;
return newValue;
}, [...dependencies]);
}
function useNodeRef(onChange) {
const onChangeHandler = useEvent(onChange);
const node = (0, import_react$4.useRef)(null);
return [node, (0, import_react$4.useCallback)((element) => {
if (element !== node.current) onChangeHandler?.(element, node.current);
node.current = element;
}, [])];
}
function usePrevious(value) {
const ref = (0, import_react$4.useRef)();
(0, import_react$4.useEffect)(() => {
ref.current = value;
}, [value]);
return ref.current;
}
var ids = {};
function useUniqueId(prefix, value) {
return (0, import_react$4.useMemo)(() => {
if (value) return value;
const id = ids[prefix] == null ? 0 : ids[prefix] + 1;
ids[prefix] = id;
return prefix + "-" + id;
}, [prefix, value]);
}
function createAdjustmentFn(modifier) {
return function(object) {
for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) adjustments[_key - 1] = arguments[_key];
return adjustments.reduce((accumulator, adjustment) => {
const entries = Object.entries(adjustment);
for (const [key, valueAdjustment] of entries) {
const value = accumulator[key];
if (value != null) accumulator[key] = value + modifier * valueAdjustment;
}
return accumulator;
}, { ...object });
};
}
var add = /*#__PURE__*/ createAdjustmentFn(1);
var subtract = /*#__PURE__*/ createAdjustmentFn(-1);
function hasViewportRelativeCoordinates(event) {
return "clientX" in event && "clientY" in event;
}
function isKeyboardEvent(event) {
if (!event) return false;
const { KeyboardEvent } = getWindow(event.target);
return KeyboardEvent && event instanceof KeyboardEvent;
}
function isTouchEvent(event) {
if (!event) return false;
const { TouchEvent } = getWindow(event.target);
return TouchEvent && event instanceof TouchEvent;
}
/**
* Returns the normalized x and y coordinates for mouse and touch events.
*/
function getEventCoordinates(event) {
if (isTouchEvent(event)) {
if (event.touches && event.touches.length) {
const { clientX: x, clientY: y } = event.touches[0];
return {
x,
y
};
} else if (event.changedTouches && event.changedTouches.length) {
const { clientX: x, clientY: y } = event.changedTouches[0];
return {
x,
y
};
}
}
if (hasViewportRelativeCoordinates(event)) return {
x: event.clientX,
y: event.clientY
};
return null;
}
var CSS = exports("H",/*#__PURE__*/ Object.freeze({
Translate: { toString(transform) {
if (!transform) return;
const { x, y } = transform;
return "translate3d(" + (x ? Math.round(x) : 0) + "px, " + (y ? Math.round(y) : 0) + "px, 0)";
} },
Scale: { toString(transform) {
if (!transform) return;
const { scaleX, scaleY } = transform;
return "scaleX(" + scaleX + ") scaleY(" + scaleY + ")";
} },
Transform: { toString(transform) {
if (!transform) return;
return [CSS.Translate.toString(transform), CSS.Scale.toString(transform)].join(" ");
} },
Transition: { toString(_ref) {
let { property, duration, easing } = _ref;
return property + " " + duration + "ms " + easing;
} }
}));
var SELECTOR = "a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";
function findFirstFocusableNode(element) {
if (element.matches(SELECTOR)) return element;
return element.querySelector(SELECTOR);
}
var hiddenStyles = { display: "none" };
function HiddenText(_ref) {
let { id, value } = _ref;
return import_react$4.createElement("div", {
id,
style: hiddenStyles
}, value);
}
function LiveRegion(_ref) {
let { id, announcement, ariaLiveType = "assertive" } = _ref;
return import_react$4.createElement("div", {
id,
style: {
position: "fixed",
top: 0,
left: 0,
width: 1,
height: 1,
margin: -1,
border: 0,
padding: 0,
overflow: "hidden",
clip: "rect(0 0 0 0)",
clipPath: "inset(100%)",
whiteSpace: "nowrap"
},
role: "status",
"aria-live": ariaLiveType,
"aria-atomic": true
}, announcement);
}
function useAnnouncement() {
const [announcement, setAnnouncement] = (0, import_react$4.useState)("");
return {
announce: (0, import_react$4.useCallback)((value) => {
if (value != null) setAnnouncement(value);
}, []),
announcement
};
}
var DndMonitorContext = /*#__PURE__*/ (0, import_react$4.createContext)(null);
function useDndMonitor(listener) {
const registerListener = (0, import_react$4.useContext)(DndMonitorContext);
(0, import_react$4.useEffect)(() => {
if (!registerListener) throw new Error("useDndMonitor must be used within a children of ");
return registerListener(listener);
}, [listener, registerListener]);
}
function useDndMonitorProvider() {
const [listeners] = (0, import_react$4.useState)(() => /* @__PURE__ */ new Set());
const registerListener = (0, import_react$4.useCallback)((listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
}, [listeners]);
return [(0, import_react$4.useCallback)((_ref) => {
let { type, event } = _ref;
listeners.forEach((listener) => {
var _listener$type;
return (_listener$type = listener[type]) == null ? void 0 : _listener$type.call(listener, event);
});
}, [listeners]), registerListener];
}
var defaultScreenReaderInstructions = { draggable: "\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n " };
var defaultAnnouncements = {
onDragStart(_ref) {
let { active } = _ref;
return "Picked up draggable item " + active.id + ".";
},
onDragOver(_ref2) {
let { active, over } = _ref2;
if (over) return "Draggable item " + active.id + " was moved over droppable area " + over.id + ".";
return "Draggable item " + active.id + " is no longer over a droppable area.";
},
onDragEnd(_ref3) {
let { active, over } = _ref3;
if (over) return "Draggable item " + active.id + " was dropped over droppable area " + over.id;
return "Draggable item " + active.id + " was dropped.";
},
onDragCancel(_ref4) {
let { active } = _ref4;
return "Dragging was cancelled. Draggable item " + active.id + " was dropped.";
}
};
function Accessibility(_ref) {
let { announcements = defaultAnnouncements, container, hiddenTextDescribedById, screenReaderInstructions = defaultScreenReaderInstructions } = _ref;
const { announce, announcement } = useAnnouncement();
const liveRegionId = useUniqueId("DndLiveRegion");
const [mounted, setMounted] = (0, import_react$4.useState)(false);
(0, import_react$4.useEffect)(() => {
setMounted(true);
}, []);
useDndMonitor((0, import_react$4.useMemo)(() => ({
onDragStart(_ref2) {
let { active } = _ref2;
announce(announcements.onDragStart({ active }));
},
onDragMove(_ref3) {
let { active, over } = _ref3;
if (announcements.onDragMove) announce(announcements.onDragMove({
active,
over
}));
},
onDragOver(_ref4) {
let { active, over } = _ref4;
announce(announcements.onDragOver({
active,
over
}));
},
onDragEnd(_ref5) {
let { active, over } = _ref5;
announce(announcements.onDragEnd({
active,
over
}));
},
onDragCancel(_ref6) {
let { active, over } = _ref6;
announce(announcements.onDragCancel({
active,
over
}));
}
}), [announce, announcements]));
if (!mounted) return null;
const markup = import_react$4.createElement(import_react$4.Fragment, null, import_react$4.createElement(HiddenText, {
id: hiddenTextDescribedById,
value: screenReaderInstructions.draggable
}), import_react$4.createElement(LiveRegion, {
id: liveRegionId,
announcement
}));
return container ? (0, import_react_dom$1.createPortal)(markup, container) : markup;
}
var Action;
(function(Action) {
Action["DragStart"] = "dragStart";
Action["DragMove"] = "dragMove";
Action["DragEnd"] = "dragEnd";
Action["DragCancel"] = "dragCancel";
Action["DragOver"] = "dragOver";
Action["RegisterDroppable"] = "registerDroppable";
Action["SetDroppableDisabled"] = "setDroppableDisabled";
Action["UnregisterDroppable"] = "unregisterDroppable";
})(Action || (Action = {}));
function noop() {}
function useSensor(sensor, options) {
return (0, import_react$4.useMemo)(() => ({
sensor,
options: {}
}), [sensor, options]);
}
function useSensors() {
for (var _len = arguments.length, sensors = new Array(_len), _key = 0; _key < _len; _key++) sensors[_key] = arguments[_key];
return (0, import_react$4.useMemo)(() => [...sensors].filter((sensor) => sensor != null), [...sensors]);
}
var defaultCoordinates = /*#__PURE__*/ Object.freeze({
x: 0,
y: 0
});
/**
* Returns the distance between two points
*/
function distanceBetween(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}
/**
* Sort collisions from smallest to greatest value
*/
function sortCollisionsAsc(_ref, _ref2) {
let { data: { value: a } } = _ref;
let { data: { value: b } } = _ref2;
return a - b;
}
/**
* Sort collisions from greatest to smallest value
*/
function sortCollisionsDesc(_ref3, _ref4) {
let { data: { value: a } } = _ref3;
let { data: { value: b } } = _ref4;
return b - a;
}
function getFirstCollision(collisions, property) {
if (!collisions || collisions.length === 0) return null;
const [firstCollision] = collisions;
return firstCollision[property] ;
}
/**
* Returns the coordinates of the center of a given ClientRect
*/
function centerOfRectangle(rect, left, top) {
if (left === void 0) left = rect.left;
if (top === void 0) top = rect.top;
return {
x: left + rect.width * .5,
y: top + rect.height * .5
};
}
/**
* Returns the closest rectangles from an array of rectangles to the center of a given
* rectangle.
*/
var closestCenter = exports("k",(_ref) => {
let { collisionRect, droppableRects, droppableContainers } = _ref;
const centerRect = centerOfRectangle(collisionRect, collisionRect.left, collisionRect.top);
const collisions = [];
for (const droppableContainer of droppableContainers) {
const { id } = droppableContainer;
const rect = droppableRects.get(id);
if (rect) {
const distBetween = distanceBetween(centerOfRectangle(rect), centerRect);
collisions.push({
id,
data: {
droppableContainer,
value: distBetween
}
});
}
}
return collisions.sort(sortCollisionsAsc);
});
/**
* Returns the intersecting rectangle area between two rectangles
*/
function getIntersectionRatio(entry, target) {
const top = Math.max(target.top, entry.top);
const left = Math.max(target.left, entry.left);
const right = Math.min(target.left + target.width, entry.left + entry.width);
const bottom = Math.min(target.top + target.height, entry.top + entry.height);
const width = right - left;
const height = bottom - top;
if (left < right && top < bottom) {
const targetArea = target.width * target.height;
const entryArea = entry.width * entry.height;
const intersectionArea = width * height;
const intersectionRatio = intersectionArea / (targetArea + entryArea - intersectionArea);
return Number(intersectionRatio.toFixed(4));
}
return 0;
}
/**
* Returns the rectangles that has the greatest intersection area with a given
* rectangle in an array of rectangles.
*/
var rectIntersection = (_ref) => {
let { collisionRect, droppableRects, droppableContainers } = _ref;
const collisions = [];
for (const droppableContainer of droppableContainers) {
const { id } = droppableContainer;
const rect = droppableRects.get(id);
if (rect) {
const intersectionRatio = getIntersectionRatio(rect, collisionRect);
if (intersectionRatio > 0) collisions.push({
id,
data: {
droppableContainer,
value: intersectionRatio
}
});
}
}
return collisions.sort(sortCollisionsDesc);
};
function adjustScale(transform, rect1, rect2) {
return {
...transform,
scaleX: rect1 && rect2 ? rect1.width / rect2.width : 1,
scaleY: rect1 && rect2 ? rect1.height / rect2.height : 1
};
}
function getRectDelta(rect1, rect2) {
return rect1 && rect2 ? {
x: rect1.left - rect2.left,
y: rect1.top - rect2.top
} : defaultCoordinates;
}
function createRectAdjustmentFn(modifier) {
return function adjustClientRect(rect) {
for (var _len = arguments.length, adjustments = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) adjustments[_key - 1] = arguments[_key];
return adjustments.reduce((acc, adjustment) => ({
...acc,
top: acc.top + modifier * adjustment.y,
bottom: acc.bottom + modifier * adjustment.y,
left: acc.left + modifier * adjustment.x,
right: acc.right + modifier * adjustment.x
}), { ...rect });
};
}
var getAdjustedRect = /*#__PURE__*/ createRectAdjustmentFn(1);
function parseTransform(transform) {
if (transform.startsWith("matrix3d(")) {
const transformArray = transform.slice(9, -1).split(/, /);
return {
x: +transformArray[12],
y: +transformArray[13],
scaleX: +transformArray[0],
scaleY: +transformArray[5]
};
} else if (transform.startsWith("matrix(")) {
const transformArray = transform.slice(7, -1).split(/, /);
return {
x: +transformArray[4],
y: +transformArray[5],
scaleX: +transformArray[0],
scaleY: +transformArray[3]
};
}
return null;
}
function inverseTransform(rect, transform, transformOrigin) {
const parsedTransform = parseTransform(transform);
if (!parsedTransform) return rect;
const { scaleX, scaleY, x: translateX, y: translateY } = parsedTransform;
const x = rect.left - translateX - (1 - scaleX) * parseFloat(transformOrigin);
const y = rect.top - translateY - (1 - scaleY) * parseFloat(transformOrigin.slice(transformOrigin.indexOf(" ") + 1));
const w = scaleX ? rect.width / scaleX : rect.width;
const h = scaleY ? rect.height / scaleY : rect.height;
return {
width: w,
height: h,
top: y,
right: x + w,
bottom: y + h,
left: x
};
}
var defaultOptions = { ignoreTransform: false };
/**
* Returns the bounding client rect of an element relative to the viewport.
*/
function getClientRect(element, options) {
if (options === void 0) options = defaultOptions;
let rect = element.getBoundingClientRect();
if (options.ignoreTransform) {
const { transform, transformOrigin } = getWindow(element).getComputedStyle(element);
if (transform) rect = inverseTransform(rect, transform, transformOrigin);
}
const { top, left, width, height, bottom, right } = rect;
return {
top,
left,
width,
height,
bottom,
right
};
}
/**
* Returns the bounding client rect of an element relative to the viewport.
*
* @remarks
* The ClientRect returned by this method does not take into account transforms
* applied to the element it measures.
*
*/
function getTransformAgnosticClientRect(element) {
return getClientRect(element, { ignoreTransform: true });
}
function getWindowClientRect(element) {
const width = element.innerWidth;
const height = element.innerHeight;
return {
top: 0,
left: 0,
right: width,
bottom: height,
width,
height
};
}
function isFixed(node, computedStyle) {
if (computedStyle === void 0) computedStyle = getWindow(node).getComputedStyle(node);
return computedStyle.position === "fixed";
}
function isScrollable(element, computedStyle) {
if (computedStyle === void 0) computedStyle = getWindow(element).getComputedStyle(element);
const overflowRegex = /(auto|scroll|overlay)/;
return [
"overflow",
"overflowX",
"overflowY"
].some((property) => {
const value = computedStyle[property];
return typeof value === "string" ? overflowRegex.test(value) : false;
});
}
function getScrollableAncestors(element, limit) {
const scrollParents = [];
function findScrollableAncestors(node) {
if (limit != null && scrollParents.length >= limit) return scrollParents;
if (!node) return scrollParents;
if (isDocument(node) && node.scrollingElement != null && !scrollParents.includes(node.scrollingElement)) {
scrollParents.push(node.scrollingElement);
return scrollParents;
}
if (!isHTMLElement(node) || isSVGElement(node)) return scrollParents;
if (scrollParents.includes(node)) return scrollParents;
const computedStyle = getWindow(element).getComputedStyle(node);
if (node !== element) {
if (isScrollable(node, computedStyle)) scrollParents.push(node);
}
if (isFixed(node, computedStyle)) return scrollParents;
return findScrollableAncestors(node.parentNode);
}
if (!element) return scrollParents;
return findScrollableAncestors(element);
}
function getFirstScrollableAncestor(node) {
const [firstScrollableAncestor] = getScrollableAncestors(node, 1);
return firstScrollableAncestor != null ? firstScrollableAncestor : null;
}
function getScrollableElement(element) {
if (!canUseDOM || !element) return null;
if (isWindow(element)) return element;
if (!isNode(element)) return null;
if (isDocument(element) || element === getOwnerDocument(element).scrollingElement) return window;
if (isHTMLElement(element)) return element;
return null;
}
function getScrollXCoordinate(element) {
if (isWindow(element)) return element.scrollX;
return element.scrollLeft;
}
function getScrollYCoordinate(element) {
if (isWindow(element)) return element.scrollY;
return element.scrollTop;
}
function getScrollCoordinates(element) {
return {
x: getScrollXCoordinate(element),
y: getScrollYCoordinate(element)
};
}
var Direction;
(function(Direction) {
Direction[Direction["Forward"] = 1] = "Forward";
Direction[Direction["Backward"] = -1] = "Backward";
})(Direction || (Direction = {}));
function isDocumentScrollingElement(element) {
if (!canUseDOM || !element) return false;
return element === document.scrollingElement;
}
function getScrollPosition(scrollingContainer) {
const minScroll = {
x: 0,
y: 0
};
const dimensions = isDocumentScrollingElement(scrollingContainer) ? {
height: window.innerHeight,
width: window.innerWidth
} : {
height: scrollingContainer.clientHeight,
width: scrollingContainer.clientWidth
};
const maxScroll = {
x: scrollingContainer.scrollWidth - dimensions.width,
y: scrollingContainer.scrollHeight - dimensions.height
};
return {
isTop: scrollingContainer.scrollTop <= minScroll.y,
isLeft: scrollingContainer.scrollLeft <= minScroll.x,
isBottom: scrollingContainer.scrollTop >= maxScroll.y,
isRight: scrollingContainer.scrollLeft >= maxScroll.x,
maxScroll,
minScroll
};
}
var defaultThreshold = {
x: .2,
y: .2
};
function getScrollDirectionAndSpeed(scrollContainer, scrollContainerRect, _ref, acceleration, thresholdPercentage) {
let { top, left, right, bottom } = _ref;
if (acceleration === void 0) acceleration = 10;
if (thresholdPercentage === void 0) thresholdPercentage = defaultThreshold;
const { isTop, isBottom, isLeft, isRight } = getScrollPosition(scrollContainer);
const direction = {
x: 0,
y: 0
};
const speed = {
x: 0,
y: 0
};
const threshold = {
height: scrollContainerRect.height * thresholdPercentage.y,
width: scrollContainerRect.width * thresholdPercentage.x
};
if (!isTop && top <= scrollContainerRect.top + threshold.height) {
direction.y = Direction.Backward;
speed.y = acceleration * Math.abs((scrollContainerRect.top + threshold.height - top) / threshold.height);
} else if (!isBottom && bottom >= scrollContainerRect.bottom - threshold.height) {
direction.y = Direction.Forward;
speed.y = acceleration * Math.abs((scrollContainerRect.bottom - threshold.height - bottom) / threshold.height);
}
if (!isRight && right >= scrollContainerRect.right - threshold.width) {
direction.x = Direction.Forward;
speed.x = acceleration * Math.abs((scrollContainerRect.right - threshold.width - right) / threshold.width);
} else if (!isLeft && left <= scrollContainerRect.left + threshold.width) {
direction.x = Direction.Backward;
speed.x = acceleration * Math.abs((scrollContainerRect.left + threshold.width - left) / threshold.width);
}
return {
direction,
speed
};
}
function getScrollElementRect(element) {
if (element === document.scrollingElement) {
const { innerWidth, innerHeight } = window;
return {
top: 0,
left: 0,
right: innerWidth,
bottom: innerHeight,
width: innerWidth,
height: innerHeight
};
}
const { top, left, right, bottom } = element.getBoundingClientRect();
return {
top,
left,
right,
bottom,
width: element.clientWidth,
height: element.clientHeight
};
}
function getScrollOffsets(scrollableAncestors) {
return scrollableAncestors.reduce((acc, node) => {
return add(acc, getScrollCoordinates(node));
}, defaultCoordinates);
}
function getScrollXOffset(scrollableAncestors) {
return scrollableAncestors.reduce((acc, node) => {
return acc + getScrollXCoordinate(node);
}, 0);
}
function getScrollYOffset(scrollableAncestors) {
return scrollableAncestors.reduce((acc, node) => {
return acc + getScrollYCoordinate(node);
}, 0);
}
function scrollIntoViewIfNeeded(element, measure) {
if (measure === void 0) measure = getClientRect;
if (!element) return;
const { top, left, bottom, right } = measure(element);
if (!getFirstScrollableAncestor(element)) return;
if (bottom <= 0 || right <= 0 || top >= window.innerHeight || left >= window.innerWidth) element.scrollIntoView({
block: "center",
inline: "center"
});
}
var properties = [[
"x",
["left", "right"],
getScrollXOffset
], [
"y",
["top", "bottom"],
getScrollYOffset
]];
var Rect = class {
constructor(rect, element) {
this.rect = void 0;
this.width = void 0;
this.height = void 0;
this.top = void 0;
this.bottom = void 0;
this.right = void 0;
this.left = void 0;
const scrollableAncestors = getScrollableAncestors(element);
const scrollOffsets = getScrollOffsets(scrollableAncestors);
this.rect = { ...rect };
this.width = rect.width;
this.height = rect.height;
for (const [axis, keys, getScrollOffset] of properties) for (const key of keys) Object.defineProperty(this, key, {
get: () => {
const currentOffsets = getScrollOffset(scrollableAncestors);
const scrollOffsetsDeltla = scrollOffsets[axis] - currentOffsets;
return this.rect[key] + scrollOffsetsDeltla;
},
enumerable: true
});
Object.defineProperty(this, "rect", { enumerable: false });
}
};
var Listeners = class {
constructor(target) {
this.target = void 0;
this.listeners = [];
this.removeAll = () => {
this.listeners.forEach((listener) => {
var _this$target;
return (_this$target = this.target) == null ? void 0 : _this$target.removeEventListener(...listener);
});
};
this.target = target;
}
add(eventName, handler, options) {
var _this$target2;
(_this$target2 = this.target) == null || _this$target2.addEventListener(eventName, handler, options);
this.listeners.push([
eventName,
handler,
options
]);
}
};
function getEventListenerTarget(target) {
const { EventTarget } = getWindow(target);
return target instanceof EventTarget ? target : getOwnerDocument(target);
}
function hasExceededDistance(delta, measurement) {
const dx = Math.abs(delta.x);
const dy = Math.abs(delta.y);
if (typeof measurement === "number") return Math.sqrt(dx ** 2 + dy ** 2) > measurement;
if ("x" in measurement && "y" in measurement) return dx > measurement.x && dy > measurement.y;
if ("x" in measurement) return dx > measurement.x;
if ("y" in measurement) return dy > measurement.y;
return false;
}
var EventName;
(function(EventName) {
EventName["Click"] = "click";
EventName["DragStart"] = "dragstart";
EventName["Keydown"] = "keydown";
EventName["ContextMenu"] = "contextmenu";
EventName["Resize"] = "resize";
EventName["SelectionChange"] = "selectionchange";
EventName["VisibilityChange"] = "visibilitychange";
})(EventName || (EventName = {}));
function preventDefault(event) {
event.preventDefault();
}
function stopPropagation(event) {
event.stopPropagation();
}
var KeyboardCode;
(function(KeyboardCode) {
KeyboardCode["Space"] = "Space";
KeyboardCode["Down"] = "ArrowDown";
KeyboardCode["Right"] = "ArrowRight";
KeyboardCode["Left"] = "ArrowLeft";
KeyboardCode["Up"] = "ArrowUp";
KeyboardCode["Esc"] = "Escape";
KeyboardCode["Enter"] = "Enter";
KeyboardCode["Tab"] = "Tab";
})(KeyboardCode || (KeyboardCode = {}));
var defaultKeyboardCodes = {
start: [KeyboardCode.Space, KeyboardCode.Enter],
cancel: [KeyboardCode.Esc],
end: [
KeyboardCode.Space,
KeyboardCode.Enter,
KeyboardCode.Tab
]
};
var defaultKeyboardCoordinateGetter = (event, _ref) => {
let { currentCoordinates } = _ref;
switch (event.code) {
case KeyboardCode.Right: return {
...currentCoordinates,
x: currentCoordinates.x + 25
};
case KeyboardCode.Left: return {
...currentCoordinates,
x: currentCoordinates.x - 25
};
case KeyboardCode.Down: return {
...currentCoordinates,
y: currentCoordinates.y + 25
};
case KeyboardCode.Up: return {
...currentCoordinates,
y: currentCoordinates.y - 25
};
}
};
var KeyboardSensor = class {
constructor(props) {
this.props = void 0;
this.autoScrollEnabled = false;
this.referenceCoordinates = void 0;
this.listeners = void 0;
this.windowListeners = void 0;
this.props = props;
const { event: { target } } = props;
this.props = props;
this.listeners = new Listeners(getOwnerDocument(target));
this.windowListeners = new Listeners(getWindow(target));
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.attach();
}
attach() {
this.handleStart();
this.windowListeners.add(EventName.Resize, this.handleCancel);
this.windowListeners.add(EventName.VisibilityChange, this.handleCancel);
setTimeout(() => this.listeners.add(EventName.Keydown, this.handleKeyDown));
}
handleStart() {
const { activeNode, onStart } = this.props;
const node = activeNode.node.current;
if (node) scrollIntoViewIfNeeded(node);
onStart(defaultCoordinates);
}
handleKeyDown(event) {
if (isKeyboardEvent(event)) {
const { active, context, options } = this.props;
const { keyboardCodes = defaultKeyboardCodes, coordinateGetter = defaultKeyboardCoordinateGetter, scrollBehavior = "smooth" } = options;
const { code } = event;
if (keyboardCodes.end.includes(code)) {
this.handleEnd(event);
return;
}
if (keyboardCodes.cancel.includes(code)) {
this.handleCancel(event);
return;
}
const { collisionRect } = context.current;
const currentCoordinates = collisionRect ? {
x: collisionRect.left,
y: collisionRect.top
} : defaultCoordinates;
if (!this.referenceCoordinates) this.referenceCoordinates = currentCoordinates;
const newCoordinates = coordinateGetter(event, {
active,
context: context.current,
currentCoordinates
});
if (newCoordinates) {
const coordinatesDelta = subtract(newCoordinates, currentCoordinates);
const scrollDelta = {
x: 0,
y: 0
};
const { scrollableAncestors } = context.current;
for (const scrollContainer of scrollableAncestors) {
const direction = event.code;
const { isTop, isRight, isLeft, isBottom, maxScroll, minScroll } = getScrollPosition(scrollContainer);
const scrollElementRect = getScrollElementRect(scrollContainer);
const clampedCoordinates = {
x: Math.min(direction === KeyboardCode.Right ? scrollElementRect.right - scrollElementRect.width / 2 : scrollElementRect.right, Math.max(direction === KeyboardCode.Right ? scrollElementRect.left : scrollElementRect.left + scrollElementRect.width / 2, newCoordinates.x)),
y: Math.min(direction === KeyboardCode.Down ? scrollElementRect.bottom - scrollElementRect.height / 2 : scrollElementRect.bottom, Math.max(direction === KeyboardCode.Down ? scrollElementRect.top : scrollElementRect.top + scrollElementRect.height / 2, newCoordinates.y))
};
const canScrollX = direction === KeyboardCode.Right && !isRight || direction === KeyboardCode.Left && !isLeft;
const canScrollY = direction === KeyboardCode.Down && !isBottom || direction === KeyboardCode.Up && !isTop;
if (canScrollX && clampedCoordinates.x !== newCoordinates.x) {
const newScrollCoordinates = scrollContainer.scrollLeft + coordinatesDelta.x;
const canScrollToNewCoordinates = direction === KeyboardCode.Right && newScrollCoordinates <= maxScroll.x || direction === KeyboardCode.Left && newScrollCoordinates >= minScroll.x;
if (canScrollToNewCoordinates && !coordinatesDelta.y) {
scrollContainer.scrollTo({
left: newScrollCoordinates,
behavior: scrollBehavior
});
return;
}
if (canScrollToNewCoordinates) scrollDelta.x = scrollContainer.scrollLeft - newScrollCoordinates;
else scrollDelta.x = direction === KeyboardCode.Right ? scrollContainer.scrollLeft - maxScroll.x : scrollContainer.scrollLeft - minScroll.x;
if (scrollDelta.x) scrollContainer.scrollBy({
left: -scrollDelta.x,
behavior: scrollBehavior
});
break;
} else if (canScrollY && clampedCoordinates.y !== newCoordinates.y) {
const newScrollCoordinates = scrollContainer.scrollTop + coordinatesDelta.y;
const canScrollToNewCoordinates = direction === KeyboardCode.Down && newScrollCoordinates <= maxScroll.y || direction === KeyboardCode.Up && newScrollCoordinates >= minScroll.y;
if (canScrollToNewCoordinates && !coordinatesDelta.x) {
scrollContainer.scrollTo({
top: newScrollCoordinates,
behavior: scrollBehavior
});
return;
}
if (canScrollToNewCoordinates) scrollDelta.y = scrollContainer.scrollTop - newScrollCoordinates;
else scrollDelta.y = direction === KeyboardCode.Down ? scrollContainer.scrollTop - maxScroll.y : scrollContainer.scrollTop - minScroll.y;
if (scrollDelta.y) scrollContainer.scrollBy({
top: -scrollDelta.y,
behavior: scrollBehavior
});
break;
}
}
this.handleMove(event, add(subtract(newCoordinates, this.referenceCoordinates), scrollDelta));
}
}
}
handleMove(event, coordinates) {
const { onMove } = this.props;
event.preventDefault();
onMove(coordinates);
}
handleEnd(event) {
const { onEnd } = this.props;
event.preventDefault();
this.detach();
onEnd();
}
handleCancel(event) {
const { onCancel } = this.props;
event.preventDefault();
this.detach();
onCancel();
}
detach() {
this.listeners.removeAll();
this.windowListeners.removeAll();
}
};
KeyboardSensor.activators = [{
eventName: "onKeyDown",
handler: (event, _ref, _ref2) => {
let { keyboardCodes = defaultKeyboardCodes, onActivation } = _ref;
let { active } = _ref2;
const { code } = event.nativeEvent;
if (keyboardCodes.start.includes(code)) {
const activator = active.activatorNode.current;
if (activator && event.target !== activator) return false;
event.preventDefault();
onActivation?.({ event: event.nativeEvent });
return true;
}
return false;
}
}];
function isDistanceConstraint(constraint) {
return Boolean(constraint && "distance" in constraint);
}
function isDelayConstraint(constraint) {
return Boolean(constraint && "delay" in constraint);
}
var AbstractPointerSensor = class {
constructor(props, events, listenerTarget) {
var _getEventCoordinates;
if (listenerTarget === void 0) listenerTarget = getEventListenerTarget(props.event.target);
this.props = void 0;
this.events = void 0;
this.autoScrollEnabled = true;
this.document = void 0;
this.activated = false;
this.initialCoordinates = void 0;
this.timeoutId = null;
this.listeners = void 0;
this.documentListeners = void 0;
this.windowListeners = void 0;
this.props = props;
this.events = events;
const { event } = props;
const { target } = event;
this.props = props;
this.events = events;
this.document = getOwnerDocument(target);
this.documentListeners = new Listeners(this.document);
this.listeners = new Listeners(listenerTarget);
this.windowListeners = new Listeners(getWindow(target));
this.initialCoordinates = (_getEventCoordinates = getEventCoordinates(event)) != null ? _getEventCoordinates : defaultCoordinates;
this.handleStart = this.handleStart.bind(this);
this.handleMove = this.handleMove.bind(this);
this.handleEnd = this.handleEnd.bind(this);
this.handleCancel = this.handleCancel.bind(this);
this.handleKeydown = this.handleKeydown.bind(this);
this.removeTextSelection = this.removeTextSelection.bind(this);
this.attach();
}
attach() {
const { events, props: { options: { activationConstraint, bypassActivationConstraint } } } = this;
this.listeners.add(events.move.name, this.handleMove, { passive: false });
this.listeners.add(events.end.name, this.handleEnd);
if (events.cancel) this.listeners.add(events.cancel.name, this.handleCancel);
this.windowListeners.add(EventName.Resize, this.handleCancel);
this.windowListeners.add(EventName.DragStart, preventDefault);
this.windowListeners.add(EventName.VisibilityChange, this.handleCancel);
this.windowListeners.add(EventName.ContextMenu, preventDefault);
this.documentListeners.add(EventName.Keydown, this.handleKeydown);
if (activationConstraint) {
if (bypassActivationConstraint != null && bypassActivationConstraint({
event: this.props.event,
activeNode: this.props.activeNode,
options: this.props.options
})) return this.handleStart();
if (isDelayConstraint(activationConstraint)) {
this.timeoutId = setTimeout(this.handleStart, activationConstraint.delay);
this.handlePending(activationConstraint);
return;
}
if (isDistanceConstraint(activationConstraint)) {
this.handlePending(activationConstraint);
return;
}
}
this.handleStart();
}
detach() {
this.listeners.removeAll();
this.windowListeners.removeAll();
setTimeout(this.documentListeners.removeAll, 50);
if (this.timeoutId !== null) {
clearTimeout(this.timeoutId);
this.timeoutId = null;
}
}
handlePending(constraint, offset) {
const { active, onPending } = this.props;
onPending(active, constraint, this.initialCoordinates, offset);
}
handleStart() {
const { initialCoordinates } = this;
const { onStart } = this.props;
if (initialCoordinates) {
this.activated = true;
this.documentListeners.add(EventName.Click, stopPropagation, { capture: true });
this.removeTextSelection();
this.documentListeners.add(EventName.SelectionChange, this.removeTextSelection);
onStart(initialCoordinates);
}
}
handleMove(event) {
var _getEventCoordinates2;
const { activated, initialCoordinates, props } = this;
const { onMove, options: { activationConstraint } } = props;
if (!initialCoordinates) return;
const coordinates = (_getEventCoordinates2 = getEventCoordinates(event)) != null ? _getEventCoordinates2 : defaultCoordinates;
const delta = subtract(initialCoordinates, coordinates);
if (!activated && activationConstraint) {
if (isDistanceConstraint(activationConstraint)) {
if (activationConstraint.tolerance != null && hasExceededDistance(delta, activationConstraint.tolerance)) return this.handleCancel();
if (hasExceededDistance(delta, activationConstraint.distance)) return this.handleStart();
}
if (isDelayConstraint(activationConstraint)) {
if (hasExceededDistance(delta, activationConstraint.tolerance)) return this.handleCancel();
}
this.handlePending(activationConstraint, delta);
return;
}
if (event.cancelable) event.preventDefault();
onMove(coordinates);
}
handleEnd() {
const { onAbort, onEnd } = this.props;
this.detach();
if (!this.activated) onAbort(this.props.active);
onEnd();
}
handleCancel() {
const { onAbort, onCancel } = this.props;
this.detach();
if (!this.activated) onAbort(this.props.active);
onCancel();
}
handleKeydown(event) {
if (event.code === KeyboardCode.Esc) this.handleCancel();
}
removeTextSelection() {
var _this$document$getSel;
(_this$document$getSel = this.document.getSelection()) == null || _this$document$getSel.removeAllRanges();
}
};
var events = {
cancel: { name: "pointercancel" },
move: { name: "pointermove" },
end: { name: "pointerup" }
};
var PointerSensor = exports("P",class extends AbstractPointerSensor {
constructor(props) {
const { event } = props;
const listenerTarget = getOwnerDocument(event.target);
super(props, events, listenerTarget);
}
});
PointerSensor.activators = [{
eventName: "onPointerDown",
handler: (_ref, _ref2) => {
let { nativeEvent: event } = _ref;
let { onActivation } = _ref2;
if (!event.isPrimary || event.button !== 0) return false;
onActivation?.({ event });
return true;
}
}];
var events$1 = {
move: { name: "mousemove" },
end: { name: "mouseup" }
};
var MouseButton;
(function(MouseButton) {
MouseButton[MouseButton["RightClick"] = 2] = "RightClick";
})(MouseButton || (MouseButton = {}));
var MouseSensor = class extends AbstractPointerSensor {
constructor(props) {
super(props, events$1, getOwnerDocument(props.event.target));
}
};
MouseSensor.activators = [{
eventName: "onMouseDown",
handler: (_ref, _ref2) => {
let { nativeEvent: event } = _ref;
let { onActivation } = _ref2;
if (event.button === MouseButton.RightClick) return false;
onActivation?.({ event });
return true;
}
}];
var events$2 = {
cancel: { name: "touchcancel" },
move: { name: "touchmove" },
end: { name: "touchend" }
};
var TouchSensor = class extends AbstractPointerSensor {
constructor(props) {
super(props, events$2);
}
static setup() {
window.addEventListener(events$2.move.name, noop, {
capture: false,
passive: false
});
return function teardown() {
window.removeEventListener(events$2.move.name, noop);
};
function noop() {}
}
};
TouchSensor.activators = [{
eventName: "onTouchStart",
handler: (_ref, _ref2) => {
let { nativeEvent: event } = _ref;
let { onActivation } = _ref2;
const { touches } = event;
if (touches.length > 1) return false;
onActivation?.({ event });
return true;
}
}];
var AutoScrollActivator;
(function(AutoScrollActivator) {
AutoScrollActivator[AutoScrollActivator["Pointer"] = 0] = "Pointer";
AutoScrollActivator[AutoScrollActivator["DraggableRect"] = 1] = "DraggableRect";
})(AutoScrollActivator || (AutoScrollActivator = {}));
var TraversalOrder;
(function(TraversalOrder) {
TraversalOrder[TraversalOrder["TreeOrder"] = 0] = "TreeOrder";
TraversalOrder[TraversalOrder["ReversedTreeOrder"] = 1] = "ReversedTreeOrder";
})(TraversalOrder || (TraversalOrder = {}));
function useAutoScroller(_ref) {
let { acceleration, activator = AutoScrollActivator.Pointer, canScroll, draggingRect, enabled, interval = 5, order = TraversalOrder.TreeOrder, pointerCoordinates, scrollableAncestors, scrollableAncestorRects, delta, threshold } = _ref;
const scrollIntent = useScrollIntent({
delta,
disabled: !enabled
});
const [setAutoScrollInterval, clearAutoScrollInterval] = useInterval();
const scrollSpeed = (0, import_react$4.useRef)({
x: 0,
y: 0
});
const scrollDirection = (0, import_react$4.useRef)({
x: 0,
y: 0
});
const rect = (0, import_react$4.useMemo)(() => {
switch (activator) {
case AutoScrollActivator.Pointer: return pointerCoordinates ? {
top: pointerCoordinates.y,
bottom: pointerCoordinates.y,
left: pointerCoordinates.x,
right: pointerCoordinates.x
} : null;
case AutoScrollActivator.DraggableRect: return draggingRect;
}
}, [
activator,
draggingRect,
pointerCoordinates
]);
const scrollContainerRef = (0, import_react$4.useRef)(null);
const autoScroll = (0, import_react$4.useCallback)(() => {
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;
const scrollLeft = scrollSpeed.current.x * scrollDirection.current.x;
const scrollTop = scrollSpeed.current.y * scrollDirection.current.y;
scrollContainer.scrollBy(scrollLeft, scrollTop);
}, []);
const sortedScrollableAncestors = (0, import_react$4.useMemo)(() => order === TraversalOrder.TreeOrder ? [...scrollableAncestors].reverse() : scrollableAncestors, [order, scrollableAncestors]);
(0, import_react$4.useEffect)(() => {
if (!enabled || !scrollableAncestors.length || !rect) {
clearAutoScrollInterval();
return;
}
for (const scrollContainer of sortedScrollableAncestors) {
if ((canScroll == null ? void 0 : canScroll(scrollContainer)) === false) continue;
const index = scrollableAncestors.indexOf(scrollContainer);
const scrollContainerRect = scrollableAncestorRects[index];
if (!scrollContainerRect) continue;
const { direction, speed } = getScrollDirectionAndSpeed(scrollContainer, scrollContainerRect, rect, acceleration, threshold);
for (const axis of ["x", "y"]) if (!scrollIntent[axis][direction[axis]]) {
speed[axis] = 0;
direction[axis] = 0;
}
if (speed.x > 0 || speed.y > 0) {
clearAutoScrollInterval();
scrollContainerRef.current = scrollContainer;
setAutoScrollInterval(autoScroll, interval);
scrollSpeed.current = speed;
scrollDirection.current = direction;
return;
}
}
scrollSpeed.current = {
x: 0,
y: 0
};
scrollDirection.current = {
x: 0,
y: 0
};
clearAutoScrollInterval();
}, [
acceleration,
autoScroll,
canScroll,
clearAutoScrollInterval,
enabled,
interval,
JSON.stringify(rect),
JSON.stringify(scrollIntent),
setAutoScrollInterval,
scrollableAncestors,
sortedScrollableAncestors,
scrollableAncestorRects,
JSON.stringify(threshold)
]);
}
var defaultScrollIntent = {
x: {
[Direction.Backward]: false,
[Direction.Forward]: false
},
y: {
[Direction.Backward]: false,
[Direction.Forward]: false
}
};
function useScrollIntent(_ref2) {
let { delta, disabled } = _ref2;
const previousDelta = usePrevious(delta);
return useLazyMemo((previousIntent) => {
if (disabled || !previousDelta || !previousIntent) return defaultScrollIntent;
const direction = {
x: Math.sign(delta.x - previousDelta.x),
y: Math.sign(delta.y - previousDelta.y)
};
return {
x: {
[Direction.Backward]: previousIntent.x[Direction.Backward] || direction.x === -1,
[Direction.Forward]: previousIntent.x[Direction.Forward] || direction.x === 1
},
y: {
[Direction.Backward]: previousIntent.y[Direction.Backward] || direction.y === -1,
[Direction.Forward]: previousIntent.y[Direction.Forward] || direction.y === 1
}
};
}, [
disabled,
delta,
previousDelta
]);
}
function useCachedNode(draggableNodes, id) {
const draggableNode = id != null ? draggableNodes.get(id) : void 0;
const node = draggableNode ? draggableNode.node.current : null;
return useLazyMemo((cachedNode) => {
var _ref;
if (id == null) return null;
return (_ref = node != null ? node : cachedNode) != null ? _ref : null;
}, [node, id]);
}
function useCombineActivators(sensors, getSyntheticHandler) {
return (0, import_react$4.useMemo)(() => sensors.reduce((accumulator, sensor) => {
const { sensor: Sensor } = sensor;
const sensorActivators = Sensor.activators.map((activator) => ({
eventName: activator.eventName,
handler: getSyntheticHandler(activator.handler, sensor)
}));
return [...accumulator, ...sensorActivators];
}, []), [sensors, getSyntheticHandler]);
}
var MeasuringStrategy;
(function(MeasuringStrategy) {
MeasuringStrategy[MeasuringStrategy["Always"] = 0] = "Always";
MeasuringStrategy[MeasuringStrategy["BeforeDragging"] = 1] = "BeforeDragging";
MeasuringStrategy[MeasuringStrategy["WhileDragging"] = 2] = "WhileDragging";
})(MeasuringStrategy || (MeasuringStrategy = {}));
var MeasuringFrequency;
(function(MeasuringFrequency) {
MeasuringFrequency["Optimized"] = "optimized";
})(MeasuringFrequency || (MeasuringFrequency = {}));
var defaultValue = /*#__PURE__*/ new Map();
function useDroppableMeasuring(containers, _ref) {
let { dragging, dependencies, config } = _ref;
const [queue, setQueue] = (0, import_react$4.useState)(null);
const { frequency, measure, strategy } = config;
const containersRef = (0, import_react$4.useRef)(containers);
const disabled = isDisabled();
const disabledRef = useLatestValue(disabled);
const measureDroppableContainers = (0, import_react$4.useCallback)(function(ids) {
if (ids === void 0) ids = [];
if (disabledRef.current) return;
setQueue((value) => {
if (value === null) return ids;
return value.concat(ids.filter((id) => !value.includes(id)));
});
}, [disabledRef]);
const timeoutId = (0, import_react$4.useRef)(null);
const droppableRects = useLazyMemo((previousValue) => {
if (disabled && !dragging) return defaultValue;
if (!previousValue || previousValue === defaultValue || containersRef.current !== containers || queue != null) {
const map = /* @__PURE__ */ new Map();
for (let container of containers) {
if (!container) continue;
if (queue && queue.length > 0 && !queue.includes(container.id) && container.rect.current) {
map.set(container.id, container.rect.current);
continue;
}
const node = container.node.current;
const rect = node ? new Rect(measure(node), node) : null;
container.rect.current = rect;
if (rect) map.set(container.id, rect);
}
return map;
}
return previousValue;
}, [
containers,
queue,
dragging,
disabled,
measure
]);
(0, import_react$4.useEffect)(() => {
containersRef.current = containers;
}, [containers]);
(0, import_react$4.useEffect)(() => {
if (disabled) return;
measureDroppableContainers();
}, [dragging, disabled]);
(0, import_react$4.useEffect)(() => {
if (queue && queue.length > 0) setQueue(null);
}, [JSON.stringify(queue)]);
(0, import_react$4.useEffect)(() => {
if (disabled || typeof frequency !== "number" || timeoutId.current !== null) return;
timeoutId.current = setTimeout(() => {
measureDroppableContainers();
timeoutId.current = null;
}, frequency);
}, [
frequency,
disabled,
measureDroppableContainers,
...dependencies
]);
return {
droppableRects,
measureDroppableContainers,
measuringScheduled: queue != null
};
function isDisabled() {
switch (strategy) {
case MeasuringStrategy.Always: return false;
case MeasuringStrategy.BeforeDragging: return dragging;
default: return !dragging;
}
}
}
function useInitialValue(value, computeFn) {
return useLazyMemo((previousValue) => {
if (!value) return null;
if (previousValue) return previousValue;
return typeof computeFn === "function" ? computeFn(value) : value;
}, [computeFn, value]);
}
function useInitialRect(node, measure) {
return useInitialValue(node, measure);
}
/**
* Returns a new MutationObserver instance.
* If `MutationObserver` is undefined in the execution environment, returns `undefined`.
*/
function useMutationObserver(_ref) {
let { callback, disabled } = _ref;
const handleMutations = useEvent(callback);
const mutationObserver = (0, import_react$4.useMemo)(() => {
if (disabled || typeof window === "undefined" || typeof window.MutationObserver === "undefined") return;
const { MutationObserver } = window;
return new MutationObserver(handleMutations);
}, [handleMutations, disabled]);
(0, import_react$4.useEffect)(() => {
return () => mutationObserver == null ? void 0 : mutationObserver.disconnect();
}, [mutationObserver]);
return mutationObserver;
}
/**
* Returns a new ResizeObserver instance bound to the `onResize` callback.
* If `ResizeObserver` is undefined in the execution environment, returns `undefined`.
*/
function useResizeObserver(_ref) {
let { callback, disabled } = _ref;
const handleResize = useEvent(callback);
const resizeObserver = (0, import_react$4.useMemo)(() => {
if (disabled || typeof window === "undefined" || typeof window.ResizeObserver === "undefined") return;
const { ResizeObserver } = window;
return new ResizeObserver(handleResize);
}, [disabled]);
(0, import_react$4.useEffect)(() => {
return () => resizeObserver == null ? void 0 : resizeObserver.disconnect();
}, [resizeObserver]);
return resizeObserver;
}
function defaultMeasure(element) {
return new Rect(getClientRect(element), element);
}
function useRect(element, measure, fallbackRect) {
if (measure === void 0) measure = defaultMeasure;
const [rect, setRect] = (0, import_react$4.useState)(null);
function measureRect() {
setRect((currentRect) => {
if (!element) return null;
if (element.isConnected === false) {
var _ref;
return (_ref = currentRect != null ? currentRect : fallbackRect) != null ? _ref : null;
}
const newRect = measure(element);
if (JSON.stringify(currentRect) === JSON.stringify(newRect)) return currentRect;
return newRect;
});
}
const mutationObserver = useMutationObserver({ callback(records) {
if (!element) return;
for (const record of records) {
const { type, target } = record;
if (type === "childList" && target instanceof HTMLElement && target.contains(element)) {
measureRect();
break;
}
}
} });
const resizeObserver = useResizeObserver({ callback: measureRect });
useIsomorphicLayoutEffect$1(() => {
measureRect();
if (element) {
resizeObserver?.observe(element);
mutationObserver?.observe(document.body, {
childList: true,
subtree: true
});
} else {
resizeObserver?.disconnect();
mutationObserver?.disconnect();
}
}, [element]);
return rect;
}
function useRectDelta(rect) {
return getRectDelta(rect, useInitialValue(rect));
}
var defaultValue$1 = [];
function useScrollableAncestors(node) {
const previousNode = (0, import_react$4.useRef)(node);
const ancestors = useLazyMemo((previousValue) => {
if (!node) return defaultValue$1;
if (previousValue && previousValue !== defaultValue$1 && node && previousNode.current && node.parentNode === previousNode.current.parentNode) return previousValue;
return getScrollableAncestors(node);
}, [node]);
(0, import_react$4.useEffect)(() => {
previousNode.current = node;
}, [node]);
return ancestors;
}
function useScrollOffsets(elements) {
const [scrollCoordinates, setScrollCoordinates] = (0, import_react$4.useState)(null);
const prevElements = (0, import_react$4.useRef)(elements);
const handleScroll = (0, import_react$4.useCallback)((event) => {
const scrollingElement = getScrollableElement(event.target);
if (!scrollingElement) return;
setScrollCoordinates((scrollCoordinates) => {
if (!scrollCoordinates) return null;
scrollCoordinates.set(scrollingElement, getScrollCoordinates(scrollingElement));
return new Map(scrollCoordinates);
});
}, []);
(0, import_react$4.useEffect)(() => {
const previousElements = prevElements.current;
if (elements !== previousElements) {
cleanup(previousElements);
const entries = elements.map((element) => {
const scrollableElement = getScrollableElement(element);
if (scrollableElement) {
scrollableElement.addEventListener("scroll", handleScroll, { passive: true });
return [scrollableElement, getScrollCoordinates(scrollableElement)];
}
return null;
}).filter((entry) => entry != null);
setScrollCoordinates(entries.length ? new Map(entries) : null);
prevElements.current = elements;
}
return () => {
cleanup(elements);
cleanup(previousElements);
};
function cleanup(elements) {
elements.forEach((element) => {
getScrollableElement(element)?.removeEventListener("scroll", handleScroll);
});
}
}, [handleScroll, elements]);
return (0, import_react$4.useMemo)(() => {
if (elements.length) return scrollCoordinates ? Array.from(scrollCoordinates.values()).reduce((acc, coordinates) => add(acc, coordinates), defaultCoordinates) : getScrollOffsets(elements);
return defaultCoordinates;
}, [elements, scrollCoordinates]);
}
function useScrollOffsetsDelta(scrollOffsets, dependencies) {
if (dependencies === void 0) dependencies = [];
const initialScrollOffsets = (0, import_react$4.useRef)(null);
(0, import_react$4.useEffect)(() => {
initialScrollOffsets.current = null;
}, dependencies);
(0, import_react$4.useEffect)(() => {
const hasScrollOffsets = scrollOffsets !== defaultCoordinates;
if (hasScrollOffsets && !initialScrollOffsets.current) initialScrollOffsets.current = scrollOffsets;
if (!hasScrollOffsets && initialScrollOffsets.current) initialScrollOffsets.current = null;
}, [scrollOffsets]);
return initialScrollOffsets.current ? subtract(scrollOffsets, initialScrollOffsets.current) : defaultCoordinates;
}
function useSensorSetup(sensors) {
(0, import_react$4.useEffect)(() => {
if (!canUseDOM) return;
const teardownFns = sensors.map((_ref) => {
let { sensor } = _ref;
return sensor.setup == null ? void 0 : sensor.setup();
});
return () => {
for (const teardown of teardownFns) teardown?.();
};
}, sensors.map((_ref2) => {
let { sensor } = _ref2;
return sensor;
}));
}
function useSyntheticListeners(listeners, id) {
return (0, import_react$4.useMemo)(() => {
return listeners.reduce((acc, _ref) => {
let { eventName, handler } = _ref;
acc[eventName] = (event) => {
handler(event, id);
};
return acc;
}, {});
}, [listeners, id]);
}
function useWindowRect(element) {
return (0, import_react$4.useMemo)(() => element ? getWindowClientRect(element) : null, [element]);
}
var defaultValue$2 = [];
function useRects(elements, measure) {
if (measure === void 0) measure = getClientRect;
const [firstElement] = elements;
const windowRect = useWindowRect(firstElement ? getWindow(firstElement) : null);
const [rects, setRects] = (0, import_react$4.useState)(defaultValue$2);
function measureRects() {
setRects(() => {
if (!elements.length) return defaultValue$2;
return elements.map((element) => isDocumentScrollingElement(element) ? windowRect : new Rect(measure(element), element));
});
}
const resizeObserver = useResizeObserver({ callback: measureRects });
useIsomorphicLayoutEffect$1(() => {
resizeObserver?.disconnect();
measureRects();
elements.forEach((element) => resizeObserver == null ? void 0 : resizeObserver.observe(element));
}, [elements]);
return rects;
}
function getMeasurableNode(node) {
if (!node) return null;
if (node.children.length > 1) return node;
const firstChild = node.children[0];
return isHTMLElement(firstChild) ? firstChild : node;
}
function useDragOverlayMeasuring(_ref) {
let { measure } = _ref;
const [rect, setRect] = (0, import_react$4.useState)(null);
const resizeObserver = useResizeObserver({ callback: (0, import_react$4.useCallback)((entries) => {
for (const { target } of entries) if (isHTMLElement(target)) {
setRect((rect) => {
const newRect = measure(target);
return rect ? {
...rect,
width: newRect.width,
height: newRect.height
} : newRect;
});
break;
}
}, [measure]) });
const [nodeRef, setRef] = useNodeRef((0, import_react$4.useCallback)((element) => {
const node = getMeasurableNode(element);
resizeObserver?.disconnect();
if (node) resizeObserver?.observe(node);
setRect(node ? measure(node) : null);
}, [measure, resizeObserver]));
return (0, import_react$4.useMemo)(() => ({
nodeRef,
rect,
setRef
}), [
rect,
nodeRef,
setRef
]);
}
var defaultSensors = [{
sensor: PointerSensor,
options: {}
}, {
sensor: KeyboardSensor,
options: {}
}];
var defaultData = { current: {} };
var defaultMeasuringConfiguration = {
draggable: { measure: getTransformAgnosticClientRect },
droppable: {
measure: getTransformAgnosticClientRect,
strategy: MeasuringStrategy.WhileDragging,
frequency: MeasuringFrequency.Optimized
},
dragOverlay: { measure: getClientRect }
};
var DroppableContainersMap = class extends Map {
get(id) {
var _super$get;
return id != null ? (_super$get = super.get(id)) != null ? _super$get : void 0 : void 0;
}
toArray() {
return Array.from(this.values());
}
getEnabled() {
return this.toArray().filter((_ref) => {
let { disabled } = _ref;
return !disabled;
});
}
getNodeFor(id) {
var _this$get$node$curren, _this$get;
return (_this$get$node$curren = (_this$get = this.get(id)) == null ? void 0 : _this$get.node.current) != null ? _this$get$node$curren : void 0;
}
};
var defaultPublicContext = {
activatorEvent: null,
active: null,
activeNode: null,
activeNodeRect: null,
collisions: null,
containerNodeRect: null,
draggableNodes: /*#__PURE__*/ new Map(),
droppableRects: /*#__PURE__*/ new Map(),
droppableContainers: /*#__PURE__*/ new DroppableContainersMap(),
over: null,
dragOverlay: {
nodeRef: { current: null },
rect: null,
setRef: noop
},
scrollableAncestors: [],
scrollableAncestorRects: [],
measuringConfiguration: defaultMeasuringConfiguration,
measureDroppableContainers: noop,
windowRect: null,
measuringScheduled: false
};
var defaultInternalContext = {
activatorEvent: null,
activators: [],
active: null,
activeNodeRect: null,
ariaDescribedById: { draggable: "" },
dispatch: noop,
draggableNodes: /*#__PURE__*/ new Map(),
over: null,
measureDroppableContainers: noop
};
var InternalContext = /*#__PURE__*/ (0, import_react$4.createContext)(defaultInternalContext);
var PublicContext = /*#__PURE__*/ (0, import_react$4.createContext)(defaultPublicContext);
function getInitialState() {
return {
draggable: {
active: null,
initialCoordinates: {
x: 0,
y: 0
},
nodes: /* @__PURE__ */ new Map(),
translate: {
x: 0,
y: 0
}
},
droppable: { containers: new DroppableContainersMap() }
};
}
function reducer(state, action) {
switch (action.type) {
case Action.DragStart: return {
...state,
draggable: {
...state.draggable,
initialCoordinates: action.initialCoordinates,
active: action.active
}
};
case Action.DragMove:
if (state.draggable.active == null) return state;
return {
...state,
draggable: {
...state.draggable,
translate: {
x: action.coordinates.x - state.draggable.initialCoordinates.x,
y: action.coordinates.y - state.draggable.initialCoordinates.y
}
}
};
case Action.DragEnd:
case Action.DragCancel: return {
...state,
draggable: {
...state.draggable,
active: null,
initialCoordinates: {
x: 0,
y: 0
},
translate: {
x: 0,
y: 0
}
}
};
case Action.RegisterDroppable: {
const { element } = action;
const { id } = element;
const containers = new DroppableContainersMap(state.droppable.containers);
containers.set(id, element);
return {
...state,
droppable: {
...state.droppable,
containers
}
};
}
case Action.SetDroppableDisabled: {
const { id, key, disabled } = action;
const element = state.droppable.containers.get(id);
if (!element || key !== element.key) return state;
const containers = new DroppableContainersMap(state.droppable.containers);
containers.set(id, {
...element,
disabled
});
return {
...state,
droppable: {
...state.droppable,
containers
}
};
}
case Action.UnregisterDroppable: {
const { id, key } = action;
const element = state.droppable.containers.get(id);
if (!element || key !== element.key) return state;
const containers = new DroppableContainersMap(state.droppable.containers);
containers.delete(id);
return {
...state,
droppable: {
...state.droppable,
containers
}
};
}
default: return state;
}
}
function RestoreFocus(_ref) {
let { disabled } = _ref;
const { active, activatorEvent, draggableNodes } = (0, import_react$4.useContext)(InternalContext);
const previousActivatorEvent = usePrevious(activatorEvent);
const previousActiveId = usePrevious(active == null ? void 0 : active.id);
(0, import_react$4.useEffect)(() => {
if (disabled) return;
if (!activatorEvent && previousActivatorEvent && previousActiveId != null) {
if (!isKeyboardEvent(previousActivatorEvent)) return;
if (document.activeElement === previousActivatorEvent.target) return;
const draggableNode = draggableNodes.get(previousActiveId);
if (!draggableNode) return;
const { activatorNode, node } = draggableNode;
if (!activatorNode.current && !node.current) return;
requestAnimationFrame(() => {
for (const element of [activatorNode.current, node.current]) {
if (!element) continue;
const focusableNode = findFirstFocusableNode(element);
if (focusableNode) {
focusableNode.focus();
break;
}
}
});
}
}, [
activatorEvent,
disabled,
draggableNodes,
previousActiveId,
previousActivatorEvent
]);
return null;
}
function applyModifiers(modifiers, _ref) {
let { transform, ...args } = _ref;
return modifiers != null && modifiers.length ? modifiers.reduce((accumulator, modifier) => {
return modifier({
transform: accumulator,
...args
});
}, transform) : transform;
}
function useMeasuringConfiguration(config) {
return (0, import_react$4.useMemo)(() => ({
draggable: {
...defaultMeasuringConfiguration.draggable,
...config == null ? void 0 : config.draggable
},
droppable: {
...defaultMeasuringConfiguration.droppable,
...config == null ? void 0 : config.droppable
},
dragOverlay: {
...defaultMeasuringConfiguration.dragOverlay,
...config == null ? void 0 : config.dragOverlay
}
}), [
config == null ? void 0 : config.draggable,
config == null ? void 0 : config.droppable,
config == null ? void 0 : config.dragOverlay
]);
}
function useLayoutShiftScrollCompensation(_ref) {
let { activeNode, measure, initialRect, config = true } = _ref;
const initialized = (0, import_react$4.useRef)(false);
const { x, y } = typeof config === "boolean" ? {
x: config,
y: config
} : config;
useIsomorphicLayoutEffect$1(() => {
if (!x && !y || !activeNode) {
initialized.current = false;
return;
}
if (initialized.current || !initialRect) return;
const node = activeNode == null ? void 0 : activeNode.node.current;
if (!node || node.isConnected === false) return;
const rectDelta = getRectDelta(measure(node), initialRect);
if (!x) rectDelta.x = 0;
if (!y) rectDelta.y = 0;
initialized.current = true;
if (Math.abs(rectDelta.x) > 0 || Math.abs(rectDelta.y) > 0) {
const firstScrollableAncestor = getFirstScrollableAncestor(node);
if (firstScrollableAncestor) firstScrollableAncestor.scrollBy({
top: rectDelta.y,
left: rectDelta.x
});
}
}, [
activeNode,
x,
y,
initialRect,
measure
]);
}
var ActiveDraggableContext = /*#__PURE__*/ (0, import_react$4.createContext)({
...defaultCoordinates,
scaleX: 1,
scaleY: 1
});
var Status;
(function(Status) {
Status[Status["Uninitialized"] = 0] = "Uninitialized";
Status[Status["Initializing"] = 1] = "Initializing";
Status[Status["Initialized"] = 2] = "Initialized";
})(Status || (Status = {}));
var DndContext = exports("D",/*#__PURE__*/ (0, import_react$4.memo)(function DndContext(_ref) {
var _sensorContext$curren, _dragOverlay$nodeRef$, _dragOverlay$rect, _over$rect;
let { id, accessibility, autoScroll = true, children, sensors = defaultSensors, collisionDetection = rectIntersection, measuring, modifiers, ...props } = _ref;
const [state, dispatch] = (0, import_react$4.useReducer)(reducer, void 0, getInitialState);
const [dispatchMonitorEvent, registerMonitorListener] = useDndMonitorProvider();
const [status, setStatus] = (0, import_react$4.useState)(Status.Uninitialized);
const isInitialized = status === Status.Initialized;
const { draggable: { active: activeId, nodes: draggableNodes, translate }, droppable: { containers: droppableContainers } } = state;
const node = activeId != null ? draggableNodes.get(activeId) : null;
const activeRects = (0, import_react$4.useRef)({
initial: null,
translated: null
});
const active = (0, import_react$4.useMemo)(() => {
var _node$data;
return activeId != null ? {
id: activeId,
data: (_node$data = node == null ? void 0 : node.data) != null ? _node$data : defaultData,
rect: activeRects
} : null;
}, [activeId, node]);
const activeRef = (0, import_react$4.useRef)(null);
const [activeSensor, setActiveSensor] = (0, import_react$4.useState)(null);
const [activatorEvent, setActivatorEvent] = (0, import_react$4.useState)(null);
const latestProps = useLatestValue(props, Object.values(props));
const draggableDescribedById = useUniqueId("DndDescribedBy", id);
const enabledDroppableContainers = (0, import_react$4.useMemo)(() => droppableContainers.getEnabled(), [droppableContainers]);
const measuringConfiguration = useMeasuringConfiguration(measuring);
const { droppableRects, measureDroppableContainers, measuringScheduled } = useDroppableMeasuring(enabledDroppableContainers, {
dragging: isInitialized,
dependencies: [translate.x, translate.y],
config: measuringConfiguration.droppable
});
const activeNode = useCachedNode(draggableNodes, activeId);
const activationCoordinates = (0, import_react$4.useMemo)(() => activatorEvent ? getEventCoordinates(activatorEvent) : null, [activatorEvent]);
const autoScrollOptions = getAutoScrollerOptions();
const initialActiveNodeRect = useInitialRect(activeNode, measuringConfiguration.draggable.measure);
useLayoutShiftScrollCompensation({
activeNode: activeId != null ? draggableNodes.get(activeId) : null,
config: autoScrollOptions.layoutShiftCompensation,
initialRect: initialActiveNodeRect,
measure: measuringConfiguration.draggable.measure
});
const activeNodeRect = useRect(activeNode, measuringConfiguration.draggable.measure, initialActiveNodeRect);
const containerNodeRect = useRect(activeNode ? activeNode.parentElement : null);
const sensorContext = (0, import_react$4.useRef)({
activatorEvent: null,
active: null,
activeNode,
collisionRect: null,
collisions: null,
droppableRects,
draggableNodes,
draggingNode: null,
draggingNodeRect: null,
droppableContainers,
over: null,
scrollableAncestors: [],
scrollAdjustedTranslate: null
});
const overNode = droppableContainers.getNodeFor((_sensorContext$curren = sensorContext.current.over) == null ? void 0 : _sensorContext$curren.id);
const dragOverlay = useDragOverlayMeasuring({ measure: measuringConfiguration.dragOverlay.measure });
const draggingNode = (_dragOverlay$nodeRef$ = dragOverlay.nodeRef.current) != null ? _dragOverlay$nodeRef$ : activeNode;
const draggingNodeRect = isInitialized ? (_dragOverlay$rect = dragOverlay.rect) != null ? _dragOverlay$rect : activeNodeRect : null;
const usesDragOverlay = Boolean(dragOverlay.nodeRef.current && dragOverlay.rect);
const nodeRectDelta = useRectDelta(usesDragOverlay ? null : activeNodeRect);
const windowRect = useWindowRect(draggingNode ? getWindow(draggingNode) : null);
const scrollableAncestors = useScrollableAncestors(isInitialized ? overNode != null ? overNode : activeNode : null);
const scrollableAncestorRects = useRects(scrollableAncestors);
const modifiedTranslate = applyModifiers(modifiers, {
transform: {
x: translate.x - nodeRectDelta.x,
y: translate.y - nodeRectDelta.y,
scaleX: 1,
scaleY: 1
},
activatorEvent,
active,
activeNodeRect,
containerNodeRect,
draggingNodeRect,
over: sensorContext.current.over,
overlayNodeRect: dragOverlay.rect,
scrollableAncestors,
scrollableAncestorRects,
windowRect
});
const pointerCoordinates = activationCoordinates ? add(activationCoordinates, translate) : null;
const scrollOffsets = useScrollOffsets(scrollableAncestors);
const scrollAdjustment = useScrollOffsetsDelta(scrollOffsets);
const activeNodeScrollDelta = useScrollOffsetsDelta(scrollOffsets, [activeNodeRect]);
const scrollAdjustedTranslate = add(modifiedTranslate, scrollAdjustment);
const collisionRect = draggingNodeRect ? getAdjustedRect(draggingNodeRect, modifiedTranslate) : null;
const collisions = active && collisionRect ? collisionDetection({
active,
collisionRect,
droppableRects,
droppableContainers: enabledDroppableContainers,
pointerCoordinates
}) : null;
const overId = getFirstCollision(collisions, "id");
const [over, setOver] = (0, import_react$4.useState)(null);
const transform = adjustScale(usesDragOverlay ? modifiedTranslate : add(modifiedTranslate, activeNodeScrollDelta), (_over$rect = over == null ? void 0 : over.rect) != null ? _over$rect : null, activeNodeRect);
const activeSensorRef = (0, import_react$4.useRef)(null);
const instantiateSensor = (0, import_react$4.useCallback)((event, _ref2) => {
let { sensor: Sensor, options } = _ref2;
if (activeRef.current == null) return;
const activeNode = draggableNodes.get(activeRef.current);
if (!activeNode) return;
const activatorEvent = event.nativeEvent;
const sensorInstance = new Sensor({
active: activeRef.current,
activeNode,
event: activatorEvent,
options,
context: sensorContext,
onAbort(id) {
if (!draggableNodes.get(id)) return;
const { onDragAbort } = latestProps.current;
const event = { id };
onDragAbort?.(event);
dispatchMonitorEvent({
type: "onDragAbort",
event
});
},
onPending(id, constraint, initialCoordinates, offset) {
if (!draggableNodes.get(id)) return;
const { onDragPending } = latestProps.current;
const event = {
id,
constraint,
initialCoordinates,
offset
};
onDragPending?.(event);
dispatchMonitorEvent({
type: "onDragPending",
event
});
},
onStart(initialCoordinates) {
const id = activeRef.current;
if (id == null) return;
const draggableNode = draggableNodes.get(id);
if (!draggableNode) return;
const { onDragStart } = latestProps.current;
const event = {
activatorEvent,
active: {
id,
data: draggableNode.data,
rect: activeRects
}
};
(0, import_react_dom$1.unstable_batchedUpdates)(() => {
onDragStart?.(event);
setStatus(Status.Initializing);
dispatch({
type: Action.DragStart,
initialCoordinates,
active: id
});
dispatchMonitorEvent({
type: "onDragStart",
event
});
setActiveSensor(activeSensorRef.current);
setActivatorEvent(activatorEvent);
});
},
onMove(coordinates) {
dispatch({
type: Action.DragMove,
coordinates
});
},
onEnd: createHandler(Action.DragEnd),
onCancel: createHandler(Action.DragCancel)
});
activeSensorRef.current = sensorInstance;
function createHandler(type) {
return async function handler() {
const { active, collisions, over, scrollAdjustedTranslate } = sensorContext.current;
let event = null;
if (active && scrollAdjustedTranslate) {
const { cancelDrop } = latestProps.current;
event = {
activatorEvent,
active,
collisions,
delta: scrollAdjustedTranslate,
over
};
if (type === Action.DragEnd && typeof cancelDrop === "function") {
if (await Promise.resolve(cancelDrop(event))) type = Action.DragCancel;
}
}
activeRef.current = null;
(0, import_react_dom$1.unstable_batchedUpdates)(() => {
dispatch({ type });
setStatus(Status.Uninitialized);
setOver(null);
setActiveSensor(null);
setActivatorEvent(null);
activeSensorRef.current = null;
const eventName = type === Action.DragEnd ? "onDragEnd" : "onDragCancel";
if (event) {
const handler = latestProps.current[eventName];
handler?.(event);
dispatchMonitorEvent({
type: eventName,
event
});
}
});
};
}
}, [draggableNodes]);
const activators = useCombineActivators(sensors, (0, import_react$4.useCallback)((handler, sensor) => {
return (event, active) => {
const nativeEvent = event.nativeEvent;
const activeDraggableNode = draggableNodes.get(active);
if (activeRef.current !== null || !activeDraggableNode || nativeEvent.dndKit || nativeEvent.defaultPrevented) return;
const activationContext = { active: activeDraggableNode };
if (handler(event, sensor.options, activationContext) === true) {
nativeEvent.dndKit = { capturedBy: sensor.sensor };
activeRef.current = active;
instantiateSensor(event, sensor);
}
};
}, [draggableNodes, instantiateSensor]));
useSensorSetup(sensors);
useIsomorphicLayoutEffect$1(() => {
if (activeNodeRect && status === Status.Initializing) setStatus(Status.Initialized);
}, [activeNodeRect, status]);
(0, import_react$4.useEffect)(() => {
const { onDragMove } = latestProps.current;
const { active, activatorEvent, collisions, over } = sensorContext.current;
if (!active || !activatorEvent) return;
const event = {
active,
activatorEvent,
collisions,
delta: {
x: scrollAdjustedTranslate.x,
y: scrollAdjustedTranslate.y
},
over
};
(0, import_react_dom$1.unstable_batchedUpdates)(() => {
onDragMove?.(event);
dispatchMonitorEvent({
type: "onDragMove",
event
});
});
}, [scrollAdjustedTranslate.x, scrollAdjustedTranslate.y]);
(0, import_react$4.useEffect)(() => {
const { active, activatorEvent, collisions, droppableContainers, scrollAdjustedTranslate } = sensorContext.current;
if (!active || activeRef.current == null || !activatorEvent || !scrollAdjustedTranslate) return;
const { onDragOver } = latestProps.current;
const overContainer = droppableContainers.get(overId);
const over = overContainer && overContainer.rect.current ? {
id: overContainer.id,
rect: overContainer.rect.current,
data: overContainer.data,
disabled: overContainer.disabled
} : null;
const event = {
active,
activatorEvent,
collisions,
delta: {
x: scrollAdjustedTranslate.x,
y: scrollAdjustedTranslate.y
},
over
};
(0, import_react_dom$1.unstable_batchedUpdates)(() => {
setOver(over);
onDragOver?.(event);
dispatchMonitorEvent({
type: "onDragOver",
event
});
});
}, [overId]);
useIsomorphicLayoutEffect$1(() => {
sensorContext.current = {
activatorEvent,
active,
activeNode,
collisionRect,
collisions,
droppableRects,
draggableNodes,
draggingNode,
draggingNodeRect,
droppableContainers,
over,
scrollableAncestors,
scrollAdjustedTranslate
};
activeRects.current = {
initial: draggingNodeRect,
translated: collisionRect
};
}, [
active,
activeNode,
collisions,
collisionRect,
draggableNodes,
draggingNode,
draggingNodeRect,
droppableRects,
droppableContainers,
over,
scrollableAncestors,
scrollAdjustedTranslate
]);
useAutoScroller({
...autoScrollOptions,
delta: translate,
draggingRect: collisionRect,
pointerCoordinates,
scrollableAncestors,
scrollableAncestorRects
});
const publicContext = (0, import_react$4.useMemo)(() => {
return {
active,
activeNode,
activeNodeRect,
activatorEvent,
collisions,
containerNodeRect,
dragOverlay,
draggableNodes,
droppableContainers,
droppableRects,
over,
measureDroppableContainers,
scrollableAncestors,
scrollableAncestorRects,
measuringConfiguration,
measuringScheduled,
windowRect
};
}, [
active,
activeNode,
activeNodeRect,
activatorEvent,
collisions,
containerNodeRect,
dragOverlay,
draggableNodes,
droppableContainers,
droppableRects,
over,
measureDroppableContainers,
scrollableAncestors,
scrollableAncestorRects,
measuringConfiguration,
measuringScheduled,
windowRect
]);
const internalContext = (0, import_react$4.useMemo)(() => {
return {
activatorEvent,
activators,
active,
activeNodeRect,
ariaDescribedById: { draggable: draggableDescribedById },
dispatch,
draggableNodes,
over,
measureDroppableContainers
};
}, [
activatorEvent,
activators,
active,
activeNodeRect,
dispatch,
draggableDescribedById,
draggableNodes,
over,
measureDroppableContainers
]);
return import_react$4.createElement(DndMonitorContext.Provider, { value: registerMonitorListener }, import_react$4.createElement(InternalContext.Provider, { value: internalContext }, import_react$4.createElement(PublicContext.Provider, { value: publicContext }, import_react$4.createElement(ActiveDraggableContext.Provider, { value: transform }, children)), import_react$4.createElement(RestoreFocus, { disabled: (accessibility == null ? void 0 : accessibility.restoreFocus) === false })), import_react$4.createElement(Accessibility, {
...accessibility,
hiddenTextDescribedById: draggableDescribedById
}));
function getAutoScrollerOptions() {
const activeSensorDisablesAutoscroll = (activeSensor == null ? void 0 : activeSensor.autoScrollEnabled) === false;
const autoScrollGloballyDisabled = typeof autoScroll === "object" ? autoScroll.enabled === false : autoScroll === false;
const enabled = isInitialized && !activeSensorDisablesAutoscroll && !autoScrollGloballyDisabled;
if (typeof autoScroll === "object") return {
...autoScroll,
enabled
};
return { enabled };
}
}));
var NullContext = /*#__PURE__*/ (0, import_react$4.createContext)(null);
var defaultRole = "button";
var ID_PREFIX$1 = "Draggable";
function useDraggable(_ref) {
let { id, data, disabled = false, attributes } = _ref;
const key = useUniqueId(ID_PREFIX$1);
const { activators, activatorEvent, active, activeNodeRect, ariaDescribedById, draggableNodes, over } = (0, import_react$4.useContext)(InternalContext);
const { role = defaultRole, roleDescription = "draggable", tabIndex = 0 } = attributes != null ? attributes : {};
const isDragging = (active == null ? void 0 : active.id) === id;
const transform = (0, import_react$4.useContext)(isDragging ? ActiveDraggableContext : NullContext);
const [node, setNodeRef] = useNodeRef();
const [activatorNode, setActivatorNodeRef] = useNodeRef();
const listeners = useSyntheticListeners(activators, id);
const dataRef = useLatestValue(data);
useIsomorphicLayoutEffect$1(() => {
draggableNodes.set(id, {
id,
key,
node,
activatorNode,
data: dataRef
});
return () => {
const node = draggableNodes.get(id);
if (node && node.key === key) draggableNodes.delete(id);
};
}, [draggableNodes, id]);
return {
active,
activatorEvent,
activeNodeRect,
attributes: (0, import_react$4.useMemo)(() => ({
role,
tabIndex,
"aria-disabled": disabled,
"aria-pressed": isDragging && role === defaultRole ? true : void 0,
"aria-roledescription": roleDescription,
"aria-describedby": ariaDescribedById.draggable
}), [
disabled,
role,
tabIndex,
isDragging,
roleDescription,
ariaDescribedById.draggable
]),
isDragging,
listeners: disabled ? void 0 : listeners,
node,
over,
setNodeRef,
setActivatorNodeRef,
transform
};
}
function useDndContext() {
return (0, import_react$4.useContext)(PublicContext);
}
var ID_PREFIX$1$1 = "Droppable";
var defaultResizeObserverConfig = { timeout: 25 };
function useDroppable(_ref) {
let { data, disabled = false, id, resizeObserverConfig } = _ref;
const key = useUniqueId(ID_PREFIX$1$1);
const { active, dispatch, over, measureDroppableContainers } = (0, import_react$4.useContext)(InternalContext);
const previous = (0, import_react$4.useRef)({ disabled });
const resizeObserverConnected = (0, import_react$4.useRef)(false);
const rect = (0, import_react$4.useRef)(null);
const callbackId = (0, import_react$4.useRef)(null);
const { disabled: resizeObserverDisabled, updateMeasurementsFor, timeout: resizeObserverTimeout } = {
...defaultResizeObserverConfig,
...resizeObserverConfig
};
const ids = useLatestValue(updateMeasurementsFor != null ? updateMeasurementsFor : id);
const resizeObserver = useResizeObserver({
callback: (0, import_react$4.useCallback)(() => {
if (!resizeObserverConnected.current) {
resizeObserverConnected.current = true;
return;
}
if (callbackId.current != null) clearTimeout(callbackId.current);
callbackId.current = setTimeout(() => {
measureDroppableContainers(Array.isArray(ids.current) ? ids.current : [ids.current]);
callbackId.current = null;
}, resizeObserverTimeout);
}, [resizeObserverTimeout]),
disabled: resizeObserverDisabled || !active
});
const [nodeRef, setNodeRef] = useNodeRef((0, import_react$4.useCallback)((newElement, previousElement) => {
if (!resizeObserver) return;
if (previousElement) {
resizeObserver.unobserve(previousElement);
resizeObserverConnected.current = false;
}
if (newElement) resizeObserver.observe(newElement);
}, [resizeObserver]));
const dataRef = useLatestValue(data);
(0, import_react$4.useEffect)(() => {
if (!resizeObserver || !nodeRef.current) return;
resizeObserver.disconnect();
resizeObserverConnected.current = false;
resizeObserver.observe(nodeRef.current);
}, [nodeRef, resizeObserver]);
(0, import_react$4.useEffect)(() => {
dispatch({
type: Action.RegisterDroppable,
element: {
id,
key,
disabled,
node: nodeRef,
rect,
data: dataRef
}
});
return () => dispatch({
type: Action.UnregisterDroppable,
key,
id
});
}, [id]);
(0, import_react$4.useEffect)(() => {
if (disabled !== previous.current.disabled) {
dispatch({
type: Action.SetDroppableDisabled,
id,
key,
disabled
});
previous.current.disabled = disabled;
}
}, [
id,
key,
disabled,
dispatch
]);
return {
active,
rect,
isOver: (over == null ? void 0 : over.id) === id,
node: nodeRef,
over,
setNodeRef
};
}
/**
* Move an array item to a different position. Returns a new array with the item moved to the new position.
*/
function arrayMove(array, from, to) {
const newArray = array.slice();
newArray.splice(to < 0 ? newArray.length + to : to, 0, newArray.splice(from, 1)[0]);
return newArray;
}
function getSortedRects(items, rects) {
return items.reduce((accumulator, id, index) => {
const rect = rects.get(id);
if (rect) accumulator[index] = rect;
return accumulator;
}, Array(items.length));
}
function isValidIndex(index) {
return index !== null && index >= 0;
}
function itemsEqual(a, b) {
if (a === b) return true;
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
return true;
}
function normalizeDisabled(disabled) {
if (typeof disabled === "boolean") return {
draggable: disabled,
droppable: disabled
};
return disabled;
}
var rectSortingStrategy = (_ref) => {
let { rects, activeIndex, overIndex, index } = _ref;
const newRects = arrayMove(rects, overIndex, activeIndex);
const oldRect = rects[index];
const newRect = newRects[index];
if (!newRect || !oldRect) return null;
return {
x: newRect.left - oldRect.left,
y: newRect.top - oldRect.top,
scaleX: newRect.width / oldRect.width,
scaleY: newRect.height / oldRect.height
};
};
var defaultScale$1 = {
scaleX: 1,
scaleY: 1
};
var verticalListSortingStrategy = exports("v",(_ref) => {
var _rects$activeIndex;
let { activeIndex, activeNodeRect: fallbackActiveRect, index, rects, overIndex } = _ref;
const activeNodeRect = (_rects$activeIndex = rects[activeIndex]) != null ? _rects$activeIndex : fallbackActiveRect;
if (!activeNodeRect) return null;
if (index === activeIndex) {
const overIndexRect = rects[overIndex];
if (!overIndexRect) return null;
return {
x: 0,
y: activeIndex < overIndex ? overIndexRect.top + overIndexRect.height - (activeNodeRect.top + activeNodeRect.height) : overIndexRect.top - activeNodeRect.top,
...defaultScale$1
};
}
const itemGap = getItemGap$1(rects, index, activeIndex);
if (index > activeIndex && index <= overIndex) return {
x: 0,
y: -activeNodeRect.height - itemGap,
...defaultScale$1
};
if (index < activeIndex && index >= overIndex) return {
x: 0,
y: activeNodeRect.height + itemGap,
...defaultScale$1
};
return {
x: 0,
y: 0,
...defaultScale$1
};
});
function getItemGap$1(clientRects, index, activeIndex) {
const currentRect = clientRects[index];
const previousRect = clientRects[index - 1];
const nextRect = clientRects[index + 1];
if (!currentRect) return 0;
if (activeIndex < index) return previousRect ? currentRect.top - (previousRect.top + previousRect.height) : nextRect ? nextRect.top - (currentRect.top + currentRect.height) : 0;
return nextRect ? nextRect.top - (currentRect.top + currentRect.height) : previousRect ? currentRect.top - (previousRect.top + previousRect.height) : 0;
}
var ID_PREFIX = "Sortable";
var Context = /*#__PURE__*/ import_react$4.createContext({
activeIndex: -1,
containerId: ID_PREFIX,
disableTransforms: false,
items: [],
overIndex: -1,
useDragOverlay: false,
sortedRects: [],
strategy: rectSortingStrategy,
disabled: {
draggable: false,
droppable: false
}
});
function SortableContext(_ref) {
let { children, id, items: userDefinedItems, strategy = rectSortingStrategy, disabled: disabledProp = false } = _ref;
const { active, dragOverlay, droppableRects, over, measureDroppableContainers } = useDndContext();
const containerId = useUniqueId(ID_PREFIX, id);
const useDragOverlay = Boolean(dragOverlay.rect !== null);
const items = (0, import_react$4.useMemo)(() => userDefinedItems.map((item) => typeof item === "object" && "id" in item ? item.id : item), [userDefinedItems]);
const isDragging = active != null;
const activeIndex = active ? items.indexOf(active.id) : -1;
const overIndex = over ? items.indexOf(over.id) : -1;
const previousItemsRef = (0, import_react$4.useRef)(items);
const itemsHaveChanged = !itemsEqual(items, previousItemsRef.current);
const disableTransforms = overIndex !== -1 && activeIndex === -1 || itemsHaveChanged;
const disabled = normalizeDisabled(disabledProp);
useIsomorphicLayoutEffect$1(() => {
if (itemsHaveChanged && isDragging) measureDroppableContainers(items);
}, [
itemsHaveChanged,
items,
isDragging,
measureDroppableContainers
]);
(0, import_react$4.useEffect)(() => {
previousItemsRef.current = items;
}, [items]);
const contextValue = (0, import_react$4.useMemo)(() => ({
activeIndex,
containerId,
disabled,
disableTransforms,
items,
overIndex,
useDragOverlay,
sortedRects: getSortedRects(items, droppableRects),
strategy
}), [
activeIndex,
containerId,
disabled.draggable,
disabled.droppable,
disableTransforms,
items,
overIndex,
droppableRects,
useDragOverlay,
strategy
]);
return import_react$4.createElement(Context.Provider, { value: contextValue }, children);
}
var defaultNewIndexGetter = (_ref) => {
let { id, items, activeIndex, overIndex } = _ref;
return arrayMove(items, activeIndex, overIndex).indexOf(id);
};
var defaultAnimateLayoutChanges = (_ref2) => {
let { containerId, isSorting, wasDragging, index, items, newIndex, previousItems, previousContainerId, transition } = _ref2;
if (!transition || !wasDragging) return false;
if (previousItems !== items && index === newIndex) return false;
if (isSorting) return true;
return newIndex !== index && containerId === previousContainerId;
};
var defaultTransition = {
duration: 200,
easing: "ease"
};
var transitionProperty = "transform";
var disabledTransition = /*#__PURE__*/ CSS.Transition.toString({
property: transitionProperty,
duration: 0,
easing: "linear"
});
var defaultAttributes = { roleDescription: "sortable" };
function useDerivedTransform(_ref) {
let { disabled, index, node, rect } = _ref;
const [derivedTransform, setDerivedtransform] = (0, import_react$4.useState)(null);
const previousIndex = (0, import_react$4.useRef)(index);
useIsomorphicLayoutEffect$1(() => {
if (!disabled && index !== previousIndex.current && node.current) {
const initial = rect.current;
if (initial) {
const current = getClientRect(node.current, { ignoreTransform: true });
const delta = {
x: initial.left - current.left,
y: initial.top - current.top,
scaleX: initial.width / current.width,
scaleY: initial.height / current.height
};
if (delta.x || delta.y) setDerivedtransform(delta);
}
}
if (index !== previousIndex.current) previousIndex.current = index;
}, [
disabled,
index,
node,
rect
]);
(0, import_react$4.useEffect)(() => {
if (derivedTransform) setDerivedtransform(null);
}, [derivedTransform]);
return derivedTransform;
}
function useSortable(_ref) {
let { animateLayoutChanges = defaultAnimateLayoutChanges, attributes: userDefinedAttributes, disabled: localDisabled, data: customData, getNewIndex = defaultNewIndexGetter, id, strategy: localStrategy, resizeObserverConfig, transition = defaultTransition } = _ref;
const { items, containerId, activeIndex, disabled: globalDisabled, disableTransforms, sortedRects, overIndex, useDragOverlay, strategy: globalStrategy } = (0, import_react$4.useContext)(Context);
const disabled = normalizeLocalDisabled(localDisabled, globalDisabled);
const index = items.indexOf(id);
const data = (0, import_react$4.useMemo)(() => ({
sortable: {
containerId,
index,
items
},
...customData
}), [
containerId,
customData,
index,
items
]);
const itemsAfterCurrentSortable = (0, import_react$4.useMemo)(() => items.slice(items.indexOf(id)), [items, id]);
const { rect, node, isOver, setNodeRef: setDroppableNodeRef } = useDroppable({
id,
data,
disabled: disabled.droppable,
resizeObserverConfig: {
updateMeasurementsFor: itemsAfterCurrentSortable,
...resizeObserverConfig
}
});
const { active, activatorEvent, activeNodeRect, attributes, setNodeRef: setDraggableNodeRef, listeners, isDragging, over, setActivatorNodeRef, transform } = useDraggable({
id,
data,
attributes: {
...defaultAttributes,
...userDefinedAttributes
},
disabled: disabled.draggable
});
const setNodeRef = useCombinedRefs(setDroppableNodeRef, setDraggableNodeRef);
const isSorting = Boolean(active);
const displaceItem = isSorting && !disableTransforms && isValidIndex(activeIndex) && isValidIndex(overIndex);
const shouldDisplaceDragSource = !useDragOverlay && isDragging;
const dragSourceDisplacement = shouldDisplaceDragSource && displaceItem ? transform : null;
const finalTransform = displaceItem ? dragSourceDisplacement != null ? dragSourceDisplacement : (localStrategy != null ? localStrategy : globalStrategy)({
rects: sortedRects,
activeNodeRect,
activeIndex,
overIndex,
index
}) : null;
const newIndex = isValidIndex(activeIndex) && isValidIndex(overIndex) ? getNewIndex({
id,
items,
activeIndex,
overIndex
}) : index;
const activeId = active == null ? void 0 : active.id;
const previous = (0, import_react$4.useRef)({
activeId,
items,
newIndex,
containerId
});
const itemsHaveChanged = items !== previous.current.items;
const shouldAnimateLayoutChanges = animateLayoutChanges({
active,
containerId,
isDragging,
isSorting,
id,
index,
items,
newIndex: previous.current.newIndex,
previousItems: previous.current.items,
previousContainerId: previous.current.containerId,
transition,
wasDragging: previous.current.activeId != null
});
const derivedTransform = useDerivedTransform({
disabled: !shouldAnimateLayoutChanges,
index,
node,
rect
});
(0, import_react$4.useEffect)(() => {
if (isSorting && previous.current.newIndex !== newIndex) previous.current.newIndex = newIndex;
if (containerId !== previous.current.containerId) previous.current.containerId = containerId;
if (items !== previous.current.items) previous.current.items = items;
}, [
isSorting,
newIndex,
containerId,
items
]);
(0, import_react$4.useEffect)(() => {
if (activeId === previous.current.activeId) return;
if (activeId != null && previous.current.activeId == null) {
previous.current.activeId = activeId;
return;
}
const timeoutId = setTimeout(() => {
previous.current.activeId = activeId;
}, 50);
return () => clearTimeout(timeoutId);
}, [activeId]);
return {
active,
activeIndex,
attributes,
data,
rect,
index,
newIndex,
items,
isOver,
isSorting,
isDragging,
listeners,
node,
overIndex,
over,
setNodeRef,
setActivatorNodeRef,
setDroppableNodeRef,
setDraggableNodeRef,
transform: derivedTransform != null ? derivedTransform : finalTransform,
transition: getTransition()
};
function getTransition() {
if (derivedTransform || itemsHaveChanged && previous.current.newIndex === index) return disabledTransition;
if (shouldDisplaceDragSource && !isKeyboardEvent(activatorEvent) || !transition) return;
if (isSorting || shouldAnimateLayoutChanges) return CSS.Transition.toString({
...transition,
property: transitionProperty
});
}
}
function normalizeLocalDisabled(localDisabled, globalDisabled) {
var _localDisabled$dragga, _localDisabled$droppa;
if (typeof localDisabled === "boolean") return {
draggable: localDisabled,
droppable: false
};
return {
draggable: (_localDisabled$dragga = localDisabled == null ? void 0 : localDisabled.draggable) != null ? _localDisabled$dragga : globalDisabled.draggable,
droppable: (_localDisabled$droppa = localDisabled == null ? void 0 : localDisabled.droppable) != null ? _localDisabled$droppa : globalDisabled.droppable
};
}
KeyboardCode.Down, KeyboardCode.Right, KeyboardCode.Up, KeyboardCode.Left;
function useShortcut({ shortcutEnabled, minIndex = 0, maxIndex, gridRef, getScrollerRect, changeScrollY, videoCardEmitters, activeLargePreviewItemIndex }) {
const [activeIndex, setActiveIndex] = (0, import_react$4.useState)(void 0);
const activeIndexIsValid = useMemoizedFn(() => {
if (typeof activeIndex !== "number") return false;
if (!gridRef.current) return false;
const scrollerRect = getScrollerRect();
const rect = gridRef.current.querySelector(`.${APP_CLS_CARD}.${APP_CLS_CARD_ACTIVE}`)?.getBoundingClientRect();
if (!scrollerRect || !rect) return false;
if (rect.top - scrollerRect.top < -(scrollerRect.height + rect.height)) return false;
if (rect.top - scrollerRect.top > scrollerRect.height * 2 + rect.height) return false;
return true;
});
function getStep(direction) {
const card = getCardAt(activeIndex);
const activeLeft = card.getBoundingClientRect().left;
const isLeftSame = (left) => Math.abs(activeLeft - left) < 1;
/**
* quick try +- col-count
*/
{
const col = getColumnCount(gridRef.current);
const step = direction === "down" ? col : -col;
const newCard = getCardAt(activeIndex + step);
if (newCard) {
const left = newCard.getBoundingClientRect().left;
if (isLeftSame(left)) return step;
}
}
/**
* step by step
*/
let step = 0;
let cur = card;
const next = () => direction === "down" ? cur.nextElementSibling : cur.previousElementSibling;
while (next()) {
cur = next();
if (!cur.classList.contains(APP_CLS_CARD)) continue;
direction === "down" ? step++ : step--;
const left = cur.getBoundingClientRect().left;
if (isLeftSame(left)) return step;
}
return 0;
}
const addActiveIndex = (step) => (e) => {
if (!shortcutEnabled) return;
e?.preventDefault();
let newActiveIndex;
if (activeIndexIsValid()) {
const _step = typeof step === "number" ? step : getStep(step);
newActiveIndex = activeIndex + _step;
} else newActiveIndex = getInitialIndex();
if (newActiveIndex < minIndex) {
makeVisible(minIndex);
return;
}
if (newActiveIndex > maxIndex) {
makeVisible(maxIndex);
return;
}
setActiveIndex(newActiveIndex);
makeVisible(newActiveIndex);
};
const clearActiveIndex = () => {
if (!shortcutEnabled) return;
setActiveIndex(void 0);
};
const getActiveEmitter = () => {
if (!shortcutEnabled || typeof activeIndex !== "number") return;
return videoCardEmitters[activeIndex];
};
const hotkeyOptions = {
enabled: shortcutEnabled,
conflictBehavior: "allow"
};
useHotkeys([
{
hotkey: "ArrowLeft",
callback: addActiveIndex(-1)
},
{
hotkey: "ArrowRight",
callback: addActiveIndex(1)
},
{
hotkey: "Shift+Tab",
callback: addActiveIndex(-1)
},
{
hotkey: "Tab",
callback: addActiveIndex(1)
},
{
hotkey: "ArrowUp",
callback: addActiveIndex("up")
},
{
hotkey: "ArrowDown",
callback: addActiveIndex("down")
},
{
hotkey: "Escape",
callback: clearActiveIndex
}
], {
...hotkeyOptions,
requireReset: false
});
useHotkeys([{
hotkey: "Enter",
callback: (e) => {
if (typeof activeIndex === "number") {
e.preventDefault();
return videoCardEmitters[activeIndex]?.emit("open");
}
if (typeof activeLargePreviewItemIndex === "number") {
e.preventDefault();
return videoCardEmitters[activeLargePreviewItemIndex]?.emit("open-with-large-preview-visible");
}
},
options: { enabled: shortcutEnabled && (typeof activeIndex === "number" || typeof activeLargePreviewItemIndex === "number") }
}, {
hotkey: "X",
callback: () => getActiveEmitter()?.emit("open-in-popup"),
options: { enabled: shortcutEnabled && typeof activeIndex === "number" }
}], {
...hotkeyOptions,
requireReset: false
});
useHotkeys([
{
hotkey: "Backspace",
callback: () => getActiveEmitter()?.emit("trigger-dislike")
},
{
hotkey: "S",
callback: () => getActiveEmitter()?.emit("toggle-watch-later")
},
{
hotkey: "W",
callback: () => getActiveEmitter()?.emit("toggle-watch-later")
},
{
hotkey: ".",
callback: () => getActiveEmitter()?.emit("hotkey-preview-animation")
},
{
hotkey: "P",
callback: () => getActiveEmitter()?.emit("hotkey-preview-animation")
}
], {
...hotkeyOptions,
requireReset: true
});
function getInitialIndex() {
const scrollerRect = getScrollerRect();
if (!scrollerRect) return 0;
const cards = getCards();
for (const [i, card] of cards.entries()) if (card.getBoundingClientRect().top >= scrollerRect.top) return i;
return 0;
}
const CARDS_SELECTOR = `.${APP_CLS_CARD}`;
function getCards() {
return [...gridRef.current?.querySelectorAll(CARDS_SELECTOR) || []];
}
function getCardAt(index) {
return getCards()[index];
}
function makeVisible(index) {
const card = getCardAt(index);
card?.scrollIntoViewIfNeeded?.(false);
/**
* for PureRecommend 手动检测
*/
const scrollerRect = getScrollerRect();
const rect = card.getBoundingClientRect();
if (!scrollerRect || !rect) return;
if (rect.top <= scrollerRect.top) {
const offset = -(scrollerRect.top - rect.top + 10);
changeScrollY?.({ offset });
return;
}
if (scrollerRect.bottom - rect.bottom < 20) {
const offset = 20 - (scrollerRect.bottom - rect.bottom);
changeScrollY?.({ offset });
return;
}
}
return {
activeIndex,
clearActiveIndex
};
}
var countCache1 = /* @__PURE__ */ new Map();
var countCache2 = /* @__PURE__ */ new Map();
function getColumnCount(gridEl, mayHaveNarrowMode = true) {
const { gridDisplayMode, useCustomGrid, enableForceColumn, forceColumnCount, cardMinWidth } = settings.grid;
if (gridDisplayMode === EGridDisplayMode.List) return 1;
if (mayHaveNarrowMode && gridDisplayMode === EGridDisplayMode.TwoColumnGrid) return 2;
if (useCustomGrid && enableForceColumn && forceColumnCount) return forceColumnCount;
gridEl ||= document.querySelector(`.${videoGrid}`);
if (!gridEl) {
appWarn("getColumnCount(): gridEl not found");
return 0;
}
const countCache = useCustomGrid ? countCache1 : countCache2;
const cacheKey = new URLSearchParams({
width: Math.round(gridEl.clientWidth).toString(),
cardMinWidth: useCustomGrid ? cardMinWidth.toString() : ""
}).toString();
{
const count = countCache.get(cacheKey);
if (count) return count;
}
const style = window.getComputedStyle(gridEl);
if (style.display !== "grid") {
appWarn("getColumnCount(): gridEl.style.display !== \"grid\"");
return 0;
}
const count = style.gridTemplateColumns.split(" ").length;
countCache.set(cacheKey, count);
return count;
}
var debug$3 = baseDebug.extend("service");
var recItemUniqer = (item) => {
if (item.api === EApiType.Separator) return item.uniqId;
const { bvid } = normalizeCardData(item);
if (checkIsDynamicFeed(item)) return !!bvid && DynamicFeedItemHelper.isVideo(item) ? `${EApiType.DynamicFeed}:video:${bvid}` : item.uniqId;
return bvid || item.uniqId;
};
function concatRecItems(existing, newItems) {
return uniqBy([...existing, ...newItems], recItemUniqer);
}
var willUsePcApi = (tab) => tab === ETab.PcRecommend || tab === ETab.KeepFollowOnly;
async function fetchMinCount(count, fetcherOptions, filterMultiplier = 5) {
const { tab, service, abortSignal } = fetcherOptions;
const loadControl = service;
let items = [];
let hasMore = true;
const concatMore = (more) => {
const moreFiltered = filterRecItems(more, tab);
items = concatRecItems(items, moreFiltered);
};
const addMore = async (restCount) => {
if (!isRecTab(tab)) {
const more = await service.loadMore(abortSignal) ?? [];
concatMore(more);
hasMore = service.hasMore;
return;
}
/**
* REC_TABS
*/
let times;
if (tab === ETab.KeepFollowOnly) {
times = 8;
debug$3("getMinCount: addMore(restCount = %s) times=%s", restCount, times);
} else {
const pagesize = willUsePcApi(tab) ? PcRecService.PAGE_SIZE : AppRecService.PAGE_SIZE;
const multipler = (() => {
const f = settings.filter;
if (!f.enabled) return false;
return f.minDuration.enabled || f.minPlayCount.enabled || f.minDanmakuCount.enabled || f.byAuthor.enabled && !!f.byAuthor.keywords.length || f.byTitle.enabled && !!f.byTitle.keywords.length || f.hideGotoTypePicture || f.hideGotoTypeBangumi;
})() ? filterMultiplier : 1.2;
times = Math.ceil(restCount * multipler / pagesize);
debug$3("getMinCount: addMore(restCount = %s) multipler=%s pagesize=%s times=%s", restCount, multipler, pagesize, times);
}
if (willUsePcApi(tab)) {
const s = service;
if (s.isFirstAuthedFetch) await s.preloadPcInitialRecItems(abortSignal);
await s.preloadTimesFromApiIfNeeded(abortSignal, times);
} else {
const s = service;
if (!s.config.addOtherTabContents) await s.preloadTimesFromApiIfNeeded(abortSignal, times);
}
const more = await service.loadMore(abortSignal) || [];
concatMore(more);
hasMore = service.hasMore;
};
loadControl.beginLoadOperation?.(abortSignal);
try {
await addMore(count);
const len = () => items.filter((x) => x.api !== EApiType.Separator).length;
const shouldContinue = () => len() < count && hasMore && !loadControl.autoLoadPaused && !abortSignal?.aborted;
let stagnant = 0;
for (let iterations = 0; shouldContinue(); iterations++) {
const before = len();
await addMore(count - items.length);
stagnant = len() === before ? stagnant + 1 : 0;
if (stagnant >= 2 || iterations >= 20) break;
}
return items;
} finally {
loadControl.endLoadOperation?.();
}
}
var getGridRefreshCount = () => getColumnCount() * 4;
async function refreshForGrid(fetcherOptions) {
let minCount = getGridRefreshCount();
if (fetcherOptions.tab === ETab.TagRecFav || fetcherOptions.tab === ETab.TagRecCustom) minCount = Math.min(minCount, fetcherOptions.service.qs.ps);
if (fetcherOptions.tab === ETab.DynamicFeed) {
if (await fetcherOptions.service.shouldReduceMinCount()) minCount = 1;
}
return fetchMinCount(minCount, fetcherOptions, 5);
}
var newSignedForm = (params) => {
const sign = appSign(params, TVKeyInfo.appkey, TVKeyInfo.appsec);
return new URLSearchParams({
...params,
sign
});
};
async function getQrCodeInfo() {
const json = (await request.post("https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code", newSignedForm({
appkey: TVKeyInfo.appkey,
local_id: "0",
ts: "0"
}))).data;
if (!isWebApiSuccess(json)) {
toast(json?.message || "获取 auth_code 失败");
return;
}
return json.data;
}
async function poll(auth_code) {
const json = (await request.post("https://passport.bilibili.com/x/passport-tv-login/qrcode/poll", newSignedForm({
appkey: TVKeyInfo.appkey,
auth_code,
local_id: "0",
ts: "0"
}))).data;
const msgMap = {
"0": "成功",
"-3": "API校验密匙错误",
"-400": "请求错误",
"-404": "啥都木有",
"86038": "二维码已失效",
"86039": "二维码尚未确认",
"86090": "二维码已扫码未确认"
};
if (!isWebApiSuccess(json)) {
const code = json.code.toString();
const message = json.message || msgMap[code] || "未知错误";
if (code === "86038") return {
success: false,
message,
action: "refresh"
};
if (code === "86039" || code === "86090") return {
success: false,
message,
action: "wait"
};
return {
success: false,
message,
action: void 0
};
}
return {
success: true,
message: "获取成功",
accessKey: json.data.access_token,
accessKeyExpireAt: Date.now() + json.data.expires_in * 1e3
};
}
var initialValue = {
show: false,
qrcodeUrl: "",
auth_code: "",
message: "",
expired: false
};
var store$1 = proxy({ ...initialValue });
function updateStore(data) {
renderOnce();
Object.assign(store$1, data);
}
function showQrCodeModal(data) {
updateStore({
...initialValue,
...data,
show: true
});
}
var emitter = new Emittery();
function hideQrCodeModal() {
updateStore({ ...initialValue });
emitter.emit("hide");
}
function whenQrCodeModalHide() {
return emitter.once("hide");
}
function whenQrCodeRefresh() {
return emitter.once("refresh");
}
function TvQrCodeAuth() {
const { qrcodeUrl, show, message, expired } = useSnapshot(store$1);
const onHide = hideQrCodeModal;
const onRefresh = () => {
emitter.emit("refresh");
};
return /* @__PURE__ */ jsxs(BaseModal, {
show,
onHide,
hideWhenMaskOnClick: false,
hideWhenEsc: false,
clsModalMask: "backdrop-blur-10px",
clsModal: "aspect-ratio-10/16",
width: 260,
children: [/* @__PURE__ */ jsxs("div", {
className: BaseModalClassNames.modalHeader,
children: [/* @__PURE__ */ jsx("div", { className: BaseModalClassNames.modalTitle }), /* @__PURE__ */ jsx(ModalClose, { onClick: onHide })]
}), /* @__PURE__ */ jsxs("div", {
className: clsx(BaseModalClassNames.modalBody, "flex flex-col items-center justify-center text-center"),
children: [
/* @__PURE__ */ jsx("div", {
className: "mb-2px min-h-25px flex-center text-size-14px",
children: message || ""
}),
qrcodeUrl && /* @__PURE__ */ jsxs("div", {
className: "relative mx-auto mb-40px flex-shrink-0",
children: [/* @__PURE__ */ jsx(QRCode, {
className: "p-8px",
value: qrcodeUrl,
size: 200,
icon: "https://is1-ssl.mzstatic.com/image/thumb/Purple211/v4/72/9c/b6/729cb6d8-75f5-0a56-0508-3a26cbba69ae/AppIcon-1x_U007emarketing-0-6-0-0-85-220-0.png/230x0w.webp"
}), expired && /* @__PURE__ */ jsxs("div", {
className: "absolute inset-0 flex flex-col cursor-pointer items-center justify-center bg-black/65 text-white",
onClick: onRefresh,
children: [/* @__PURE__ */ jsx("div", {
className: "mb-8px text-size-14px font-600",
children: "二维码已过期"
}), /* @__PURE__ */ jsx("div", {
className: "border border-white/40 rounded-6px px-12px py-6px text-size-13px",
children: "点击刷新"
})]
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "footnote text-size-13px line-height-snug",
children: [
"打开「哔哩哔哩」或「bilibili」App ",
/* @__PURE__ */ jsx("br", {}),
"扫码获取 access_key"
]
})
]
})]
});
}
var renderOnce = once(function render() {
const container = document.createElement("div");
container.classList.add("modal-tv-qrcode-auth", APP_CLS_ROOT);
document.body.appendChild(container);
createReactRoot(container).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(TvQrCodeAuth, {}) }));
});
/**
* 模拟 TV 端获取 access_key
* https://socialsisteryi.github.io/bilibili-API-collect/docs/login/login_action/QR.html#tv%E7%AB%AF%E6%89%AB%E7%A0%81%E7%99%BB%E5%BD%95
*/
async function refreshQrCode() {
const qrinfo = await getQrCodeInfo();
if (!qrinfo) return;
showQrCodeModal({
qrcodeUrl: qrinfo.url,
auth_code: qrinfo.auth_code
});
return true;
}
async function getAccessKeyByQrCode() {
let pollfor;
let res;
if (!await refreshQrCode()) return;
pollfor = store$1.auth_code;
function shouldBreak() {
if (!store$1.show) return true;
if (!store$1.auth_code) return true;
if (pollfor !== store$1.auth_code) return true;
}
function shouldContinue() {
return !shouldBreak();
}
while (shouldContinue()) {
const p1 = delay$1(1500);
const p2 = whenQrCodeModalHide();
await Promise.race([p1, p2]);
p2.off();
if (shouldBreak()) return;
res = await poll(store$1.auth_code);
const { success, accessKey, accessKeyExpireAt, message, action } = res;
if (shouldBreak()) return;
/**
* handle result
*/
updateStore({ message });
if (success) {
await delay$1(1e3);
hideQrCodeModal();
return {
accessKey,
accessKeyExpireAt
};
}
if (action === "refresh") {
updateStore({
message: "二维码已过期,请点击刷新",
expired: true
});
const p1 = whenQrCodeModalHide();
const p2 = whenQrCodeRefresh();
await Promise.race([p1, p2]);
p1.off();
p2.off();
if (shouldBreak()) return;
if (!await refreshQrCode()) return;
pollfor = store$1.auth_code;
updateStore({
message: "已刷新二维码",
expired: false
});
continue;
}
if (action === "wait") continue;
updateStore({ message });
toast(message);
return;
}
}
async function getAccessKey() {
const { accessKey, accessKeyExpireAt } = await getAccessKeyByQrCode() || {};
if (!accessKey || !accessKeyExpireAt) return;
settings.accessKey = accessKey;
settings.accessKeyExpireAt = accessKeyExpireAt;
toast("获取成功");
}
function deleteAccessKey() {
settings.accessKey = "";
settings.accessKeyExpireAt = 0;
toast("已删除 access_key");
}
var btnAccessKeyHelpLink = /* @__PURE__ */ jsxs(button_default, {
target: "_blank",
href: "https://github.com/indefined/UserScripts/tree/master/bilibiliHome#%E6%8E%88%E6%9D%83%E8%AF%B4%E6%98%8E",
children: [/* @__PURE__ */ jsx(IconForHelp, { className: "size-1em" }), "access_key 说明"]
});
function AccessKeyManage({ style, className }) {
const { runAsync, loading } = useRequest_default(getAccessKey, { manual: true });
const { accessKey } = useSettingsSnapshot();
return /* @__PURE__ */ jsx(Space, {
size: "small",
style,
className,
children: !accessKey ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(button_default, {
onClick: runAsync,
loading,
children: [/* @__PURE__ */ jsx(IconForAccessKey, { className: "size-1em" }), "获取 access_key"]
}), btnAccessKeyHelpLink] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(button_default, {
onClick: runAsync,
loading,
children: [/* @__PURE__ */ jsx(IconForReload, { className: "size-1em" }), "重新获取 access_key"]
}),
/* @__PURE__ */ jsx(Popconfirm, {
onConfirm: deleteAccessKey,
title: "确定删除 access_key?",
children: /* @__PURE__ */ jsxs(button_default, { children: [/* @__PURE__ */ jsx(IconForDelete, { className: "size-1em" }), "删除 access_key"] })
}),
btnAccessKeyHelpLink
] })
});
}
var import_dist = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
(function(e, t) {
if ("object" == typeof exports && "object" == typeof module) module.exports = t();
else if ("function" == typeof define && define.amd) define([], t);
else {
var r = t();
for (var s in r) ("object" == typeof exports ? exports : e)[s] = r[s];
}
})(exports, (() => (() => {
var e = {
d: (t, r) => {
for (var s in r) e.o(r, s) && !e.o(t, s) && Object.defineProperty(t, s, {
enumerable: true,
get: r[s]
});
},
o: (e, t) => Object.prototype.hasOwnProperty.call(e, t),
r: (e) => {
"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(e, Symbol.toStringTag, { value: "Module" }), Object.defineProperty(e, "__esModule", { value: true });
}
}, t = {};
e.r(t), e.d(t, { RingBuffer: () => r });
class r {
static fromArray(e, t = 0) {
const s = new r(t);
return s.fromArray(e, 0 === t), s;
}
constructor(e) {
if (this.buffer = [], this.pos = 0, e < 0) throw new RangeError("Invalid size.");
this.size = e;
}
getSize() {
return this.size;
}
getPos() {
return this.pos;
}
getBufferLength() {
return this.buffer.length;
}
add(...e) {
e.forEach(((e) => {
this.buffer[this.pos] = e, this.pos = (this.pos + 1) % this.size;
}));
}
get(e) {
if (e < 0 && (e += this.buffer.length), !(e < 0 || e > this.buffer.length)) return this.buffer.length < this.size ? this.buffer[e] : this.buffer[(this.pos + e) % this.size];
}
getFirst() {
return this.get(0);
}
getLast() {
return this.get(-1);
}
getFirstN(e) {
return 0 === e ? [] : e < 0 ? this.getLastN(-e) : this.toArray().slice(0, e);
}
getLastN(e) {
return 0 === e ? [] : e < 0 ? this.getFirstN(-e) : this.toArray().slice(-e);
}
remove(e, t = 1) {
if (e < 0 && (e += this.buffer.length), e < 0 || e > this.buffer.length) return [];
const r = this.toArray(), s = r.splice(e, t);
return this.fromArray(r), s;
}
removeFirst() {
return this.remove(0)[0];
}
removeLast() {
return this.remove(-1)[0];
}
toArray() {
return this.buffer.slice(this.pos).concat(this.buffer.slice(0, this.pos));
}
fromArray(e, t = false) {
if (!Array.isArray(e)) throw new TypeError("Input value is not an array.");
t && this.resize(e.length), 0 !== this.size && (this.buffer = e.slice(-this.size), this.pos = this.buffer.length % this.size);
}
clear() {
this.buffer = [], this.pos = 0;
}
resize(e) {
if (e < 0) throw new RangeError("The size does not allow negative values.");
if (0 === e) this.clear();
else if (e !== this.size) {
const t = this.toArray();
this.fromArray(t.slice(-e)), this.pos = this.buffer.length % e;
}
this.size = e;
}
isFull() {
return this.buffer.length === this.size;
}
isEmpty() {
return 0 === this.buffer.length;
}
}
return t;
})()));
})))();
/**
* refresh for same tab, but conditions changed
*/
function checkIsSameTabButConditionsChanged(tab, serviceRegistry) {
let s;
switch (tab) {
case ETab.DynamicFeed:
s = serviceRegistry[ETab.DynamicFeed];
return !isEqual$1(s?.config, getDynamicFeedServiceConfig());
case ETab.Fav:
s = serviceRegistry[ETab.Fav];
return !isEqual$1(s?.config, getFavServiceConfig());
case ETab.Hot:
s = serviceRegistry[ETab.Hot];
if (s?.subtab !== hotStore.subtab) return true;
if (s.subtab === EHotSubTab.Rank && s.service instanceof RankRecService && s.service.rankTab.slug !== rankStore.slug) return true;
return false;
case ETab.SpaceUpload:
s = serviceRegistry[ETab.SpaceUpload];
return !isEqual$1(s?.config, getSpaceUploadServiceConfig());
case ETab.TagRecFav:
case ETab.TagRecCustom: return checkTagRecContextDrift(tab, serviceRegistry[tab]);
default: return false;
}
}
function addServiceToQueueHistory(recSelf, tab, service) {
if (!isRecTab(tab)) return;
recSelf.serviceQueueMap[tab] ||= new import_dist.RingBuffer(5);
const queue = recSelf.serviceQueueMap[tab];
queue.add(service);
const len = queue.getBufferLength();
recSelf.setTabServiceQueueState(tab, {
len,
cursor: len - 1
});
}
var reportRefreshError = (e) => appError("refresh() error", e);
/**
* [tag-rec] AUDIT-04: refresh 主体抽为非 hook 工厂 —— node 测试可直驱泄漏/顶替语义,
* useRefresh 只负责 mount/unmount/emit 接线
*/
function createRefreshFn({ tab, debug, fetcher, preAction, postAction, updateViewFromService, self, recSelf }) {
const { serviceRegistry } = recSelf;
return async function refresh(refreshType = "refresh") {
if (recSelf.refreshing && recSelf.refreshingTab === tab) {
if (checkIsSameTabButConditionsChanged(tab, serviceRegistry)) {
debug("refresh(): tab=%s [start], current refreshing, sametab but conditions change, abort existing", tab);
self.abortController.abort();
} else {
debug("refresh() tab=%s [start], current refreshing, prevent same tab refresh()", tab);
return;
}
} else debug("refresh(): tab=%s [start]", tab);
const start = performance.now();
self.abortController.abort();
self.abortController = new AbortController();
const controller = self.abortController;
const { signal } = controller;
try {
recSelf.setStore({
refreshing: true,
refreshingTab: tab
});
self.setStore({
refreshError: void 0,
hasMore: true,
items: [],
refreshKey: Date.now()
});
await preAction?.(signal);
if (signal.aborted) return;
function _onAny() {
if (signal.aborted) return;
recSelf.setStore({
refreshing: false,
refreshingTab: void 0
});
self.setStore({ showSkeleton: false });
}
function onError(err) {
if (signal.aborted) return;
_onAny();
self.setStore({ refreshError: err });
appError(err);
}
function onSuccess() {
if (signal.aborted) return;
_onAny();
self.setStore({ hasMore: getServiceFromRegistry(serviceRegistry, tab).hasMore });
}
async function doFetch(service) {
const result = await ae.tryPromise(() => fetcher({
tab,
service,
abortSignal: signal
}));
if (signal.aborted) return false;
if (result.isErr()) {
onError(result.error.cause);
return false;
}
self.setStore({ items: result.value });
return true;
}
let existingService;
let willRecreateService;
switch (refreshType) {
case "refresh": break;
case "reuse":
existingService = serviceRegistry[tab];
if (existingService && checkTagRecContextDrift(tab, existingService)) {
delete serviceRegistry[tab];
existingService = void 0;
}
if (!existingService) {
const prewarmed = takePrewarmedTabService(tab);
if (prewarmed) {
if (!prewarmed.service.qs.fetchedCount) self.setStore({ showSkeleton: true });
await prewarmed.firstFetch;
if (signal.aborted) {
restorePrewarmedTabService(tab, prewarmed);
return;
}
if (checkTagRecContextDrift(tab, prewarmed.service)) {
willRecreateService = true;
break;
}
serviceRegistry[tab] = prewarmed.service;
existingService = prewarmed.service;
updateViewFromService?.();
addServiceToQueueHistory(recSelf, tab, prewarmed.service);
}
}
break;
case "back":
case "forward": {
invariant$1(isRecTab(tab), `only rec tab can perform back/forward`);
const [canGoBack, canGoForward] = recSelf.getTabBackForwardStatus(tab);
if (refreshType === "back" && !canGoBack) throw new Error("cannot go back");
if (refreshType === "forward" && !canGoForward) throw new Error("cannot go forward");
const state = recSelf.getTabServiceQueueState(tab);
invariant$1(state, `state should not be nil`);
const { len, cursor } = state;
const newCursor = cursor + (refreshType === "back" ? -1 : 1);
if (newCursor < 0 || newCursor > len - 1) throw new Error("cannot go back/forward");
const service = recSelf.serviceQueueMap[tab]?.get(newCursor);
invariant$1(service, `service should not be nil`);
if (checkTagRecContextDrift(tab, service)) {
recSelf.setTabServiceQueueState(tab, {
cursor: newCursor,
len
});
delete serviceRegistry[tab];
willRecreateService = true;
break;
}
existingService = service;
recSelf.setTabServiceQueueState(tab, {
cursor: newCursor,
len
});
serviceRegistry[tab] = service;
willRecreateService = false;
break;
}
default: assertNever(refreshType);
}
if (existingService) {
await existingService.restore();
if (checkTagRecContextDrift(tab, existingService)) {
delete serviceRegistry[tab];
existingService = void 0;
willRecreateService = true;
} else {
const cachedItems = filterRecItems(existingService.qs.bufferQueue, tab).slice(0, getGridRefreshCount());
if (signal.aborted) return;
self.setStore({ items: cachedItems });
const success = await doFetch(existingService);
if (signal.aborted) return;
willRecreateService ??= success && !!TabConfig[tab].swr;
}
} else {
if (signal.aborted) return;
self.setStore({ showSkeleton: true });
willRecreateService = true;
}
if (willRecreateService) {
if (tab === ETab.TagRecFav || tab === ETab.TagRecCustom) {
await Promise.all([tagRecStoresHydrated, loadTagRecServiceClasses()]);
if (signal.aborted) return;
}
const result = ae.try(() => createServiceMap[tab]({ existingService }));
if (result.isErr()) return onError(result.error.cause);
const service = result.value;
serviceRegistry[tab];
if (signal.aborted) return;
serviceRegistry[tab] = service;
updateViewFromService?.();
addServiceToQueueHistory(recSelf, tab, service);
const success = await doFetch(service);
if (signal.aborted || !success) return;
}
if (signal.aborted) return;
onSuccess();
await postAction?.(signal);
debug("refresh(): tab=%s [success] cost %s ms", tab, (performance.now() - start).toFixed(0));
} finally {
if (self.abortController === controller) {
recSelf.setStore({
refreshing: false,
refreshingTab: void 0
});
self.setStore({ showSkeleton: false });
}
}
};
}
function useRefresh({ tab, debug, fetcher, preAction, postAction, updateViewFromService, self }) {
const recSelf = useRecSelfContext();
const refresh = useMemoizedFn(createRefreshFn({
tab,
debug,
fetcher,
preAction,
postAction,
updateViewFromService,
self,
recSelf
}));
useMount(() => {
Promise.resolve(refresh("reuse")).catch(reportRefreshError);
});
useUnmount(() => {
self.abortController.abort();
});
const { recSharedEmitter } = recSelf;
useEmitterOn(recSharedEmitter, "refresh", ({ data: refreshType }) => Promise.resolve(refresh(refreshType)).catch(reportRefreshError));
return { refresh };
}var checkIfAllInShadow = (targets) => {
return targets.every((item) => {
const targetElement = getTargetElement(item);
if (!targetElement) return false;
if (targetElement.getRootNode() instanceof ShadowRoot) return true;
return false;
});
};
var getShadow = (node) => {
if (!node) return document;
return node.getRootNode();
};
var getDocumentOrShadow = (target) => {
if (!target || !document.getRootNode) return document;
const targets = Array.isArray(target) ? target : [target];
if (checkIfAllInShadow(targets)) return getShadow(getTargetElement(targets[0]));
return document;
};
var useClickAway = (onClickAway, target, eventName = "click") => {
const onClickAwayRef = useLatest(onClickAway);
useEffectWithTarget(() => {
const handler = (event) => {
if ((Array.isArray(target) ? target : [target]).some((item) => {
const targetElement = getTargetElement(item);
return !targetElement || targetElement.contains(event.target);
})) return;
onClickAwayRef.current(event);
};
const documentOrShadow = getDocumentOrShadow(target);
const eventNames = Array.isArray(eventName) ? eventName : [eventName];
eventNames.forEach((event) => {
documentOrShadow.addEventListener(event, handler);
});
return () => {
eventNames.forEach((event) => {
documentOrShadow.removeEventListener(event, handler);
});
};
}, Array.isArray(eventName) ? eventName : [eventName], target);
};
var import_react$3 = /* @__PURE__ */ __toESM(require_react(), 1);
function useRefState(initialValue) {
const [state, setState] = (0, import_react$3.useState)(initialValue);
const ref = (0, import_react$3.useRef)(state);
return [
state,
(0, import_react$3.useCallback)((payload) => {
const nextState = typeof payload === "function" ? payload(ref.current) : payload;
ref.current = nextState;
setState(nextState);
}, [setState]),
(0, import_react$3.useCallback)(() => ref.current, [])
];
}
function useRefStateBox(initialValue) {
const [state, set, get] = useRefState(initialValue);
const box = (0, import_react$3.useMemo)(() => ({
state,
get,
set,
get val() {
return get();
},
set val(newValue) {
set(newValue);
}
}), [get, set]);
box.state = state;
return box;
}
function useRefBox(initialValue) {
const ref = (0, import_react$3.useRef)(initialValue);
const get = (0, import_react$3.useCallback)(() => ref.current, []);
const set = (0, import_react$3.useCallback)((newValue) => ref.current = newValue, []);
return (0, import_react$3.useMemo)(() => ({
get,
set,
get val() {
return get();
},
set val(newValue) {
set(newValue);
}
}), [get, set]);
}
var require_typeof = /* @__PURE__ */ __commonJSMin(((exports, module) => {
function _typeof(o) {
"@babel/helpers - typeof";
return module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
return typeof o;
} : function(o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof(o);
}
module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports;
}));
var require_utils = /* @__PURE__ */ __commonJSMin(((exports) => {
var _interopRequireDefault = require_interopRequireDefault();
Object.defineProperty(exports, "__esModule", { value: true });
exports.isUndef = exports.isThenable = exports.isString = exports.isObject = exports.isNumber = exports.isNonNullable = exports.isFunction = exports.isBoolean = void 0;
var _typeof2 = _interopRequireDefault(require_typeof());
exports.isObject = function isObject(val) {
return val !== null && (0, _typeof2["default"])(val) === "object";
};
var isFunction = exports.isFunction = function isFunction(val) {
return typeof val === "function";
};
exports.isString = function isString(val) {
return typeof val === "string";
};
exports.isBoolean = function isBoolean(val) {
return typeof val === "boolean";
};
exports.isNumber = function isNumber(val) {
return typeof val === "number" && !Number.isNaN(val);
};
exports.isUndef = function isUndef(val) {
return typeof val === "undefined";
};
var isNonNullable = exports.isNonNullable = function isNonNullable(val) {
return val !== void 0 && val !== null;
};
exports.isThenable = function isThenable(val) {
return isNonNullable(val) && isFunction(val.then);
};
}));
var require_isBrowser = /* @__PURE__ */ __commonJSMin(((exports) => {
Object.defineProperty(exports, "__esModule", { value: true });
exports["default"] = void 0;
exports["default"] = !!(typeof window !== "undefined" && window.document && window.document.createElement);
}));
var require_domTarget = /* @__PURE__ */ __commonJSMin(((exports) => {
var _interopRequireDefault = require_interopRequireDefault();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTargetElement = getTargetElement;
var _index = require_utils();
var _isBrowser = _interopRequireDefault(require_isBrowser());
function getTargetElement(target, defaultElement) {
if (!_isBrowser["default"]) return;
if (!target) return defaultElement;
var targetElement;
if ((0, _index.isFunction)(target)) targetElement = target();
else if ("current" in target) targetElement = target.current;
else targetElement = target;
return targetElement;
}
}));
var import_ms$1 = /* @__PURE__ */ __toESM(require_ms(), 1);
var import_domTarget = require_domTarget();
/**
* see https://socialsisteryi.github.io/bilibili-API-collect/docs/video/videostream_url.html
*/
async function getVideoPlayUrl(videoId, cid, useMp4 = false, usePreferredCdn = false) {
const params = {
cid,
fnver: 0,
fnval: useMp4 ? 1 : 16
};
const _videoId = videoId.toString();
if (_videoId.startsWith("BV")) params.bvid = _videoId;
else if (/^\d+$/.test(_videoId)) params.avid = _videoId;
else throw new Error("Invalid videoId provided, must be avid | bvid");
const json = (await request.get("/x/player/wbi/playurl", { params })).data;
function reOrderUrls(urls) {
return fastOrderBy(urls, [(u) => getUrlPriority(u, usePreferredCdn)], ["desc"]);
}
if (json?.data?.durl) {
const urls = (json.data.durl || []).map((x) => [x.url, ...x.backup_url || []]).flat().filter(Boolean);
if (urls.length) return reOrderUrls(urls);
}
return fastOrderBy(json.data?.dash?.video || [], ["id", "codecid"], ["desc", "desc"]).map((x) => reOrderUrls([x.baseUrl, ...x.backupUrl || []])[0]).filter(Boolean);
}
function getUrlPriority(url, usePreferredCdn) {
if (!usePreferredCdn) return 10;
const { hostname, searchParams, pathname } = new URL(url);
if (hostname.includes("mcdn") || searchParams.get("os") === "mcdn") return 2;
if (pathname.startsWith("/v1/resource/")) return 1;
if (hostname.includes("-mirror")) return 100;
return 10;
}
function isVideoshotDataValid(videoshotData) {
return Boolean(videoshotData?.image?.length && videoshotData?.index?.length);
}
/**
* cacheable: no result | valid result
* 但不能是 half-valid: ({ image: [1,2,3], index: [空] })
*/
function isVideoshotJsonCacheable(json) {
if (!isWebApiSuccess(json)) return true;
else return isVideoshotDataValid(json.data);
}
async function __fetchVideoshotJson(bvid) {
const json = (await request.get("/x/player/videoshot", { params: {
bvid,
index: "1"
} })).data;
if (!isWebApiSuccess(json)) appWarn("videoshot error for %s: %o", bvid, json);
if (!isVideoshotDataValid(json.data)) appWarn("videoshot data invalid bvid=%s: %o", bvid, json.data);
return json;
}
var videoshotCache = new QuickLRU({ maxSize: 1e4 });
var getVideoshotJson = reusePendingPromise(async (bvid) => {
if (videoshotCache.has(bvid)) {
const cached = videoshotCache.get(bvid);
if (cached) return cached;
}
const MAX_RETRY = 5;
const DELAY = 200;
let retryTimes = 0;
let videoshotJson;
do {
retryTimes++;
videoshotJson = await __fetchVideoshotJson(bvid);
if (isVideoshotJsonCacheable(videoshotJson)) break;
else await delay$1(DELAY);
} while (retryTimes < MAX_RETRY);
if (isVideoshotJsonCacheable(videoshotJson)) videoshotCache.set(bvid, videoshotJson);
const videoshotData = videoshotJson.data;
if (settings.videoCard.imgPreview.autoPreviewWhenHover) {
const imgs = videoshotData?.image || [];
await preloadImg(imgs[0]);
(async () => {
for (const src of imgs.slice(1)) await preloadImg(src);
})();
}
return videoshotJson;
});
var debug$1$1 = baseDebug.extend("VideoCard:services");
/**
* 添加/删除 "稍后再看"
* add 支持 avid & bvid, del 只支持 avid. 故使用 avid
*/
function watchlaterFactory(action) {
return function watchlaterOp(avid) {
return ae.gen(async function* () {
const form = new URLSearchParams({
aid: avid,
csrf: getCsrfToken()
});
const resp = yield* await request.safePost(`/x/v2/history/toview/${action}`, form);
const json = yield* WebApiError.validateAxiosResponse(resp, `${action === "add" ? "添加" : "移除"}稍后再看失败`);
return ae.ok(json);
});
};
}
var watchlaterAdd = watchlaterFactory("add");
var watchlaterDel = watchlaterFactory("del");
async function fetchImagePreviewData(bvid) {
return { videoshotJson: await getVideoshotJson(bvid) };
}
function isImagePreviewDataValid(data) {
return isVideoshotDataValid(data?.videoshotJson?.data);
}
var isVideoPreviewDataValid = (data) => {
return !!data?.playUrls?.length;
};
async function __fetchVideoPreviewData({ videoTitle, bvid, cid, useMp4, usePreferredCdn, aspectRatioFromItem }) {
let dimension;
if (cid === void 0 || aspectRatioFromItem === void 0) {
const pages = await getVideoPageList(bvid);
cid = pages[0]?.cid;
dimension = pages[0]?.dimension;
if (cid === void 0) throw new TypeError(`can not get cid by bvid=${bvid}`);
}
const playUrlResult = await ae.tryPromise(() => getVideoPlayUrl(bvid, cid, useMp4, usePreferredCdn));
debug$1$1("playUrl: bvid=%s cid=%s %o", bvid, cid, playUrlResult);
if (playUrlResult.isErr()) {
const err = playUrlResult.error.cause;
antNotification.error({
title: "获取视频播放地址失败",
description: err.message || err
});
throw err;
}
const playUrls = playUrlResult.value;
if (!playUrls.length) antNotification.info({
title: "视频播放地址: 空",
description: /* @__PURE__ */ jsxs(Fragment, { children: ["视频: ", videoTitle || bvid] })
});
return {
playUrls,
dimension
};
}
var fetchVideoPreviewData = pMemoize(__fetchVideoPreviewData, {
cache: new QuickLRU({
maxSize: 1e4,
maxAge: (0, import_ms$1.default)("1h")
}),
cacheKey([{ bvid, useMp4, usePreferredCdn }]) {
return new URLSearchParams({
bvid: bvid.toString(),
useMp4: useMp4.toString(),
usePreferredCdn: usePreferredCdn.toString()
}).toString();
},
shouldCache: isVideoPreviewDataValid
});
var debug$2 = baseDebug.extend("large-preview:LargePreview");
var DirectionConfig = {
right: {
multiplier: 1,
axis: "x",
reverse: "left"
},
left: {
multiplier: -1,
axis: "x",
reverse: "right"
},
bottom: {
multiplier: 1,
axis: "y",
reverse: "top"
},
top: {
multiplier: -1,
axis: "y",
reverse: "bottom"
}
};
var VisualPadding = {
border: 40,
card: 10
};
function getCoverRect(anchorElement) {
return anchorElement?.closest(classListToSelector(APP_CLS_CARD, "bili-video-card"))?.querySelector(classListToSelector(APP_CLS_CARD_COVER, "bili-video-card__image"))?.getBoundingClientRect();
}
var LargePreview = (0, import_react$3.memo)(function LargePreview({ children, aspectRatio = 1.7777777777777777, cardDescendantTarget, ref: propRef, ...restProps }) {
const popoverRef = useDelegatedRef(propRef);
const [visible, setVisible] = (0, import_react$3.useState)(false);
const [position, setPosition] = (0, import_react$3.useState)(void 0);
debug$2("render state: %o", {
visible,
position
});
const hide = useMemoizedFn(() => {
setVisible(false);
setPosition(void 0);
});
const calculatePostion = useMemoizedFn(() => {
const cardCoverRect = getCoverRect((0, import_domTarget.getTargetElement)(cardDescendantTarget) ?? void 0);
if (!cardCoverRect) return hide();
const [viewportWidth, viewportHeight] = [document.documentElement.clientWidth, document.documentElement.clientHeight];
const tolerance = 40;
if (cardCoverRect.top > viewportHeight - tolerance || cardCoverRect.bottom < 40 || cardCoverRect.left > viewportWidth - tolerance || cardCoverRect.right < 40) return hide();
const possibleBoundingBox = {
top: {
x: 0,
y: 0,
width: viewportWidth,
height: cardCoverRect.top
},
bottom: {
x: 0,
y: cardCoverRect.bottom,
width: viewportWidth,
height: viewportHeight - cardCoverRect.bottom
},
left: {
x: 0,
y: 0,
width: cardCoverRect.left,
height: viewportHeight
},
right: {
x: cardCoverRect.right,
y: 0,
width: viewportWidth - cardCoverRect.right,
height: viewportHeight
}
};
const getScaleInBox = (bbox) => {
const w = aspectRatio;
const h = 1;
const scaleX = bbox.width / w;
const scaleY = bbox.height / h;
return {
scale: Math.min(scaleX, scaleY),
scaleLimit: scaleX > scaleY ? "height" : "width"
};
};
const picked = orderBy(Object.entries(possibleBoundingBox).map(([direction, bbox]) => ({
direction,
bbox,
...getScaleInBox(bbox)
})), ["scale", (x) => {
switch (x.direction) {
case "top": return cardCoverRect.top;
case "bottom": return viewportHeight - cardCoverRect.bottom;
case "left": return cardCoverRect.left;
case "right": return viewportWidth - cardCoverRect.right;
}
}], ["desc", "desc"])[0];
debug$2("picked direction", picked);
const { direction, bbox, scale, scaleLimit } = picked;
let elWidth;
let elHeight;
if (scaleLimit === "width") {
elWidth = Math.floor(bbox.width - (VisualPadding.card + VisualPadding.border));
elHeight = elWidth / aspectRatio;
} else if (scaleLimit === "height") {
elHeight = Math.floor(bbox.height - (VisualPadding.card + VisualPadding.border));
elWidth = elHeight * aspectRatio;
} else throw new Error("unexpected scaleLimit");
let elPosX = 0;
let elPosY = 0;
let arrowTop = 0;
let arrowLeft = 0;
const setArrowTop = () => {
arrowTop = cardCoverRect.y + cardCoverRect.height / 2 - elPosY;
};
const setArrowLeft = () => {
arrowLeft = cardCoverRect.x + cardCoverRect.width / 2 - elPosX;
};
const fixX = () => {
if (elPosX < VisualPadding.border) {
elPosX = VisualPadding.border;
return;
}
if (elPosX + elWidth > viewportWidth - VisualPadding.border) {
elPosX = viewportWidth - VisualPadding.border - elWidth;
return;
}
};
const fixY = () => {
if (elPosY < VisualPadding.border) {
elPosY = VisualPadding.border;
return;
}
if (elPosY + elHeight > viewportHeight - VisualPadding.border) {
elPosY = viewportHeight - VisualPadding.border - elHeight;
return;
}
};
switch (direction) {
case "top":
elPosX = cardCoverRect.x + cardCoverRect.width / 2 - elWidth / 2;
elPosY = cardCoverRect.top - VisualPadding.card - elHeight;
fixX();
setArrowLeft();
break;
case "bottom":
elPosX = cardCoverRect.x + cardCoverRect.width / 2 - elWidth / 2;
elPosY = cardCoverRect.bottom + VisualPadding.card;
fixX();
setArrowLeft();
break;
case "right":
elPosX = cardCoverRect.right + VisualPadding.card;
elPosY = cardCoverRect.y + cardCoverRect.height / 2 - elHeight / 2;
fixY();
setArrowTop();
break;
case "left":
elPosX = cardCoverRect.left - VisualPadding.card - elWidth;
elPosY = cardCoverRect.y + cardCoverRect.height / 2 - elHeight / 2;
fixY();
setArrowTop();
}
elPosX = Math.floor(elPosX);
elPosY = Math.floor(elPosY);
const newPosition = {
direction,
elWidth,
elHeight,
elPosX,
elPosY,
arrowTop,
arrowLeft
};
debug$2("updating state to: visible: ✅, position: %o ", newPosition);
setVisible(true);
setPosition(newPosition);
});
const calculatePostionThrottled = (0, import_react$3.useMemo)(() => throttle(calculatePostion, 100), [calculatePostion]);
useMount(calculatePostion);
useEventListener("resize", calculatePostionThrottled, { target: window });
useEventListener("scroll", calculatePostionThrottled, { target: window });
const { useScale } = useSettingsSnapshot().videoCard.videoPreview;
const animationDuration = useScale ? .2 : .3;
const initial = (0, import_react$3.useMemo)(() => {
const direction = position?.direction;
if (!direction) return;
const { axis, multiplier, reverse } = DirectionConfig[direction];
if (!useScale) {
let animateDistance = 30;
if (direction === "top") animateDistance = 20;
if (axis === "x") return {
x: -multiplier * animateDistance,
y: 0
};
else return {
x: 0,
y: -multiplier * animateDistance
};
} else if (axis === "x") return {
scale: .5,
transformOrigin: `${reverse} ${position.arrowTop}px`
};
else return {
scale: .5,
transformOrigin: `${position.arrowLeft}px ${reverse}`
};
}, [position, useScale]);
return /* @__PURE__ */ jsx("div", {
...restProps,
ref: popoverRef,
className: clsx("fixed", clsZVideoCardLargePreview, visible ? "block" : "hidden"),
css: position && /* @__PURE__ */ css("width:", position.elWidth, "px;height:", position.elHeight, "px;top:", position.elPosY, "px;left:", position.elPosX, "px;"),
children: visible && /* @__PURE__ */ jsxs(motion$1.div, {
className: "relative h-100%",
initial: {
opacity: 0,
...initial
},
animate: {
opacity: 1,
x: 0,
y: 0,
scale: 1
},
transition: {
bounce: 0,
duration: animationDuration
},
children: [position?.direction && /* @__PURE__ */ jsx(PopoverArrow, {
size: 7,
direction: position.direction,
arrowTop: position.arrowTop,
arrowLeft: position.arrowLeft
}), /* @__PURE__ */ jsx("div", {
className: "relative h-full overflow-hidden rounded-20px bg-white/50% ring-1px ring-gate-primary backdrop-blur-10px",
children
})]
})
});
});
function PopoverArrow({ size, direction, arrowTop, arrowLeft }) {
const { axis, multiplier, reverse } = DirectionConfig[direction];
const extra = (0, import_react$3.useMemo)(() => {
if (axis === "x") return {
[direction]: "100%",
"top": `${arrowTop}px`,
[`margin-${direction}`]: `-1px`,
"margin-top": `-${size / 2}px`
};
else return {
[direction]: `100%`,
"left": `${arrowLeft}px`,
[`margin-${direction}`]: `-1px`,
"margin-left": `-${size / 2}px`
};
}, [
size,
direction,
axis,
arrowTop,
arrowLeft
]);
return /* @__PURE__ */ jsx("div", {
className: "absolute box-content h-0 w-0",
style: {
border: `${size}px solid transparent`,
[`border-${direction}-color`]: appPrimaryColorValue,
...extra
}
});
}
var store = await proxyWithGmStorage({
volume: void 0,
muted: void 0
}, "large-preview-store");
var RecoverableVideo = function({ currentTimeRef, ref: propRef, ...videoProps }) {
const ref = useDelegatedRef(propRef);
const mountedRef = (0, import_react$3.useRef)(false);
useMount(() => {
if (ref.current) {
if (typeof currentTimeRef.current === "number") ref.current.currentTime = currentTimeRef.current;
if (typeof store.volume === "number") ref.current.volume = store.volume;
if (typeof store.muted === "boolean") ref.current.muted = store.muted;
}
mountedRef.current = true;
});
const onTimeUpdate = useMemoizedFn(() => {
if (!mountedRef.current) return;
currentTimeRef.current = ref.current?.currentTime;
});
const onVolumeChange = useMemoizedFn(() => {
if (!mountedRef.current) return;
if (!ref.current) return;
store.volume = ref.current.volume;
store.muted = ref.current.muted;
});
return /* @__PURE__ */ jsx("video", {
ref,
...videoProps,
onTimeUpdate,
onVolumeChange
});
};
var iconParkOutlinePin = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinejoin: "round",
strokeWidth: 4,
d: "M10.696 17.504c2.639-2.638 5.774-2.565 9.182-.696L32.62 9.745l-.721-4.958L43.213 16.1l-4.947-.71l-7.074 12.73c1.783 3.638 1.942 6.544-.697 9.182l-7.778-7.778L6.443 41.556l11.995-16.31z"
})]
});
var ForwardRef$1$2 = (0, import_react$3.forwardRef)(iconParkOutlinePin);
var iconParkOutlineVideoTwo = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
children: [/* @__PURE__ */ jsx("path", { d: "M39 6H9a3 3 0 0 0-3 3v30a3 3 0 0 0 3 3h30a3 3 0 0 0 3-3V9a3 3 0 0 0-3-3" }), /* @__PURE__ */ jsx("path", { d: "M20.5 28v-6.062l5.25 3.03L31 28l-5.25 3.031l-5.25 3.031zM6 15h36m-9-9l-6 9m-6-9l-6 9" })]
})]
});
var ForwardRef$a = (0, import_react$3.forwardRef)(iconParkOutlineVideoTwo);
function clearTimerRef(timerRef) {
if (timerRef.current === void 0) return;
clearTimeout(timerRef.current);
timerRef.current = void 0;
}
var debugTrigger = baseVerboseDebug.extend("large-preview:trigger");
function useLargePreviewRelated({ shouldFetchPreviewData, hasLargePreviewActionButton, actionButtonVisible, actionButtonProps, actionButtonCss, bvid, cid, uniqId, recSharedEmitter, cardTarget, aspectRatioFromItem, cover, videoCardAsTriggerRef, videoTitle }) {
const { useMp4, useVideoCardAsTrigger, usePreferredCdn } = useSnapshot(settings.videoCard.videoPreview);
const videoPreviewDataBox = useRefStateBox(void 0);
const tryFetchVideoPreviewData = useLockFn(async () => {
if (!shouldFetchPreviewData) return;
if (isVideoPreviewDataValid(videoPreviewDataBox.val)) return;
const data = await fetchVideoPreviewData({
videoTitle,
bvid,
cid,
useMp4,
usePreferredCdn,
aspectRatioFromItem
});
videoPreviewDataBox.set(data);
});
useUpdateEffect_default(() => {
videoPreviewDataBox.set(void 0);
}, [useMp4, usePreferredCdn]);
const $req = useRequest_default(tryFetchVideoPreviewData, {
manual: true,
loadingDelay: 100
});
const [visible, setVisible] = (0, import_react$3.useState)(false);
const triggerAction = useRefStateBox(void 0);
const triggerElement = useRefStateBox(void 0);
const hideAt = useRefStateBox(void 0);
const hoveringRef = useRefBox({});
const isRecentlyHidden = useMemoizedFn(() => {
if (!hideAt.val) return false;
return Date.now() - hideAt.val < 1200;
});
const enterTimer = (0, import_react$3.useRef)(void 0);
const leaveTimer = (0, import_react$3.useRef)(void 0);
const clearTimers = useMemoizedFn(() => {
clearTimerRef(enterTimer);
clearTimerRef(leaveTimer);
});
const showBy = useMemoizedFn((action, el) => {
debugTrigger("%s: showBy %s %s", bvid, action, el);
setVisible(true);
triggerAction.set(action);
triggerElement.set(el);
recSharedEmitter.emit("show-large-preview", uniqId);
hideAt.set(void 0);
});
const hide = useMemoizedFn((debug = true) => {
if (debug) debugTrigger("%s: hide", bvid);
setVisible(false);
triggerAction.set(void 0);
triggerElement.set(void 0);
hideAt.set(Date.now());
});
useEmitterOn(recSharedEmitter, "show-large-preview", ({ data: srcUniqId }) => {
if (srcUniqId === uniqId) return;
clearTimers();
hide(false);
});
const handleMouseEnter = useMemoizedFn((triggerEl) => {
debugTrigger("%s: onMouseEnter %s", bvid, triggerEl);
hoveringRef.set({
...hoveringRef.val,
[triggerEl]: true
});
if (triggerAction.val === "click") return;
$req.run();
clearTimers();
let delayMs = 0;
if (triggerEl === "video-card-action-button") delayMs = 200;
if (triggerEl === "video-card") delayMs = 1e3;
if (!delayMs) showBy("hover", triggerEl);
else enterTimer.current = setTimeout(() => showBy("hover", triggerEl), delayMs);
});
const handleMouseLeave = useMemoizedFn((triggerEl) => {
debugTrigger("%s: onMouseLeave %s", bvid, triggerEl);
hoveringRef.set({
...hoveringRef.val,
[triggerEl]: false
});
if (triggerAction.val === "click") return;
const checkHide = () => {
if (hoveringRef.val.popover) return;
if (hoveringRef.val["video-card-action-button"]) return;
hide();
};
clearTimers();
if (triggerEl === "video-card-action-button" || triggerEl === "video-card" || triggerEl === "popover") leaveTimer.current = setTimeout(checkHide, 250);
else checkHide();
});
const handleClick = useMemoizedFn((el) => {
clearTimers();
if (triggerAction.val === "click") hide();
else showBy("click", el);
});
const getLargePreviewCurrentTime = useMemoizedFn(() => {
if (!currentTimeRef.current) return;
return Math.floor(currentTimeRef.current);
});
const shouldUseLargePreviewCurrentTime = useMemoizedFn(() => {
if (visible) return true;
if (isRecentlyHidden()) return true;
return false;
});
const onOpenInNewTab = useMemoizedFn(() => {
if (!bvid) return;
const u = new URL(`https://${BiliDomain.Main}/video/${bvid}`);
const t = getLargePreviewCurrentTime();
if (t) u.searchParams.set("t", t.toString());
openNewTab(u.href);
videoRef.current?.pause();
hide();
});
const usingAspectRatio = (0, import_react$3.useMemo)(() => {
return getRecItemDimension({ dimensionFromApi: videoPreviewDataBox.state?.dimension })?.aspectRatio ?? aspectRatioFromItem;
}, [videoPreviewDataBox.state?.dimension]);
const videoRef = (0, import_react$3.useRef)(null);
const currentTimeRef = (0, import_react$3.useRef)(void 0);
const largePreviewRef = (0, import_react$3.useRef)(null);
const willRenderLargePreview = visible && !!videoPreviewDataBox.state?.playUrls?.length;
let largePreviewEl;
{
const onMouseEnter = (0, import_react$3.useCallback)(() => handleMouseEnter("popover"), [handleMouseEnter]);
const onMouseLeave = (0, import_react$3.useCallback)(() => handleMouseLeave("popover"), [handleMouseLeave]);
largePreviewEl = willRenderLargePreview && /* @__PURE__ */ jsxs(LargePreview, {
ref: largePreviewRef,
aspectRatio: usingAspectRatio,
onMouseEnter,
onMouseLeave,
cardDescendantTarget: cardTarget,
children: [/* @__PURE__ */ jsx(RecoverableVideo, {
ref: videoRef,
currentTimeRef,
autoPlay: true,
controls: true,
loop: true,
poster: cover,
className: "block size-full object-contain",
children: videoPreviewDataBox.state?.playUrls?.map((url, i) => /* @__PURE__ */ jsx("source", { src: url }, i))
}), /* @__PURE__ */ jsxs("div", {
className: "absolute right-10px top-10px flex flex-row-reverse items-center justify-start gap-x-5px",
children: [triggerAction.state === "click" ? /* @__PURE__ */ jsx(VideoCardActionButton, {
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(ForwardRef$31, { className: "size-14px" }),
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: ["关闭", /* @__PURE__ */ jsx(HotkeyDisplay, {
k: "Esc",
className: "ml-1"
})] }),
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
hide();
}
}) : /* @__PURE__ */ jsx(VideoCardActionButton, {
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(ForwardRef$1$2, { className: "size-14px" }),
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: ["固定", /* @__PURE__ */ jsx(HotkeyDisplay, {
k: "P",
className: "ml-1"
})] }),
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleClick("popover-action-button");
}
}), /* @__PURE__ */ jsx(VideoCardActionButton, {
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(ForwardRef$32, { className: "size-14px" }),
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: ["新窗口打开", /* @__PURE__ */ jsx(HotkeyDisplay, {
k: "Enter",
className: "ml-1"
})] }),
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
onOpenInNewTab();
}
})]
})]
});
}
let largePreviewActionButtonEl;
{
const onMouseEnter = (0, import_react$3.useCallback)(() => handleMouseEnter("video-card-action-button"), [handleMouseEnter]);
const onMouseLeave = (0, import_react$3.useCallback)(() => handleMouseLeave("video-card-action-button"), [handleMouseLeave]);
const onClick = (0, import_react$3.useCallback)((e) => {
e.preventDefault();
e.stopPropagation();
handleClick("video-card-action-button");
}, [handleClick]);
largePreviewActionButtonEl = hasLargePreviewActionButton && shouldFetchPreviewData && /* @__PURE__ */ jsx(VideoCardActionButton, {
css: actionButtonCss,
...actionButtonProps,
visible: actionButtonVisible,
active: willRenderLargePreview,
inlinePosition: "right",
icon: $req.loading ? /* @__PURE__ */ jsx(IconForLoading, { className: "size-16px" }) : /* @__PURE__ */ jsx(ForwardRef$a, { className: "size-15px" }),
tooltip: triggerAction.state === "click" ? visible ? "关闭浮动预览" : "浮动预览" : "浮动预览",
onMouseEnter,
onMouseLeave,
onClick
});
}
/**
* Pin hotkey
*/
useHotkey("P", () => handleClick("popover-action-button"), {
conflictBehavior: "allow",
enabled: visible && !!largePreviewEl && !!largePreviewActionButtonEl && triggerAction.state === "hover"
});
/**
* trigger by click, more ways to close
*/
useHotkey("Escape", hide, { conflictBehavior: "allow" });
useClickAway(() => hide(), [largePreviewRef, () => (0, import_domTarget.getTargetElement)(cardTarget)?.closest(classListToSelector(APP_CLS_CARD, "bili-video-card"))]);
/**
* trigger by hover, when video goes into fullscreen, switch trigger to click
* 这样可以防止 "一进入全屏, 马上触发 mouseleave, 触发关闭" 的 case
*/
useEventListener("fullscreenchange", () => {
if (!document.fullscreenElement) return;
if (!visible || triggerAction.val === "click") return;
if (document.fullscreenElement === videoRef.current) showBy("click", "popover-video-fullscreen-button");
}, { target: document });
const emptyRef = (0, import_react$3.useRef)(null);
const target = videoCardAsTriggerRef || emptyRef;
useEventListener("mouseenter", () => {
if (!useVideoCardAsTrigger || !videoCardAsTriggerRef) return;
handleMouseEnter("video-card");
}, { target });
useEventListener("mouseleave", () => {
if (!useVideoCardAsTrigger || !videoCardAsTriggerRef) return;
handleMouseLeave("video-card");
}, { target });
return {
largePreviewActionButtonEl,
largePreviewEl,
getLargePreviewCurrentTime,
shouldUseLargePreviewCurrentTime,
largePreviewVisible: visible,
hideLargePreview: hide
};
}var clsGateVideoGridDivider = "_cls--gate-video-grid--divider_h4766_1";
var clsGateVideoCardContextMenuRoot = "_cls--gate-video-card--context-menu--root_h4766_13";
/**
* Checks if `value` is an Error object.
*
* @param value The value to check.
* @returns Returns `true` if `value` is an Error object, `false` otherwise.
*
* @example
* ```typescript
* console.log(isError(new Error())); // true
* console.log(isError('Error')); // false
* console.log(isError({ name: 'Error', message: '' })); // false
* ```
*/
function isError(value) {
return value instanceof Error;
}
var wrap = (min, max, v) => {
const rangeSize = max - min;
return ((v - min) % rangeSize + rangeSize) % rangeSize + min;
};
/*#__NO_SIDE_EFFECTS__*/
function getEasingForSegment(easing, i) {
return isEasingArray(easing) ? easing[wrap(0, easing.length, i)] : easing;
}
var GroupAnimation = class {
constructor(animations) {
this.stop = () => this.runAll("stop");
this.animations = animations.filter(Boolean);
}
get finished() {
return Promise.all(this.animations.map((animation) => animation.finished));
}
/**
* TODO: Filter out cancelled or stopped animations before returning
*/
getAll(propName) {
return this.animations[0][propName];
}
setAll(propName, newValue) {
for (let i = 0; i < this.animations.length; i++) this.animations[i][propName] = newValue;
}
attachTimeline(timeline) {
const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline));
return () => {
subscriptions.forEach((cancel, i) => {
cancel && cancel();
this.animations[i].stop();
});
};
}
get time() {
return this.getAll("time");
}
set time(time) {
this.setAll("time", time);
}
get speed() {
return this.getAll("speed");
}
set speed(speed) {
this.setAll("speed", speed);
}
get state() {
return this.getAll("state");
}
get startTime() {
return this.getAll("startTime");
}
get duration() {
return getMax(this.animations, "duration");
}
get iterationDuration() {
return getMax(this.animations, "iterationDuration");
}
runAll(methodName) {
this.animations.forEach((controls) => controls[methodName]());
}
play() {
this.runAll("play");
}
pause() {
this.runAll("pause");
}
cancel() {
this.runAll("cancel");
}
complete() {
this.runAll("complete");
}
};
function getMax(animations, propName) {
let max = 0;
for (let i = 0; i < animations.length; i++) {
const value = animations[i][propName];
if (value !== null && value > max) max = value;
}
return max;
}
var GroupAnimationWithThen = class extends GroupAnimation {
then(onResolve, _onReject) {
return this.finished.finally(onResolve).then(() => {});
}
};
function isObjectKey(key, object) {
return key in object;
}
var ObjectVisualElement = class extends VisualElement {
constructor() {
super(...arguments);
this.type = "object";
}
readValueFromInstance(instance, key) {
if (isObjectKey(key, instance)) {
const value = instance[key];
if (typeof value === "string" || typeof value === "number") return value;
}
}
getBaseTargetFromProps() {}
removeValueFromRenderState(key, renderState) {
delete renderState.output[key];
}
measureInstanceViewportBox() {
return createBox();
}
build(renderState, latestValues) {
Object.assign(renderState.output, latestValues);
}
renderInstance(instance, { output }) {
Object.assign(instance, output);
}
sortInstanceNodePosition() {
return 0;
}
};
var import_react$2 = /* @__PURE__ */ __toESM(require_react(), 1);
function useUnmountEffect(callback) {
return (0, import_react$2.useEffect)(() => () => callback(), []);
}
/**
* A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting.
*
* This can be used to implement changes to your UI based on Reduced Motion. For instance, replacing motion-sickness inducing
* `x`/`y` animations with `opacity`, disabling the autoplay of background videos, or turning off parallax motion.
*
* It will actively respond to changes and re-render your components with the latest setting.
*
* ```jsx
* export function Sidebar({ isOpen }) {
* const shouldReduceMotion = useReducedMotion()
* const closedX = shouldReduceMotion ? 0 : "-100%"
*
* return (
*
* )
* }
* ```
*
* @return boolean
*
* @public
*/
function useReducedMotion() {
/**
* Lazy initialisation of prefersReducedMotion
*/
!hasReducedMotionListener.current && initPrefersReducedMotion();
const [shouldReduceMotion] = (0, import_react$2.useState)(prefersReducedMotion.current);
/**
* TODO See if people miss automatically updating shouldReduceMotion setting
*/
return shouldReduceMotion;
}
function useReducedMotionConfig() {
const reducedMotionPreference = useReducedMotion();
const { reducedMotion } = (0, import_react$2.useContext)(MotionConfigContext);
if (reducedMotion === "never") return false;
else if (reducedMotion === "always") return true;
else return reducedMotionPreference;
}
function isDOMKeyframes(keyframes) {
return typeof keyframes === "object" && !Array.isArray(keyframes);
}
function resolveSubjects(subject, keyframes, scope, selectorCache) {
if (subject == null) return [];
if (typeof subject === "string" && isDOMKeyframes(keyframes)) return resolveElements(subject, scope, selectorCache);
else if (subject instanceof NodeList) return Array.from(subject);
else if (Array.isArray(subject)) return subject.filter((s) => s != null);
else return [subject];
}
function calculateRepeatDuration(duration, repeat, repeatDelay) {
return duration * (repeat + 1) + repeatDelay * repeat;
}
/**
* Given a absolute or relative time definition and current/prev time state of the sequence,
* calculate an absolute time for the next keyframes.
*/
function calcNextTime(current, next, prev, labels) {
if (typeof next === "number") return next;
else if (next.startsWith("-") || next.startsWith("+")) return Math.max(0, current + parseFloat(next));
else if (next === "<") return prev;
else if (next.startsWith("<")) return Math.max(0, prev + parseFloat(next.slice(1)));
else return labels.get(next) ?? current;
}
function eraseKeyframes(sequence, startTime, endTime) {
for (let i = 0; i < sequence.length; i++) {
const keyframe = sequence[i];
if (keyframe.at > startTime && keyframe.at < endTime) {
removeItem(sequence, keyframe);
i--;
}
}
}
function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
/**
* Erase every existing value between currentTime and targetTime,
* this will essentially splice this timeline into any currently
* defined ones.
*/
eraseKeyframes(sequence, startTime, endTime);
for (let i = 0; i < keyframes.length; i++) sequence.push({
value: keyframes[i],
at: mixNumber$1(startTime, endTime, offset[i]),
easing: /* @__PURE__ */ getEasingForSegment(easing, i)
});
}
/**
* Take an array of times that represent repeated keyframes. For instance
* if we have original times of [0, 0.5, 1] then our repeated times will
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
* down to a 0-1 scale.
*
* `repeatDelayUnits` is the repeatDelay expressed in units of a single
* iteration's duration, so the total span equals `(repeat + 1) + repeat * repeatDelayUnits`.
*/
function normalizeTimes(times, repeat, repeatDelayUnits = 0) {
const totalUnits = repeat + 1 + repeat * repeatDelayUnits;
for (let i = 0; i < times.length; i++) times[i] = times[i] / totalUnits;
}
function compareByTime(a, b) {
if (a.at === b.at) {
if (a.value === null) return 1;
if (b.value === null) return -1;
return 0;
} else return a.at - b.at;
}
var defaultSegmentEasing = "easeInOut";
var MAX_REPEAT = 20;
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
const defaultDuration = defaultTransition.duration || .3;
const animationDefinitions = /* @__PURE__ */ new Map();
const sequences = /* @__PURE__ */ new Map();
const elementCache = {};
const timeLabels = /* @__PURE__ */ new Map();
let prevTime = 0;
let currentTime = 0;
let totalDuration = 0;
/**
* Build the timeline by mapping over the sequence array and converting
* the definitions into keyframes and offsets with absolute time values.
* These will later get converted into relative offsets in a second pass.
*/
for (let i = 0; i < sequence.length; i++) {
const segment = sequence[i];
/**
* If this is a timeline label, mark it and skip the rest of this iteration.
*/
if (typeof segment === "string") {
timeLabels.set(segment, currentTime);
continue;
} else if (!Array.isArray(segment)) {
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
continue;
}
let [subject, keyframes, transition = {}] = segment;
/**
* If a relative or absolute time value has been specified we need to resolve
* it in relation to the currentTime.
*/
if (transition.at !== void 0) currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
/**
* Keep track of the maximum duration in this definition. This will be
* applied to currentTime once the definition has been parsed.
*/
let maxDuration = 0;
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
const { delay = 0, times = defaultOffset(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
/**
* Resolve stagger() if defined.
*/
const calculatedDelay = typeof delay === "function" ? delay(elementIndex, numSubjects) : delay;
/**
* If this animation should and can use a spring, generate a spring easing function.
*/
const numKeyframes = valueKeyframesAsList.length;
const createGenerator = isGenerator(type) ? type : generators?.[type || "keyframes"];
if (numKeyframes <= 2 && createGenerator) {
/**
* As we're creating an easing function from a spring,
* ideally we want to generate it using the real distance
* between the two keyframes. However this isn't always
* possible - in these situations we use 0-100.
*/
let absoluteDelta = 100;
if (numKeyframes === 2 && isNumberKeyframesArray(valueKeyframesAsList)) {
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
absoluteDelta = Math.abs(delta);
}
const springTransition = {
...defaultTransition,
...remainingTransition
};
if (duration !== void 0) springTransition.duration = secondsToMilliseconds(duration);
const springEasing = createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
ease = springEasing.ease;
duration = springEasing.duration;
}
duration ?? (duration = defaultDuration);
const startTime = currentTime + calculatedDelay;
/**
* If there's only one time offset of 0, fill in a second with length 1
*/
if (times.length === 1 && times[0] === 0) times[1] = 1;
/**
* Fill out if offset if fewer offsets than keyframes
*/
const remainder = times.length - valueKeyframesAsList.length;
remainder > 0 && fillOffset(times, remainder);
/**
* If only one value has been set, ie [1], push a null to the start of
* the keyframe array. This will let us mark a keyframe at this point
* that will later be hydrated with the previous value.
*/
valueKeyframesAsList.length === 1 && valueKeyframesAsList.unshift(null);
if (repeat && repeat < MAX_REPEAT) {
/**
* Express repeatDelay in units of a single iteration's duration
* so it can be added to the per-iteration time offsets below
* before they're normalized to 0-1.
*/
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
const originalKeyframes = [...valueKeyframesAsList];
const originalTimes = [...times];
ease = Array.isArray(ease) ? [...ease] : [ease];
const originalEase = [...ease];
/**
* For reverse/mirror, alternate iterations play the segment
* backwards. mirror matches JSAnimation's mirroredGenerator:
* reversed keyframes, easings unchanged. reverse matches
* JSAnimation's iterationProgress = 1 - p: reversed
* keyframes, easing array reversed AND each function easing
* mapped through reverseEasing (string easings unchanged —
* they're resolved later by the keyframes engine).
*/
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
let flippedKeyframes = originalKeyframes;
let flippedEases = originalEase;
if (isFlipping) {
flippedKeyframes = [...originalKeyframes].reverse();
if (repeatType === "reverse") flippedEases = [...originalEase].reverse().map((e) => typeof e === "function" ? reverseEasing(e) : e);
}
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
const isFlipped = isFlipping && repeatIndex % 2 === 0;
const iterKeyframes = isFlipped ? flippedKeyframes : originalKeyframes;
const iterEase = isFlipped ? flippedEases : originalEase;
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
/**
* If repeatDelay is set, hold the previous iteration's
* final value through the delay by inserting a keyframe
* at the moment the next iteration begins.
*/
if (repeatDelayUnits > 0) {
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
times.push(iterStartOffset);
ease.push("linear");
}
valueKeyframesAsList.push(...iterKeyframes);
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
times.push(originalTimes[keyframeIndex] + iterStartOffset);
ease.push(keyframeIndex === 0 ? "linear" : /* @__PURE__ */ getEasingForSegment(iterEase, keyframeIndex - 1));
}
}
normalizeTimes(times, repeat, repeatDelayUnits);
}
const targetTime = startTime + duration;
/**
* Add keyframes, mapping offsets to absolute time.
*/
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
totalDuration = Math.max(targetTime, totalDuration);
};
if (isMotionValue(subject)) {
const subjectSequence = getSubjectSequence(subject, sequences);
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
} else {
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
const numSubjects = subjects.length;
/**
* For every element in this segment, process the defined values.
*/
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
/**
* Cast necessary, but we know these are of this type
*/
keyframes = keyframes;
transition = transition;
const thisSubject = subjects[subjectIndex];
const subjectSequence = getSubjectSequence(thisSubject, sequences);
for (const key in keyframes) resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
}
}
prevTime = currentTime;
currentTime += maxDuration;
}
/**
* For every element and value combination create a new animation.
*/
sequences.forEach((valueSequences, element) => {
for (const key in valueSequences) {
const valueSequence = valueSequences[key];
/**
* Arrange all the keyframes in ascending time order.
*/
valueSequence.sort(compareByTime);
const keyframes = [];
const valueOffset = [];
const valueEasing = [];
/**
* For each keyframe, translate absolute times into
* relative offsets based on the total duration of the timeline.
*/
for (let i = 0; i < valueSequence.length; i++) {
const { at, value, easing } = valueSequence[i];
keyframes.push(value);
valueOffset.push(progress(0, totalDuration, at));
valueEasing.push(easing || "easeOut");
}
/**
* If the first keyframe doesn't land on offset: 0
* provide one by duplicating the initial keyframe. This ensures
* it snaps to the first keyframe when the animation starts.
*/
if (valueOffset[0] !== 0) {
valueOffset.unshift(0);
keyframes.unshift(keyframes[0]);
valueEasing.unshift(defaultSegmentEasing);
}
/**
* If the last keyframe doesn't land on offset: 1
* provide one with a null wildcard value. This will ensure it
* stays static until the end of the animation.
*/
if (valueOffset[valueOffset.length - 1] !== 1) {
valueOffset.push(1);
keyframes.push(null);
}
if (!animationDefinitions.has(element)) animationDefinitions.set(element, {
keyframes: {},
transition: {}
});
const definition = animationDefinitions.get(element);
definition.keyframes[key] = keyframes;
/**
* Exclude `type` from defaultTransition since springs have been
* converted to duration-based easing functions in resolveValueSequence.
* Including `type: "spring"` would cause JSAnimation to error when
* the merged keyframes array has more than 2 keyframes.
*/
const { type: _type, ...remainingDefaultTransition } = defaultTransition;
definition.transition[key] = {
...remainingDefaultTransition,
duration: totalDuration,
ease: valueEasing,
times: valueOffset,
...sequenceTransition
};
}
});
return animationDefinitions;
}
function getSubjectSequence(subject, sequences) {
!sequences.has(subject) && sequences.set(subject, {});
return sequences.get(subject);
}
function getValueSequence(name, sequences) {
if (!sequences[name]) sequences[name] = [];
return sequences[name];
}
function keyframesAsList(keyframes) {
return Array.isArray(keyframes) ? keyframes : [keyframes];
}
function getValueTransition(transition, key) {
return transition && transition[key] ? {
...transition,
...transition[key]
} : { ...transition };
}
var isNumber = (keyframe) => typeof keyframe === "number";
var isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
function createDOMVisualElement(element) {
const options = {
presenceContext: null,
props: {},
visualState: {
renderState: {
transform: {},
transformOrigin: {},
style: {},
vars: {},
attrs: {}
},
latestValues: {}
}
};
const node = isSVGElement$1(element) && !isSVGSVGElement(element) ? new SVGVisualElement(options) : new HTMLVisualElement(options);
node.mount(element);
visualElementStore.set(element, node);
}
function createObjectVisualElement(subject) {
const node = new ObjectVisualElement({
presenceContext: null,
props: {},
visualState: {
renderState: { output: {} },
latestValues: {}
}
});
node.mount(subject);
visualElementStore.set(subject, node);
}
function isSingleValue(subject, keyframes) {
return isMotionValue(subject) || typeof subject === "number" || typeof subject === "string" && !isDOMKeyframes(keyframes);
}
/**
* Implementation
*/
function animateSubject(subject, keyframes, options, scope) {
const animations = [];
if (isSingleValue(subject, keyframes)) animations.push(animateSingleValue(subject, isDOMKeyframes(keyframes) ? keyframes.default || keyframes : keyframes, options ? options.default || options : options));
else {
if (subject == null) return animations;
const subjects = resolveSubjects(subject, keyframes, scope);
const numSubjects = subjects.length;
for (let i = 0; i < numSubjects; i++) {
const thisSubject = subjects[i];
const createVisualElement = thisSubject instanceof Element ? createDOMVisualElement : createObjectVisualElement;
if (!visualElementStore.has(thisSubject)) createVisualElement(thisSubject);
const visualElement = visualElementStore.get(thisSubject);
const transition = { ...options };
/**
* Resolve stagger function if provided.
*/
if ("delay" in transition && typeof transition.delay === "function") transition.delay = transition.delay(i, numSubjects);
animations.push(...animateTarget(visualElement, {
...keyframes,
transition
}, {}));
}
}
return animations;
}
function animateSequence(sequence, options, scope) {
const animations = [];
createAnimationsFromSequence(sequence.map((segment) => {
if (Array.isArray(segment) && typeof segment[0] === "function") {
const callback = segment[0];
const mv = motionValue(0);
mv.on("change", callback);
if (segment.length === 1) return [mv, [0, 1]];
else if (segment.length === 2) return [
mv,
[0, 1],
segment[1]
];
else return [
mv,
segment[1],
segment[2]
];
}
return segment;
}), options, scope, { spring }).forEach(({ keyframes, transition }, subject) => {
animations.push(...animateSubject(subject, keyframes, transition));
});
return animations;
}
function isSequence(value) {
return Array.isArray(value) && value.some(Array.isArray);
}
/**
* Creates an animation function that is optionally scoped
* to a specific element.
*/
function createScopedAnimate(options = {}) {
const { scope, reduceMotion, skipAnimations } = options;
/**
* Implementation
*/
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
let animations = [];
let animationOnComplete;
const inherited = {};
if (reduceMotion !== void 0) inherited.reduceMotion = reduceMotion;
if (skipAnimations !== void 0) inherited.skipAnimations = skipAnimations;
if (isSequence(subjectOrSequence)) {
const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
if (typeof onComplete === "function") animationOnComplete = onComplete;
animations = animateSequence(subjectOrSequence, {
...inherited,
...sequenceOptions
}, scope);
} else {
const { onComplete, ...rest } = options || {};
if (typeof onComplete === "function") animationOnComplete = onComplete;
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, {
...inherited,
...rest
}, scope);
}
const animation = new GroupAnimationWithThen(animations);
if (animationOnComplete) animation.finished.then(animationOnComplete);
if (scope) {
scope.animations.push(animation);
animation.finished.then(() => {
removeItem(scope.animations, animation);
});
}
return animation;
}
return scopedAnimate;
}
createScopedAnimate();
function useAnimate() {
const scope = useConstant(() => ({
current: null,
animations: []
}));
const reduceMotion = useReducedMotionConfig() ?? void 0;
const { skipAnimations } = (0, import_react$2.useContext)(MotionConfigContext);
const animate = (0, import_react$2.useMemo)(() => createScopedAnimate({
scope,
reduceMotion,
skipAnimations
}), [
scope,
reduceMotion,
skipAnimations
]);
useUnmountEffect(() => {
scope.animations.forEach((animation) => animation.stop());
scope.animations.length = 0;
});
return [scope, animate];
}
function useRafState(initialState) {
const ref = (0, import_react$2.useRef)(0);
const [state, setState] = (0, import_react$2.useState)(initialState);
const setRafState = (0, import_react$2.useCallback)((value) => {
cancelAnimationFrame(ref.current);
ref.current = requestAnimationFrame(() => {
setState(value);
});
}, []);
useUnmount(() => {
cancelAnimationFrame(ref.current);
});
return [state, setRafState];
}
var useUnmountedRef = () => {
const unmountedRef = (0, import_react$2.useRef)(false);
(0, import_react$2.useEffect)(() => {
unmountedRef.current = false;
return () => {
unmountedRef.current = true;
};
}, []);
return unmountedRef;
};
var iconParkOutlineRight = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
d: "m19 12l12 12l-12 12"
})]
});
var ForwardRef$8 = exports("_",(0, import_react$2.forwardRef)(iconParkOutlineRight));
var import_ms = /* @__PURE__ */ __toESM(require_ms(), 1);
var observerMap = /* @__PURE__ */ new Map();
var RootIds = /* @__PURE__ */ new WeakMap();
var rootId = 0;
var unsupportedValue;
function getRootId(root) {
if (!root) return "0";
if (RootIds.has(root)) return RootIds.get(root);
rootId += 1;
RootIds.set(root, rootId.toString());
return RootIds.get(root);
}
function optionsToId(options) {
return Object.keys(options).sort().filter((key) => options[key] !== void 0).map((key) => {
return `${key}_${key === "root" ? getRootId(options.root) : options[key]}`;
}).toString();
}
function createObserver(options) {
const id = optionsToId(options);
let instance = observerMap.get(id);
if (!instance) {
const elements = /* @__PURE__ */ new Map();
let thresholds;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
var _a;
const inView = entry.isIntersecting && thresholds.some((threshold) => entry.intersectionRatio >= threshold);
if (options.trackVisibility && typeof entry.isVisible === "undefined") entry.isVisible = inView;
[...(_a = elements.get(entry.target)) != null ? _a : []].forEach((callback) => {
callback(inView, entry);
});
});
}, options);
thresholds = observer.thresholds || (Array.isArray(options.threshold) ? options.threshold : [options.threshold || 0]);
instance = {
id,
observer,
elements
};
observerMap.set(id, instance);
}
return instance;
}
function observe(element, callback, options = {}, fallbackInView = unsupportedValue) {
if (typeof window.IntersectionObserver === "undefined" && fallbackInView !== void 0) {
const bounds = element.getBoundingClientRect();
callback(fallbackInView, {
isIntersecting: fallbackInView,
target: element,
intersectionRatio: typeof options.threshold === "number" ? options.threshold : 0,
time: 0,
boundingClientRect: bounds,
intersectionRect: bounds,
rootBounds: bounds
});
return () => {};
}
const { id, observer, elements } = createObserver(options);
const callbacks = elements.get(element) || [];
if (!elements.has(element)) elements.set(element, callbacks);
callbacks.push(callback);
observer.observe(element);
let unobserved = false;
return function unobserve() {
if (unobserved) return;
unobserved = true;
callbacks.splice(callbacks.indexOf(callback), 1);
if (callbacks.length === 0) {
elements.delete(element);
observer.unobserve(element);
}
if (elements.size === 0) {
observer.disconnect();
observerMap.delete(id);
}
};
}
import_react$2.Component;
var useInsertionEffect = Reflect.get(import_react$2, "useInsertionEffect");
var useSyncEffect = useInsertionEffect != null ? useInsertionEffect : import_react$2.useEffect;
function supportsRefCleanup(version2) {
return (version2 == null ? void 0 : version2.startsWith("19.")) || false;
}
var canUseRefCleanup = supportsRefCleanup("19.3.0");
function useIntersectionObserverRef(onIntersectionChange, { threshold, root, rootMargin, scrollMargin, trackVisibility, delay, fallbackInView, skip, triggerOnce }) {
const onIntersectionChangeRef = import_react$2.useRef(onIntersectionChange);
const observerStateRef = import_react$2.useRef({
node: null,
stop: void 0,
owner: null
});
if (!useInsertionEffect) onIntersectionChangeRef.current = onIntersectionChange;
useSyncEffect(() => {
onIntersectionChangeRef.current = onIntersectionChange;
}, [onIntersectionChange]);
return import_react$2.useCallback(function setRef(element) {
const observerState = observerStateRef.current;
if (!element && observerState.owner !== setRef) return;
if (element === observerState.node) {
observerState.owner = setRef;
return canUseRefCleanup ? observerState.stop : void 0;
}
const cleanup = observerState.stop;
observerState.stop = void 0;
cleanup?.();
if (!element || skip) {
observerState.node = null;
observerState.owner = element ? setRef : null;
return;
}
observerState.node = element;
observerState.owner = setRef;
let destroyObserver;
let previousInView;
function stopObserving() {
destroyObserver?.();
if (observerState.stop === stopObserving) {
observerState.node = null;
observerState.stop = void 0;
}
}
observerState.stop = stopObserving;
destroyObserver = observe(element, (inView, entry) => {
onIntersectionChangeRef.current(inView, entry, previousInView);
previousInView = inView;
if (triggerOnce && inView) stopObserving();
}, {
threshold,
root,
rootMargin,
scrollMargin,
trackVisibility,
delay
}, fallbackInView);
if (observerState.stop !== stopObserving) destroyObserver();
return canUseRefCleanup ? observerState.stop : void 0;
}, [
Array.isArray(threshold) ? threshold.toString() : threshold,
root,
rootMargin,
scrollMargin,
trackVisibility,
delay,
fallbackInView,
skip,
triggerOnce
]);
}
var useIsomorphicLayoutEffect = typeof window === "undefined" ? import_react$2.useEffect : import_react$2.useLayoutEffect;
function useInView({ threshold, delay, trackVisibility, rootMargin, scrollMargin, root, triggerOnce, skip, initialInView, fallbackInView, onChange } = {}) {
const lastInViewRef = import_react$2.useRef(initialInView);
const [state, setState] = import_react$2.useState({
inView: !!initialInView,
entry: void 0
});
const observerRef = useIntersectionObserverRef((inView, entry) => {
const previousInView = lastInViewRef.current;
lastInViewRef.current = inView;
if (previousInView === void 0 && !inView) return;
setState({
inView,
entry
});
onChange?.(inView, entry);
}, {
threshold,
root,
rootMargin,
scrollMargin,
trackVisibility,
delay,
fallbackInView,
skip,
triggerOnce
});
const refState = import_react$2.useRef({
node: null,
reset: false
});
const setRef = import_react$2.useCallback(function setRef2(node) {
if (node) {
refState.current.node = node;
refState.current.reset = false;
} else if (refState.current.node) {
refState.current.node = null;
refState.current.reset = true;
}
const cleanup = observerRef(node);
if (!cleanup) return;
return () => {
cleanup();
if (refState.current.node === node) {
refState.current.node = null;
refState.current.reset = true;
}
};
}, [observerRef]);
useIsomorphicLayoutEffect(() => {
if (!refState.current.reset) return;
refState.current.reset = false;
if (triggerOnce || skip) return;
setState({
inView: !!initialInView,
entry: void 0
});
lastInViewRef.current = initialInView;
});
const result = [
setRef,
state.inView,
state.entry
];
result.ref = result[0];
result.inView = result[1];
result.entry = result[2];
return result;
}
var useOnInView = (onIntersectionChange, { threshold, root, rootMargin, scrollMargin, trackVisibility, delay, triggerOnce, skip } = {}) => {
return useIntersectionObserverRef((inView, entry, previousInView) => {
if (previousInView === void 0 && !inView) return;
onIntersectionChange(inView, entry);
}, {
threshold,
root,
rootMargin,
scrollMargin,
trackVisibility,
delay,
triggerOnce,
skip
});
};
function isEmptyFragment(fragment) {
return !import_react$2.Children.toArray(fragment.props?.children).length;
}
var dislikedMap = proxyMap();
function useDislikedReason(id) {
const map = useSnapshot(dislikedMap);
return id ? map.get(id) : void 0;
}
function delDisliked(id) {
dislikedMap.delete(id);
}
function calcRecItemDislikedMapKey(item) {
if (checkIsAppRecommend(item)) return item.param;
if (checkIsPcRecommend(item)) return item.id.toString();
}
/**
* export/bind (functions & variables) to unsafeWindow
*/
var win = typeof unsafeWindow !== "undefined" ? unsafeWindow : globalThis;
var setGlobalValue = (key, val) => {
return ae.try(() => win[key] = val).isOk();
};
var gridItemsKey = `${APP_KEY_PREFIX}_gridItems`;
function setGlobalGridItems(itemsWithSep) {
const items = itemsWithSep.filter((x) => x.api !== EApiType.Separator);
setCurrentGridItems(items);
setGlobalValue(gridItemsKey, items);
}
var BIND_TO_UNSAFE_WINDOW_FNS = {
getGenericCardDatas,
copyBvidsSingleLine,
copyBvidInfos
};
setTimeout(() => {
Object.entries(BIND_TO_UNSAFE_WINDOW_FNS).forEach(([fnName, fn]) => {
setGlobalValue(`${APP_KEY_PREFIX}_${fnName}`, fn);
});
});
function useInFilterByAuthorList(authorMid) {
const { enabled, keywords } = useSnapshot(settings.filter.byAuthor);
const blockUpMids = (0, import_react$2.useMemo)(() => enabled && authorMid ? getSnapshotAuthorFilter(snapshot(settings.filter.byAuthor).keywords).blockUpMids : void 0, [
keywords,
enabled,
authorMid
]);
if (!authorMid) return false;
return blockUpMids?.has(authorMid) ?? false;
}
var indices = /* @__PURE__ */ new WeakMap();
function lookupSnapshotCardOwners(groups, sourceNames, sourceIds) {
if (!sourceIds?.length && !sourceNames.length) return {
matched: [],
hasUnowned: false
};
let index = indices.get(groups);
if (!index) {
index = {
ids: /* @__PURE__ */ new Map(),
names: /* @__PURE__ */ new Map()
};
groups.forEach((group, position) => {
for (const [map, key] of [[index.ids, group.id], [index.names, group.name]]) {
const positions = map.get(key);
if (positions) positions.push(position);
else map.set(key, [position]);
}
});
indices.set(groups, index);
}
const positions = /* @__PURE__ */ new Set();
let hasUnowned = false;
if (sourceIds?.length) for (const id of sourceIds) for (const position of index.ids.get(id) ?? []) positions.add(position);
else for (const sourceName of sourceNames) {
const found = index.names.get(stripRelatedSuffix(sourceName));
if (!found) hasUnowned = true;
else if (found.length === 1) positions.add(found[0]);
}
return {
matched: [...positions].sort((a, b) => a - b).map((position) => groups[position]),
hasUnowned
};
}
/** 菜单先说动作,再用次行说明范围;范围徽标保持在同一列。 */
function TagRecFilterMenuLabel({ title, description, scope, disabled }) {
const { token } = theme.useToken();
return /* @__PURE__ */ jsxs("span", {
className: "block py-2px",
style: {
width: scope ? 248 : 190,
maxWidth: "calc(100vw - 96px)"
},
children: [/* @__PURE__ */ jsxs("span", {
className: "flex items-center justify-between gap-x-12px",
style: { lineHeight: "20px" },
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-0 truncate font-medium",
children: title
}), scope && /* @__PURE__ */ jsx("span", {
className: "shrink-0 rounded-4px px-5px text-12px font-medium",
style: {
background: scope === "临时" && !disabled ? token.colorPrimaryBg : token.colorFillTertiary,
color: disabled ? token.colorTextDisabled : scope === "临时" ? token.colorPrimaryTextActive : token.colorTextLabel,
lineHeight: "18px"
},
children: scope
})]
}), /* @__PURE__ */ jsx("span", {
className: "mt-2px block truncate text-12px",
title: description,
style: {
color: disabled ? token.colorTextDisabled : token.colorTextLabel,
lineHeight: "18px"
},
children: description
})]
});
}
function TagRecFilterMenuSection({ mode }) {
const { token } = theme.useToken();
const Icon = mode === "block" ? IconForTagBlock : IconForTagAllow;
return /* @__PURE__ */ jsxs("span", {
className: "flex items-center gap-x-6px text-12px font-medium",
style: { color: mode === "block" ? token.colorErrorTextActive : token.colorPrimaryTextActive },
children: [/* @__PURE__ */ jsx(Icon, {
className: "size-12px",
"aria-hidden": true
}), mode === "block" ? "不想看 · 屏蔽匹配内容" : "只想看 · 保留匹配内容"]
});
}
/**
* [tag-rec] 🏷 标签弹层 (spec §4.6, 两挂点共用)
* - 点开才拉 view/detail/tag (会话缓存 + 同 bvid >=3s 去重); 本组件仅在弹层打开时挂载, 天然懒取
* - 行: tag_name + #tag_id(小字) + 状态区 + 动作列 (★ 小菜单 / ⛔ 负向切换);
* `#id` = 新标签页直达「标签发掘」以此标签开新组合的真链接 (id+名零解析落成试验, 可中键/复制链);
* tag_type 徽标不渲 (old_channel 噪音挤行宽, 数据仍留 schema/回填收藏表)
* - 票 19: ⛔/★ 判定集与动作集同收窄到「本卡落点集」= 归因名解析出的来源组 + 未归属名的草稿组;
* 落点集外宿主不给本卡反向通路 (解/管去对应归因卡或面板); 票 23: 「·他组」chip 与集内 ★/⛔ 文字
* chip 全删 (图标亮灭/红灰自足), 状态区仅留「组X变体」chip, 他组归属走 ★菜单③相关组 Modal 知情
* - 票 20: ★ 菜单重构四档入组 —— ①新建收藏项 (未收藏分支, toast 带「新标签页查看」动作,
* 票 24 改道「选中并重开」: 新 tab 经 tag-rec-group 深链直接进选定组语境, 当前 tab 零扰动)
* ②加入当前组 (选定组 > 草稿组, 皆无不渲) ③加入相关组…(Modal, 组 tag_ids ∩ 本视频 tag_ids)
* ④加入全部组…(Modal, enabled 组); 已收藏分支 = 逐宿主移出/停用 + ②③④
* - 票 22: ②两语境统一写 expandedIds 暂存位 (与下拉「扩进」同义, 下一轮进查询单元生效;
* 入表收口到「顺手存成项/并入当前组」) —— ∈暂存位 => ★ 紫染暂存态, 直点取消 (票 27,
* 图标三态: 灰空心=可收/紫=暂存可撤/金=真成员); 菜单「已扩进」禁选位留给锚点态
* - 负向语义绑定标签组 (票 14): ⛔ 写进落点集 negative_tag_ids, 多归因写全部落点;
* 落点集为空 (非 tag-rec 卡) => 仅点击 ⛔ 时弹组选择 Modal 兜底
* - 已知 id 路径零解析直进组, 不经过沉淀池; tag_type opportunistic 回填收藏表
*/
/** ⛔ tooltip 固定文案 (spec §4.6/票 19: 拉黑/解黑 = 本卡落点组负向二态翻转) */
var TAG_POPOVER_BLACKLIST_TOOLTIP = "拉黑/解黑本卡落点组的负向成员 (仅作用于「收藏推荐/指定标签」tab)";
/** 宿主组名展示: 草稿组伪组前缀「草稿组」, 收藏表组直接「名」 */
var hostLabel = (g) => g.id === "__draft__" ? `草稿组「${g.name}」` : `「${g.name}」`;
/**
* 同名组消歧 (实机数据可存在同名双胞胎组): peers 内有同名牌 => 附「N个id」区分;
* 计数仍撞 => 再附 id 短码兜底 (收藏表 id 是 uuid)
*/
function hostLabelEx(g, peers) {
const same = peers.filter((p) => p.name === g.name);
if (same.length <= 1) return hostLabel(g);
const suffix = same.filter((p) => p.tag_ids.length === g.tag_ids.length).length <= 1 ? `${g.tag_ids.length}个id` : `${g.tag_ids.length}个id·#${g.id.slice(0, 4)}`;
return `${hostLabel(g)}(${suffix})`;
}
function pickVideoTags(items) {
return (items ?? []).filter((t) => !!t.tag_id && !!t.tag_name);
}
function TagRecTagPopover({ bvid, workbenchMode = false, sourceGroupNames = [], sourceGroupIds }) {
const [tags, setTags] = (0, import_react$2.useState)([]);
const [state, setState] = (0, import_react$2.useState)("loading");
const requestRef = (0, import_react$2.useRef)(void 0);
const load = useMemoizedFn(async () => {
requestRef.current?.abort();
const ctrl = new AbortController();
requestRef.current = ctrl;
setState("loading");
const res = await fetchVideoTags(bvid, {
interactive: true,
signal: ctrl.signal
}).catch(() => void 0);
if (ctrl.signal.aborted) return;
setTags(pickVideoTags(res));
setState(res === void 0 ? "error" : "ok");
});
(0, import_react$2.useEffect)(() => void load(), [bvid]);
(0, import_react$2.useEffect)(() => () => requestRef.current?.abort(), []);
(0, import_react$2.useEffect)(() => {
if (state !== "ok" || !tags.length) return;
favorites.backfillTagTypes(tags.map((t) => ({
id: t.tag_id,
tag_type: parseTagType(t.tag_type)
})));
}, [state, tags]);
const { list: groups } = useSnapshot(favoritesStore);
const draftGroup = useSnapshot(tagRecCustomSession).draftGroup;
const statusGroups = [...groups, ...draftGroup ? [{
id: DRAFT_GROUP_ID,
name: draftGroup.name,
tag_ids: [],
negative_tag_ids: [...draftGroup.negative_tag_ids],
weight: 0,
enabled: true,
addedAt: 0
}] : []];
const landingGroups = resolveLandingGroups(groups, statusGroups, sourceGroupNames, sourceGroupIds);
const relatedGroups = findGroupsByTagIds(groups, tags.map((t) => t.tag_id));
const enabledGroups = groups.filter((g) => g.enabled);
return /* @__PURE__ */ jsxs("div", {
className: "w-310px",
onClick: (e) => e.stopPropagation(),
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex items-center justify-between",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-13px",
children: "视频标签"
}), state === "ok" && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"共 ",
tags.length,
" 个"
]
})]
}),
state === "loading" && /* @__PURE__ */ jsxs("div", {
className: "flex-center py-12px",
children: [/* @__PURE__ */ jsx(Spin, { size: "small" }), /* @__PURE__ */ jsx("span", {
className: "ml-8px text-12px",
children: "加载标签中…"
})]
}),
state === "error" && /* @__PURE__ */ jsxs("div", {
className: "flex-center py-12px text-12px",
children: ["加载失败", /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "px-4px",
onClick: () => {
invalidateVideoTagsCache(bvid);
load();
},
children: "重试"
})]
}),
state === "ok" && !tags.length && /* @__PURE__ */ jsx("div", {
className: "py-12px text-center",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "该视频暂无标签"
})
}),
state === "ok" && !!tags.length && /* @__PURE__ */ jsx("div", {
className: "max-h-320px overflow-y-auto",
children: tags.map((t) => workbenchMode ? /* @__PURE__ */ jsx(WorkingTagRow, {
tag: t,
groups,
relatedGroups,
enabledGroups
}, t.tag_id) : /* @__PURE__ */ jsx(TagRow, {
tag: t,
groups,
statusGroups,
landingGroups,
relatedGroups,
enabledGroups
}, t.tag_id))
})
]
});
}
function addNegativeToDraft(entry) {
const d = tagRecCustomSession.draftGroup;
if (!d || d.negative_tag_ids.some((x) => x.id === entry.id)) return false;
d.negative_tag_ids.push({ ...entry });
return true;
}
function removeNegativeFromTarget(groupId, tagId) {
if (groupId === "__draft__") {
const d = tagRecCustomSession.draftGroup;
if (!d) return;
const i = d.negative_tag_ids.findIndex((e) => e.id === tagId);
if (i !== -1) d.negative_tag_ids.splice(i, 1);
return;
}
favorites.removeNegativeFromGroup(groupId, tagId);
}
/**
* ★ 入组共享件 (票 07): ①新建收藏项 + ③加入相关组… + ④加入全部组… 的 Modal/Select 与写入报告,
* TagRow / WorkingTagRow 共用同一实现保证「等价入口」
*/
function useTagGroupAddPicker({ tag, relatedGroups, enabledGroups }) {
const entry = videoTagToTagIdEntry(tag);
const [addPickerMode, setAddPickerMode] = (0, import_react$2.useState)();
const [addPickerIds, setAddPickerIds] = (0, import_react$2.useState)([]);
/** 并入单组的报告 (spec Q8-i): 「已并入组「A」,共 N 个标签」+ 互斥/他组信息; silent 走 facade 去重后自报 */
const reportAddResult = useMemoizedFn((target, res) => {
const live = favorites.list.find((x) => x.id === target.id);
const parts = res.added || res.moved ? [`已并入组「${target.name}」,共 ${live?.tag_ids.length ?? target.tag_ids.length} 个标签`] : [`「${target.name}」已含该标签`];
if (res.moved) parts.push(`${res.moved} 个由负向转正向`);
if (res.alsoIn.length) parts.push(`也存在于 ${summarizeGroupNames(res.alsoIn)}`);
toast(parts.join("; "), 3500);
});
/**
* ① 新建收藏项: addGroup + toast 带「新标签页查看」动作 —— 票 24 改道票 20「选中并重开」:
* 「选中并重开」会炸掉当前 tab 语境 (输入/草稿/滚动现场全清), 改为新 tab 经 tag-rec-group
* 深链直接进选定组语境 (消费点 = TagRecCustomTabbarView mount), 当前 tab 零扰动, 契合一 tab 一语境互斥
*/
const createGroupAndOfferOpen = useMemoizedFn(() => {
const g = favorites.addGroup({
name: tag.tag_name,
tag_ids: [entry]
})?.group;
if (!g) return;
const key = `tag-rec-new-group-${g.id}`;
antMessage.open({
key,
duration: 6,
content: /* @__PURE__ */ jsxs("span", { children: [
"已存为组「",
g.name,
"」·",
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "px-4px",
onClick: () => {
antMessage.destroy(key);
window.open(buildTagRecCustomDeepLink(location.origin, { group: g.id }), "_blank");
},
children: "新标签页查看"
})
] })
});
});
const openAddPicker = useMemoizedFn((mode) => {
setAddPickerIds([]);
setAddPickerMode(mode);
});
const confirmAddPicker = useMemoizedFn(() => {
const targets = (addPickerMode === "related" ? relatedGroups : enabledGroups).filter((g) => addPickerIds.includes(g.id));
setAddPickerMode(void 0);
if (!targets.length) return;
if (targets.length === 1) {
reportAddResult(targets[0], favorites.addTagIdsToGroup(targets[0].id, [entry], { silent: true }));
return;
}
const added = [];
const alsoIn = /* @__PURE__ */ new Set();
let skipped = 0;
let moved = 0;
for (const g of targets) {
const res = favorites.addTagIdsToGroup(g.id, [entry], { silent: true });
if (res.added || res.moved) added.push(g.name);
else skipped++;
moved += res.moved;
res.alsoIn.forEach((n) => alsoIn.add(n));
}
const parts = [];
if (added.length) parts.push(`已并入 ${summarizeGroupNames(added, 5)}`);
if (skipped) parts.push(`${skipped} 组已含该标签`);
if (moved) parts.push(`${moved} 个由负向转正向`);
if (alsoIn.size) parts.push(`也存在于 ${summarizeGroupNames([...alsoIn])}`);
toast(parts.join("; ") || "未变动", 3500);
});
return {
reportAddResult,
createGroupAndOfferOpen,
openAddPicker,
addPickerModal: /* @__PURE__ */ jsx(Modal, {
title: addPickerMode === "related" ? "加入相关组 (含本视频 tag 的组)" : "加入全部组",
open: addPickerMode !== void 0,
onCancel: () => setAddPickerMode(void 0),
onOk: confirmAddPicker,
okText: "加入",
okButtonProps: { disabled: !addPickerIds.length },
width: 360,
children: /* @__PURE__ */ jsx(Select, {
mode: "multiple",
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: "选择标签组",
value: addPickerIds,
onChange: setAddPickerIds,
options: (addPickerMode === "related" ? relatedGroups : enabledGroups).map((g) => ({
value: g.id,
label: `${g.name} (${g.tag_ids.length} 个 id)`
}))
})
})
};
}
function TagRow({ tag, groups, statusGroups, landingGroups, relatedGroups, enabledGroups }) {
const status = classifyVideoTagStatus({
id: tag.tag_id,
name: tag.tag_name
}, landingGroups, statusGroups);
const entry = videoTagToTagIdEntry(tag);
/** 落点集内正向宿主 (★ 激活 + 菜单动作对象); 空 => 本语境按未收藏渲染 (含仅他组收藏) */
const favedHosts = status.faved;
/** ⛔ 二态: 落点集内存在负向宿主 = 黑 */
const blacked = status.negative.length > 0;
const [pickerOpen, setPickerOpen] = (0, import_react$2.useState)(false);
const [pickerIds, setPickerIds] = (0, import_react$2.useState)([]);
const { token } = theme.useToken();
const { selectedGroupIds, draftGroup, expandedIds, anchor } = useSnapshot(tagRecCustomSession);
const addNegativeToTargets = useMemoizedFn((targets) => {
const names = [];
for (const t of targets) if (t.id === "__draft__") {
if (addNegativeToDraft(entry)) names.push(`草稿组「${t.name}」`);
} else {
const res = favorites.addNegativeToGroup(t.id, [entry]);
if (res.added || res.moved) names.push(hostLabelEx(t, statusGroups));
}
if (names.length) toast(`已加入 ${names.join("、")} 负向`);
else toast("该标签已在所选组负向中");
});
const onToggleNegative = useMemoizedFn(() => {
if (blacked) {
for (const g of status.negative) removeNegativeFromTarget(g.id, tag.tag_id);
toast(`已从 ${status.negative.map(hostLabel).join("、")} 负向移出`);
return;
}
if (!landingGroups.length || landingGroups.length > 1) {
setPickerIds(landingGroups.map((g) => g.id));
setPickerOpen(true);
return;
}
addNegativeToTargets(landingGroups);
});
/**
* ②「当前组」语境: tab2 互斥后恒 1 (票 21) —— 选定组 (pickTagGroupsByIds 停用防御同 service);
* 无选定组但有草稿组 => 草稿组; 皆无 (tab1/非 tag-rec 语境) => 此项不渲染
*/
const selectedGroup = pickTagGroupsByIds(groups, selectedGroupIds)[0];
const alreadyInSelected = !!selectedGroup && selectedGroup.tag_ids.some((e) => e.id === tag.tag_id);
/** 票 22: ∈expandedIds 暂存位 (或本就是锚点) => 菜单禁选标「已扩进」; 票 27: ★ 紫染暂存态直点取消 */
const inExpanded = expandedIds.some((e) => e.id === tag.tag_id);
const isAnchor = anchor?.id === tag.tag_id;
const alreadyExpanded = inExpanded || isAnchor;
const expandedMark = inExpanded ? " (已扩进)" : isAnchor ? " (已在查询单元)" : "";
const { createGroupAndOfferOpen, openAddPicker, addPickerModal } = useTagGroupAddPicker({
tag,
relatedGroups,
enabledGroups
});
/**
* ② 落地 (票 22): 两语境统一写 expandedIds 暂存位 —— 与「下拉勾选扩进」同义, 下一轮进查询单元;
* 不再即时 addTagIdsToGroup, 入表收口到存成项 (选定组语境 = 并入该组; 草稿语境 = 升级建组)。
* pushExpandedIds 收口去重 (已在暂存/锚点/当前组真成员跳过), 菜单禁选已先拦
*/
const addToCurrentCtx = useMemoizedFn(() => {
if (!selectedGroup && !tagRecCustomSession.draftGroup) return;
if (alreadyExpanded || alreadyInSelected) return;
if (!pushExpandedIds([{ ...entry }], selectedGroup?.tag_ids).length) {
toast("该标签已在组或扩选暂存中");
return;
}
toast(selectedGroup ? "已扩进本次查询,「并入当前组」时随暂存层固化" : "已扩进本次查询,存成项时随组入表");
});
const favedOutside = status.favedOutside;
const [removePickerOpen, setRemovePickerOpen] = (0, import_react$2.useState)(false);
const [removePickerIds, setRemovePickerIds] = (0, import_react$2.useState)([]);
const removeFromLanding = useMemoizedFn(() => {
for (const g of favedHosts) favorites.removeTagIdFromGroup(g.id, tag.tag_id);
});
const favMenuItems = defineAntMenus([
...favedHosts.length === 1 ? [favedHosts[0].tag_ids.length === 1 && {
key: `fav-empty-group-${favedHosts[0].id}`,
label: `从${hostLabelEx(favedHosts[0], statusGroups)}移出此标签 (保留空组)`,
onClick: () => favorites.removeTagIdFromGroup(favedHosts[0].id, tag.tag_id)
}, favedHosts[0].tag_ids.length > 1 ? {
key: `fav-remove-id-${favedHosts[0].id}`,
label: `从${hostLabelEx(favedHosts[0], statusGroups)}移出此标签`,
onClick: () => favorites.removeTagIdFromGroup(favedHosts[0].id, tag.tag_id)
} : {
key: `fav-remove-group-${favedHosts[0].id}`,
label: `删除标签组「${favedHosts[0].name}」`,
danger: true,
onClick: () => favorites.removeGroup(favedHosts[0].id)
}] : [],
favedHosts.length > 1 && {
key: "fav-remove-landing",
label: `从当前组移出此标签 (${favedHosts.length} 个落点组)`,
onClick: removeFromLanding
},
!!favedOutside.length && {
key: "fav-remove-related",
label: `移出相关组… (还有 ${favedOutside.length} 组收藏了此标签)`,
onClick: () => {
setRemovePickerIds([]);
setRemovePickerOpen(true);
}
},
...favedHosts.map((g) => ({
key: `fav-toggle-enabled-${g.id}`,
label: `${g.enabled ? "停用" : "启用"}组${hostLabelEx(g, favedHosts)}`,
onClick: () => favorites.updateGroup(g.id, { enabled: !g.enabled })
})),
!!favedHosts.length && {
type: "divider",
key: "fav-add-divider"
},
!favedHosts.length && {
key: "fav-new-group",
label: "新建收藏项",
onClick: createGroupAndOfferOpen
},
!!(selectedGroup || draftGroup) && (selectedGroup ? {
key: "fav-into-current",
label: `加入当前组「${selectedGroup.name}」${alreadyInSelected ? " (已在组内)" : expandedMark}`,
disabled: alreadyInSelected || alreadyExpanded,
onClick: addToCurrentCtx
} : {
key: "fav-into-current",
label: `加入当前组(草稿)「${draftGroup.name}」${expandedMark}`,
disabled: alreadyExpanded,
onClick: addToCurrentCtx
}),
{
key: "fav-into-related",
label: relatedGroups.length ? "加入相关组…" : "加入相关组… (本视频 tag 无交集组)",
disabled: !relatedGroups.length,
onClick: () => openAddPicker("related")
},
{
key: "fav-into-all",
label: enabledGroups.length ? "加入全部组…" : "加入全部组… (暂无可用组)",
disabled: !enabledGroups.length,
onClick: () => openAddPicker("all")
}
]);
return /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-4px b-b-1px b-gate-border b-b-solid py-4px last:b-b-0",
children: [
/* @__PURE__ */ jsxs("div", {
className: "min-w-0 flex-1",
children: [/* @__PURE__ */ jsx("span", {
className: "truncate",
title: tag.tag_name,
children: tag.tag_name
}), /* @__PURE__ */ jsxs(Typography.Link, {
className: "ml-6px text-12px",
href: buildTagRecCustomDeepLink(location.origin, { seed: {
id: tag.tag_id,
name: tag.tag_name
} }),
target: "_blank",
rel: "noopener noreferrer",
title: "新标签页打开「标签发掘」· 以此标签开新组合",
children: ["#", tag.tag_id]
})]
}),
status.variant && /* @__PURE__ */ jsx(AntdTooltip, {
title: `与组「${status.variant.group.name}」内「${status.variant.entry.name}」归一化同形 (不同 tag_id)`,
children: /* @__PURE__ */ jsxs(Tag, {
color: "blue",
className: "mr-0",
children: [
"组「",
status.variant.group.name,
"」变体"
]
})
}),
inExpanded ? /* @__PURE__ */ jsx(AntdTooltip, {
title: "扩选暂存中 · 点击取消扩选 (下一轮生效)",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "px-2px",
icon: /* @__PURE__ */ jsx("span", {
className: "inline-flex",
style: { color: token.purple6 },
children: /* @__PURE__ */ jsx(IconForFav, { className: "size-14px" })
}),
onClick: () => removeExpandedId(tag.tag_id)
})
}) : /* @__PURE__ */ jsx(Dropdown, {
menu: { items: favMenuItems },
trigger: ["click"],
placement: "bottomRight",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "px-2px",
icon: favedHosts.length ? /* @__PURE__ */ jsx(IconForFaved, { className: "size-14px color-gate-primary" }) : /* @__PURE__ */ jsx(IconForFav, { className: "size-14px" }),
title: favedHosts.length ? `已收藏于 ${favedHosts.map((g) => hostLabelEx(g, statusGroups)).join("")}` : "收藏此标签"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: TAG_POPOVER_BLACKLIST_TOOLTIP,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
danger: blacked,
className: "px-2px",
icon: /* @__PURE__ */ jsx(IconForTagBlock, { className: "size-14px" }),
onClick: onToggleNegative
})
}),
/* @__PURE__ */ jsx(Modal, {
title: "拉进哪组的负向",
open: pickerOpen,
onCancel: () => setPickerOpen(false),
onOk: () => {
const targets = statusGroups.filter((g) => pickerIds.includes(g.id));
setPickerOpen(false);
if (targets.length) addNegativeToTargets(targets);
},
okText: "加入负向",
okButtonProps: { disabled: !pickerIds.length },
width: 360,
children: /* @__PURE__ */ jsx(Select, {
mode: "multiple",
className: "mt-12px w-full",
placeholder: "选择标签组 (含草稿组)",
value: pickerIds,
onChange: setPickerIds,
options: statusGroups.map((g) => ({
value: g.id,
label: g.id === "__draft__" ? `草稿组「${g.name}」` : `${g.name} (${g.tag_ids.length} 个 id·#${g.id.slice(0, 4)})`
}))
})
}),
/* @__PURE__ */ jsx(Modal, {
title: "移出相关组 (本卡落点外也收藏了此标签的组)",
open: removePickerOpen,
onCancel: () => setRemovePickerOpen(false),
onOk: () => {
const targets = favedOutside.filter((g) => removePickerIds.includes(g.id));
setRemovePickerOpen(false);
for (const g of targets) favorites.removeTagIdFromGroup(g.id, tag.tag_id);
if (targets.length) toast(`已从 ${targets.map((g) => hostLabelEx(g, statusGroups)).join("、")} 移出`);
},
okText: "移出",
okButtonProps: { disabled: !removePickerIds.length },
width: 360,
children: /* @__PURE__ */ jsx(Select, {
mode: "multiple",
className: "mt-12px w-full",
placeholder: "选择标签组",
value: removePickerIds,
onChange: setRemovePickerIds,
options: favedOutside.map((g) => ({
value: g.id,
label: `${g.name} (${g.tag_ids.length} 个 id)`
}))
})
}),
addPickerModal
]
});
}
function WorkingTagRow({ tag, groups, relatedGroups, enabledGroups }) {
const combination = useSnapshot(tagRecCustomSession).workingGroup?.combination;
const positives = combination ? tagRecConditionAtoms(combination.include).filter((a) => a.field === "tag" && a.value.id === tag.tag_id) : [];
const negatives = combination?.exclude.filter((a) => a.field === "tag" && a.value.id === tag.tag_id) ?? [];
const atom = () => tagRecConditionAtom("tag", videoTagToTagIdEntry(tag));
const add = () => addTagRecWorkingCondition(atom());
const exclude = () => excludeTagRecWorkingCondition(atom());
const only = () => exploreTagRecTagOnly(videoTagToTagIdEntry(tag));
const favedGroups = groups.filter((g) => g.tag_ids.some((e) => e.id === tag.tag_id));
const { createGroupAndOfferOpen, openAddPicker, addPickerModal } = useTagGroupAddPicker({
tag,
relatedGroups,
enabledGroups
});
const favMenuItems = defineAntMenus([
...favedGroups.map((g) => ({
key: `fav-remove-${g.id}`,
label: `从${hostLabelEx(g, favedGroups)}移出此标签`,
onClick: () => favorites.removeTagIdFromGroup(g.id, tag.tag_id)
})),
!!favedGroups.length && {
type: "divider",
key: "fav-remove-divider"
},
!favedGroups.length && {
key: "fav-new-group",
label: "新建收藏项",
onClick: createGroupAndOfferOpen
},
{
key: "fav-into-related",
label: relatedGroups.length ? "加入相关组…" : "加入相关组… (本视频 tag 无交集组)",
disabled: !relatedGroups.length,
onClick: () => openAddPicker("related")
},
{
key: "fav-into-all",
label: enabledGroups.length ? "加入全部组…" : "加入全部组… (暂无可用组)",
disabled: !enabledGroups.length,
onClick: () => openAddPicker("all")
}
]);
return /* @__PURE__ */ jsx(Dropdown, {
trigger: ["contextMenu"],
menu: { items: [
{
key: "only",
label: "仅看这个标签",
onClick: only
},
{
key: "add",
label: "选入当前组合",
onClick: add
},
{
key: "exclude",
label: "这个组合不看",
onClick: exclude
},
{
key: "global",
label: "以后都不看(全部标签推荐)",
onClick: () => {
blockTagRecConditionGlobally(atom());
toast("已加入全局排除,可在工作台直接移除");
}
}
] },
children: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px py-4px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
className: "min-w-0 flex-1",
children: [
tag.tag_name,
/* @__PURE__ */ jsxs(Typography.Link, {
className: "ml-6px text-12px",
href: buildTagRecCustomDeepLink(location.origin, { seed: {
id: tag.tag_id,
name: tag.tag_name
} }),
target: "_blank",
rel: "noopener noreferrer",
title: "新标签页打开「标签发掘」· 以此标签开新组合",
children: ["#", tag.tag_id]
}),
/* @__PURE__ */ jsx(Typography.Link, {
className: "ml-6px text-12px",
title: "仅看这个标签 · 当前组合记入最近尝试,开始解析共现标签",
onClick: only,
children: "仅看"
})
]
}),
/* @__PURE__ */ jsx(Dropdown, {
menu: { items: favMenuItems },
trigger: ["click"],
placement: "bottomRight",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "px-2px",
icon: favedGroups.length ? /* @__PURE__ */ jsx(IconForFaved, { className: "size-14px color-gate-primary" }) : /* @__PURE__ */ jsx(IconForFav, { className: "size-14px" }),
title: favedGroups.length ? `已收藏于 ${favedGroups.map((g) => `「${g.name}」`).join("")}` : "收藏此标签"
})
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: positives.length ? "primary" : "default",
onClick: () => positives.length ? positives.forEach((a) => removeTagRecWorkingCondition(a.id)) : add(),
children: positives.length ? "已选 · 移出" : "选入"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
danger: !!negatives.length,
onClick: () => negatives.length ? negatives.forEach((a) => removeTagRecWorkingCondition(a.id)) : exclude(),
children: negatives.length ? "恢复" : "不看"
}),
addPickerModal
]
})
});
}
function filterMenuSections(key, block, allow) {
return [
{
key: `${key}:block-section`,
type: "group",
label: /* @__PURE__ */ jsx(TagRecFilterMenuSection, { mode: "block" }),
children: block
},
{
key: `${key}:divider`,
type: "divider"
},
{
key: `${key}:allow-section`,
type: "group",
label: /* @__PURE__ */ jsx(TagRecFilterMenuSection, { mode: "allow" }),
children: allow
}
];
}
/**
* [tag-rec] 🏷 标签管理入口 hook (spec §4.6 / §5.3)
* - 产出 VideoCardActionButton (inlinePosition='right' 进 topRightActionsEl) + 右键菜单项「标签」
* - 挂点 tab 无关: 全 tab 卡片生效 (不写 tab 门控); 仅 av 视频启用 (view/detail/tag 需要 bvid)
* - 弹层挂卡片就近容器 (与右键菜单 Dropdown 同策略, 不挂 body); 点开才发请求
* - 票 33: 右键组加「屏蔽此 UP」「按标题关键词屏蔽…」= 全局 filter.byAuthor/byTitle
* + tagRec.filter.dims.upBlacklist/titleKeywords 双写 (单写任一边另一面漏放, spec §4.7);
* 纯规划逻辑在 model/block-write.ts, 此处只做落定
* - filter-scope 票02/12: 屏蔽项分档「本组/全局」—— 本组 = 逐认领组 overrides.negative 写入
* (归因名剥 ·相关 后缀找组, 只杀本组流量); 全局档维持票33双写 (byAuthor/byTitle 连带主 feed)
* - 「分区名单 (屏蔽/仅看)」子菜单 (拆池实修): 当前视频的子分区(tidv2)/一级分区(pid_v2)/旧体系(tid)
* ×「本组(含草稿组兜底)/全局」×「屏蔽/仅看」; 屏蔽写 partitionBlock (命中即滤),
* 仅看写 partitionAllow (域收窄池, 入池即开闸); 同分区两池同存时屏蔽先判赢
* - 对象入口配独立图标与说明;屏蔽/仅看分段,临时/本组/全局用范围徽标和生效说明。
* 仅看家族 (upAllow/titleAllow/descAllow/partitionAllow) 域收窄, 开启 toast 防呆「可能刷不出视频」;
* 简介词入口依赖 cardData.tagRecDesc (tag/top 池条目无 desc => 该项禁用)
* - 临时档写所在页的过滤差分并记账;主题推荐换查看范围清除,标签发掘换锚点池清除。
*/
function useTagManageRelated({ cardData, actionButtonVisible, tab, item }) {
const { bvid, goto, title, authorName, authorMid } = cardData;
/** R8: 简介词入口原料 (tag/top 池条目无 desc => 简介子菜单禁用) */
const descText = cardData.tagRecDesc;
const enabled = !!bvid && checkIsNormalVideo(goto);
const tagRecItem = tab === ETab.TagRecCustom && item?.api === EApiType.TagRec ? item : void 0;
const [popoverOpen, setPopoverOpen] = (0, import_react$2.useState)(false);
const anchorWrapRef = (0, import_react$2.useRef)(null);
const getPopupContainer = (0, import_react$2.useCallback)(() => {
return anchorWrapRef.current?.closest(`.${APP_CLS_CARD}`) ?? document.body;
}, []);
const borderColor = usePopoverBorderColor();
const openTagPopover = useMemoizedFn(() => setPopoverOpen(true));
const canUseTemp = tab === ETab.TagRecCustom || tab === ETab.TagRecFav;
const tempScope = tab === ETab.TagRecFav ? "fav" : "custom";
const tempResetLabel = tab === ETab.TagRecFav ? "切换查看范围后恢复" : "切换查询后恢复";
const onRefresh = useOnRefresh();
/** R8 临时账用认领组名 (剥 ·相关; 与 writeOwningGroups 同语义) */
const ownerNames = (0, import_react$2.useMemo)(() => [...new Set((cardData.tagRecGroupNames ?? []).map(stripRelatedSuffix))], [cardData.tagRecGroupNames]);
/**
* 组级名单写位 (票02): 逐认领组 (tagRecGroupNames 剥 ·相关 后缀) overrides.negative 容器;
* fn 返回 true=该组有新写入; 返回写入组数 (0 => 调用侧 toast 未归属/已存在)
* 实修: 未归属来源名回落草稿组 (与 resolver fallbackGroup 同语义) —— tab2 解析未存成项也可先屏蔽,
* 「顺手存成项」升级时草稿组 negative 随组入表
*/
const writeOwningGroups = useMemoizedFn((fn) => {
const names = new Set((cardData.tagRecGroupNames ?? []).map(stripRelatedSuffix));
let n = 0;
for (const g of resolveLandingGroups(favorites.list, [], [...names], cardData.tagRecGroupIds)) {
g.overrides ??= {};
g.overrides.negative ??= {};
if (fn(g.overrides.negative)) n++;
}
const draft = tagRecCustomSession.draftGroup;
if (draft && !cardData.tagRecGroupIds?.length && (!names.size || [...names].some((x) => !favorites.list.some((g) => g.name === x)))) {
draft.negative ??= {};
if (fn(draft.negative)) n++;
}
return n;
});
const favSnapList = useSnapshot(favoritesStore).list;
const draftName = useSnapshot(tagRecCustomSession).draftGroup?.name;
const [ownerGroupCount, ownerLabel, groupScopeDescription] = (0, import_react$2.useMemo)(() => {
const { matched, hasUnowned } = lookupSnapshotCardOwners(snapshot(favoritesStore).list, ownerNames, cardData.tagRecGroupIds);
const draft = draftName !== void 0 && !cardData.tagRecGroupIds?.length && (!ownerNames.length || hasUnowned);
const parts = [matched.length ? `${matched.length} 个认领组` : "", draft ? "草稿组" : ""].filter(Boolean);
const description = [matched.length ? `长期 · 仅${matched.map((g) => `「${g.name}」`).join("、")}` : "", draft ? `当前查询「${draftName}」· 可随标签组保存` : ""].filter(Boolean).join(" / ");
return [
matched.length + (draft ? 1 : 0),
parts.join("+") || "未归属",
description || "此视频没有所属标签组"
];
}, [
favSnapList,
ownerNames,
cardData.tagRecGroupIds,
draftName
]);
const scopeDescription = (scope, includesHome = false) => scope === "temp" ? "仅本次查询 · 切换后恢复" : scope === "group" ? groupScopeDescription : includesHome ? "长期生效 · 首页与标签推荐" : "长期生效 · 全部标签推荐";
const actionLabel = (mode, target, scope, includesHome = false) => /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: `${mode === "block" ? "屏蔽" : "仅看"}${target}`,
scope: scope === "temp" ? "临时" : scope === "group" ? "本组" : "全局",
description: scopeDescription(scope, includesHome),
disabled: scope === "group" && !ownerGroupCount
});
const [kwModal, setKwModal] = (0, import_react$2.useState)();
const [kwDraft, setKwDraft] = (0, import_react$2.useState)("");
const [checkedSegs, setCheckedSegs] = (0, import_react$2.useState)(/* @__PURE__ */ new Set());
const kwSourceText = kwModal ? kwModal.source === "desc" ? descText ?? "" : title : "";
const kwSegs = (0, import_react$2.useMemo)(() => kwSourceText ? splitTitleToSegments(kwSourceText) : [], [kwSourceText]);
const openKwModal = useMemoizedFn((mode, scope, source) => {
setKwModal({
mode,
scope,
source
});
setKwDraft(source === "desc" ? descText ?? "" : title);
setCheckedSegs(/* @__PURE__ */ new Set());
});
const toggleSeg = useMemoizedFn((seg, checked) => {
setCheckedSegs((prev) => {
const next = new Set(prev);
if (checked) next.add(seg);
else next.delete(seg);
return next;
});
});
const onConfirmKw = useMemoizedFn(async () => {
if (!kwModal) return;
const draft = kwDraft.trim();
const words = draft ? [draft, ...checkedSegs] : [...checkedSegs];
if (!words.length) return;
const { mode, scope, source } = kwModal;
const isTitle = source === "title";
const what = `${isTitle ? "标题" : "简介"}${mode === "block" ? "屏蔽词" : "仅看词"}`;
setKwModal(void 0);
if (scope === "temp") {
let ok = 0;
for (const w of words) if (writeTagRecTempEntry({
scope: tempScope,
kind: isTitle ? mode === "block" ? "titleBlock" : "titleAllow" : mode === "block" ? "descBlock" : "descAllow",
value: w,
label: `${mode === "block" ? "屏蔽" : "仅看"} ${isTitle ? "标题" : "简介"}词: ${w}`,
owningGroupNames: ownerNames,
owningGroupIds: cardData.tagRecGroupIds
}) === "ok") ok++;
if (ok) onRefresh("refresh");
return toast(ok ? `已临时${mode === "block" ? "屏蔽" : "仅看"} ${ok} 个${what} (${tempResetLabel}; 可在「过滤」里保存)` : `${what}已存在于临时名单`);
}
if (scope === "group") {
const n = writeOwningGroups((neg) => {
if (mode === "block") {
const key = isTitle ? "title" : "desc";
neg[key] ??= [];
let hit = false;
for (const w of words) if (!neg[key].includes(w)) neg[key].push(w), hit = true;
return hit;
}
const key = isTitle ? "titleAllow" : "descAllow";
neg[key] ??= {
enabled: true,
list: []
};
let hit = false;
for (const w of words) if (!neg[key].list.includes(w)) neg[key].list.push(w), hit = true;
neg[key].enabled = true;
return hit;
});
return toast(n ? `已在本组添加 ${words.length} 个${what} (${ownerLabel}生效${mode === "allow" ? " —— 本组流量只剩命中项, 可能刷不出视频" : ""})` : `${what}均已存在 (或未归属组)`);
}
if (mode === "allow") return;
if (isTitle) {
const dims = settings.tagRec.filter.dims;
const globalKw = await getNewestValueOfSettingsInnerArray("filter.byTitle.keywords");
const plan = planTitleKeywordWrites(words, globalKw, dims.titleKeywords.list);
if (plan.globalAdds.length) await updateSettingsInnerArray("filter.byTitle.keywords", { add: plan.globalAdds });
updateSettings({ filter: { byTitle: { enabled: true } } });
if (plan.tagRecAdds.length) dims.titleKeywords.list.push(...plan.tagRecAdds);
dims.titleKeywords.enabled = true;
const added = words.length - plan.skipped.length;
const skipNote = plan.skipped.length ? `, 跳过已存在 ${plan.skipped.length} 个` : "";
return toast(added ? `已添加 ${added} 个标题关键词 (全局过滤 + 标签推荐双生效)${skipNote}` : `关键词均已存在${skipNote}`);
}
const dims = settings.tagRec.filter.dims;
let added = 0;
for (const w of words) if (!dims.descKeywords.list.includes(w)) dims.descKeywords.list.push(w), added++;
dims.descKeywords.enabled = true;
toast(added ? `已添加 ${added} 个简介屏蔽词 (标签推荐全局生效)` : "关键词均已存在");
});
const onBlockUpGlobal = useMemoizedFn(async () => {
const dims = settings.tagRec.filter.dims;
const mid = authorMid && /^\d+$/.test(authorMid) ? Number(authorMid) : void 0;
const plan = planUpBlockWrite({
name: authorName,
mid
}, settings.filter.byAuthor.keywords, dims.upBlacklist.list);
if (plan.globalAdd) await updateSettingsInnerArray("filter.byAuthor.keywords", { add: [plan.globalAdd] });
updateSettings({ filter: { byAuthor: { enabled: true } } });
if (plan.tagRecAdd) dims.upBlacklist.list.push(plan.tagRecAdd);
dims.upBlacklist.enabled = true;
if (authorMid && authorName) setNicknameCache(authorMid, authorName);
if (!plan.globalAdd && !plan.tagRecAdd) return toast(`UP「${authorName || authorMid}」已在屏蔽名单`);
toast(`已屏蔽 UP「${authorName || authorMid}」(全局过滤 + 标签推荐双生效)`);
});
/** 票02/12 本组档: 只写认领组 negative.up (不动全局; 票33 双写由全局档独享) */
const onBlockUpGroup = useMemoizedFn(() => {
const mid = authorMid && /^\d+$/.test(authorMid) ? Number(authorMid) : void 0;
const entry = {
name: authorName?.trim() ?? "",
mid
};
if (!writeOwningGroups((neg) => {
neg.up ??= [];
if (neg.up.some((e) => mid !== void 0 ? e.mid === mid : e.name === entry.name)) return false;
neg.up.push(entry);
return true;
})) return toast(`UP「${authorName || authorMid}」已在各认领组名单 (或未归属组)`);
toast(`已在本组屏蔽 UP「${authorName || authorMid}」(${ownerLabel}生效)`);
});
const onAllowUpGroup = useMemoizedFn(() => {
const mid = authorMid && /^\d+$/.test(authorMid) ? Number(authorMid) : void 0;
const entry = {
name: authorName?.trim() ?? "",
mid
};
if (!writeOwningGroups((neg) => {
neg.upAllow ??= {
enabled: true,
list: []
};
if (neg.upAllow.list.some((e) => mid !== void 0 ? e.mid === mid : e.name === entry.name)) return false;
neg.upAllow.list.push(entry);
neg.upAllow.enabled = true;
return true;
})) return toast(`UP「${authorName || authorMid}」已在各认领组仅看池 (或未归属组)`);
toast(`已把 UP「${authorName || authorMid}」加入本组仅看 (${ownerLabel});开启过滤后${mid ? "名单全部带 UID 时按标签名搜索 UP 投稿,可能漏掉仅视频标签命中的内容" : "在原结果中筛选该 UP"}`);
});
const onBlockUpTemp = useMemoizedFn(() => {
const mid = authorMid && /^\d+$/.test(authorMid) ? Number(authorMid) : void 0;
const r = writeTagRecTempEntry({
scope: tempScope,
kind: "upBlock",
value: {
name: authorName?.trim() ?? "",
mid
},
label: `屏蔽 UP: ${authorName || authorMid}`,
owningGroupNames: ownerNames,
owningGroupIds: cardData.tagRecGroupIds
});
if (r === "ok") onRefresh("refresh");
toast(r === "ok" ? `已临时屏蔽 UP「${authorName || authorMid}」(${tempResetLabel}; 可在「过滤」里保存)` : `UP「${authorName || authorMid}」已在临时名单`);
});
const onAllowUpTemp = useMemoizedFn(() => {
const mid = authorMid && /^\d+$/.test(authorMid) ? Number(authorMid) : void 0;
const r = writeTagRecTempEntry({
scope: tempScope,
kind: "upAllow",
value: {
name: authorName?.trim() ?? "",
mid
},
label: `仅看 UP: ${authorName || authorMid}`,
owningGroupNames: ownerNames,
owningGroupIds: cardData.tagRecGroupIds
});
if (r === "ok") onRefresh("refresh");
toast(r === "ok" ? `已临时仅看 UP「${authorName || authorMid}」;开启过滤后${mid ? "名单全部带 UID 时从 UP 投稿按标签名搜索" : "筛选原结果"} (${tempResetLabel})` : `UP「${authorName || authorMid}」已在临时名单`);
});
const onBlockPartition = useMemoizedFn((entry, scope) => {
if (scope === "global") {
const dim = settings.tagRec.filter.dims.partitionBlock;
if (!dim.list.some((e) => e.v1 === entry.v1 && e.v2 === entry.v2)) dim.list.push(entry);
dim.enabled = true;
return toast(`已在全局屏蔽分区「${entry.name}」(标签推荐全组生效)`);
}
if (!writeOwningGroups((neg) => {
neg.partitionBlock ??= [];
if (neg.partitionBlock.some((e) => e.v1 === entry.v1 && e.v2 === entry.v2)) return false;
neg.partitionBlock.push(entry);
return true;
})) return toast(`分区「${entry.name}」已在各认领组屏蔽池 (或未归属)`);
toast(`已在本组屏蔽分区「${entry.name}」(${ownerLabel}生效)`);
});
const onAllowPartition = useMemoizedFn((entry, scope) => {
if (scope === "global") {
const dim = settings.tagRec.filter.dims.partitionAllow;
const dup = dim.list.some((e) => e.v1 === entry.v1 && e.v2 === entry.v2);
if (dup && dim.enabled) return toast(`分区「${entry.name}」已在全局仅看池`);
if (!dup) dim.list.push(entry);
dim.enabled = true;
return toast(`已把分区「${entry.name}」加入全局仅看池 —— 标签推荐只剩命中分区 (可在设置里关)`);
}
if (!writeOwningGroups((neg) => {
neg.partitionAllow ??= {
enabled: false,
list: []
};
const pa = neg.partitionAllow;
const dup = pa.list.some((e) => e.v1 === entry.v1 && e.v2 === entry.v2);
if (dup && pa.enabled) return false;
if (!dup) pa.list.push(entry);
pa.enabled = true;
return true;
})) return toast(`分区「${entry.name}」已在各认领组仅看池 (或未归属)`);
toast(`已把分区「${entry.name}」加入本组仅看池并开启 (${ownerLabel}生效 —— 本组流量只剩命中分区)`);
});
/** R8 临时档: 分区屏蔽/仅看写会话覆盖 + 记账 */
const onTempPartition = useMemoizedFn((entry, mode) => {
const r = writeTagRecTempEntry({
scope: tempScope,
kind: mode === "block" ? "partitionBlock" : "partitionAllow",
value: entry,
label: `${mode === "block" ? "屏蔽" : "仅看"} 分区: ${entry.name}`,
owningGroupNames: ownerNames,
owningGroupIds: cardData.tagRecGroupIds
});
if (r === "ok") onRefresh("refresh");
toast(r === "ok" ? `已临时${mode === "block" ? "屏蔽" : "仅看"}分区「${entry.name}」(${tempResetLabel}; 可在「过滤」里保存)` : `分区「${entry.name}」已在临时名单`);
});
const tagManageActionButtonEl = enabled && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(Popover, {
open: popoverOpen,
onOpenChange: setPopoverOpen,
trigger: "click",
arrow: false,
placement: "bottomRight",
getPopupContainer,
styles: { container: { border: `1px solid ${borderColor}` } },
content: bvid ? /* @__PURE__ */ jsx(TagRecTagPopover, {
bvid,
workbenchMode: tab === ETab.TagRecCustom && !!tagRecCustomSession.workingGroup,
sourceGroupNames: cardData.tagRecGroupNames,
sourceGroupIds: cardData.tagRecGroupIds
}) : void 0,
children: /* @__PURE__ */ jsx("span", {
ref: anchorWrapRef,
className: "inline-flex",
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
},
children: /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible || popoverOpen,
active: popoverOpen,
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(IconForTagManage, { className: "size-16px" }),
tooltip: "标签"
})
})
}) });
const tagManageModalEl = kwModal && /* @__PURE__ */ jsx(Modal, {
title: kwModal ? `${kwModal.mode === "block" ? "屏蔽" : "仅看"}${kwModal.source === "title" ? "标题" : "简介"}含关键词的视频` : "",
open: !!kwModal,
onCancel: () => setKwModal(void 0),
onOk: onConfirmKw,
okText: kwModal?.mode === "block" ? "屏蔽" : "仅看",
okButtonProps: { disabled: !kwDraft.trim() && !checkedSegs.size },
width: 440,
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"编辑关键词,或点选下方片段。",
kwModal?.mode === "allow" ? "只保留" : "屏蔽",
"包含任意一个关键词的视频。"
]
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: ["作用范围:", kwModal && scopeDescription(kwModal.scope, kwModal.source === "title")]
}),
/* @__PURE__ */ jsx(Input, {
value: kwDraft,
onChange: (e) => setKwDraft(e.target.value),
"aria-label": "筛选关键词",
placeholder: "输入关键词,或选择下方片段"
}),
!!kwSegs.length && /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-4px",
children: kwSegs.map((seg) => /* @__PURE__ */ jsx(Tag.CheckableTag, {
checked: checkedSegs.has(seg),
onChange: (c) => toggleSeg(seg, c),
children: seg
}, seg))
})
]
})
});
const partitionEntries = [];
const parentId = cardData.tagRecPidV2 ?? (cardData.tagRecTidv2 === void 0 ? void 0 : tagRecPartitionParent(cardData.tagRecTidv2));
if (cardData.tagRecTidv2 !== void 0) {
const isParent = parentId === cardData.tagRecTidv2;
const name = cardData.tagRecTnamev2 || `新版${isParent ? "一级" : "子"}分区 #${cardData.tagRecTidv2}`;
partitionEntries.push({
key: "v2leaf",
label: `当前分区「${name}」`,
entry: {
name,
v2: cardData.tagRecTidv2,
level: isParent ? "parent" : "leaf"
}
});
}
if (parentId !== void 0 && parentId !== cardData.tagRecTidv2) {
const name = cardData.tagRecPidNameV2 || Object.entries(PARTITION_NAME_TO_PARENT).find(([, ids]) => ids.v2 === parentId)?.[0] || `新版一级分区 #${parentId}`;
partitionEntries.push({
key: "v2parent",
label: `上级分区「${name}」`,
entry: {
name,
v2: parentId,
level: "parent"
}
});
}
if (cardData.tagRecTid !== void 0) {
const name = Object.entries(PARTITION_NAME_TO_PARENT).find(([, ids]) => ids.v1 === cardData.tagRecTid)?.[0];
partitionEntries.push({
key: "v1",
label: name ? `旧版分区「${name}」` : `旧版分区 #${cardData.tagRecTid}`,
entry: {
name: name || `旧版分区 #${cardData.tagRecTid}`,
v1: cardData.tagRecTid
}
});
}
const partitionScopeItems = (entry, parentKey) => filterMenuSections(parentKey, [
...canUseTemp ? [{
key: `${parentKey}:block:temp`,
label: actionLabel("block", "这个分区", "temp"),
onClick: () => onTempPartition(entry, "block")
}] : [],
{
key: `${parentKey}:block:group`,
label: actionLabel("block", "这个分区", "group"),
disabled: !ownerGroupCount,
onClick: () => onBlockPartition(entry, "group")
},
{
key: `${parentKey}:block:global`,
label: actionLabel("block", "这个分区", "global"),
onClick: () => onBlockPartition(entry, "global")
}
], [
...canUseTemp ? [{
key: `${parentKey}:allow:temp`,
label: actionLabel("allow", "这个分区", "temp"),
onClick: () => onTempPartition(entry, "allow")
}] : [],
{
key: `${parentKey}:allow:group`,
label: actionLabel("allow", "这个分区", "group"),
disabled: !ownerGroupCount,
onClick: () => onAllowPartition(entry, "group")
},
{
key: `${parentKey}:allow:global`,
label: actionLabel("allow", "这个分区", "global"),
onClick: () => onAllowPartition(entry, "global")
}
]);
return {
tagManageActionButtonEl,
tagManageModalEl,
tagRecMenus: enabled ? [
{
key: "tag-rec:tag-popover",
label: /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: "标签管理",
description: tagRecItem ? "查看视频标签并取条件" : "查看、收藏或屏蔽标签"
}),
icon: /* @__PURE__ */ jsx(IconForTagManage, { className: "size-16px" }),
onClick: () => {
if (tagRecItem) openTagRecVideoMaterials(tagRecItem, tagRecWorkbench.activeId);
else openTagPopover();
}
},
...tab === ETab.TagRecCustom && tagRecCustomSession.workingGroup ? [{
key: "tag-rec:workbench",
label: "选入 / 排除组合条件",
children: [
...authorName || authorMid ? [{
key: "workbench:up",
label: `UP 主 · ${authorName || authorMid}`,
children: [
{
key: "workbench:up:add",
label: "选入组合",
onClick: () => addTagRecWorkingCondition(tagRecConditionAtom("up", {
name: authorName || `UID ${authorMid}`,
mid: authorMid ? Number(authorMid) : void 0
}))
},
{
key: "workbench:up:block",
label: "这个组合不看",
onClick: () => excludeTagRecWorkingCondition(tagRecConditionAtom("up", {
name: authorName || `UID ${authorMid}`,
mid: authorMid ? Number(authorMid) : void 0
}))
},
{
key: "workbench:up:global",
label: "以后都不看",
onClick: () => blockTagRecConditionGlobally(tagRecConditionAtom("up", {
name: authorName || `UID ${authorMid}`,
mid: authorMid ? Number(authorMid) : void 0
}))
}
]
}] : [],
...["title", "desc"].flatMap((field) => splitTitleToSegments(field === "title" ? title : descText ?? "").filter((word) => word.trim().length >= 2 && word.length <= 200).slice(0, 8).map((word, i) => ({
key: `workbench:${field}:${i}`,
label: `${field === "title" ? "标题" : "简介"} · ${word}`,
children: [
{
key: `workbench:${field}:${i}:add`,
label: "选入组合",
onClick: () => addTagRecWorkingCondition(tagRecConditionAtom(field, word))
},
{
key: `workbench:${field}:${i}:block`,
label: "这个组合不看",
onClick: () => excludeTagRecWorkingCondition(tagRecConditionAtom(field, word))
},
{
key: `workbench:${field}:${i}:global`,
label: "以后都不看",
onClick: () => blockTagRecConditionGlobally(tagRecConditionAtom(field, word))
}
]
}))),
...partitionEntries.map((part) => ({
key: `workbench:part:${part.key}`,
label: `分区 · ${part.label}`,
children: [{
key: `workbench:part:${part.key}:add`,
label: "选入组合",
onClick: () => addTagRecWorkingCondition(tagRecConditionAtom("partition", part.entry))
}, {
key: `workbench:part:${part.key}:block`,
label: "这个组合不看",
onClick: () => excludeTagRecWorkingCondition(tagRecConditionAtom("partition", part.entry))
}]
}))
]
}] : [],
...authorName || authorMid ? [{
key: "tag-rec:up",
label: /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: "筛选这个 UP 主",
description: authorName || `UP ${authorMid}`
}),
icon: /* @__PURE__ */ jsx(IconForFilterAuthor, { className: "size-16px" }),
children: filterMenuSections("tag-rec:up", [
...canUseTemp ? [{
key: "tag-rec:up:block:temp",
label: actionLabel("block", "这个 UP 主", "temp"),
onClick: onBlockUpTemp
}] : [],
{
key: "tag-rec:up:block:group",
label: actionLabel("block", "这个 UP 主", "group"),
disabled: !ownerGroupCount,
onClick: onBlockUpGroup
},
{
key: "tag-rec:up:block:global",
label: actionLabel("block", "这个 UP 主", "global", true),
onClick: onBlockUpGlobal
}
], [...canUseTemp ? [{
key: "tag-rec:up:allow:temp",
label: actionLabel("allow", "这个 UP 主", "temp"),
onClick: onAllowUpTemp
}] : [], {
key: "tag-rec:up:allow:group",
label: actionLabel("allow", "这个 UP 主", "group"),
disabled: !ownerGroupCount,
onClick: onAllowUpGroup
}])
}] : [],
{
key: "tag-rec:title",
label: /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: "按标题筛选",
description: "按关键词屏蔽 / 仅看视频"
}),
icon: /* @__PURE__ */ jsx(IconForFilterTitle, { className: "size-16px" }),
children: filterMenuSections("tag-rec:title", [
...canUseTemp ? [{
key: "tag-rec:title:block:temp",
label: actionLabel("block", "标题含词的视频…", "temp"),
onClick: () => openKwModal("block", "temp", "title")
}] : [],
{
key: "tag-rec:title:block:group",
label: actionLabel("block", "标题含词的视频…", "group"),
disabled: !ownerGroupCount,
onClick: () => openKwModal("block", "group", "title")
},
{
key: "tag-rec:title:block:global",
label: actionLabel("block", "标题含词的视频…", "global", true),
onClick: () => openKwModal("block", "global", "title")
}
], [...canUseTemp ? [{
key: "tag-rec:title:allow:temp",
label: actionLabel("allow", "标题含词的视频…", "temp"),
onClick: () => openKwModal("allow", "temp", "title")
}] : [], {
key: "tag-rec:title:allow:group",
label: actionLabel("allow", "标题含词的视频…", "group"),
disabled: !ownerGroupCount,
onClick: () => openKwModal("allow", "group", "title")
}])
},
{
key: "tag-rec:desc",
label: /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: "按简介筛选",
description: descText ? "按简介关键词屏蔽 / 仅看" : "此视频暂无简介,无法筛选",
disabled: !descText
}),
icon: /* @__PURE__ */ jsx(IconForFilterDescription, { className: "size-16px" }),
disabled: !descText,
children: filterMenuSections("tag-rec:desc", [
...canUseTemp ? [{
key: "tag-rec:desc:block:temp",
label: actionLabel("block", "简介含词的视频…", "temp"),
disabled: !descText,
onClick: () => openKwModal("block", "temp", "desc")
}] : [],
{
key: "tag-rec:desc:block:group",
label: actionLabel("block", "简介含词的视频…", "group"),
disabled: !ownerGroupCount || !descText,
onClick: () => openKwModal("block", "group", "desc")
},
{
key: "tag-rec:desc:block:global",
label: actionLabel("block", "简介含词的视频…", "global"),
disabled: !descText,
onClick: () => openKwModal("block", "global", "desc")
}
], [...canUseTemp ? [{
key: "tag-rec:desc:allow:temp",
label: actionLabel("allow", "简介含词的视频…", "temp"),
disabled: !descText,
onClick: () => openKwModal("allow", "temp", "desc")
}] : [], {
key: "tag-rec:desc:allow:group",
label: actionLabel("allow", "简介含词的视频…", "group"),
disabled: !ownerGroupCount || !descText,
onClick: () => openKwModal("allow", "group", "desc")
}])
},
...partitionEntries.length ? [{
key: "tag-rec:partition",
label: /* @__PURE__ */ jsx(TagRecFilterMenuLabel, {
title: "按分区筛选",
description: cardData.tagRecTnamev2 || cardData.tagRecPidNameV2 || partitionEntries[0].label
}),
icon: /* @__PURE__ */ jsx(IconForFilterPartition, { className: "size-16px" }),
children: partitionEntries.map((p) => ({
key: `tag-rec:partition:${p.key}`,
label: p.label,
children: partitionScopeItems(p.entry, `tag-rec:partition:${p.key}`)
}))
}] : []
] : [],
openTagPopover
};
}
/**
* [tag-rec] 已看标记 hook (spec §4.5) —— VideoCard/index.tsx 注册行专用
* - 仅 tab∈{TagRecFav,TagRecCustom} 生效 (其它 tab skip 早退, 不挂 observer)
* - useOnInView({threshold:0.5}) ≥50% 进视口即 seen.add(bvid),不触发卡片重渲染
* - triggerOnce: 入过视口的卡片不再重复观察
* - 被过滤/被拉黑剔除的条目不渲染 => 不计已看 (§4.5)
* @returns observer ref, 挂到卡片根元素
*/
function useTagRecSeen(item, tab) {
const { scopeEditing } = useSnapshot(tagRecSharedSession);
const enabled = isTagRecSeenTab(tab);
const bvid = item && checkIsTagRec(item) ? item.bvid : void 0;
const visibleBvid = (0, import_react$2.useRef)(void 0);
const ref = useOnInView((inView) => {
if (!inView || !enabled || !bvid) return;
visibleBvid.current = bvid;
if (!tagRecSharedSession.scopeEditing) seen.add(bvid);
}, {
threshold: .5,
triggerOnce: true,
skip: !enabled || !bvid
});
(0, import_react$2.useEffect)(() => {
if (enabled && bvid && visibleBvid.current === bvid && !scopeEditing) seen.add(bvid);
}, [
enabled,
bvid,
scopeEditing
]);
return ref;
}
function tweakColorWithOklch(originalColor, { l, c, h, alpha, deltaL, deltaC, deltaH, deltaAlpha } = {}) {
return `oklch(from ${originalColor} ${l ?? (deltaL ? `calc(l + ${deltaL})` : "l")} ${c ?? (deltaC ? `calc(c + ${deltaC})` : "c")} ${h ?? (deltaH ? `calc(h + ${deltaH})` : "h")} / ${alpha ?? (deltaAlpha ? `calc(alpha + ${deltaAlpha})` : "alpha")})`;
}
function tweakLightness(originalColor, delta) {
return tweakColorWithOklch(originalColor, { deltaL: delta });
}
/**
* inspired by styles of
* - https://ai.taobao.com
* - https://xlog.app/hottest
*
* box-shadow playground
* https://box-shadow.dev/
*/
var c = tweakLightness(appPrimaryColorValue, .1);
var Styles = {
normalBorder: /* @__PURE__ */ css("border-color:", appBorderColorValue, ";"),
activeBorder: /* @__PURE__ */ css("border-color:", appPrimaryColorValue, ";box-shadow:0px 0px 9px 4px ", c, ";"),
rounded: /* @__PURE__ */ css("border-radius:", videoCardBorderRadiusValue, ";"),
bgLv1: /* @__PURE__ */ css("background-color:", appBgLv1Value, ";"),
coverZoomEffect: /* @__PURE__ */ css(".bili-video-card__cover{transform-origin:center center;transition:transform 0.2s ease-out;transform:scale(1.05);}")
};
var multiSelectedCss = /* @__PURE__ */ css(Styles.activeBorder, " &:hover{", Styles.activeBorder, "}");
/**
* for dislike & blacklist card
* - show border ALWAYS
* - hover highlight bg
* - hover highlight separator
*/
function useBlockedCardCss(isBlockedCard) {
const sepIdentifier = `--${APP_NAMESPACE}--separator-color`;
return (0, import_react$2.useMemo)(() => {
if (!isBlockedCard) return;
return /* @__PURE__ */ css(Styles.rounded, " ", Styles.normalBorder, " background-color:", appBgValue, ";", sepIdentifier, ":", appBgLv1Value, ";&:hover{background-color:", appBgLv1Value, ";", sepIdentifier, ":", appBgLv2Value, ";}margin-inline:0;.bili-video-card__wrap{padding:0;}");
}, [isBlockedCard]);
}
/**
* designed add to {APP_CLS_CARD}
*/
function useCardBorderCss() {
const { useDelayForHover, style: { videoCard: { useBorder, useBorderOnlyOnHover, useBoxShadow } } } = useSettingsSnapshot();
const { multiSelecting } = useSnapshot(multiSelectStore);
const displayingAsList = useIsDisplayAsList();
return (0, import_react$2.useMemo)(() => {
return [/* @__PURE__ */ css("border:1px solid transparent;transition-property:border-color,box-shadow,background-color;transition-duration:0.3s;transition-timing-function:ease-in-out;"), (multiSelecting || useBorder && !displayingAsList) && [/* @__PURE__ */ css("cursor:pointer;", Styles.rounded, " &:hover{", Styles.bgLv1, " ", Styles.normalBorder, " ", useBoxShadow && Styles.activeBorder, " ", useDelayForHover && Styles.coverZoomEffect, "}"), (multiSelecting || !useBorderOnlyOnHover) && Styles.normalBorder]];
}, [
useBorder,
useBorderOnlyOnHover,
useBoxShadow,
useDelayForHover,
displayingAsList,
multiSelecting
]);
}
function getActiveCardBorderCss(active) {
return active && [Styles.rounded, Styles.activeBorder];
}
/**
* 不喜欢 / 撤销不喜欢
* https://github.com/indefined/UserScripts/blob/master/bilibiliHome/bilibiliHome.API.md
*
* 此类内容过多 reason_id = 12
* 推荐过 reason_id = 13
*/
function appDislikeFactory(action) {
const pathname = {
dislike: "/x/feed/dislike",
cancel: "/x/feed/dislike/cancel"
}[action];
return async function(item, reasonId) {
return (await gmrequest.safeGet(HOST_APP + pathname, {
responseType: "json",
params: {
goto: item.goto,
id: item.param,
reason_id: reasonId,
build: "1",
mobi_app: "android",
idx: (Date.now() / 1e3).toFixed(0)
}
})).andThen((resp) => {
const actionText = (action === "dislike" ? "提交" : "撤销") + "不喜欢失败";
return WebApiError.validateAxiosResponse(resp, actionText, "请重新获取 access_key 后重试!");
}).andThen((json) => ae.ok(json?.message || ""));
};
}
var appDislike = appDislikeFactory("dislike");
var appCancelDislike = appDislikeFactory("cancel");
function pcDislikeFactory(action) {
const pathname = {
dislike: "/x/web-interface/feedback/dislike",
cancel: "/x/web-interface/feedback/dislike/cancel"
}[action];
return async function(item, reasonId) {
const form = new URLSearchParams({
app_id: "100",
platform: "5",
from_spmid: "",
spmid: "333.1007.0.0",
goto: item.goto,
id: item.id.toString() || "0",
mid: item.owner?.mid?.toString() || "0",
track_id: item.track_id,
feedback_page: "1",
reason_id: reasonId.toString()
});
return (await request.safePost(pathname, form)).andThen((resp) => WebApiError.validateAxiosResponse(resp)).andThen((json) => ae.ok(json?.message));
};
}
var pcDislike = pcDislikeFactory("dislike");
var pcCancelDislike = pcDislikeFactory("cancel");
function handlerFactory(action) {
return async function(item, reason) {
const { reasonId } = normalizeDislikeReason(reason);
let result;
if (reason.platform === "app") {
invariant$1(checkIsAppRecommend(item), "expect app recommend");
result = await (action === "dislike" ? appDislike : appCancelDislike)(item, reasonId);
} else if (reason.platform === "pc") {
invariant$1(checkIsPcRecommend(item), "expect pc recommend");
result = await (action === "dislike" ? pcDislike : pcCancelDislike)(item, reasonId);
} else assertNever(reason);
if (result.isErr()) {
handleRequestError(result.error);
return false;
}
const dislikeKey = calcRecItemDislikedMapKey(item);
invariant$1(dislikeKey, "dislikeKey should not be empty");
if (action === "dislike") {
antMessage.success("已标记不想看");
dislikedMap.set(dislikeKey, { ...reason });
}
if (action === "cancel") {
antMessage.success("已撤销");
delDisliked(dislikeKey);
}
return true;
};
}
var handleDislike = handlerFactory("dislike");
var handleCancelDislike = handlerFactory("cancel");
var skeletonActive = "_skeleton-active_wlycr_12";
var import_dayjs_min$1 = /* @__PURE__ */ __toESM(require_dayjs_min(), 1);
function useInitFavContext(item, avid, emitter) {
const [folderNames, setFolderNames] = (0, import_react$2.useState)(void 0);
const [folderUrls, setFolderUrls] = (0, import_react$2.useState)(void 0);
const [folderIds, setFolderIds] = (0, import_react$2.useState)(void 0);
const [themeName, setThemeName] = (0, import_react$2.useState)(void 0);
const updateFavFolderNames = useMemoizedFn(async () => {
if (!getLoginStatus()) return;
if (!avid) return;
const { enable: enableQuickFav } = getQuickFavConfig(item.api);
if (!(enableQuickFav || checkIsFav(item) && item.from === "fav-folder")) return;
const result = await UserFavApi.getVideoFavState(avid);
if (result) {
setFolderNames(result.favFolderNames);
setFolderUrls(result.favFolderUrls);
setFolderIds(result.favFolderIds);
}
});
const updateThemeName = useMemoizedFn(async () => {
const groupNames = item.source?.groupNames;
if (!groupNames?.length) return setThemeName(void 0);
const m = await module.import('./fav-theme-folder-CTfuYzmX-Dcl__BwQ.js').catch(() => void 0);
setThemeName(m?.resolveCardThemeName(groupNames));
});
useEmitterOn(emitter, "context-menu-open", updateFavFolderNames);
useEmitterOn(emitter, "context-menu-open", updateThemeName);
return (0, import_react$2.useMemo)(() => ({
folderNames,
folderUrls,
folderIds,
themeName,
updateFavFolderNames
}), [
folderNames,
folderUrls,
folderIds,
themeName,
updateFavFolderNames
]);
}
function getQuickFavConfig(api) {
return {
enable: ![
EApiType.Separator,
EApiType.Live,
EApiType.Fav
].includes(api),
enableDetailMenu: api === EApiType.Watchlater || api === EApiType.Liked
};
}
function getFavContextMenus(ctx, item, avid) {
if (!avid) return;
const { enable, enableDetailMenu } = getQuickFavConfig(item.api);
if (!enable) return;
const folderNames = ctx.folderNames ?? [];
const folderUrls = ctx.folderUrls ?? [];
const folderIds = ctx.folderIds ?? [];
const favedMenus = defineAntMenus([{
key: "quick-fav:faved:modify-fav",
icon: /* @__PURE__ */ jsx(IconForFaved, { className: clsx(clsContextMenuIcon, "color-gate-primary") }),
label: `已收藏在 ${(folderNames || []).map((n) => `「${n}」`).join("")}`,
async onClick() {
invariant$1(folderIds.length, "folderIds.length should not be empty");
await startModifyFavItemToTargetFolders({
srcFolderIds: folderIds,
modifyOkAction: (targetFolder) => {
return handleModifyFavItemToFolders(avid, folderIds, targetFolder);
}
});
}
}, enableDetailMenu && {
key: "quick-fav:faved:browse-fav-folder-in-self-space",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: "size-16px" }),
label: "去个人空间查看收藏夹",
onClick() {
folderUrls.forEach((u) => {
window.open(u, getLinkTarget());
});
}
}]);
const theme = ctx.themeName;
const pickFoldersAndFav = async () => {
await startPickFavFolders(async (targetFolders) => {
const targetFolderIds = mapFavFolderIds(targetFolders);
const result = await UserFavApi.addFav(avid, targetFolderIds);
if (result.isOk()) antMessage.success(`已加入收藏夹${joinFavFolderTitles(targetFolders)}`);
return result.isOk();
});
};
const tagRecThemeMenus = theme ? defineAntMenus([{
key: "quick-fav:unfaved:tag-rec-theme",
icon: /* @__PURE__ */ jsx(IconForFav, { className: clsContextMenuIcon }),
label: `收藏到「${theme}」`,
children: [
{
key: "quick-fav:unfaved:tag-rec-theme:create",
label: `✨ 新建「${theme}」`,
async onClick() {
await (await module.import('./fav-theme-folder-modal-DEvndVyg-BlQgbjJW.js').catch(() => void 0))?.createFavThemeFolderQuick({
avid,
themeName: theme
});
}
},
{
key: "quick-fav:unfaved:tag-rec-theme:match",
label: "📁 收藏到匹配的收藏夹…",
async onClick() {
await (await module.import('./fav-theme-folder-modal-DEvndVyg-BlQgbjJW.js').catch(() => void 0))?.startFavThemeFolderModal({
avid,
themeName: theme
});
}
},
{
key: "quick-fav:unfaved:tag-rec-theme:pick",
label: "📂 选择其他文件夹…",
async onClick() {
await pickFoldersAndFav();
}
}
]
}]) : [];
const unfavedMenus = defineAntMenus([{
key: "quick-fav:unfaved:add-fav",
icon: /* @__PURE__ */ jsx(IconForFav, { className: clsContextMenuIcon }),
label: "收藏到",
async onClick() {
await pickFoldersAndFav();
}
}, {
test: enableDetailMenu,
key: "quick-fav:unfaved:add-fav-to-default-folder",
icon: /* @__PURE__ */ jsx(IconForFav, { className: clsContextMenuIcon }),
label: "收藏到「默认收藏夹」",
async onClick() {
const result = await UserFavApi.addFav(avid);
if (result.isOk()) return antMessage.success(`已加入收藏夹「${defaultFavFolderTitle}」`);
antMessage.error(typeof result.error === "string" ? result.error : result.error.message);
}
}]);
return !!folderNames.length ? favedMenus : [...tagRecThemeMenus, ...unfavedMenus];
}
function getFavTabFavRelatedMenus({ ctx, item, cardData, tab, multiSelecting, multiSelectingAppendix, onRemoveCurrent, recSharedEmitter }) {
if (!checkIsFav(item)) return [];
const { avid } = cardData;
ctx.folderNames;
ctx.folderUrls;
const folderIds = ctx.folderIds ?? [];
if (item.from === "fav-folder") {
const batchMenus = multiSelecting ? [{
key: "fav:batch-move-fav",
label: `移动到其他收藏夹${multiSelectingAppendix}`,
icon: /* @__PURE__ */ jsx(IconForFaved, { className: clsContextMenuIcon }),
async onClick() {
if (!multiSelectStore.multiSelecting) return;
const selectedFavItems = getMultiSelectedItems().filter((x) => checkIsFav(x) && x.from === "fav-folder").toReversed();
const folderIds = new Set(selectedFavItems.map((i) => i.folder.id));
if (!folderIds.size) return toast("至少选择一项视频");
if (folderIds.size > 1) return toast("多选移动: 只能批量移动同一源收藏夹下的视频");
const srcFavFolderId = selectedFavItems[0].folder.id;
const resources = selectedFavItems.map((x) => `${x.id}:${x.type}`);
const uniqIds = selectedFavItems.map((x) => x.uniqId);
const titles = selectedFavItems.map((x) => x.title);
await startModifyFavItemToTargetFolders({
srcFolderIds: [item.folder.id],
modifyOkAction: async (targetFolders) => {
const targetFolder = targetFolders?.[0];
invariant$1(targetFolder && targetFolder.id, "targetFolder should not be empty");
const targetFolderId = targetFolder.id;
const targetFolderTitle = targetFolder.title;
const result = await UserFavApi.moveFavs(resources, srcFavFolderId, targetFolderId);
if (result.isErr()) {
antMessage.error(result.error.message);
return false;
}
clearFavFolderAllItemsCache(item.folder.id);
clearFavFolderAllItemsCache(targetFolderId);
recSharedEmitter.emit("remove-cards", [
uniqIds,
titles,
{ silent: true }
]);
antMessage.success(`已移动 ${uniqIds.length} 个视频到「${targetFolderTitle}」收藏夹`);
return true;
},
modifyAllowEmpty: false,
singleSelect: true
});
}
}] : [];
return defineAntMenus([
{
key: "fav:browse-fav-folder-in-self-space",
label: "去个人空间查看收藏夹",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: clsContextMenuIcon }),
onClick() {
const { id } = item.folder;
const url = formatFavFolderUrl(id);
window.open(url, getLinkTarget());
}
},
{
key: "fav:view-fav-in-new-window",
label: "在新窗口中查看收藏夹",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: clsContextMenuIcon }),
onClick() {
const { id } = item.folder;
const url = `/?${FavQueryKey.FolderIdFull}=${id}`;
window.open(url, getLinkTarget());
}
},
{
test: !!avid,
key: "modify-fav",
icon: /* @__PURE__ */ jsx(IconForFaved, { className: clsContextMenuIcon }),
label: "移动到其他收藏夹",
async onClick() {
const srcFolderIds = folderIds;
await startModifyFavItemToTargetFolders({
srcFolderIds,
modifyAllowEmpty: false,
modifyOkAction: async (targetFolders) => {
const { targetFolderIds } = mapModalFavManagerResult(targetFolders);
invariant$1(targetFolderIds.length, "targetFolderIds should not be empty");
const success = await handleModifyFavItemToFolders(avid, srcFolderIds, targetFolders);
if (success && !targetFolderIds.includes(item.folder.id)) onRemoveCurrent?.(item, cardData, true);
return success;
}
});
}
},
{
key: "remove-fav",
label: "移除收藏",
icon: /* @__PURE__ */ jsx(IconForDelete, { className: clsContextMenuIcon }),
async onClick() {
if (!await antModal.confirm({
centered: true,
title: "移除收藏",
content: /* @__PURE__ */ jsxs(Fragment, { children: [
"确定将视频「",
item.title,
"」",
/* @__PURE__ */ jsx("br", {}),
"从收藏夹「",
item.folder.title,
"」中移除?"
] })
})) return;
const resource = `${item.id}:${item.type}`;
const result = await UserFavApi.removeFavs(item.folder.id, resource);
if (result.isErr()) return antMessage.error(result.error.message);
clearFavFolderAllItemsCache(item.folder.id);
onRemoveCurrent?.(item, cardData);
}
},
...batchMenus
]);
}
if (item.from === "fav-collection") return defineAntMenus([{
key: "open-fav-collection",
label: "浏览合集",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: clsContextMenuIcon }),
onClick() {
const { id } = item.collection;
const url = tab !== ETab.Fav || favStore.selectedKey === "all" && favStore.usingShuffle ? `/?${FavQueryKey.CollectionIdFull}=${id}` : formatFavCollectionSelfSpaceUrl(id);
window.open(url, getLinkTarget());
}
}]);
return [];
}
function useFavActionButton({ ctx, item, cardData, actionButtonVisible }) {
const { avid, goto } = cardData;
const enabled = checkIsWatchlater(item) && checkIsNormalVideo(goto) && !!avid;
const { folderIds, folderNames, updateFavFolderNames } = ctx;
const $updateFavStateReq = useRequest_default(() => updateFavFolderNames(), {
manual: true,
loadingDelay: 500
});
let favStateActionButton;
{
const loadingFavState = $updateFavStateReq.loading;
const faved = !!folderNames?.length;
const handleClick = useMemoizedFn(async (e) => {
e.preventDefault();
e.stopPropagation();
invariant$1(avid, "avid should not be empty");
if (loadingFavState) return;
if (faved) {
invariant$1(folderIds?.length, "folderIds.length should not be empty");
await startModifyFavItemToTargetFolders({
srcFolderIds: folderIds,
modifyOkAction: (targetFolder) => {
return handleModifyFavItemToFolders(avid, folderIds, targetFolder);
}
});
} else await startPickFavFolders(async (targetFolders) => {
const { targetFolderIds, targetFolderTitles } = mapModalFavManagerResult(targetFolders);
const result = await UserFavApi.addFav(avid, targetFolderIds);
if (result.isOk()) antMessage.success(`已加入收藏夹${targetFolderTitles}`);
return result.isOk();
});
});
if (enabled) {
const icon = loadingFavState ? /* @__PURE__ */ jsx(IconForLoading, { className: "size-16px" }) : faved ? /* @__PURE__ */ jsx(IconForFaved, { className: "size-18px" }) : /* @__PURE__ */ jsx(IconForFav, { className: "size-18px" });
const tooltip = faved ? `已收藏在 ${(folderNames || []).map((n) => `「${n}」`).join("")}` : "点击添加收藏";
favStateActionButton = /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible,
inlinePosition: "right",
icon,
tooltip,
onClick: handleClick
});
}
}
useUpdateEffect_default(() => {
if (actionButtonVisible && enabled) $updateFavStateReq.run();
}, [actionButtonVisible]);
return { favStateActionButton };
}
var iconParkOutlineAddTwo = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeWidth: 4,
children: [/* @__PURE__ */ jsx("path", { d: "M33 7.263A18.9 18.9 0 0 0 24 5C13.507 5 5 13.507 5 24s8.507 19 19 19a18.9 18.9 0 0 0 8-1.761" }), /* @__PURE__ */ jsx("path", {
strokeLinejoin: "round",
d: "M31 30h12m-28-8l7 7l19-18m-4 13v12"
})]
})]
});
var ForwardRef$7 = (0, import_react$2.forwardRef)(iconParkOutlineAddTwo);
var iconParkOutlinePeopleMinus = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
children: [
/* @__PURE__ */ jsx("path", {
stroke: "currentColor",
strokeLinejoin: "round",
strokeWidth: 4,
d: "M19 20a7 7 0 1 0 0-14a7 7 0 0 0 0 14Z"
}),
/* @__PURE__ */ jsx("path", {
d: "M30 35h12z",
clipRule: "evenodd"
}),
/* @__PURE__ */ jsx("path", {
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
d: "M30 35h12m-15-7h-8.2c-4.48 0-6.72 0-8.432.872a8 8 0 0 0-3.496 3.496C6 34.08 6 36.32 6 40.8V42h21"
})
]
})]
});
var ForwardRef$6 = (0, import_react$2.forwardRef)(iconParkOutlinePeopleMinus);
var letsIconsViewHide = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
children: [
/* @__PURE__ */ jsx("path", {
fill: "currentColor",
fillRule: "evenodd",
d: "M15.92 12.799Q16 12.41 16 12a4 4 0 0 0-4.799-3.92zM8.667 9.788a4 4 0 0 0 5.545 5.545l-1.474-1.474a2 2 0 0 1-2.597-2.597z",
clipRule: "evenodd"
}),
/* @__PURE__ */ jsx("path", {
fill: "currentColor",
fillRule: "evenodd",
d: "m16.52 17.64l-1.47-1.469c-.972.51-2.002.829-3.05.829c-1.526 0-3.014-.678-4.34-1.632c-1.32-.95-2.396-2.112-3.076-2.938c-.1-.121-.174-.212-.236-.291a2 2 0 0 1-.1-.139q.028-.045.1-.139c.062-.08.136-.17.236-.291c.665-.808 1.71-1.938 2.99-2.875l-1.43-1.43C4.797 8.297 3.723 9.47 3.04 10.3l-.073.088c-.314.375-.737.883-.737 1.613s.423 1.238.737 1.613l.073.088c.74.899 1.94 2.203 3.451 3.29C7.994 18.073 9.891 19 12 19c1.67 0 3.206-.581 4.52-1.36M8.806 5.686C9.79 5.269 10.864 5 12 5c2.11 0 4.006.927 5.509 2.009c1.51 1.087 2.711 2.391 3.45 3.29l.074.088c.314.375.737.883.737 1.613s-.423 1.238-.737 1.613l-.073.088a20.5 20.5 0 0 1-2.015 2.123l-1.416-1.416a18.5 18.5 0 0 0 2.123-2.27a2 2 0 0 0 .1-.138a2 2 0 0 0-.1-.139a14 14 0 0 0-.236-.291c-.68-.826-1.756-1.989-3.075-2.938C15.014 7.678 13.526 7 12 7c-.551 0-1.097.088-1.632.246z",
clipRule: "evenodd"
}),
/* @__PURE__ */ jsx("path", {
stroke: "currentColor",
strokeWidth: 2,
d: "m5 2l16 16"
})
]
})]
});
var ForwardRef$5 = (0, import_react$2.forwardRef)(letsIconsViewHide);
/**
* context menus related
*/
var clsContextMenuIcon = "size-16px";
function useContextMenus(options) {
const { item, cardData, tab, favContext, watchlaterContext, followed, hasDislikeEntry, onTriggerDislike, onRemoveCurrent, consistentOpenMenus, conditionalOpenMenus, multiSelecting, tagRecMenus } = options;
const { avid, bvid, cover, href, recommendReason, authorName, authorMid } = cardData;
const { watchlater: { itemsOrder: watchlaterItemsOrder }, filter: { dfHideOpusMids: { enabled: dfHideOpusMidsEnabled } }, dynamicFeed: { whenViewAll: { enableHideSomeContents } } } = useSettingsSnapshot();
const watchlaterReAddItemPos = watchlaterItemsOrder === WatchlaterItemsOrder.AddTimeAsc ? "end" : "start";
const watchlaterReAddItemPosText = watchlaterItemsOrder === WatchlaterItemsOrder.AddTimeAsc ? "移到最后" : "移到最前";
const { recSharedEmitter } = useRecSelfContext();
const onCopyLink = useMemoizedFn(() => {
let content = href;
if (href.startsWith("/")) content = new URL(href, location.href).href;
copyContent(content);
});
/**
* blacklist
*/
const hasBlacklistEntry = !!authorMid && isApiRecLike(item.api) && tab !== ETab.KeepFollowOnly;
const onBlacklistUp = useMemoizedFn(async () => {
if (!authorMid) return antMessage.error("UP mid 为空!");
if (await UserBlacklistService.add(authorMid)) antMessage.success(`已加入黑名单: ${authorName}`);
});
const onAddUpToFilterList = useMemoizedFn(async () => {
if (!authorMid) return antMessage.error("UP mid 为空!");
const content = `${authorMid}`;
if (settings.filter.byAuthor.keywords.includes(content)) return toast(`已在过滤名单中: ${content}`);
await updateSettingsInnerArray("filter.byAuthor.keywords", { add: [content] });
if (authorName) setNicknameCache(authorMid, authorName);
antMessage.success(`已加入过滤名单: ${authorName || authorMid}`);
});
/**
* unfollow
*/
const hasUnfollowEntry = followed;
const onUnfollowUp = useMemoizedFn(async () => {
if (!authorMid) return;
if (!await antModal.confirm({
centered: true,
title: "取消关注",
content: /* @__PURE__ */ jsxs(Fragment, { children: [
"确定取消关注「",
authorName,
"」?"
] })
})) return;
if (await UserFollowService.unfollow(authorMid)) antMessage.success("已取消关注");
});
/**
* 查看 UP 的动态 或 投稿
*/
const hasViewUpVideoListEntry = !!authorMid && !!authorName;
const onViewUpDyn = useMemoizedFn(() => {
if (!authorMid) return;
openNewTab(`/?${DynamicFeedQueryKey.Mid}=${authorMid}`);
});
const onViewUpSpaceUpload = useMemoizedFn(() => {
if (!authorMid) return;
openNewTab(`/?${SpaceUploadQueryKey.Mid}=${authorMid}`);
});
/**
* 「全部」动态筛选
*/
const hasEntry_addMidTo_dynamicFeedWhenViewAllHideIds = enableHideSomeContents && checkIsDynamicFeed(item) && dfStore.selectedKey === "all" && !!authorMid;
const onAddMidTo_dynamicFeedWhenViewAllHideIds = useMemoizedFn(async () => {
if (!hasEntry_addMidTo_dynamicFeedWhenViewAllHideIds) return;
await updateSettingsInnerArray("dynamicFeed.whenViewAll.hideIds", { add: ["up:" + authorMid] });
setNicknameCache(authorMid, authorName || "");
antMessage.success(`在「全部」动态中隐藏【${authorName}】的动态`);
});
/**
* 图文动态: 屏蔽此 UP
*/
const hasEntry_hideUpOpusDynamic = dfHideOpusMidsEnabled && checkIsDynamicFeed(item) && DynamicFeedItemHelper.isTextOrImage(item) && !!authorMid;
const onAddMidTo_dfHideOpusMids = useMemoizedFn(async () => {
if (!authorMid) return antMessage.error("UP mid 为空!");
const content = `${authorMid}`;
if (settings.filter.dfHideOpusMids.keywords.includes(content)) return toast(`已在过滤名单中: ${content}`);
await updateSettingsInnerArray("filter.dfHideOpusMids.keywords", { add: [content] });
if (authorName) setNicknameCache(authorMid, authorName);
antMessage.success(`已屏蔽【${authorName || authorMid}】的图文动态`);
});
/**
* 动态 offset & minId
*/
const hasEntry_dynamicFeed_offsetAndMinId = !!(checkIsDynamicFeed(item) && QUERY_DYNAMIC_UP_MID && dfStore.viewingSomeUp && authorMid);
const dynamicViewStartFromHere = (0, import_react$2.useMemo)(() => hasEntry_dynamicFeed_offsetAndMinId && {
label: "动态: 从此项开始查看",
key: "动态: 从此项开始查看",
icon: /* @__PURE__ */ jsx(IconForDesc, { className: "size-16px" }),
onClick() {
const u = new URL("/", location.href);
u.searchParams.set(DynamicFeedQueryKey.Mid, authorMid);
const currentIndexInGrid = getCurrentGridItems().findIndex((x) => x.api === EApiType.DynamicFeed && x.id_str === item.id_str);
const prevIdStr = getCurrentGridItems()[currentIndexInGrid - 1]?.id_str || item.id_str;
u.searchParams.set(DynamicFeedQueryKey.Offset, prevIdStr);
openNewTab(u.href);
}
}, [
hasEntry_dynamicFeed_offsetAndMinId,
item,
authorMid
]);
const dynamicViewUpdateSinceThis = (0, import_react$2.useMemo)(() => hasEntry_dynamicFeed_offsetAndMinId && {
icon: /* @__PURE__ */ jsx(IconForAsc, { className: "size-16px" }),
label: "动态: 从此项开始截止",
key: "动态: 从此项开始截止",
onClick() {
const u = new URL("/", location.href);
u.searchParams.set(DynamicFeedQueryKey.Mid, authorMid);
u.searchParams.set(DynamicFeedQueryKey.MinId, item.id_str);
openNewTab(u.href);
}
}, [
hasEntry_dynamicFeed_offsetAndMinId,
item,
authorMid
]);
const dynamicFeedOpenDetailPage = (0, import_react$2.useMemo)(() => {
return checkIsDynamicFeed(item) && {
key: "动态: 查看动态详情",
label: "动态: 查看动态详情",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: "size-16px" }),
onClick() {
openNewTab(`https://t.bilibili.com/${item.id_str}`);
}
};
}, [item]);
/**
* space-upload offset
*/
const spaceUploadViewStartFromHere = (0, import_react$2.useMemo)(() => SHOW_SPACE_UPLOAD_ONLY && checkIsSpaceUpload(item) && !!item.page && {
key: "space-upload-view-start-from-here",
label: `投稿: 从此页开始查看 (当前第${item.page}页)`,
icon: /* @__PURE__ */ jsx(IconForDesc, { className: "size-16px" }),
onClick() {
const u = new URL(location.href);
u.searchParams.set(SpaceUploadQueryKey.InitialPage, item.page.toString());
openNewTab(u.href);
}
}, [SHOW_SPACE_UPLOAD_ONLY, item]);
const viewingGroupId = checkIsDynamicFeed(item) || checkIsSpaceUpload(item) ? item.groupId : void 0;
const viewingSomeGroup = viewingGroupId !== void 0;
const followGroups = useSnapshot(dfStore.groups);
const viewingGroup = viewingGroupId !== void 0 ? followGroups.find((x) => x.tagid === viewingGroupId) : void 0;
const viewingGroupName = viewingGroup?.name;
const viewingGroupCount = viewingGroup?.count;
return (0, import_react$2.useMemo)(() => {
const { watchlaterAdded, hasWatchlaterEntry, handleToggleWatchlater, handleAddWatchlater } = watchlaterContext;
const divider = { type: "divider" };
const multiSelectingAppendix = multiSelecting ? " (多选)" : "";
const copyMenus = defineAntMenus([
{
key: "copy-link",
label: `复制视频链接${multiSelectingAppendix}`,
icon: /* @__PURE__ */ jsx(IconForCopy, { className: clsContextMenuIcon }),
onClick() {
if (multiSelectStore.multiSelecting) copyVideoLinks();
else onCopyLink();
}
},
{
test: !!bvid,
key: "copy-bvid",
label: `复制 BVID${multiSelectingAppendix}`,
icon: /* @__PURE__ */ jsx(IconForCopy, { className: clsContextMenuIcon }),
onClick() {
if (multiSelectStore.multiSelecting) copyBvidsSingleLine();
else copyContent(bvid);
}
},
{
test: !!bvid && settings.__internalEnableCopyBvidInfo,
key: "copy-bvid-info",
label: `复制 BVID 信息${multiSelectingAppendix}`,
icon: /* @__PURE__ */ jsx(IconForCopy, { className: clsContextMenuIcon }),
onClick() {
if (multiSelectStore.multiSelecting) copyBvidInfos();
else copyContent(getBvidInfo(cardData));
}
},
{
test: !!cover,
key: "view-cover",
label: "查看封面",
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: clsContextMenuIcon }),
onClick() {
if (!cover) return;
openNewTab(cover);
}
}
]);
const interestedMenus = defineAntMenus([
{
test: hasViewUpVideoListEntry && followed,
key: `查看「投稿」与「动态」的区别`,
label: `查看「投稿」与「动态」的区别`,
icon: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: clsContextMenuIcon }),
onClick: () => openNewTab("https://github.com/yunomega/Bilibili-Gate-TagRec?tab=readme-ov-file#%E5%8F%B3%E9%94%AE%E8%8F%9C%E5%8D%95-%E6%9F%A5%E7%9C%8B-up-%E7%9A%84%E6%8A%95%E7%A8%BF--%E6%9F%A5%E7%9C%8B-up-%E7%9A%84%E5%8A%A8%E6%80%81")
},
{
test: hasViewUpVideoListEntry,
key: "查看 UP 的投稿",
label: `查看 UP 的投稿`,
icon: /* @__PURE__ */ jsx(IconForSpaceUpload, { className: clsContextMenuIcon }),
onClick: onViewUpSpaceUpload
},
spaceUploadViewStartFromHere,
{
test: hasViewUpVideoListEntry && followed,
key: "查看 UP 的动态",
label: `查看 UP 的动态`,
icon: /* @__PURE__ */ jsx(IconForDynamicFeed, { className: clsContextMenuIcon }),
onClick: onViewUpDyn
},
dynamicFeedOpenDetailPage,
dynamicViewUpdateSinceThis,
dynamicViewStartFromHere,
...hasViewUpVideoListEntry && viewingSomeGroup ? defineAntMenus([
{ type: "divider" },
{
test: hasViewUpVideoListEntry && viewingSomeGroup && (viewingGroupCount === void 0 || viewingGroupCount <= 30),
key: "查看分组的投稿",
label: `查看分组的投稿`,
icon: /* @__PURE__ */ jsx(IconForSpaceUpload, { className: clsContextMenuIcon }),
onClick: () => openNewTab(`/?${SpaceUploadQueryKey.GroupId}=${viewingGroupId}`)
},
{
test: hasViewUpVideoListEntry && viewingSomeGroup,
key: "查看分组的动态",
label: `查看分组的动态`,
icon: /* @__PURE__ */ jsx(IconForDynamicFeed, { className: clsContextMenuIcon }),
onClick: () => openNewTab(`/?${DynamicFeedQueryKey.GroupId}=${viewingGroupId}`)
},
{ type: "divider" }
]) : [],
{
test: hasWatchlaterEntry,
key: "watchlater",
label: watchlaterAdded ? "移除稍后再看" : "稍后再看",
icon: watchlaterAdded ? /* @__PURE__ */ jsx(IconForDelete, { className: clsContextMenuIcon }) : /* @__PURE__ */ jsx(IconForWatchlater, { className: clsContextMenuIcon }),
onClick() {
handleToggleWatchlater();
}
},
{
test: hasWatchlaterEntry && watchlaterAdded,
key: "watchlater-readd",
label: `重新添加稍候再看${tab === ETab.Watchlater ? ` (${watchlaterReAddItemPosText})` : ""}`,
icon: /* @__PURE__ */ jsx(ForwardRef$7, { className: clsContextMenuIcon }),
async onClick() {
if (!await handleAddWatchlater(true)) return;
antMessage.success("已重新添加");
if (tab === ETab.Watchlater) recSharedEmitter.emit("move-card-to", [item.uniqId, watchlaterReAddItemPos]);
}
},
...getFavContextMenus(favContext, item, avid) ?? []
]);
const dislikeMenus = defineAntMenus([
{
test: hasDislikeEntry,
key: "dislike",
label: "我不想看",
icon: /* @__PURE__ */ jsx(IconForDislike, {
width: 15,
height: 15
}),
onClick() {
onTriggerDislike();
}
},
{
test: hasUnfollowEntry,
key: "unfollow-up",
label: "取消关注",
icon: /* @__PURE__ */ jsx(ForwardRef$6, { className: clsContextMenuIcon }),
onClick: onUnfollowUp
},
{
test: hasEntry_addMidTo_dynamicFeedWhenViewAllHideIds,
key: "hasEntry_addMidTo_dynamicFeedWhenViewAllHideIds",
label: "在「全部」动态中隐藏 UP 的动态",
icon: /* @__PURE__ */ jsx(ForwardRef$5, { className: clsContextMenuIcon }),
onClick: onAddMidTo_dynamicFeedWhenViewAllHideIds
},
{
test: hasEntry_hideUpOpusDynamic,
key: "hide-up-opus-dynamic",
label: "屏蔽此 UP 的图文动态",
icon: /* @__PURE__ */ jsx(ForwardRef$5, { className: clsContextMenuIcon }),
onClick: onAddMidTo_dfHideOpusMids
},
{
test: hasBlacklistEntry,
key: "blacklist-up",
label: "将 UP 加入黑名单",
icon: /* @__PURE__ */ jsx(IconForBlacklist, { className: clsContextMenuIcon }),
onClick: onBlacklistUp
},
{
test: hasBlacklistEntry,
key: "add-up-to-filterlist",
label: "将 UP 加入过滤列表",
icon: /* @__PURE__ */ jsx(IconForBlacklist, { className: clsContextMenuIcon }),
onClick: onAddUpToFilterList
}
]);
const favTabMenus = getFavTabFavRelatedMenus({
ctx: favContext,
item,
cardData,
tab,
multiSelecting,
multiSelectingAppendix,
onRemoveCurrent,
recSharedEmitter
});
const historyTabMenus = defineAntMenus([{
test: checkIsHistory(item),
key: "remove-history-record",
label: "移除历史记录",
icon: /* @__PURE__ */ jsx(IconForDelete, { className: clsContextMenuIcon }),
async onClick() {
if (!checkIsHistory(item)) return;
if (await removeSingleHistoryItem(item)) onRemoveCurrent?.(item, cardData);
}
}]);
return defineAntMenus([
...consistentOpenMenus,
!!copyMenus.length && divider,
...copyMenus,
!!historyTabMenus.length && divider,
...historyTabMenus,
!!interestedMenus.length && divider,
...interestedMenus,
!!dislikeMenus.length && divider,
...dislikeMenus,
!!favTabMenus.length && divider,
...favTabMenus,
!!tagRecMenus?.length && divider,
...tagRecMenus ?? [],
!!conditionalOpenMenus.length && divider,
...conditionalOpenMenus
]);
}, [
item,
cardData,
tab,
watchlaterContext,
favContext,
hasDislikeEntry,
hasUnfollowEntry,
hasBlacklistEntry,
hasViewUpVideoListEntry,
hasEntry_addMidTo_dynamicFeedWhenViewAllHideIds,
hasEntry_hideUpOpusDynamic,
consistentOpenMenus,
conditionalOpenMenus,
tagRecMenus,
multiSelecting,
viewingGroupId,
viewingSomeGroup,
viewingGroupName,
viewingGroupCount,
recSharedEmitter,
onRemoveCurrent
]);
}
/**
* @returns true: event handled, false: continue your logic
*/
function showNativeContextMenuWhenAltKeyPressed(e) {
if (e.altKey) {
e.stopPropagation();
return true;
}
}
var $now = valtioFactory(() => Date.now());
var updateWhenVisible = () => {
if (document.visibilityState === "visible") $now.update();
};
var subscribeNow = (notify) => subscribe($now.state, notify);
var firstTimeout = ((0, import_dayjs_min$1.default)().add(1, "minute").startOf("minute").valueOf() - $now.get()) % 3e4;
setTimeout(() => {
updateWhenVisible();
setInterval(updateWhenVisible, 3e4);
}, firstTimeout);
document.addEventListener("visibilitychange", updateWhenVisible);
var UnixTsDisplay = (0, import_react$2.memo)(function UnixTsDisplay({ ts, includeTime }) {
if (!ts) return null;
return isRecentTimestamp(ts) ? /* @__PURE__ */ jsx(ReactiveImpl, {
ts,
includeTime
}) : /* @__PURE__ */ jsx(PlainImpl, {
ts,
includeTime
});
});
function PlainImpl({ ts, includeTime }) {
return (0, import_react$2.useMemo)(() => formatRecentTimestamp(ts, true, includeTime ?? false), [ts, includeTime]);
}
function ReactiveImpl({ ts, includeTime }) {
const getSnapshot = (0, import_react$2.useMemo)(() => {
let previousNow;
let formatted;
return () => {
const now = $now.get();
if (now !== previousNow) {
previousNow = now;
formatted = formatRecentTimestamp(ts, true, includeTime ?? false);
}
return formatted;
};
}, [ts, includeTime]);
return (0, import_react$2.useSyncExternalStore)(subscribeNow, getSnapshot, getSnapshot);
}
/**
* take care of these
* https://greasyfork.org/zh-CN/scripts/479861-bilibili-%E9%A1%B5%E9%9D%A2%E5%87%80%E5%8C%96%E5%A4%A7%E5%B8%88/discussions/238294
*/
var clsRecommendReason = clsx(APP_CLS_CARD_RECOMMEND_REASON, "max-w-[calc(100%-6px)] w-max cursor-default overflow-hidden text-ellipsis whitespace-nowrap b-1px b-gate-border rounded-9px b-solid px-8px py-0 text-12px color-gate-primary line-height-17px");
var clsRecommendReasonInList = clsx("line-clamp-2 mt-10px h-auto whitespace-normal");
var cssRecommendReason = /* @__PURE__ */ css("background-color:oklch(from ", appPrimaryColorValue, " calc(l * 0.1 + 0.88) calc(c * 0.25) h);", appClsDarkSelector, " &{background-color:oklch(from ", appPrimaryColorValue, " calc(l * 0.3 + 0.175) calc(c * 0.25) h);}");
var descOwnerCss = /* @__PURE__ */ css("font-size:var(--subtitle-font-size);line-height:var(--subtitle-line-height);color:var(--text3);a&:visited{color:var(--text3);}display:inline-flex;width:max-content;max-width:100%;align-items:center;justify-content:flex-start;");
var VideoCardBottom = (0, import_react$2.memo)(function({ item, cardData, className, gridDisplayMode, followed, handleVideoLinkClick }) {
const { useBorder } = useSnapshot(settings.style.videoCard);
const target = useLinkTarget();
const displayingAsList = isDisplayAsList(gridDisplayMode);
const { goto, href, title, titleRender, pubts, pubdateDisplay, pubdateDisplayForTitleAttr, recommendReason, recommendReasonDetails, authorName, authorFace, authorMid, appRecommendDesc, rankingDesc, liveExtraDesc, historyDeviceIcon } = cardData;
const isNormalVideo = goto === "av";
const authorHref = authorMid ? formatSpaceUrl(authorMid) : href;
const streaming = (checkIsLive(item) || checkIsHistory(item)) && item.live_status === ELiveStatus.Streaming;
const { data: pubtsFromApi } = useRequest_default(() => fetchAppRecommendFollowedPubDate(item, cardData), { refreshDeps: [item, cardData] });
const descTitleAttribute = (0, import_react$2.useMemo)(() => {
if (!(isNormalVideo && (authorName || pubts || pubtsFromApi || pubdateDisplay || pubdateDisplayForTitleAttr))) return;
let datePartForTitleAttribute;
if (pubts || pubtsFromApi) datePartForTitleAttribute = import_dayjs_min$1.default.unix(pubts || pubtsFromApi).format("YYYY年M月D日 HH:mm");
else datePartForTitleAttribute = pubdateDisplay;
return [authorName, pubdateDisplayForTitleAttr || datePartForTitleAttribute].filter(Boolean).join(" · ");
}, [
isNormalVideo,
authorName,
pubts,
pubtsFromApi,
pubdateDisplay,
pubdateDisplayForTitleAttr
]);
const _recommendReasonClassName = r(clsRecommendReason, displayingAsList && clsRecommendReasonInList);
function renderDesc() {
const reasonLabel = /* @__PURE__ */ jsx("div", {
title: recommendReason,
css: cssRecommendReason,
className: _recommendReasonClassName,
children: recommendReason
});
const recommendReasonEl = !!recommendReason && (recommendReasonDetails?.length ? /* @__PURE__ */ jsx(Popover, {
trigger: ["hover", "click"],
content: /* @__PURE__ */ jsxs("div", {
className: "max-w-320px text-12px",
children: [/* @__PURE__ */ jsx("strong", { children: "这条视频的推荐来源" }), recommendReasonDetails.map((path) => /* @__PURE__ */ jsx("div", {
className: "mt-6px break-words",
children: path
}, path))]
}),
children: /* @__PURE__ */ jsx("button", {
type: "button",
"aria-label": "查看视频来源:" + recommendReason,
css: cssRecommendReason,
className: _recommendReasonClassName,
style: { cursor: "pointer" },
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
},
children: recommendReason
})
}) : reasonLabel);
const defaultImpl = ({ withAuthor = true, withDate = true, withRecommendReason = true } = {}) => {
let date;
if (pubts || pubtsFromApi) date = /* @__PURE__ */ jsx(UnixTsDisplay, { ts: pubts || pubtsFromApi });
else if (pubdateDisplay) date = pubdateDisplay;
return /* @__PURE__ */ jsxs(Fragment, { children: [withAuthor && /* @__PURE__ */ jsxs("a", {
className: "bili-video-card__info--owner",
href: authorHref,
target,
title: descTitleAttribute,
css: descOwnerCss,
onContextMenu: showNativeContextMenuWhenAltKeyPressed,
children: [/* @__PURE__ */ jsx("span", {
className: "bili-video-card__info--author",
children: authorName
}), withDate && !!date && /* @__PURE__ */ jsxs("span", {
className: "bili-video-card__info--date",
children: ["·", date]
})]
}), withRecommendReason && recommendReasonEl] });
};
if (checkIsHistory(item)) return /* @__PURE__ */ jsxs(Fragment, { children: [defaultImpl({
withAuthor: true,
withDate: false,
withRecommendReason: false
}), /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center justify-start gap-x-1",
children: [historyDeviceIcon, /* @__PURE__ */ jsx("span", {
css: cssRecommendReason,
className: _recommendReasonClassName,
title: formatAccurateTimestamp(item.view_at),
children: /* @__PURE__ */ jsx(UnixTsDisplay, {
ts: item.view_at,
includeTime: true
})
})]
})] });
if (isNormalVideo) return defaultImpl();
if (appRecommendDesc) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("a", {
className: "bili-video-card__info--owner",
css: descOwnerCss,
href,
target,
onContextMenu: showNativeContextMenuWhenAltKeyPressed,
children: !!appRecommendDesc && /* @__PURE__ */ jsx("span", { children: appRecommendDesc })
}), recommendReasonEl] });
if (checkIsRank(item) && rankingDesc) return /* @__PURE__ */ jsx("div", {
css: descOwnerCss,
children: rankingDesc
});
if (checkIsLive(item) || checkIsPcRecommend(item) && item.goto === PcRecGoto.Live) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("a", {
css: [descOwnerCss, /* @__PURE__ */ css("display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;overflow:hidden;text-overflow:ellipsis;max-width:100%;")],
href: authorHref,
target,
title: (authorName || "") + (liveExtraDesc || ""),
onContextMenu: showNativeContextMenuWhenAltKeyPressed,
children: [authorName, liveExtraDesc && /* @__PURE__ */ jsx("span", {
className: "ml-4px",
children: liveExtraDesc
})]
}), recommendReasonEl] });
return defaultImpl();
}
/**
* 结构
* avatar + line1: title
* line2: desc
* - when normal video => `author-name + date`
* line3: recommend-reason
*/
let avatarAddon;
if (streaming) avatarAddon = /* @__PURE__ */ jsx(IconForLive, {
active: true,
className: "absolute bottom-0 right-0 size-12px rounded-full bg-gate-primary"
});
else if (followed) avatarAddon = /* @__PURE__ */ jsx(IconAnimatedChecked, { className: "absolute bottom-0 right-0 size-12px rounded-full bg-gate-primary color-white" });
return /* @__PURE__ */ jsxs("div", {
className: clsx(!displayingAsList ? "pt-10px" : "pt-5px", "mb-5px flex gap-x-5px overflow-hidden px-5px", className),
children: [!!authorMid && /* @__PURE__ */ jsxs("a", {
href: authorHref,
target,
className: clsx("relative flex-center self-start rounded-full p-1px ring-1px", streaming ? "ring-gate-primary" : "ring-gate-border"),
children: [authorFace ? /* @__PURE__ */ jsx(Avatar, { src: getAvatarSrc(authorFace) }) : /* @__PURE__ */ jsx(Avatar, { children: authorName?.[0] || appRecommendDesc?.[0] || "" }), avatarAddon]
}), /* @__PURE__ */ jsxs("div", {
className: "ml-5px flex flex-1 flex-col gap-y-4px overflow-hidden",
children: [/* @__PURE__ */ jsx("h3", {
className: "bili-video-card__info--tit",
title,
css: /* @__PURE__ */ css("text-indent:0 !important;.bili-video-card &.bili-video-card__info--tit{padding-right:0;height:auto;max-height:calc(2 * var(--title-line-height));}"),
children: /* @__PURE__ */ jsx("a", {
onClick: handleVideoLinkClick,
onContextMenu: showNativeContextMenuWhenAltKeyPressed,
href,
target,
rel: "noopener",
css: /* @__PURE__ */ css(".bili-video-card .bili-video-card__info--tit > a&{font-family:inherit;font-weight:initial;}"),
children: titleRender ?? title
})
}), renderDesc()]
})]
});
});
var iconParkOutlineDistraughtFace = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinejoin: "round",
strokeWidth: 4,
children: [/* @__PURE__ */ jsx("path", { d: "M24 44c11.046 0 20-8.954 20-20S35.046 4 24 4S4 12.954 4 24s8.954 20 20 20Z" }), /* @__PURE__ */ jsx("path", {
strokeLinecap: "round",
d: "M24 29c5 0 7 4 7 4H17s2-4 7-4m8-12l-3 3l3 3m-16-6l3 3l-3 3"
})]
})]
});
var ForwardRef$4 = (0, import_react$2.forwardRef)(iconParkOutlineDistraughtFace);
var SkeletonCard = (0, import_react$2.memo)(function SkeletonCard({ loading }) {
return /* @__PURE__ */ jsxs("div", {
className: clsx("bili-video-card__skeleton", {
hide: !loading,
[skeletonActive]: loading
}),
children: [/* @__PURE__ */ jsx("div", {
className: "bili-video-card__skeleton--cover",
style: { borderRadius: videoCardBorderRadiusValue }
}), /* @__PURE__ */ jsxs("div", {
className: "bili-video-card__skeleton--info px-5px",
children: [/* @__PURE__ */ jsx("div", { className: "bili-video-card__skeleton--avatar size-32px rounded-full" }), /* @__PURE__ */ jsxs("div", {
className: "bili-video-card__skeleton--right ml-10px flex-1",
children: [
/* @__PURE__ */ jsx("p", { className: "bili-video-card__skeleton--text" }),
/* @__PURE__ */ jsx("p", { className: "bili-video-card__skeleton--text short" }),
/* @__PURE__ */ jsx("p", { className: "bili-video-card__skeleton--light" }),
/* @__PURE__ */ jsx("p", { className: "bili-video-card__skeleton--text tiny" })
]
})]
})]
});
});
var blockedCardClassNames = {
wrapper: "h-full flex flex-col overflow-hidden",
cover: "relative aspect-16/9 rounded-t-6px",
coverInner: "absolute left-0 top-0 h-full w-full flex flex-col items-center justify-center gap-y-2",
dislikeReason: "text-center text-1.4em",
dislikeDesc: "text-center text-1em",
action: "relative flex-1",
actionInner: "absolute left-0 top-0 h-full w-full flex items-center justify-center b-t-1px b-t-$bilibili-gate--separator-color b-t-solid transition-duration-300 transition-property-[border-color]",
actionButton: "flex cursor-pointer items-center rounded-lg bg-transparent p-15px text-1.2em color-inherit"
};
var DislikedCard = (0, import_react$2.memo)(function DislikedCard({ item, cardData, dislikedReason, emitter = defaultVideoCardEmitter }) {
const onCancelDislike = useMemoizedFn(async () => {
await handleCancelDislike(item, dislikedReason);
});
useEmitterOn(emitter, "cancel-dislike", onCancelDislike);
const { text, helpText } = (0, import_react$2.useMemo)(() => normalizeDislikeReason(dislikedReason), [dislikedReason]);
return /* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.wrapper,
children: [/* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.cover,
children: /* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.coverInner,
children: [
/* @__PURE__ */ jsx(ForwardRef$4, { className: "size-2.5em" }),
/* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.dislikeReason,
children: text
}),
/* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.dislikeDesc,
children: helpText || "将减少此类内容推荐"
})
]
})
}), /* @__PURE__ */ jsx(__BottomRevertAction, {
item,
cardData,
onClick: onCancelDislike
})]
});
});
function __BottomRevertAction({ item, cardData, onClick }) {
return /* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.action,
children: [/* @__PURE__ */ jsx(VideoCardBottom, {
item,
cardData,
className: "invisible",
followed: false
}), /* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.actionInner,
children: /* @__PURE__ */ jsxs("button", {
className: blockedCardClassNames.actionButton,
onClick,
children: [/* @__PURE__ */ jsx(IconForReset, { className: "mr-4px mt--2px" }), "撤销"]
})
})]
});
}
var BlockedCard = (0, import_react$2.memo)(function BlockedCardInner({ item, cardData, blockType }) {
const { authorMid, authorName } = cardData;
const label = blockType === "blacklist" ? "已拉黑" : "已加入过滤列表";
const onCancel = useMemoizedFn(() => {
return blockType === "blacklist" ? onCancelBlacklist() : onCancelFilter();
});
const onCancelBlacklist = useMemoizedFn(async () => {
if (!authorMid) return;
if (await UserBlacklistService.remove(authorMid)) antMessage.success(`已移出黑名单: ${authorName}`);
});
const onCancelFilter = useMemoizedFn(() => {
if (!authorMid) return;
const toRemove = settings.filter.byAuthor.keywords.filter((keyword) => parseUpRepresent(keyword).mid === authorMid);
updateSettingsInnerArray("filter.byAuthor.keywords", { remove: toRemove });
antMessage.success(`已移出过滤列表: ${authorName}`);
});
return /* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.wrapper,
children: [/* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.cover,
children: /* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.coverInner,
children: [
/* @__PURE__ */ jsx(IconForBlacklist, { className: "size-32px" }),
/* @__PURE__ */ jsx("div", {
className: blockedCardClassNames.dislikeReason,
children: label
}),
/* @__PURE__ */ jsxs("div", {
className: blockedCardClassNames.dislikeDesc,
children: ["UP: ", authorName]
})
]
})
}), /* @__PURE__ */ jsx(__BottomRevertAction, {
item,
cardData,
onClick: onCancel
})]
});
});
/**
* 关于 `pointer-events: none`
* see https://github.com/magicdawn/Bilibili-Gate/issues/112
* useMouse 使用的是 document.addEventListener, 不用它响应 mousemove 事件
*/
var clsPreviewImageWrapper = clsx("pointer-events-none absolute inset-0 overflow-hidden", "z-3", "rounded-b-none rounded-t-gate-video-card");
var PreviewImage = (0, import_react$2.memo)(function PreviewImage({ videoDuration, pvideo, progress, t, className, ref, ...restProps }) {
const rootElRef = (0, import_react$2.useRef)(null);
const [size, setSize] = (0, import_react$2.useState)(() => ({
width: 0,
height: 0
}));
useMount(() => {
const rect = rootElRef.current?.getBoundingClientRect();
if (!rect) return;
setSize({
width: rect.width,
height: rect.height
});
});
const usingProgress = (0, import_react$2.useMemo)(() => {
function getProgress() {
if (typeof progress === "number" && !Number.isNaN(progress)) return progress;
return 0;
}
return minmax(getProgress() ?? 0, 0, 1);
}, [progress]);
const usingT = (0, import_react$2.useMemo)(() => t ?? Math.floor((videoDuration || 0) * usingProgress), [
t,
videoDuration,
usingProgress
]);
/**
* expose ref as imperative handle
* 使用 pvideo.index 获取也不是很准确, 缩略图与视频有几秒偏差~
*/
const getT = useMemoizedFn(() => usingT);
(0, import_react$2.useImperativeHandle)(ref, () => ({ getT }), [getT]);
const innerProps = {
progress: usingProgress,
t: usingT,
pvideo,
elWidth: size.width,
elHeight: size.height
};
return /* @__PURE__ */ jsx("div", {
...restProps,
ref: rootElRef,
className: clsx(clsPreviewImageWrapper, className),
children: !!(pvideo && size.width && size.height && usingProgress) && /* @__PURE__ */ jsx(PreviewImageInner, { ...innerProps })
});
});
var PreviewImageInner = (0, import_react$2.memo)(function PreviewImageInner({ t, progress, pvideo, elWidth, elHeight }) {
let index = (0, import_react$2.useMemo)(() => calcIndex(pvideo?.index || [], t) ?? 0, [pvideo, t]);
const { img_x_len: colCount, img_y_len: rowCount, img_x_size: w, img_y_size: h } = pvideo;
const countPerPreview = rowCount * colCount;
index = index + 1;
const snapshotIndex = Math.floor(index / countPerPreview);
const indexInSnapshot = index - snapshotIndex * countPerPreview;
const snapshotUrl = pvideo.image?.[snapshotIndex] || "";
const indexRow = Math.floor(indexInSnapshot / colCount) + 1;
const indexCol = indexInSnapshot - (indexRow - 1) * colCount;
const newImgWidth = elWidth * colCount;
const newImgHeight = elHeight * rowCount;
const startY = (indexRow - 1) * elHeight;
const startX = (indexCol - 1) * elWidth;
return /* @__PURE__ */ jsx("div", {
className: "size-full",
style: {
backgroundColor: "black",
backgroundImage: `url(${snapshotUrl})`,
backgroundPosition: `-${startX}px -${startY}px`,
backgroundSize: `${newImgWidth}px ${newImgHeight}px`
},
children: /* @__PURE__ */ jsx(SimpleProgressBar, { progress })
});
});
function SimpleProgressBar({ progress, className, ...props }) {
return /* @__PURE__ */ jsx("div", {
...props,
"data-role": "track",
className: clsx("absolute inset-x-0 bottom-0 h-2px bg-gate-bg-lv1", className),
children: /* @__PURE__ */ jsx("div", {
"data-role": "bar",
className: "h-full bg-gate-primary",
style: { width: `${progress * 100}%` }
})
});
}
function calcIndex(arr, t) {
let index = findIndex(arr, t);
if (index !== -1) return index;
if (t > arr.at(-1)) {
index = arr.length - 1;
return index;
}
}
function findIndex(arr, target) {
let l = 0;
let r = arr.length - 1;
let possible = -1;
while (l <= r) {
const mid = Math.floor((l + r) / 2);
const mv = arr[mid];
if (target === mv) return mid;
if (mv < target) {
l = mid + 1;
possible = mid;
} else r = mid - 1;
}
if (possible === -1) return -1;
const v = arr[possible];
const v1 = arr[possible + 1] ?? 0;
if (v < target && target < v1) return possible;
else return -1;
}
/**
* 我不想看
*/
var dislikeIcon = /* @__PURE__ */ jsx(IconForDislike, { className: "size-16px" });
function useDislikeRelated({ item, authed, actionButtonVisible }) {
const hasDislikeEntry = checkIsAppRecommend(item) && !!item.three_point?.dislike_reasons?.length || checkIsPcRecommend(item);
const onTriggerDislike = useMemoizedFn(async (e) => {
e?.preventDefault();
e?.stopPropagation();
if (!hasDislikeEntry) return antMessage.error("当前视频不支持提交「我不想看」");
const dislikeKey = calcRecItemDislikedMapKey(item);
if (dislikeKey && dislikedMap.has(dislikeKey)) return;
let reasons;
if (checkIsAppRecommend(item)) {
if (!authed) return toast("请先获取 access_key !");
reasons = (item.three_point?.dislike_reasons || []).map((x) => ({
platform: "app",
...x
}));
} else if (checkIsPcRecommend(item)) reasons = PcDislikeReasons;
else assertNever(item);
await pickDislikeReason(reasons, (reason) => handleDislike(item, reason));
});
return {
dislikeButtonEl: hasDislikeEntry && /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible,
inlinePosition: "left",
icon: dislikeIcon,
tooltip: "我不想看",
onClick: onTriggerDislike
}),
hasDislikeEntry,
onTriggerDislike
};
}
function useInitFollowedStatusContext(item, cardData, emitter) {
const { authorMid } = cardData;
const followedFromCardData = (0, import_react$2.useMemo)(() => {
if (isNotNil(cardData.followed)) return cardData.followed;
if (checkIsLive(item)) return true;
if (checkIsAppRecommend(item) || checkIsPcRecommend(item)) return getFollowedStatus(cardData.recommendReason);
}, [item, cardData]);
const followedFromApi = useTrackedSnapshot(followedMidSet, (set) => !!authorMid && set.has(authorMid.toString()));
const updateFollowedStatus = useLockFn(async () => {
if (!authorMid) return;
if (!getLoginStatus()) return;
if (followedFromCardData !== void 0) return;
await queryFollowedStatus(authorMid);
});
useEmitterOn(emitter, "context-menu-open", () => {
if (![
EApiType.Watchlater,
EApiType.Fav,
EApiType.History,
EApiType.Liked,
EApiType.SpaceUpload
].includes(item.api)) return;
updateFollowedStatus();
});
return {
followed: followedFromCardData ?? followedFromApi ?? false,
updateFollowedStatus
};
}
function useRemoveHistoryRelated({ item, cardData, actionButtonVisible, onRemoveCurrent }) {
const enabled = checkIsHistory(item);
const $req = useRequest_default((item) => removeSingleHistoryItem(item), { manual: true });
const loading = $req.loading;
const [removed, setRemoved] = (0, import_react$2.useState)(false);
const handleRemoveHistory = useLockFn(async (e) => {
if (!checkIsHistory(item)) return;
if (loading) return;
e.stopPropagation();
e.preventDefault();
if (await $req.runAsync(item)) {
setRemoved(true);
await delay$1(250);
onRemoveCurrent?.(item, cardData);
}
});
let removeHistoryActionButton;
if (enabled) removeHistoryActionButton = /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible,
inlinePosition: "right",
icon: loading ? /* @__PURE__ */ jsx(IconForLoading, { className: "size-16px" }) : removed ? /* @__PURE__ */ jsx(IconAnimatedChecked, {
size: 18,
useAnimation: true
}) : /* @__PURE__ */ jsx(IconForDelete, { className: "size-18px" }),
tooltip: removed ? "已移除" : "点击移除历史记录",
onClick: handleRemoveHistory
});
return {
removeHistoryActionButton,
handleRemoveHistory
};
}
var lucideCircle = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("circle", {
cx: 12,
cy: 12,
r: 10,
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2
})]
});
var ForwardRef$3 = (0, import_react$2.forwardRef)(lucideCircle);
var lucideCircleCheck = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
children: [/* @__PURE__ */ jsx("circle", {
cx: 12,
cy: 12,
r: 10
}), /* @__PURE__ */ jsx("path", { d: "m16 9l-5.5 5.5L8 12" })]
})]
});
var ForwardRef$2 = (0, import_react$2.forwardRef)(lucideCircleCheck);
var IconForMultiSelectUnchecked = ForwardRef$3;
var IconForMultiSelectChecked = ForwardRef$2;
function useMultiSelectRelated({ multiSelecting, multiSelected, uniqId }) {
const toggleMultiSelect = useMemoizedFn((e) => {
const { selectedIdSet, multiSelecting } = multiSelectStore;
if (!multiSelecting) return;
e?.preventDefault();
e?.stopPropagation();
const shiftSelecting = !!e?.shiftKey;
multiSelectStore.shiftMultiSelectAnchorUniqId ??= uniqId;
if (!shiftSelecting) multiSelectStore.shiftMultiSelectAnchorUniqId = uniqId;
if (shiftSelecting) handleMultiSelectWithShiftKey(multiSelectStore.shiftMultiSelectAnchorUniqId, uniqId);
else multiSelected ? selectedIdSet.delete(uniqId) : selectedIdSet.add(uniqId);
});
return {
toggleMultiSelect,
multiSelectBgEl: multiSelecting && /* @__PURE__ */ jsx("div", {
onClick: toggleMultiSelect,
className: clsx("absolute inset-0 flex items-center justify-center bg-black/10", "z-4")
}),
multiSelectEl: multiSelecting && /* @__PURE__ */ jsx(multiSelected ? IconForMultiSelectChecked : IconForMultiSelectUnchecked, {
onClick: toggleMultiSelect,
className: clsx("size-30px cursor-pointer text-white [&_g]:fill-inherit", multiSelected ? "fill-gate-primary" : "fill-none")
})
};
}
function debugAnimation(...args) {
return;
}
/**
* 自动以动画方式预览
*/
function usePreviewRelated({ uniqId, recSharedEmitter, title, active, videoDuration, tryFetchImagePreviewData, imagePreviewDataBox, autoPreviewWhenHover, videoPreviewWrapperRef }) {
const hasVideoData = useMemoizedFn(() => {
const data = imagePreviewDataBox.val?.videoshotJson?.data;
return Boolean(data?.index?.length && data?.image?.length);
});
const [autoPreviewing, setAutoPreviewing] = (0, import_react$2.useState)(false);
const [previewProgress, setPreviewProgress] = useRafState();
const [previewT, setPreviewT] = useRafState();
const getProgress = useMemoizedFn(() => previewProgress || 0);
const [mouseMoved, setMouseMoved] = (0, import_react$2.useState)(false);
const isHoveringBox = useRefStateBox(false);
const isHoveringAfterDelayBox = useRefStateBox(false);
const startByHoverBox = useRefBox(false);
const [mouseProgress, setMouseProgress] = useRafState(void 0);
const updateMouseProgress = (e) => {
const rect = videoPreviewWrapperRef.current?.getBoundingClientRect();
if (!rect) return;
const { x, width } = rect;
const relativeX = e.pageX - window.scrollX - x;
setMouseProgress(relativeX / width);
};
useEventListener("mouseenter", async (e) => {
recSharedEmitter.emit("mouseenter", uniqId);
isHoveringBox.set(true);
updateMouseProgress(e);
const p = tryFetchImagePreviewData();
const HOVER_DELAY = 800;
let delayPromise;
if (settings.useDelayForHover) delayPromise = delay$1(HOVER_DELAY);
await Promise.all([p, delayPromise].filter(Boolean));
if (!isHoveringBox.val) return;
isHoveringAfterDelayBox.set(true);
if (autoPreviewWhenHover && !idBox.val && hasVideoData()) {
onStartPreviewAnimation(true);
}
}, { target: videoPreviewWrapperRef });
const _mouseleaveAction = useMemoizedFn(() => {
if (isHoveringBox.val) isHoveringBox.set(false);
if (isHoveringAfterDelayBox.val) isHoveringAfterDelayBox.set(false);
});
useEventListener("mouseleave", _mouseleaveAction, { target: videoPreviewWrapperRef });
useEmitterOn(recSharedEmitter, "mouseenter", ({ data: srcUniqId }) => {
if (srcUniqId === uniqId) return;
_mouseleaveAction();
});
useEventListener("mousemove", (e) => {
setMouseMoved(true);
if (isHoveringBox.val && (!isHoveringAfterDelayBox.val || !autoPreviewWhenHover)) updateMouseProgress(e);
if (!autoPreviewWhenHover) animationController.stop();
}, { target: videoPreviewWrapperRef });
const idBox = useRefBox(void 0);
const onResume = (0, import_react$2.useRef)(void 0);
const animationController = useAnimationController({
startByHoverBox,
isHoveringBox,
active,
mouseMoved,
idBox,
autoPreviewWhenHover,
setAutoPreviewing,
setPreviewT,
setPreviewProgress,
onResume() {
onResume.current?.();
}
});
const onHotkeyPreviewAnimation = useMemoizedFn(async () => {
if (!idBox.val) {
await tryFetchImagePreviewData();
if (hasVideoData()) onStartPreviewAnimation(false);
return;
}
animationController.togglePaused();
});
const onStartPreviewAnimation = useMemoizedFn((startByHover) => {
startByHoverBox.set(startByHover);
setMouseMoved(false);
animationController.reset();
animationController.stop(true);
setAutoPreviewing(true);
setPreviewProgress((val) => val === void 0 ? 0 : val);
setPreviewT(void 0);
const RUN_DURATION = 8e3;
let start = performance.now();
let updateAt = 0;
onResume.current = () => {
start = performance.now() - getProgress() * RUN_DURATION;
};
function frame(t) {
if (animationController.shouldStop()) return animationController.stop();
if (!animationController.paused) {
const now = performance.now();
const elapsed = now - start;
const p = minmax(elapsed % RUN_DURATION / RUN_DURATION, 0, 1);
setPreviewProgress(p);
if (!updateAt || now - updateAt >= settings.autoPreviewUpdateInterval) {
updateAt = now;
if (videoDuration) {
const t = minmax(Math.round(p * videoDuration), 0, videoDuration);
setPreviewT(t);
}
}
}
idBox.val = requestAnimationFrame(frame);
}
idBox.val = requestAnimationFrame(frame);
});
const isHovering = isHoveringBox.state;
const isHoveringAfterDelay = isHoveringAfterDelayBox.state;
const videoshotData = imagePreviewDataBox.state?.videoshotJson?.data;
const shouldShowPreview = !!videoshotData?.image?.length && !!videoDuration && (isHoveringAfterDelay || active) && (autoPreviewWhenHover ? autoPreviewing : true);
const previewImageRef = (0, import_react$2.useRef)(null);
let previewImgProps;
let previewImageEl;
if (shouldShowPreview) {
const sharedProps = {
videoDuration,
pvideo: videoshotData
};
if (autoPreviewWhenHover) previewImgProps = {
...sharedProps,
progress: previewProgress,
t: previewT
};
else previewImgProps = {
...sharedProps,
progress: mouseProgress
};
previewImageEl = /* @__PURE__ */ jsx(PreviewImage, {
ref: previewImageRef,
...previewImgProps
});
}
return {
onHotkeyPreviewAnimation,
onStartPreviewAnimation,
autoPreviewing,
mouseProgress,
previewProgress,
previewT,
isHovering,
isHoveringAfterDelay,
shouldShowPreview,
previewImageRef,
previewImgProps,
previewImageEl
};
}
function useAnimationController({ startByHoverBox, isHoveringBox, idBox, active, mouseMoved, autoPreviewWhenHover, setAutoPreviewing, setPreviewT, setPreviewProgress, onResume }) {
const unmounted = useUnmountedRef();
const shouldStop = useMemoizedFn(() => {
if (unmounted.current) return true;
if (startByHoverBox.val) {
if (!isHoveringBox.val) return true;
} else {
if (!active) return true;
if (mouseMoved) return true;
}
return false;
});
const stop = useMemoizedFn((isClear = false) => {
if (!isClear) debugAnimation(`stopAnimation: %o`, {
autoPreviewWhenHover,
unmounted: unmounted.current,
isHovering: isHoveringBox.val,
active,
mouseMoved
});
if (idBox.val) cancelAnimationFrame(idBox.val);
idBox.val = void 0;
setAutoPreviewing(false);
setPreviewProgress(void 0);
setPreviewT(void 0);
controller.reset();
});
const _paused = (0, import_react$2.useRef)(false);
const controller = (0, import_react$2.useMemo)(() => {
return {
shouldStop,
stop,
get paused() {
return _paused.current;
},
set paused(val) {
_paused.current = val;
},
togglePaused() {
const prev = this.paused;
this.paused = !this.paused;
if (prev) onResume?.();
},
reset() {
this.paused = false;
}
};
}, [
shouldStop,
stop,
_paused,
onResume
]);
return controller;
}
/**
* 稍候再看
*/
function useWatchlaterRelated({ item, cardData, onRemoveCurrent, actionButtonVisible, watchlaterAdded }) {
const { avid, bvid } = cardData;
const hasWatchlaterEntry = (0, import_react$2.useMemo)(() => {
if (checkIsAppRecommend(item) || checkIsPcRecommend(item)) return item.goto === "av";
if (checkIsRank(item)) return cardData.goto === "av";
if (checkIsLive(item)) return false;
return !!bvid;
}, [item, cardData]);
const $req = useRequest_default((apiService, avid) => apiService(avid), { manual: true });
const watchlaterAddedPrevious = usePrevious$1(watchlaterAdded);
const runWatchlaterApiService = useMemoizedFn(async (apiService, silentSuccessMessage = false) => {
if ($req.loading) return false;
if (!avid || !bvid) return false;
const result = await $req.runAsync(apiService, avid);
if (result.isErr()) {
handleRequestError(result.error);
return false;
}
const targetState = apiService === watchlaterAdd ? true : false;
if (targetState) watchlaterState.bvidSet.add(bvid);
else watchlaterState.bvidSet.delete(bvid);
if (item.api === EApiType.Watchlater) {
if (!targetState) {
await delay$1(250);
onRemoveCurrent?.(item, cardData);
}
} else if (!silentSuccessMessage) antMessage.success(`已${targetState ? "添加" : "移除"}稍后再看`);
return result.isOk();
});
const handleAddWatchlater = useMemoizedFn((silentSuccessMessage) => runWatchlaterApiService(watchlaterAdd, silentSuccessMessage));
const handleDelWatchlater = useMemoizedFn((silentSuccessMessage) => runWatchlaterApiService(watchlaterDel, silentSuccessMessage));
const handleToggleWatchlater = useMemoizedFn((e, silentSuccessMessage) => {
e?.preventDefault();
e?.stopPropagation();
return runWatchlaterApiService(watchlaterAdded ? watchlaterDel : watchlaterAdd, silentSuccessMessage);
});
const addedSize = 18;
const icon = (() => {
if ($req.loading) return /* @__PURE__ */ jsx(IconForLoading, { className: "size-16px" });
if (item.api === EApiType.Watchlater) return watchlaterAdded ? /* @__PURE__ */ jsx(IconForDelete, { className: "size-18px" }) : /* @__PURE__ */ jsx(IconAnimatedChecked, {
size: addedSize,
useAnimation: watchlaterAddedPrevious === true
});
return watchlaterAdded ? /* @__PURE__ */ jsx(IconAnimatedChecked, {
size: addedSize,
useAnimation: watchlaterAddedPrevious === false
}) : /* @__PURE__ */ jsx(IconForWatchlater, { className: "size-20px" });
})();
const tooltip = item.api === EApiType.Watchlater ? watchlaterAdded ? "已添加稍后再看, 点击移除" : "已移除稍后再看" : watchlaterAdded ? "已添加稍后再看, 点击移除" : "稍后再看";
const watchlaterButtonEl = hasWatchlaterEntry && /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible,
inlinePosition: "right",
icon,
tooltip,
onClick: handleToggleWatchlater
});
return {
context: (0, import_react$2.useMemo)(() => {
return {
watchlaterAdded,
hasWatchlaterEntry,
handleToggleWatchlater,
handleAddWatchlater,
handleDelWatchlater
};
}, [
watchlaterAdded,
hasWatchlaterEntry,
handleToggleWatchlater,
handleAddWatchlater,
handleDelWatchlater
]),
watchlaterButtonEl
};
}
var VideoCard = (0, import_react$2.memo)(function VideoCard({ style, className, item, loading, active, onRemoveCurrent, emitter, recSharedEmitter, tab, baseCss, gridDisplayMode, multiSelecting, eagerCover, highPriorityCover, ...restProps }) {
loading = loading ?? !item;
const cardData = (0, import_react$2.useMemo)(() => item && normalizeCardData(item), [item]);
const tagRecSeenRef = useTagRecSeen(item, tab);
const dislikedReason = useDislikedReason((0, import_react$2.useMemo)(() => item ? calcRecItemDislikedMapKey(item) : void 0, [item]));
const blacklisted = useInBlacklist(cardData?.authorMid);
const blocked = useInFilterByAuthorList(cardData?.authorMid);
const watchlaterAdded = useWatchlaterState(cardData?.bvid);
const multiSelected = useMultiSelectState(item?.uniqId);
const showingDislikeCard = !!dislikedReason;
const showingBlacklistCard = blacklisted;
const showingBlockedCard = blocked;
const blockedCardCss = useBlockedCardCss(showingDislikeCard || showingBlacklistCard || showingBlockedCard);
const _className = clsx("bili-video-card", APP_CLS_CARD, { [APP_CLS_CARD_ACTIVE]: active }, "relative", className);
const _css = [
baseCss,
getActiveCardBorderCss(!!active),
blockedCardCss,
isDisplayAsList(gridDisplayMode) && displayAsListCss.card,
multiSelecting && multiSelected && multiSelectedCss
];
return /* @__PURE__ */ jsx("div", {
"data-bvid": cardData?.bvid,
style,
className: _className,
css: _css,
...restProps,
ref: tagRecSeenRef,
children: loading ? /* @__PURE__ */ jsx(SkeletonCard, { loading }) : item && cardData && (showingDislikeCard ? /* @__PURE__ */ jsx(DislikedCard, {
item,
cardData,
emitter,
dislikedReason
}) : showingBlacklistCard ? /* @__PURE__ */ jsx(BlockedCard, {
item,
cardData,
blockType: "blacklist"
}) : showingBlockedCard ? /* @__PURE__ */ jsx(BlockedCard, {
item,
cardData,
blockType: "filter"
}) : /* @__PURE__ */ jsx(VideoCardInner, {
item,
cardData,
active,
emitter,
recSharedEmitter,
tab,
onRemoveCurrent,
watchlaterAdded,
gridDisplayMode,
multiSelecting,
multiSelected,
eagerCover,
highPriorityCover
}))
});
});
/** [tag-rec] 左上角角标通用样式 (未核实/回放等 marks 行共用) */
var tagRecMarkBadge = (background, title, text) => /* @__PURE__ */ jsx("span", {
style: {
padding: "1px 6px",
fontSize: 11,
lineHeight: "16px",
background,
color: "#fff",
borderRadius: 4
},
title,
children: text
});
var VideoCardInner = (0, import_react$2.memo)(function VideoCardInner({ item, cardData, tab, active = false, onRemoveCurrent, emitter = defaultVideoCardEmitter, recSharedEmitter = defaultRecSharedEmitter, watchlaterAdded, gridDisplayMode, multiSelecting = false, multiSelected, eagerCover, highPriorityCover }) {
const { accessKey, style: { videoCard: { useBorder: cardUseBorder, useBorderOnlyOnHover: cardUseBorderOnlyOnHover } }, videoCard: { actions: videoCardActions, imgPreview: { enabled: imgPreviewEnabled, autoPreviewWhenHover, disableWhenMultiSelecting } }, spaceUpload: spaceUploadSettings, watchlater: watchlaterSettings, __internalEnableCopyBvidInfo } = useSettingsSnapshot();
const { avid, bvid, cid, goto, href: _hrefFromNormalize, title, cover, duration, durationDisplay, watchedProgress, statItems, authorMid } = cardData;
const authed = !!accessKey;
if (!KNOWN_GOTO.includes(goto)) appWarn(`none (${KNOWN_GOTO.join(",")}) goto type %s`, goto, item);
const { usingOrder: spaceUploadItemsOrder, isDisplayingSingleUpAllItems: spaceUploadIsDisplayingSingleUpAllItems } = useSnapshot(store$2);
const href = (0, import_react$2.useMemo)(() => {
if (checkIsWatchlater(item) && item.bvid) return buildWatchlaterVideoCardUrl(item.bvid, item.aid, watchlaterSettings);
if (checkIsSpaceUpload(item) && authorMid && item.bvid) return buildSpaceUploadVideoCardUrl(authorMid, item.bvid, item.aid, {
continuePlay: spaceUploadSettings.continuePlay,
continuePlayDirection: spaceUploadSettings.continuePlayDirection,
useCollectionUrl: spaceUploadSettings.useCollectionUrl,
collectionId: SpaceUploadItemHelper.checkIsCollection(item) ? item.season_id.toString() : void 0,
itemsOrder: spaceUploadItemsOrder,
isDisplayingSingleUpAllItems: spaceUploadIsDisplayingSingleUpAllItems
});
return _hrefFromNormalize;
}, [
item,
authorMid,
_hrefFromNormalize,
watchlaterSettings.itemsOrder,
watchlaterSettings.continuePlay,
watchlaterSettings.continuePlayDirection,
spaceUploadSettings.continuePlay,
spaceUploadSettings.continuePlayDirection,
spaceUploadItemsOrder,
spaceUploadIsDisplayingSingleUpAllItems
]);
const displayingAsList = isDisplayAsList(gridDisplayMode);
const aspectRatioFromItem = (0, import_react$2.useMemo)(() => getRecItemDimension({ item })?.aspectRatio, [item]);
const shouldFetchPreviewData = (0, import_react$2.useMemo)(() => {
if (!bvid) return false;
if (!bvid.startsWith("BV")) return false;
if (goto !== "av") return false;
return true;
}, [bvid, goto]);
const showPreviewImageEl = (() => {
if (!imgPreviewEnabled) return false;
if (disableWhenMultiSelecting && multiSelecting) return false;
return true;
})();
const imagePreviewDataBox = useRefStateBox(void 0);
const tryFetchImagePreviewData = useLockFn(async () => {
if (!bvid) return;
if (!shouldFetchPreviewData) return;
if (!showPreviewImageEl) return;
if (isImagePreviewDataValid(imagePreviewDataBox.val)) return;
const data = await fetchImagePreviewData(bvid);
imagePreviewDataBox.set(data);
if (!isWebApiSuccess(data.videoshotJson)) warnNoPreview(data.videoshotJson);
});
const warnNoPreview = useLessFrequentFn((json) => {
antNotification.warning({
title: `${json.message} (code: ${json.code})`,
description: `${title} (${bvid})`,
duration: 2
});
}, 3, false);
/**
* 预览 hover state
*/
const cardRef = (0, import_react$2.useRef)(null);
const coverRef = (0, import_react$2.useRef)(null);
const videoPreviewWrapperRef = cardUseBorder && !displayingAsList ? cardRef : coverRef;
const { onStartPreviewAnimation, onHotkeyPreviewAnimation, isHovering, previewImageEl } = usePreviewRelated({
uniqId: item.uniqId,
recSharedEmitter,
title,
active,
videoDuration: duration,
tryFetchImagePreviewData,
imagePreviewDataBox,
autoPreviewWhenHover,
videoPreviewWrapperRef
});
useUpdateEffect_default(() => {
if (!active) return;
setGlobalValue(`${APP_KEY_PREFIX}_activeItem`, item);
setGlobalValue(`${APP_KEY_PREFIX}_activeCardData`, cardData);
if (settings.videoCard.imgPreview.autoPreviewWhenKeyboardSelect) tryFetchImagePreviewData().then(() => {
onStartPreviewAnimation(false);
});
}, [active]);
const actionButtonVisible = active || isHovering;
const { watchlaterButtonEl, context: watchlaterContext } = useWatchlaterRelated({
item,
cardData,
onRemoveCurrent,
actionButtonVisible,
watchlaterAdded
});
const { dislikeButtonEl, hasDislikeEntry, onTriggerDislike } = useDislikeRelated({
item,
authed,
actionButtonVisible
});
const { largePreviewActionButtonEl, largePreviewEl, shouldUseLargePreviewCurrentTime, getLargePreviewCurrentTime, largePreviewVisible, hideLargePreview } = useLargePreviewRelated({
shouldFetchPreviewData,
actionButtonVisible,
hasLargePreviewActionButton: videoCardActions.showLargePreview,
bvid,
cid,
uniqId: item.uniqId,
recSharedEmitter,
cardTarget: cardRef,
aspectRatioFromItem,
cover,
videoCardAsTriggerRef: videoPreviewWrapperRef,
videoTitle: title
});
const favContext = useInitFavContext(item, avid, emitter);
const { followed } = useInitFollowedStatusContext(item, cardData, emitter);
const { onOpenWithMode, handleVideoLinkClick, consistentOpenMenus, conditionalOpenMenus, openInPopupActionButtonEl, onOpenInPopup } = useOpenRelated({
href,
item,
cardData,
actionButtonVisible,
hasOpenInPopupActionButton: videoCardActions.openInPipWindow,
getLargePreviewCurrentTime,
hideLargePreview,
shouldUseLargePreviewCurrentTime
});
const { multiSelectBgEl, multiSelectEl, toggleMultiSelect } = useMultiSelectRelated({
multiSelecting,
multiSelected,
uniqId: item.uniqId
});
const handleCardClick = useMemoizedFn((e) => {
if (!cardUseBorder) return;
if (isClickInAntdOverlay(e.target)) return;
if (e.target.closest("a")) return;
onOpenWithMode();
});
/**
* expose actions
*/
useEmitterOn(emitter, "open", () => onOpenWithMode());
useEmitterOn(emitter, "open-in-popup", onOpenInPopup);
useEmitterOn(emitter, "open-with-large-preview-visible", () => {
if (!largePreviewVisible) return;
hideLargePreview();
onOpenWithMode();
});
useEmitterOn(emitter, "toggle-watch-later", () => void watchlaterContext.handleToggleWatchlater());
useEmitterOn(emitter, "trigger-dislike", () => void onTriggerDislike());
useEmitterOn(emitter, "start-preview-animation", onStartPreviewAnimation);
useEmitterOn(emitter, "hotkey-preview-animation", onHotkeyPreviewAnimation);
const { tagManageActionButtonEl, tagManageModalEl, tagRecMenus } = useTagManageRelated({
cardData,
actionButtonVisible,
tab,
item
});
/**
* context menu
*/
const contextMenus = useContextMenus({
item,
cardData,
tab,
watchlaterContext,
favContext,
followed,
hasDislikeEntry,
onTriggerDislike,
onRemoveCurrent,
consistentOpenMenus,
conditionalOpenMenus,
multiSelecting,
tagRecMenus
});
const onContextMenuOpenChange = useMemoizedFn((open) => {
if (!open) return;
emitter.emit("context-menu-open").catch((e) => appError("VideoCard context-menu-open error", e));
});
/**
* top marks
*/
const hasGeneralCardBadges = !!cardData.cardBadges?.some(checkIsCardBadgeValid);
const isRank = checkIsRank(item);
const isStreaming = (checkIsLive(item) || checkIsHistory(item)) && item.live_status === ELiveStatus.Streaming || checkIsPcRecommend(item) && item.goto === PcRecGoto.Live;
const hasApiTypeBadge = tab === ETab.AppRecommend && !checkIsAppRecommend(item) && !checkIsLive(item);
const hasVolMark = checkIsSpaceUpload(item) && spaceUploadSettings.showVol || checkIsFav(item) && !!item.vol && !hasApiTypeBadge;
const copyBvidInfoButtonEl = __internalEnableCopyBvidInfo && bvid && /* @__PURE__ */ jsx(VideoCardActionButton, {
visible: actionButtonVisible,
inlinePosition: "right",
icon: /* @__PURE__ */ jsx(IconForCopy, { className: "size-14px" }),
tooltip: "复制 BVID 信息",
onClick: (e) => {
e.stopPropagation();
e.preventDefault();
copyContent(getBvidInfo(cardData));
}
});
const topLeftMarksEl = /* @__PURE__ */ jsxs(Fragment, { children: [
multiSelecting && multiSelectEl,
item.api === EApiType.TagRec && item.combinationUnverified && tagRecMarkBadge("rgba(173, 104, 0, 0.9)", "你正在临时查看:这条视频尚未确认符合组合条件", "未核实"),
item.api === EApiType.TagRec && item.tagRecReplayed && tagRecMarkBadge("rgba(90, 90, 90, 0.85)", "这条是本地回放:该条件之前交付过的内容,零请求复用", "回放"),
isRank && /* @__PURE__ */ jsx(RankNumBadge, { item }),
isStreaming && /* @__PURE__ */ jsx(LiveBadge, {}),
hasApiTypeBadge && /* @__PURE__ */ jsx(ApiTypeBadge, { item }),
hasVolMark && !!item.vol && /* @__PURE__ */ jsx(VolBadge, {
vol: item.vol,
volTooltip: checkIsFav(item) ? item.volTooltip : void 0
}),
hasGeneralCardBadges && cardData.cardBadges?.map((tag) => /* @__PURE__ */ jsx(GeneralCardBadge, { tag }, tag.key)),
dislikeButtonEl
] });
const { removeHistoryActionButton } = useRemoveHistoryRelated({
item,
cardData,
actionButtonVisible,
onRemoveCurrent
});
const { favStateActionButton } = useFavActionButton({
item,
cardData,
ctx: favContext,
actionButtonVisible
});
const topRightActionsEl = /* @__PURE__ */ jsxs(Fragment, { children: [
removeHistoryActionButton,
watchlaterButtonEl,
favStateActionButton,
tab === ETab.TagRecCustom && item.api === EApiType.TagRec ? /* @__PURE__ */ jsx(TagRecDiscoveryCardActions, {
item,
visible: actionButtonVisible
}) : tagManageActionButtonEl,
copyBvidInfoButtonEl,
openInPopupActionButtonEl,
largePreviewActionButtonEl
] });
const hasTopLeftMarks = !isEmptyFragment(topLeftMarksEl);
const hasTopRightActions = !isEmptyFragment(topRightActionsEl);
const clsTopLeftMarksContainer = r("left-top-marks", C$1.topContainer("left"), multiSelecting && "gap-x-10px");
const watchedProgressBar = !!watchedProgress && /* @__PURE__ */ jsx(SimpleProgressBar, {
progress: watchedProgress,
className: clsx("h-3px", "z-2")
});
const clsVideoCardPrefix = `.${APP_CLS_ROOT} .${APP_CLS_CARD}`;
const coverRoundCss = (0, import_react$2.useMemo)(() => {
return [/* @__PURE__ */ css(clsVideoCardPrefix, " &{overflow:hidden;border-radius:", videoCardBorderRadiusValue, ";transition:border-radius 0.2s ease;}"), !displayingAsList && [(active || multiSelecting || cardUseBorder && !cardUseBorderOnlyOnHover) && /* @__PURE__ */ css(clsVideoCardPrefix, " &{border-bottom-left-radius:0;border-bottom-right-radius:0;}"), cardUseBorder && /* @__PURE__ */ css(clsVideoCardPrefix, ":hover &{border-bottom-left-radius:0;border-bottom-right-radius:0;}")]];
}, [
displayingAsList,
active,
multiSelecting,
cardUseBorder,
cardUseBorderOnlyOnHover
]);
const shouldMakeCoverClear = (0, import_react$2.useMemo)(() => {
if (cardUseBorder && !cardUseBorderOnlyOnHover) return false;
if (multiSelecting) return false;
return !cardUseBorder || cardUseBorder && cardUseBorderOnlyOnHover && !isHovering;
}, [
cardUseBorder,
cardUseBorderOnlyOnHover,
isHovering,
multiSelecting
]);
const target = useLinkTarget();
const coverContent = /* @__PURE__ */ jsxs("a", {
ref: coverRef,
href,
target,
className: clsx(APP_CLS_CARD_COVER, "relative isolate block aspect-16/9 w-100% overflow-hidden", shouldMakeCoverClear && "ring-1px ring-gate-border"),
css: [coverRoundCss, displayingAsList && displayAsListCss.cover],
onClick: handleVideoLinkClick,
onContextMenu: (e) => {
if (showNativeContextMenuWhenAltKeyPressed(e)) return;
e.preventDefault();
},
children: [
cover ? /* @__PURE__ */ jsx(Picture, {
src: `${cover}@672w_378h_1c_!web-home-common-cover`,
className: "bili-video-card__cover size-full",
eager: eagerCover,
imgProps: {
alt: isFirefoxLike ? void 0 : title,
fetchPriority: highPriorityCover ? "high" : void 0
},
style: { borderRadius: 0 }
}) : /* @__PURE__ */ jsx("div", {
className: "size-full flex-center px-4 py-2",
children: /* @__PURE__ */ jsx("div", {
className: "line-clamp-3 text-center text-1.2em color-gate-text line-height-snug hover:color-gate-primary",
children: title
})
}),
/* @__PURE__ */ jsxs("div", {
className: "bili-video-card__stats",
css: /* @__PURE__ */ css(clsVideoCardPrefix, " &{pointer-events:none;border-radius:0;}"),
children: [/* @__PURE__ */ jsx("div", {
className: "bili-video-card__stats--left gap-x-4px xl:gap-x-8px",
children: statItems.map(({ field, value }) => /* @__PURE__ */ jsx(StatItemDisplay, {
field,
value
}, field))
}), durationDisplay && /* @__PURE__ */ jsx("span", {
className: "bili-video-card__stats__duration relative top-0.5px",
children: durationDisplay
})]
}),
watchedProgressBar,
showPreviewImageEl && previewImageEl,
multiSelectBgEl,
hasTopLeftMarks && /* @__PURE__ */ jsx("div", {
className: clsTopLeftMarksContainer,
children: topLeftMarksEl
}),
hasTopRightActions && /* @__PURE__ */ jsx("div", {
className: clsx("right-actions", C$1.topContainer("right")),
children: topRightActionsEl
}),
tagManageModalEl
]
});
const bottomContent = /* @__PURE__ */ jsx(VideoCardBottom, {
item,
cardData,
gridDisplayMode,
followed,
handleVideoLinkClick: multiSelecting ? toggleMultiSelect : handleVideoLinkClick
});
const extraContent = /* @__PURE__ */ jsx(Fragment, { children: largePreviewEl });
function wrapDropdown(c) {
return /* @__PURE__ */ jsx(Dropdown, {
trigger: ["contextMenu"],
onOpenChange: onContextMenuOpenChange,
getPopupContainer: () => {
return cardRef.current?.closest(`.${APP_CLS_CARD}`) ?? document.body;
},
rootClassName: clsx(clsZVideoCardContextMenu, clsGateVideoCardContextMenuRoot),
menu: {
items: contextMenus,
className: "w-max"
},
children: c
});
}
function wrapCardWrapper(c) {
return /* @__PURE__ */ jsx("div", {
className: "bili-video-card__wrap",
ref: cardRef,
css: [/* @__PURE__ */ css("background-color:unset;position:static;height:100%;"), displayingAsList && displayAsListCss.cardWrap],
onClick: multiSelecting ? toggleMultiSelect : handleCardClick,
onContextMenu: (e) => {
if (cardUseBorder) e.preventDefault();
},
children: c
});
}
const wrappedContent = cardUseBorder && !displayingAsList ? wrapDropdown(wrapCardWrapper(/* @__PURE__ */ jsxs(Fragment, { children: [coverContent, bottomContent] }))) : wrapCardWrapper(/* @__PURE__ */ jsxs(Fragment, { children: [wrapDropdown(coverContent), bottomContent] }));
return /* @__PURE__ */ jsxs(Fragment, { children: [wrappedContent, extraContent] });
});
/** Extra verification/dig batches follow scrolling, with a quiet interval and a no-progress stop. */
var TagRecAutoContinuation = class {
finishedAt = -Infinity;
running = false;
emptyRounds = 0;
get stalled() {
return this.emptyRounds >= 2;
}
delay(now) {
if (this.running || this.stalled) return void 0;
return Math.max(0, 8e3 - (now - this.finishedAt));
}
start(now) {
if (this.delay(now) !== 0) return false;
this.running = true;
return true;
}
finish(now, added) {
this.running = false;
this.finishedAt = now;
this.emptyRounds = added > 0 ? 0 : this.emptyRounds + 1;
}
resume() {
this.emptyRounds = 0;
}
};
var tablerFaceIdError = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M4 8V6a2 2 0 0 1 2-2h2M4 16v2a2 2 0 0 0 2 2h2m8-16h2a2 2 0 0 1 2 2v2m-4 12h2a2 2 0 0 0 2-2v-2M9 10h.01M15 10h.01M9.5 15.05a3.5 3.5 0 0 1 5 0"
})]
});
var ForwardRef$1$1 = (0, import_react$2.forwardRef)(tablerFaceIdError);
function isAxiosError(err) {
return err instanceof Error && err.name === "AxiosError";
}
function wrapWithParagraph(node) {
if (!node) return node;
return /* @__PURE__ */ jsx("p", {
className: "mt-10px",
children: node
});
}
function inspectErrDetail(err) {
if (!err) return;
if (!isError(err)) return wrapWithParagraph(JSON.stringify(err));
if ((x.is(err) || n$2.is(err)) && isError(err.cause)) return inspectErrDetail(err.cause);
const errMessage = !!err.message && /* @__PURE__ */ jsxs(Fragment, { children: ["Error Message: ", err.message] });
const errStack = !!err.stack && /* @__PURE__ */ jsxs(Fragment, { children: [
"Error Stack: ",
/* @__PURE__ */ jsx("br", {}),
err.stack
] });
let errCause;
let errAxiosErrorDetail;
if (err.cause) errCause = /* @__PURE__ */ jsxs(Fragment, { children: ["Error Cause: ", inspectErrDetail(err.cause)] });
if (isAxiosError(err)) {
const _err = cloneDeep(err);
if (_err.config?.params?.access_key) _err.config.params.access_key = "*".repeat(_err.config.params.access_key.length);
errAxiosErrorDetail = /* @__PURE__ */ jsxs(Fragment, { children: ["axios config: ", JSON.stringify(_err.config, null, 2)] });
}
return /* @__PURE__ */ jsxs(Fragment, { children: [
wrapWithParagraph(errMessage),
wrapWithParagraph(errStack),
wrapWithParagraph(errCause),
wrapWithParagraph(errAxiosErrorDetail)
] });
}
function getErrLabel(err) {
if (err && err instanceof NeedValidAccessKeyError) return err.message;
if (err && err instanceof WebApiError) return err.message;
if (err && n$2.is(err)) return isError(err.cause) ? getErrLabel(err.cause) : err.message;
return "出错了, 请刷新重试!";
}
function ErrorDetail({ err, tab, onRetry }) {
const target = useLinkTarget();
const errLabel = (0, import_react$2.useMemo)(() => getErrLabel(err), [err]);
const errDetail = (0, import_react$2.useMemo)(() => inspectErrDetail(err), [err]);
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col items-center gap-y-0.5em p-20px text-center text-size-20px",
children: [
/* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs("div", {
className: "py-10px",
children: [/* @__PURE__ */ jsx("h3", { children: "错误详情" }), /* @__PURE__ */ jsx("div", {
className: "max-h-50vh overflow-hidden overflow-y-auto whitespace-pre-wrap break-normal",
children: errDetail
})]
}),
children: /* @__PURE__ */ jsxs("p", {
className: "flex cursor-pointer items-center justify-center",
children: [/* @__PURE__ */ jsx(ForwardRef$1$1, { className: "mr-4px" }), errLabel]
})
}),
tab === ETab.Hot && hotStore.subtab === EHotSubTab.PopularWeekly && /* @__PURE__ */ jsxs("p", {
className: "mt-8px flex items-center justify-center",
children: [
"可能需手动输入验证码",
/* @__PURE__ */ jsx(IconForOpenExternalLink, { className: "ml-12px" }),
/* @__PURE__ */ jsx("a", {
href: "https://www.bilibili.com/v/popular/weekly",
target,
className: "ml-2px",
children: "每周必看"
})
]
}),
tab === ETab.Liked && err instanceof NeedValidAccessKeyError && /* @__PURE__ */ jsx(AccessKeyManage, { className: "mt-0.5em" }),
/* @__PURE__ */ jsx(button_default, {
onClick: onRetry,
children: "重试"
})
]
});
}
var iconParkOutlineLoading = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
children: [/* @__PURE__ */ jsx("path", { d: "M4 24c0 11.046 8.954 20 20 20v0c11.046 0 20-8.954 20-20S35.046 4 24 4" }), /* @__PURE__ */ jsx("path", { d: "M36 24c0-6.627-5.373-12-12-12s-12 5.373-12 12s5.373 12 12 12v0" })]
})]
});
var ForwardRef$9 = (0, import_react$2.forwardRef)(iconParkOutlineLoading);
var debug$1 = baseDebug.extend("components:RecGrid");
var runEmitterListener = (fn) => {
try {
fn();
} catch (e) {
appError("recSharedEmitter listener error", e);
}
};
var clsGridColSpanFull = "grid-col-span-full";
var TAG_REC_CUSTOM_EMPTY_HINT = "输入关键词,或从左侧打开二级组试看;视频上的「选条件」可临时补条件。";
var RecGridSelf = class {
store = proxy({
refreshKey: -1,
showSkeleton: false,
hasMore: true,
refreshError: void 0,
loadMoreError: void 0,
items: []
});
get refreshKey() {
return this.store.refreshKey;
}
get refreshKeyValid() {
return this.refreshKey > 0;
}
get showSkeleton() {
return this.store.showSkeleton;
}
get items() {
return this.store.items;
}
get hasMore() {
return this.store.hasMore;
}
get refreshError() {
return this.store.refreshError;
}
get loadMoreError() {
return this.store.loadMoreError;
}
useStore = () => {
return useSnapshot(this.store);
};
setStore = (payload) => {
for (const key of [
"items",
"refreshError",
"loadMoreError"
]) if (typeof payload[key] === "object") payload[key] = ref(payload[key]);
Object.assign(this.store, payload);
};
loadedPage = 0;
abortController = new AbortController();
loadMoreLocker = /* @__PURE__ */ new Map();
isLocked = (lockKey) => !!this.loadMoreLocker.get(lockKey);
lock = (lockKey) => void (this.loadMoreLocker = /* @__PURE__ */ new Map([[lockKey, true]]));
unlock = (lockKey) => void this.loadMoreLocker.set(lockKey, false);
get loadMoreRunning() {
return this.isLocked(this.store.refreshKey);
}
};
var RecGrid = exports("X",(0, import_react$2.memo)(function RecGrid({ className: propClassName, containerClassName: propContainerClassName, shortcutEnabled, tab, infiniteScrollUseWindow, onScrollToTop, scrollerRef, ref }) {
const recSelf = useRecSelfContext();
const { serviceRegistry, recSharedEmitter } = recSelf;
const { refreshing } = recSelf.useStore();
const self = useCreation(() => new RecGridSelf(), []);
const { items, hasMore, refreshError, refreshKey, showSkeleton, loadMoreError } = self.useStore();
const { useCustomGrid, gridDisplayMode, enableForceColumn, forceColumnCount, cardMinWidth, enableForceRowGap, forceRowGap } = useSnapshot(settings.grid);
const { multiSelecting } = useSnapshot(multiSelectStore);
const discoveryDisplay = useSnapshot(tagRecDiscoveryPreferences);
const discoverySession = useSnapshot(tagRecCustomSession);
const unmountedRef = useUnmountedRef();
useSetupGridState();
const updateViewFromService = useMemoizedFn(() => {
if (unmountedRef.current) return;
recSelf.setStore({
tabbarView: serviceRegistry[tab]?.tabbarView,
sidebarView: serviceRegistry[tab]?.sidebarView
});
});
useMount(updateViewFromService);
const preAction = useMemoizedFn(() => {
clearActiveIndex();
updateViewFromService();
onScrollToTop?.();
});
const postAction = useMemoizedFn(() => {
clearActiveIndex();
updateViewFromService();
self.loadedPage = 1;
checkShouldLoadMore();
});
const { refresh } = useRefresh({
tab,
debug: debug$1,
fetcher: refreshForGrid,
preAction,
postAction,
updateViewFromService,
self
});
(0, import_react$2.useImperativeHandle)(ref, () => ({ refresh }), [refresh]);
(0, import_react$2.useEffect)(() => setGlobalGridItems(items), [items]);
const goOutAt = (0, import_react$2.useRef)(void 0);
useEventListener("visibilitychange", (_e) => {
if (!(document.visibilityState === "visible")) {
goOutAt.current = Date.now();
return;
}
if (recSelf.refreshing) return;
if (self.loadMoreRunning) return;
if ((tab === ETab.Watchlater || tab === ETab.History) && goOutAt.current && Date.now() - goOutAt.current > (0, import_ms.default)("30s")) Promise.resolve(refresh("reuse")).catch((e) => appError("refresh() error", e));
}, { target: document });
const checkShouldLoadMore = useMemoizedFn(async () => {
await delay$1(isSafari ? 100 : 0);
debug$1("checkShouldLoadMore(): footerInViewRef = %s", footerInViewRef.current);
if (footerInViewRef.current) return loadMore();
});
const loadMorePrecheck = useMemoizedFn(() => {
if (recSelf.refreshing || unmountedRef.current || self.abortController.signal.aborted || self.loadMoreRunning || !self.refreshKeyValid || !self.hasMore || getServiceFromRegistry(serviceRegistry, tab).autoLoadPaused || self.refreshError || self.loadMoreError) return false;
return true;
});
const loadMore = useMemoizedFn(async () => {
if (!loadMorePrecheck()) return;
const getRefreshKey = () => self.refreshKey;
const startingRefreshKey = getRefreshKey();
self.lock(startingRefreshKey);
let newItems = self.items;
let newHasMore = true;
let err;
try {
const service = getServiceFromRegistry(serviceRegistry, tab);
service.beginLoadOperation?.(self.abortController.signal);
let more;
try {
more = await service.loadMore(self.abortController.signal) || [];
} finally {
service.endLoadOperation?.();
}
more = filterRecItems(more, tab);
newItems = concatRecItems(newItems, more);
newHasMore = service.hasMore;
} catch (e) {
err = e;
}
if (unmountedRef.current || self.abortController.signal.aborted) {
debug$1("loadMore: skip update for RecGrid unmounted or aborted: tab=%s startingRefreshKey=%s", tab, startingRefreshKey);
self.unlock(startingRefreshKey);
return;
}
if (startingRefreshKey !== getRefreshKey()) {
debug$1("loadMore: skip update for refreshKey mismatch, expect %o but found %o", startingRefreshKey, getRefreshKey());
self.unlock(startingRefreshKey);
return;
}
if (err) {
self.unlock(startingRefreshKey);
antNotification.error({
title: "加载失败",
description: err.message || err.stack
});
self.setStore({ loadMoreError: err });
throw err;
}
self.loadedPage++;
debug$1("loadMore: loadedPage(%s) len(%s -> %s)", self.loadedPage, self.items.length, newItems.length);
self.setStore({
hasMore: newHasMore,
items: newItems
});
self.unlock(startingRefreshKey);
checkShouldLoadMore();
});
const loadMoreRetry = useMemoizedFn(() => {
self.setStore({ loadMoreError: void 0 });
return loadMore();
});
const loadToEnd = useMemoizedFn(async () => {
const notifyErrorArgs = {
key: "RecGrid:loadToEnd:error",
title: "加载全部",
duration: 15
};
const notifySuccessArgs = { title: "加载全部" };
const error = (description) => {
antNotification.error({
...notifyErrorArgs,
description
});
throw new Error("loadToEnd error", { cause: description });
};
if (!querySupportsLoadToEnd()) return error("当前 Tab 不支持加载全部");
if (self.refreshError) return error("预检查失败: 先前错误");
if (!self.hasMore) return antNotification.success({
...notifySuccessArgs,
description: "没有更多了"
});
while (!loadMorePrecheck()) await delay$1(500);
while (loadMorePrecheck()) await loadMore();
antNotification.success({
...notifySuccessArgs,
description: "已完成"
});
});
useEmitterOn(recSharedEmitter, "load-to-end", loadToEnd);
const loadMoreOnExternalRefill = useMemoizedFn(() => {
const service = getServiceFromRegistry(serviceRegistry, tab);
if (service.hasMore && !self.hasMore) self.setStore({ hasMore: service.hasMore });
checkShouldLoadMore();
});
useEmitterOn(recSharedEmitter, "load-more", () => runEmitterListener(loadMoreOnExternalRefill));
const [continuingDiscovery, setContinuingDiscovery] = (0, import_react$2.useState)(false);
const continuationLock = (0, import_react$2.useRef)(false);
const autoContinuation = (0, import_react$2.useMemo)(() => new TagRecAutoContinuation(), [refreshKey]);
const [autoContinueTick, setAutoContinueTick] = (0, import_react$2.useState)(0);
const continueDiscovery = useMemoizedFn(async (automatic = false) => {
if (tab !== ETab.TagRecCustom || continuationLock.current || self.loadMoreRunning || recSelf.refreshing || !self.refreshKeyValid) return;
const service = serviceRegistry[ETab.TagRecCustom];
if (!service) return;
const key = self.refreshKey;
if (!automatic) autoContinuation.resume();
continuationLock.current = true;
setContinuingDiscovery(true);
try {
tagRecCustomSession.supplyPaused = false;
service.retryPartitions();
if (tagRecCustomSession.digPending || !service.hasMore) await service.digMore(automatic);
if (unmountedRef.current || self.abortController.signal.aborted || key !== self.refreshKey) return;
self.setStore({
hasMore: service.hasMore,
loadMoreError: void 0
});
await loadMore();
} catch (error) {
appError("tag discovery continuation failed", error);
} finally {
continuationLock.current = false;
setContinuingDiscovery(false);
}
});
useEmitterOn(recSharedEmitter, "tag-rec:continue", () => continueDiscovery());
const fullList = items;
const videoList = (0, import_react$2.useMemo)(() => fullList.filter((x) => x.api !== EApiType.Separator), [fullList]);
const videoIndexMap = (0, import_react$2.useMemo)(() => {
const m = /* @__PURE__ */ new Map();
videoList.forEach((x, i) => {
if (!m.has(x.uniqId)) m.set(x.uniqId, i);
});
return m;
}, [videoList]);
const gridRef = (0, import_react$2.useRef)(null);
const getScrollerRect = useMemoizedFn(() => {
if (infiniteScrollUseWindow) {
const yStart = $headerHeight.get() + 50;
return new DOMRect(0, yStart, window.innerWidth, window.innerHeight - yStart);
} else return scrollerRef?.current?.getBoundingClientRect();
});
const videoCardEmitterCache = (0, import_react$2.useMemo)(() => /* @__PURE__ */ new Map(), [refreshKey]);
const videoCardEmitters = (0, import_react$2.useMemo)(() => {
return videoList.map(({ uniqId }) => {
const cacheKey = uniqId;
return videoCardEmitterCache.get(cacheKey) || (() => {
const instance = new Emittery();
videoCardEmitterCache.set(cacheKey, instance);
return instance;
})();
});
}, [videoList]);
const [activeLargePreviewUniqId, setActiveLargePreviewUniqId] = (0, import_react$2.useState)(void 0);
useEmitterOn(recSharedEmitter, "show-large-preview", ({ data: uniqId }) => setActiveLargePreviewUniqId(uniqId));
const activeLargePreviewItemIndex = (0, import_react$2.useMemo)(() => {
if (!activeLargePreviewUniqId) return;
return videoIndexMap.get(activeLargePreviewUniqId);
}, [videoIndexMap, activeLargePreviewUniqId]);
const { activeIndex, clearActiveIndex } = useShortcut({
shortcutEnabled,
maxIndex: videoList.length - 1,
gridRef,
getScrollerRect,
videoCardEmitters,
activeLargePreviewItemIndex,
changeScrollY: infiniteScrollUseWindow ? function({ offset, absolute }) {
const scroller = document.documentElement;
if (typeof offset === "number") {
scroller.scrollTop += offset;
return;
}
if (typeof absolute === "number") {
scroller.scrollTop = absolute;
return;
}
} : void 0
});
const modifyItems = useMemoizedFn((fn) => {
const val = self.items;
self.setStore({ items: fn(val) ?? val });
});
const handleRemoveCards = useMemoizedFn((uniqIds, titles, messageConfig) => {
modifyItems((items) => {
const pending = new Map(uniqIds.map((uniqId, i) => [uniqId, i]));
const newItems = [];
const removedTitlePairs = [];
for (const item of items) {
const pos = pending.get(item.uniqId);
if (pos === void 0) {
newItems.push(item);
continue;
}
pending.delete(item.uniqId);
const title = titles?.[pos];
if (title) removedTitlePairs.push([pos, title]);
if (tab === ETab.Watchlater) serviceRegistry[tab]?.decreaseTotal();
if (tab === ETab.Fav) serviceRegistry[tab]?.decreaseTotal();
}
const removedTitles = removedTitlePairs.sort((a, b) => a[0] - b[0]).map(([, t]) => t);
if (tab === ETab.Watchlater && newItems.length > 2 && newItems.slice(0, 2).every((x) => x.api === EApiType.Separator)) newItems.splice(0, 1);
const silent = messageConfig?.silent ?? false;
const itemsDescription = messageConfig?.itemsDescription ?? "个视频";
if (!silent && removedTitles.length) {
if (removedTitles.length <= 3) removedTitles.forEach((t) => antMessage.success(`已移除: ${t}`));
else antMessage.success(`已移除: ${removedTitles.length}${itemsDescription}`);
}
return newItems;
});
});
const handleMoveCardTo = useMemoizedFn((itemUniqId, pos) => {
modifyItems((items) => {
const index = items.findIndex((x) => x.uniqId === itemUniqId);
if (index === -1) return;
if (pos === "start") {
const firstNoneSeparatorIndex = items.findIndex((x) => x.api !== EApiType.Separator);
return arrayMove(items, index, firstNoneSeparatorIndex);
} else if (pos === "end") return arrayMove(items, index, items.length - 1);
});
});
const onRemoveCardCurrent = useMemoizedFn((item, data, silent) => handleRemoveCards([item.uniqId], [data.title], { silent }));
useEmitterOn(recSharedEmitter, "remove-cards", ({ data }) => runEmitterListener(() => handleRemoveCards(...data)));
useEmitterOn(recSharedEmitter, "move-card-to", ({ data }) => runEmitterListener(() => handleMoveCardTo(...data)));
/**
* footer for infinite scroll
*/
const { ref: footerRef, inView: __footerInView } = useInView({
root: infiniteScrollUseWindow ? null : scrollerRef?.current || null,
rootMargin: `0px 0px ${window.innerHeight}px 0px`,
onChange(inView) {
if (inView) {
debug$1("footerInView change to visible");
ae.try(() => footerInViewRef.current = true);
checkShouldLoadMore();
}
}
});
const footerInViewRef = useLatest(__footerInView);
const footerElement = (0, import_react$2.useRef)(null);
const setFooterRef = useMemoizedFn((node) => {
footerRef(node);
footerElement.current = node;
});
const [scrollDemand, setScrollDemand] = (0, import_react$2.useState)(false);
const [scrollTick, setScrollTick] = (0, import_react$2.useState)(0);
const scrollPosition = useMemoizedFn(() => infiniteScrollUseWindow ? window.scrollY : scrollerRef?.current?.scrollTop ?? 0);
const lastScrollPosition = (0, import_react$2.useRef)(0);
(0, import_react$2.useEffect)(() => {
setScrollDemand(false);
lastScrollPosition.current = scrollPosition();
}, [refreshKey, scrollPosition]);
useEventListener("scroll", () => {
if (tab !== ETab.TagRecCustom) return;
const position = scrollPosition();
if (position > lastScrollPosition.current + 1) setScrollDemand(true);
lastScrollPosition.current = position;
setScrollTick((tick) => tick + 1);
}, { target: () => infiniteScrollUseWindow ? window : scrollerRef?.current });
useEventListener("visibilitychange", () => setScrollTick((tick) => tick + 1), { target: document });
const nearDiscoveryEnd = useMemoizedFn(() => {
const rect = footerElement.current?.getBoundingClientRect();
const bottom = infiniteScrollUseWindow ? window.innerHeight : scrollerRef?.current?.getBoundingClientRect().bottom ?? 0;
return !!rect && rect.top <= bottom + 240 && rect.bottom >= 0;
});
(0, import_react$2.useEffect)(() => {
if (tab !== ETab.TagRecCustom || !scrollDemand || refreshing || continuingDiscovery || loadMoreError || refreshError || discoverySession.supplyPaused || !discoverySession.partitionPaused && !discoverySession.digPending || document.visibilityState !== "visible" || !nearDiscoveryEnd()) return;
const wait = autoContinuation.delay(Date.now());
if (wait === void 0) return;
const timer = setTimeout(() => {
if (document.visibilityState !== "visible" || !nearDiscoveryEnd() || self.loadMoreRunning || recSelf.refreshing || continuationLock.current || self.abortController.signal.aborted || !autoContinuation.start(Date.now())) return;
const key = self.refreshKey;
const before = self.items.filter((item) => item.api !== EApiType.Separator).length;
continueDiscovery(true).finally(() => {
const after = self.items.filter((item) => item.api !== EApiType.Separator).length;
autoContinuation.finish(Date.now(), after - before);
if (!unmountedRef.current && key === self.refreshKey) setAutoContinueTick((tick) => tick + 1);
});
}, wait);
return () => clearTimeout(timer);
}, [
tab,
scrollDemand,
scrollTick,
autoContinueTick,
refreshing,
continuingDiscovery,
loadMoreError,
refreshError,
discoverySession.supplyPaused,
discoverySession.partitionPaused,
discoverySession.digPending,
items,
autoContinuation,
nearDiscoveryEnd,
self,
recSelf,
continueDiscovery,
unmountedRef
]);
const footer = /* @__PURE__ */ jsx("div", {
ref: setFooterRef,
className: clsx(clsGridColSpanFull, "flex items-center justify-center py-30px text-size-120%"),
children: (() => {
if (refreshing) return;
if (loadMoreError) return /* @__PURE__ */ jsx(ErrorDetail, {
err: loadMoreError,
onRetry: tab === ETab.TagRecCustom ? () => continueDiscovery() : loadMoreRetry
});
if (tab === ETab.TagRecCustom) {
const paused = discoverySession.supplyPaused || discoverySession.partitionPaused || discoverySession.digPending;
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col items-center gap-10px",
style: { fontSize: 13 },
children: [
/* @__PURE__ */ jsxs("span", {
"aria-live": "polite",
children: [
"已展示 ",
videoList.length,
" 条",
discoverySession.supplyPaused ? " · 只浏览缓存" : autoContinuation.stalled ? " · 暂时没有新视频,自动发掘已暂停" : continuingDiscovery ? " · 正在继续发掘…" : discoverySession.partitionPaused ? ` · ${discoverySession.partitionPending} 条待核实,向下浏览会自动继续` : discoverySession.digPending ? " · 向下浏览会自动深挖下一批" : !hasMore ? videoList.length ? " · 当前来源暂时没有新结果" : ` · ${TAG_REC_CUSTOM_EMPTY_HINT}` : " · 向下滚动会自动加载"
]
}),
(hasMore || paused) && /* @__PURE__ */ jsx(button_default, {
type: "primary",
loading: continuingDiscovery || discoverySession.digRunning,
onClick: () => void continueDiscovery(),
children: discoverySession.supplyPaused ? "继续发掘" : "加载更多视频"
}),
paused && !discoverySession.supplyPaused && /* @__PURE__ */ jsx("span", {
style: {
fontSize: 12,
opacity: .7
},
children: "约 8 秒后可自动续批,也可点按钮继续;已有视频会保留。"
})
]
});
}
if (!hasMore) return "没有更多了~";
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ForwardRef$9, { className: "mr-10px size-40px animate-spin color-gate-primary" }), "加载中~"] });
})()
});
const containerClassName = clsx("min-h-100vh @container-inline-size", propContainerClassName);
const gridStyleProps = (0, import_react$2.useMemo)(() => {
if (tab === ETab.TagRecCustom) return {
className: clsx(APP_CLS_GRID, videoGrid, videoGridCustom, propClassName),
style: {
"gridTemplateColumns": `repeat(auto-fill, minmax(min(100%, ${{
small: 220,
medium: 285,
large: 370
}[discoveryDisplay.size] ?? 285}px), 1fr))`,
"--row-gap": "24px"
}
};
const { videoGrid: videoGrid$1, videoGridBiliFeed4, videoGridCustom: videoGridCustom$1, videoGridAddonCenterEmpty, gridTemplateColumnsUsingVarCol, gridTemplateColumnsUsingCardMinWidth, narrowMode } = video_grid_module_exports;
const clsGridTemplateColumns = useCustomGrid && !(enableForceColumn && forceColumnCount) && gridDisplayMode !== EGridDisplayMode.TwoColumnGrid ? gridTemplateColumnsUsingCardMinWidth : gridTemplateColumnsUsingVarCol;
const baseClass = [
APP_CLS_GRID,
videoGrid$1,
clsGridTemplateColumns,
useCustomGrid ? videoGridCustom$1 : videoGridBiliFeed4
];
let baseStyle;
if (enableForceRowGap && forceRowGap && useCustomGrid) {
baseStyle ||= {};
baseStyle["--row-gap"] = `${forceRowGap}px`;
}
const renderClassName = (...more) => clsx(baseClass, ...more, propClassName);
if (gridDisplayMode === EGridDisplayMode.TwoColumnGrid) return {
className: renderClassName(narrowMode),
style: baseStyle
};
if (gridDisplayMode === EGridDisplayMode.CenterEmptyGrid) baseClass.push(videoGridAddonCenterEmpty);
if (!useCustomGrid) return {
className: renderClassName(),
style: baseStyle
};
if (enableForceColumn && forceColumnCount) return {
className: renderClassName(),
style: {
...baseStyle,
"--col": forceColumnCount.toString()
}
};
else return {
className: renderClassName(),
style: {
...baseStyle,
"--card-min-width": `${cardMinWidth}px`
}
};
}, [
video_grid_module_exports,
useCustomGrid,
gridDisplayMode,
enableForceColumn,
forceColumnCount,
cardMinWidth,
enableForceRowGap,
forceRowGap,
propClassName,
tab,
discoveryDisplay.size
]);
const cardBorderCss = useCardBorderCss();
const render = ({ gridChildren, gridSiblings } = {}) => {
return /* @__PURE__ */ jsxs("div", {
"data-tab": tab,
className: containerClassName,
children: [/* @__PURE__ */ jsx("div", {
"data-tab": tab,
ref: gridRef,
...gridStyleProps,
children: gridChildren
}), gridSiblings]
});
};
if (refreshError) {
appError("RecGrid.refresh error:", refreshError);
return render({ gridSiblings: /* @__PURE__ */ jsx(ErrorDetail, {
tab,
err: refreshError
}) });
}
if (refreshing && showSkeleton) {
const cardCount = getGridRefreshCount();
return render({ gridChildren: Array.from({ length: cardCount }).fill(0).map((_, index) => {
return /* @__PURE__ */ jsx(VideoCard, {
loading: true,
tab
}, index);
}) });
}
const eagerCoverCount = Math.max(8, getColumnCount(gridRef.current) * 2);
const renderItem = (item) => {
if (item.api === EApiType.Separator) {
const shouldWrap = item.wrapWithDivider ?? true;
return /* @__PURE__ */ jsx(import_react$2.Fragment, { children: shouldWrap ? wrapWithDivider(item.content) : item.content }, item.uniqId);
} else {
const index = videoIndexMap.get(item.uniqId) ?? -1;
return /* @__PURE__ */ jsx(VideoCard, {
baseCss: cardBorderCss,
tab,
item,
active: index === activeIndex,
onRemoveCurrent: onRemoveCardCurrent,
emitter: videoCardEmitters[index],
recSharedEmitter,
gridDisplayMode: tab === ETab.TagRecCustom ? EGridDisplayMode.NormalGrid : gridDisplayMode,
multiSelecting,
eagerCover: index !== -1 && index < eagerCoverCount,
highPriorityCover: index === 0
}, item.uniqId);
}
};
return render({ gridChildren: /* @__PURE__ */ jsxs(Fragment, { children: [fullList.map(renderItem), footer] }) });
}));
function wrapWithDivider(children) {
return /* @__PURE__ */ jsx(Divider, {
className: clsx(clsGridColSpanFull, clsGateVideoGridDivider),
orientation: "horizontal",
titlePlacement: "left",
children
});
}
function RefreshButton({ className = "", style, tab, refreshHotkeyEnabled, ref }) {
refreshHotkeyEnabled ??= true;
const { refreshing } = useRecSelfContext().useStore();
const onRefresh = useOnRefresh();
const btn = (0, import_react$2.useRef)(null);
const click = useMemoizedFn(() => {
if (!btn.current) return;
if (btn.current.disabled) return;
btn.current.click();
});
(0, import_react$2.useImperativeHandle)(ref, () => ({ click }), []);
useHotkey("R", click, { enabled: refreshHotkeyEnabled });
const { watchlater: { itemsOrder }, popularWeeklyUseShuffle } = useSettingsSnapshot();
const { usingShuffle: favUsingShuffle } = useSnapshot(favStore);
const text = tab === ETab.AppRecommend || tab === ETab.PcRecommend || tab === ETab.KeepFollowOnly || tab === ETab.Watchlater && itemsOrder === WatchlaterItemsOrder.Shuffle || tab === ETab.Fav && favUsingShuffle || tab === ETab.Hot && isHotTabUsingShuffle(popularWeeklyUseShuffle) ? "换一换" : "刷新";
const [scope, animate] = useAnimate();
const onClick = useMemoizedFn((e) => {
animate(scope.current, { rotate: [0, 360] }, {
duration: .5,
type: "tween"
});
onRefresh();
});
return /* @__PURE__ */ jsxs(button_default, {
ref: btn,
style,
className: clsx(clsAntdButton, "[&:disabled]:cursor-wait", className),
disabled: refreshing,
onClick,
children: [/* @__PURE__ */ jsx(IconForRoll, {
ref: scope,
className: "size-14px"
}), text]
});
}
var sidebarViewWrapperCss = /* @__PURE__ */ css("scrollbar-width:thin;.ant-menu-item-group-title{padding:4px 8px;}.ant-menu-item{margin-block:1px;}ul.ant-menu-item-group-list{font-size:inherit;}");
function useTabExtraClassName(tab) {
const hotSubTab = useSnapshot(hotStore).subtab;
if (tab === ETab.Fav) return "max-w-300px";
if (tab === ETab.DynamicFeed) return "min-w-200px";
if (tab === ETab.Hot && hotSubTab === EHotSubTab.Rank) return "min-w-130px";
}
function RecSidebar({ className: propClassName, style: propStyle, sidebarView, tab }) {
const visible = useSidebarVisible(tab);
const usingClassName = r("h-fit max-w-250px w-max flex-none overflow-x-hidden overflow-y-auto b-1px b-gate-border rounded-15px b-solid", useTabExtraClassName(tab), propClassName);
return visible && sidebarView && /* @__PURE__ */ jsx("div", {
className: usingClassName,
css: sidebarViewWrapperCss,
style: propStyle,
children: /* @__PURE__ */ jsx(ConfigProvider, {
theme: { components: { Menu: { itemHeight: 30 } } },
children: sidebarView
})
});
}var import_react$1 = /* @__PURE__ */ __toESM(require_react(), 1);
var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom(), 1);
function normalizeLargePreviewAdapter(adapter) {
return {
observerTarget: adapter.observerTarget ?? document.body,
itemsSelector: adapter.itemsSelector,
throttleDelay: adapter.throttleDelay || 300,
anchorSelector: adapter.anchorSelector || ".bili-watch-later--wrap",
reactRootInsertPosition: adapter.reactRootInsertPosition ?? "afterend",
parseCardInfo: adapter.parseCardInfo ?? parseCardInfo,
cardElSelector: adapter.cardElSelector || ".bili-video-card",
coverElSelector: adapter.coverElSelector || ".bili-video-card__wrap > a"
};
}
function setupLargePreview(adapter) {
const normalizedAdapter = normalizeLargePreviewAdapter(adapter);
const { itemsSelector, throttleDelay, observerTarget } = normalizedAdapter;
const run = throttle(() => {
const list = document.querySelectorAll(itemsSelector);
for (const el of list) addLargePreview(el, normalizedAdapter);
}, throttleDelay);
run();
new MutationObserver(() => run()).observe(observerTarget, {
childList: true,
subtree: true
});
}
var processed = /* @__PURE__ */ new WeakSet();
var processedAttr = `data-${APP_SHORT_PREFIX}-large-preview-added`;
function addLargePreview(el, adapter) {
if (processed.has(el)) return;
if (el.getAttribute(processedAttr)) return;
const anchorEl = el.querySelector(adapter.anchorSelector);
if (!anchorEl) return;
const reactRootEl = document.createElement("div");
processed.add(el);
el.setAttribute(processedAttr, "true");
anchorEl.insertAdjacentElement(adapter.reactRootInsertPosition, reactRootEl);
createReactRoot(reactRootEl).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(LargePreviewSetup, {
el,
parseCardInfo: adapter.parseCardInfo,
cardElSelector: adapter.cardElSelector,
coverElSelector: adapter.coverElSelector
}) }));
}
var actionButtonProps = {
useMotion: true,
motionProps: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: {
opacity: 0,
transition: { delay: 0 }
},
transition: {
duration: .2,
ease: "linear",
delay: .2
}
}
};
function LargePreviewSetup({ el, parseCardInfo, cardElSelector, coverElSelector }) {
const { bvid = "", cover } = (0, import_react$1.useMemo)(() => parseCardInfo(el), [el, parseCardInfo]);
const cardEl = (0, import_react$1.useMemo)(() => el.querySelector(cardElSelector) ?? el, [el, cardElSelector]);
const coverEl = (0, import_react$1.useMemo)(() => cardEl.querySelector(coverElSelector), [cardEl, coverElSelector]);
const hovering = useHover_default(coverEl);
const videoCardAsTriggerRef = (0, import_react$1.useRef)(coverEl);
const { largePreviewActionButtonEl, largePreviewEl } = useLargePreviewRelated({
shouldFetchPreviewData: !!bvid,
hasLargePreviewActionButton: true,
actionButtonVisible: hovering,
actionButtonProps,
bvid,
cid: void 0,
uniqId: bvid,
recSharedEmitter: defaultRecSharedEmitter,
cardTarget: cardEl,
cover,
videoCardAsTriggerRef
});
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
className: r(C$1.top("right"), "right-[calc(8px+28px+5px)]"),
children: largePreviewActionButtonEl
}), (0, import_react_dom.createPortal)(largePreviewEl, cardEl)] });
}
function parseCardInfo(el) {
let bvid;
{
const link = el.querySelector(".bili-video-card__wrap > a")?.href;
if (link) bvid = /^\/video\/(?BV\w+)\/?/i.exec(new URL(link).pathname)?.groups?.bvid;
}
const cover = el.querySelector("picture.bili-video-card__cover img")?.currentSrc;
return {
bvid,
cover
};
}_css("@charset \"UTF-8\";\n:root {\n --bilibili-gate--font: \"Alibaba PuHuiTi 3.0\", \"PingFang SC\", \"HarmonyOS_Regular\", \"Helvetica Neue\", \"Microsoft YaHei\", sans-serif;\n}\n\n:root {\n --bilibili-gate--primary-color--lv1: oklch(from var(--bilibili-gate--primary-color) calc(l - 0.04) c h/alpha);\n --bilibili-gate--primary-color--lv2: oklch(from var(--bilibili-gate--primary-color) calc(l - 0.08) c h/alpha);\n --bilibili-gate--primary-color--lv3: oklch(from var(--bilibili-gate--primary-color) calc(l - 0.12) c h/alpha);\n --bilibili-gate--border-color: oklch(from var(--bilibili-gate--bg) calc(l - 0.05) c h/alpha);\n --bilibili-gate--bg--lv1: oklch(from var(--bilibili-gate--bg) calc(l - 0.04) c h/alpha);\n --bilibili-gate--bg--lv2: oklch(from var(--bilibili-gate--bg) calc(l - 0.08) c h/alpha);\n --bilibili-gate--bg--lv3: oklch(from var(--bilibili-gate--bg) calc(l - 0.12) c h/alpha);\n}\n:root.bilibili-gate-using-dark {\n --bilibili-gate--border-color: oklch(from var(--bilibili-gate--bg) calc(l + 0.07) c h/alpha);\n --bilibili-gate--bg--lv1: oklch(from var(--bilibili-gate--bg) calc(l + 0.03) c h/alpha);\n --bilibili-gate--bg--lv2: oklch(from var(--bilibili-gate--bg) calc(l + 0.08) c h/alpha);\n --bilibili-gate--bg--lv3: oklch(from var(--bilibili-gate--bg) calc(l + 0.12) c h/alpha);\n}\n\n.bilibili-gate-root {\n font-family: var(--custom-font-family--options--font-family, var(--bilibili-gate--font)) !important;\n font-size: 14px;\n --back-top-right: 24px;\n}\n.bilibili-gate-root dd,\n.bilibili-gate-root dl,\n.bilibili-gate-root ol,\n.bilibili-gate-root ul {\n font-size: inherit;\n}\n.bilibili-gate-root li {\n list-style: unset;\n}\n.bilibili-gate-root .bili-video-card .bili-video-card__info--tit:hover,\n.bilibili-gate-root .bili-video-card a:not(.disable-hover):hover {\n color: var(--bilibili-gate--primary-color) !important;\n}\n.bilibili-gate-root .ant-btn {\n font-size: 13px;\n}\n.bilibili-gate-root img:-moz-loading {\n visibility: hidden;\n}\n\n@media (max-width: 1440px) {\n .bilibili-gate-root {\n --back-top-right: 16px;\n }\n}\nbody .ant-tooltip a {\n color: #1677ff;\n transition: color 0.3s;\n}\nbody .ant-tooltip a:visited {\n color: #1677ff;\n}\nbody .ant-tooltip a:hover {\n color: #69b1ff;\n}\nbody .ant-tooltip a:active {\n color: #0958d9;\n}\n\nbody button:where(.ant-switch):focus, body button:where(.ant-switch):active {\n background-color: rgba(0, 0, 0, 0.25);\n outline: unset;\n}\n\n.ant-tooltip-placement-top .ant-tooltip-arrow,\n.ant-popover-placement-top .ant-popover-arrow {\n bottom: 1px !important;\n}\n\n.ant-tooltip-placement-bottom .ant-tooltip-arrow,\n.ant-popover-placement-bottom .ant-popover-arrow {\n top: 1.5px !important;\n}\n\n/**\nB站自带 css, 影响了 solid button :focus 样式\nbody button:focus, body button:active {\n background-color: transparent;\n outline: none;\n}\n */\nbody .ant-btn.ant-btn-variant-solid:focus, body .ant-btn.ant-btn-variant-solid:active {\n color: var(--ant-button-primary-color);\n background: var(--ant-color-primary);\n}\n\n#slide_ad {\n display: none !important;\n}");
function throttleByAnimationFrame(fn) {
let requestId = null;
const later = (args) => () => {
requestId = null;
fn.apply(void 0, _toConsumableArray(args));
};
const throttled = (...args) => {
if (requestId === null) requestId = wrapperRaf(later(args));
};
throttled.cancel = () => {
wrapperRaf.cancel(requestId);
requestId = null;
};
return throttled;
}
var VerticalAlignTopOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M859.9 168H164.1c-4.5 0-8.1 3.6-8.1 8v60c0 4.4 3.6 8 8.1 8h695.8c4.5 0 8.1-3.6 8.1-8v-60c0-4.4-3.6-8-8.1-8zM518.3 355a8 8 0 00-12.6 0l-112 141.7a7.98 7.98 0 006.3 12.9h73.9V848c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V509.7H624c6.7 0 10.4-7.7 6.3-12.9L518.3 355z" }
}]
},
"name": "vertical-align-top",
"theme": "outlined"
};
var import_react = /* @__PURE__ */ __toESM(require_react());
function _extends$1() {
_extends$1 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$1.apply(this, arguments);
}
var VerticalAlignTopOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$1({}, props, {
ref,
icon: VerticalAlignTopOutlined$1
}));
/** */
var RefIcon$1 = /*#__PURE__*/ import_react.forwardRef(VerticalAlignTopOutlined);
var flexWrapValues = [
"wrap",
"nowrap",
"wrap-reverse"
];
var justifyContentValues = [
"flex-start",
"flex-end",
"start",
"end",
"center",
"space-between",
"space-around",
"space-evenly",
"stretch",
"normal",
"left",
"right"
];
var alignItemsValues = [
"center",
"start",
"end",
"flex-start",
"flex-end",
"self-start",
"self-end",
"baseline",
"normal",
"stretch"
];
var genClsWrap = (prefixCls, props) => {
const wrap = props.wrap === true ? "wrap" : props.wrap;
return { [`${prefixCls}-wrap-${wrap}`]: wrap && flexWrapValues.includes(wrap) };
};
var genClsAlign = (prefixCls, props) => {
const alignCls = {};
alignItemsValues.forEach((cssKey) => {
alignCls[`${prefixCls}-align-${cssKey}`] = props.align === cssKey;
});
alignCls[`${prefixCls}-align-stretch`] = !props.align && !!props.vertical;
return alignCls;
};
var genClsJustify = (prefixCls, props) => {
const justifyCls = {};
justifyContentValues.forEach((cssKey) => {
justifyCls[`${prefixCls}-justify-${cssKey}`] = props.justify === cssKey;
});
return justifyCls;
};
var createFlexClassNames = (prefixCls, props) => {
return clsx({
...genClsWrap(prefixCls, props),
...genClsAlign(prefixCls, props),
...genClsJustify(prefixCls, props)
});
};
var genFlexStyle = (token) => {
const { componentCls } = token;
return { [componentCls]: {
display: "flex",
margin: 0,
padding: 0,
"&-vertical": { flexDirection: "column" },
"&-rtl": { direction: "rtl" },
"&:empty": { display: "none" }
} };
};
var genFlexGapStyle = (token) => {
const { componentCls } = token;
return { [componentCls]: {
"&-gap-small": { gap: token.flexGapSM },
"&-gap-medium, &-gap-middle": { gap: token.flexGap },
"&-gap-large": { gap: token.flexGapLG }
} };
};
var genFlexWrapStyle = (token) => {
const { componentCls } = token;
const wrapStyle = {};
flexWrapValues.forEach((value) => {
wrapStyle[`${componentCls}-wrap-${value}`] = { flexWrap: value };
});
return wrapStyle;
};
var genAlignItemsStyle = (token) => {
const { componentCls } = token;
const alignStyle = {};
alignItemsValues.forEach((value) => {
alignStyle[`${componentCls}-align-${value}`] = { alignItems: value };
});
return alignStyle;
};
var genJustifyContentStyle = (token) => {
const { componentCls } = token;
const justifyStyle = {};
justifyContentValues.forEach((value) => {
justifyStyle[`${componentCls}-justify-${value}`] = { justifyContent: value };
});
return justifyStyle;
};
var prepareComponentToken$1 = () => ({});
var style_default$1 = genStyleHooks("Flex", (token) => {
const { paddingXS, padding, paddingLG } = token;
const flexToken = merge(token, {
flexGapSM: paddingXS,
flexGap: padding,
flexGapLG: paddingLG
});
return [
genFlexStyle(flexToken),
genFlexGapStyle(flexToken),
genFlexWrapStyle(flexToken),
genAlignItemsStyle(flexToken),
genJustifyContentStyle(flexToken)
];
}, prepareComponentToken$1, { resetStyle: false });
var Flex = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { prefixCls: customizePrefixCls, rootClassName, className, style, flex, gap, vertical, orientation, component: Component = "div", children, ...othersProps } = props;
const { flex: ctxFlex, direction: ctxDirection, getPrefixCls } = import_react.useContext(ConfigContext);
const prefixCls = getPrefixCls("flex", customizePrefixCls);
const [hashId, cssVarCls] = style_default$1(prefixCls);
const [, mergedVertical] = useOrientation(orientation, vertical ?? ctxFlex?.vertical);
const mergedCls = clsx(className, rootClassName, ctxFlex?.className, prefixCls, hashId, cssVarCls, createFlexClassNames(prefixCls, {
...props,
vertical: mergedVertical
}), {
[`${prefixCls}-rtl`]: ctxDirection === "rtl",
[`${prefixCls}-gap-${gap}`]: isPresetSize(gap),
[`${prefixCls}-vertical`]: mergedVertical
});
const mergedStyle = {
...ctxFlex?.style,
...style
};
if (isNonNullable$1(flex)) mergedStyle.flex = flex;
if (isNonNullable$1(gap) && !isPresetSize(gap)) mergedStyle.gap = gap;
return /*#__PURE__*/ import_react.createElement(Component, {
ref,
className: mergedCls,
style: mergedStyle,
...omit(othersProps, [
"justify",
"wrap",
"align"
])
}, children);
});
var GroupContext = /*#__PURE__*/ import_react.createContext(null);
var FileTextOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494zM504 618H320c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zM312 490v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H320c-4.4 0-8 3.6-8 8z" }
}]
},
"name": "file-text",
"theme": "outlined"
};
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends.apply(this, arguments);
}
var FileTextOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends({}, props, {
ref,
icon: FileTextOutlined$1
}));
/** */
var RefIcon = /*#__PURE__*/ import_react.forwardRef(FileTextOutlined);
var convertToTooltipProps = (tooltip, context) => {
if (!isReactRenderable(tooltip)) return null;
if (isPlainObject(tooltip) && !/*#__PURE__*/ (0, import_react.isValidElement)(tooltip)) return {
...context,
...tooltip
};
return {
...context,
title: tooltip
};
};
var genFloatButtonStyle = (token) => {
const { componentCls, floatButtonSize, iconCls, antCls, floatButtonIconSize } = token;
const [varName, varRef] = genCssVar(antCls, "float-btn");
const [, buttonVarRef] = genCssVar(antCls, "btn");
const getProgressBackground = (backgroundColor) => [`linear-gradient(${backgroundColor}, ${backgroundColor})`, `conic-gradient(${token.colorPrimary} ${varRef("progress", "0turn")}, ${token.colorBorderSecondary} 0)`].join(", ");
const badgeCls = `${componentCls}-badge`;
const R = Math.SQRT2;
const offsetR = (R - 1) / R;
const offsetSquare = token.calc(token.borderRadius).mul(offsetR).equal();
const offsetCircle = token.calc(token.controlHeight).div(2).mul(offsetR).equal();
return { [componentCls]: [{ [varName("size")]: unit$1(floatButtonSize) }, {
flexDirection: "column",
margin: 0,
padding: `${unit$1(token.paddingXXS)} 0`,
width: varRef("size"),
minHeight: varRef("size"),
height: "auto",
wordBreak: "break-word",
whiteSpace: "normal",
gap: token.calc(token.paddingXXS).div(2).equal(),
"&-rtl": { direction: "rtl" },
[`&${componentCls}-individual`]: {
position: "fixed",
zIndex: token.zIndexPopupBase,
insetInlineEnd: token.floatButtonInsetInlineEnd,
bottom: token.floatButtonInsetBlockEnd,
boxShadow: token.boxShadowSecondary
},
[`&${componentCls}-pure`]: {
position: "relative",
inset: "auto"
},
"&:empty": { display: "none" },
[`${componentCls}-icon`]: { lineHeight: 1 },
[`&${antCls}-btn${componentCls}-progress`]: {
borderWidth: unit$1(token.lineWidthBold),
borderColor: "transparent",
backgroundImage: getProgressBackground(buttonVarRef("bg-color")),
backgroundOrigin: "border-box",
backgroundClip: "padding-box, border-box",
[`&:not(:disabled):not(${antCls}-btn-disabled)`]: {
"&:hover": {
borderColor: "transparent",
backgroundImage: getProgressBackground(buttonVarRef("bg-color-hover"))
},
"&:active": {
borderColor: "transparent",
backgroundImage: getProgressBackground(buttonVarRef("bg-color-active"))
}
},
[`&:disabled, &${antCls}-btn-disabled`]: {
borderColor: "transparent",
backgroundImage: getProgressBackground(buttonVarRef("bg-color-disabled"))
}
},
[`&${componentCls}-icon-only`]: { [iconCls]: { fontSize: floatButtonIconSize } },
[`${componentCls}-content`]: { fontSize: token.fontSizeSM },
[badgeCls]: {
position: "absolute",
top: 0,
insetInlineEnd: 0,
[`&:not(${badgeCls}-dot)`]: { transform: "translate(50%, -50%)" }
},
[`&-rtl ${badgeCls}:not(${badgeCls}-dot)`]: { transform: "translate(-50%, -50%)" },
"&-square": { [`${badgeCls}-dot`]: {
marginTop: offsetSquare,
marginInlineEnd: offsetSquare
} },
"&-circle": { [badgeCls]: {
marginTop: offsetCircle,
marginInlineEnd: offsetCircle
} }
}] };
};
var genGroupStyle = (token) => {
const { componentCls, antCls, floatButtonSize, padding } = token;
const groupCls = `${componentCls}-group`;
const listCls = `${groupCls}-list`;
const [varName, varRef] = genCssVar(antCls, "float-btn");
const menuBridgeOffset = unit$1(token.calc(padding).mul(-1).equal());
return { [groupCls]: [
{
[varName("list-transform-start")]: `translate(0,${unit$1(floatButtonSize)})`,
[varName("list-trigger-offset")]: `calc(${unit$1(floatButtonSize)} + ${unit$1(padding)})`
},
{
...resetComponent(token),
position: "fixed",
zIndex: token.zIndexPopupBase,
insetInlineEnd: token.floatButtonInsetInlineEnd,
bottom: token.floatButtonInsetBlockEnd,
gap: padding,
"&-rtl": { direction: "rtl" },
[`&${componentCls}-pure`]: {
position: "relative",
inset: "auto"
},
[componentCls]: {
position: "relative",
inset: "auto"
}
},
{
[`&:not(${groupCls}-individual) ${listCls}`]: { boxShadow: token.boxShadowSecondary },
[`&${groupCls}-individual ${listCls}`]: { gap: padding },
[`&-menu-mode ${listCls}`]: {
position: "absolute",
"&::after": {
content: "\"\"",
position: "absolute",
inset: 0
}
},
[listCls]: {
borderRadius: token.borderRadiusLG,
"&-motion": {
transition: `all ${token.motionDurationSlow}`,
"&-enter, &-appear": {
opacity: 0,
transform: varRef("list-transform-start"),
"&-active": {
opacity: 1,
transform: `translate(0, 0)`
}
},
"&-leave": { "&-active": {
opacity: 0,
transform: varRef("list-transform-start")
} }
}
},
"&-top": { [listCls]: {
bottom: varRef("list-trigger-offset"),
"&::after": {
top: "100%",
bottom: menuBridgeOffset
}
} },
"&-bottom": { [listCls]: {
[varName("list-transform-start")]: `translate(0, calc(${unit$1(floatButtonSize)} * -1))`,
top: varRef("list-trigger-offset"),
"&::after": {
top: menuBridgeOffset,
bottom: "100%"
}
} },
"&-left": { [listCls]: {
[varName("list-transform-start")]: `translate(${unit$1(floatButtonSize)}, 0)`,
right: varRef("list-trigger-offset"),
"&::after": {
left: "100%",
right: menuBridgeOffset
}
} },
"&-right": { [listCls]: {
[varName("list-transform-start")]: `translate(calc(${unit$1(floatButtonSize)} * -1), 0)`,
left: varRef("list-trigger-offset"),
"&::after": {
left: menuBridgeOffset,
right: "100%"
}
} }
}
] };
};
var prepareComponentToken = () => ({});
var style_default = genStyleHooks("FloatButton", (token) => {
const { controlHeightLG, marginXXL, marginLG, fontSizeIcon, calc } = token;
const floatButtonToken = merge(token, {
floatButtonIconSize: calc(fontSizeIcon).mul(1.5).equal(),
floatButtonSize: controlHeightLG,
floatButtonInsetBlockEnd: marginXXL,
floatButtonInsetInlineEnd: marginLG
});
return [
genFloatButtonStyle(floatButtonToken),
genGroupStyle(floatButtonToken),
initFadeMotion(token)
];
}, prepareComponentToken, { order: -998 });
var floatButtonPrefixCls = "float-btn";
var FloatButton = /* @__PURE__ */ import_react.forwardRef((props, ref) => {
const { prefixCls: customizePrefixCls, className, rootClassName, style, type = "default", shape = "circle", icon, description, content, tooltip, badge = {}, classNames, styles, ...restProps } = props;
const { getPrefixCls, direction } = import_react.useContext(ConfigContext);
const groupContext = import_react.useContext(GroupContext);
const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const { shape: contextShape, individual: contextIndividual, classNames: contextClassNames, styles: contextStyles } = groupContext || {};
const mergedShape = contextShape || shape;
const mergedIndividual = contextIndividual ?? true;
const mergedContent = content ?? description;
const mergedProps = {
...props,
type,
shape: mergedShape
};
const [hashId, cssVarCls] = style_default(prefixCls, rootCls);
const floatButtonClassNames = import_react.useMemo(() => ({
icon: `${prefixCls}-icon`,
content: `${prefixCls}-content`
}), [prefixCls]);
const [mergedClassNames, mergedStyles] = useMergeSemantic([
floatButtonClassNames,
contextClassNames,
classNames
], [contextStyles, styles], { props: mergedProps });
const mergedIcon = !isReactRenderable(mergedContent) && !icon ? /*#__PURE__*/ import_react.createElement(RefIcon, null) : icon;
const [zIndex] = useZIndex("FloatButton", style?.zIndex);
const mergedStyle = {
...style,
zIndex
};
const badgeProps = omit(badge, [
"title",
"children",
"status",
"text"
]);
const badgeNode = "badge" in props && /*#__PURE__*/ import_react.createElement(Badge, {
...badgeProps,
className: clsx(badgeProps.className, `${prefixCls}-badge`, { [`${prefixCls}-badge-dot`]: badgeProps.dot })
});
const tooltipProps = convertToTooltipProps(tooltip);
let node = /*#__PURE__*/ import_react.createElement(Button, {
...restProps,
ref,
className: clsx(hashId, cssVarCls, rootCls, prefixCls, className, rootClassName, `${prefixCls}-${type}`, `${prefixCls}-${mergedShape}`, {
[`${prefixCls}-rtl`]: direction === "rtl",
[`${prefixCls}-individual`]: mergedIndividual,
[`${prefixCls}-icon-only`]: !isReactRenderable(mergedContent)
}),
classNames: mergedClassNames,
styles: mergedStyles,
style: mergedStyle,
shape: mergedShape,
type,
size: "large",
icon: mergedIcon,
_skipSemantic: true
}, mergedContent, badgeNode);
if (tooltipProps) node = /*#__PURE__*/ import_react.createElement(Tooltip, { ...tooltipProps }, node);
return node;
});
var getScrollProgress = (target) => {
const scrollTop = getScroll(target);
let scrollElement = null;
if (isWindow$1(target)) scrollElement = target.document.documentElement;
else if (isDocument$1(target)) scrollElement = target.documentElement;
else if (isHTMLElement$1(target)) scrollElement = target;
if (!scrollElement) return 0;
const maxScroll = Math.max(scrollElement.scrollHeight - scrollElement.clientHeight, 0);
return maxScroll > 0 ? Math.min(Math.max(scrollTop / maxScroll, 0), 1) : 0;
};
var useScroll = (options) => {
const { getTarget, showProgress, visibilityHeight } = options;
const [visible, setVisible] = import_react.useState(visibilityHeight === 0);
const [scrollProgress, setScrollProgress] = import_react.useState(0);
import_react.useEffect(() => {
const container = getTarget();
const syncScrollState = () => {
setVisible(getScroll(container) >= visibilityHeight);
if (showProgress) setScrollProgress(getScrollProgress(container));
};
const handleScroll = throttleByAnimationFrame(syncScrollState);
syncScrollState();
container?.addEventListener("scroll", handleScroll);
return () => {
handleScroll.cancel();
container?.removeEventListener("scroll", handleScroll);
};
}, [
getTarget,
showProgress,
visibilityHeight
]);
import_react.useEffect(() => {
if (!showProgress) return;
const handleResize = throttleByAnimationFrame(() => {
setScrollProgress(getScrollProgress(getTarget()));
});
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
handleResize.cancel();
};
}, [getTarget, showProgress]);
return {
scrollProgress,
visible
};
};
var defaultIcon = /*#__PURE__*/ import_react.createElement(RefIcon$1, null);
var BackTop = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { backTopIcon: contextIcon } = useComponentConfig("floatButton");
const { prefixCls: customizePrefixCls, className, style, type = "default", shape = "circle", visibilityHeight = 400, icon, target, onClick, duration = 450, showProgress = false, ...restProps } = props;
const mergedIcon = icon ?? contextIcon ?? defaultIcon;
const internalRef = import_react.useRef(null);
import_react.useImperativeHandle(ref, () => ({ nativeElement: internalRef.current }));
const getDefaultTarget = import_react.useCallback(() => {
return internalRef.current?.ownerDocument || window;
}, []);
const getTarget = target || getDefaultTarget;
const { scrollProgress, visible } = useScroll({
getTarget,
showProgress,
visibilityHeight
});
const scrollToTop = (e) => {
const prefersReducedMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)");
scrollTo(0, {
getContainer: getTarget,
duration: prefersReducedMotion?.matches ? 0 : duration
});
onClick?.(e);
};
const { getPrefixCls } = (0, import_react.useContext)(ConfigContext);
const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const [varName] = genCssVar(rootPrefixCls, "float-btn");
const contentProps = {
prefixCls,
icon: mergedIcon,
type,
shape: (0, import_react.useContext)(GroupContext)?.shape || shape,
style: showProgress ? {
[varName("progress")]: `${scrollProgress}turn`,
...style
} : style,
...restProps
};
return /*#__PURE__*/ import_react.createElement(es_default$5, {
visible,
motionName: `${rootPrefixCls}-fade`
}, ({ className: motionClassName }, setRef) => /*#__PURE__*/ import_react.createElement(FloatButton, {
ref: composeRef(internalRef, setRef),
...contentProps,
onClick: scrollToTop,
className: clsx(className, motionClassName, { [`${prefixCls}-progress`]: showProgress })
}));
});
var InternalFloatButtonGroup = (props, ref) => {
const { prefixCls: customizePrefixCls, className, style, classNames, styles, rootClassName, shape = "circle", type = "default", placement, icon = /*#__PURE__*/ import_react.createElement(RefIcon, null), closeIcon, trigger, children, onOpenChange, open: customOpen, onClick: onTriggerButtonClick, disabled: customDisabled, ...floatButtonProps } = props;
const { direction, getPrefixCls, closeIcon: contextCloseIcon, classNames: contextClassNames, styles: contextStyles, className: contextClassName, style: contextStyle } = useComponentConfig("floatButtonGroup");
const mergedCloseIcon = closeIcon ?? contextCloseIcon ?? /*#__PURE__*/ import_react.createElement(RefIcon$4, null);
const prefixCls = getPrefixCls(floatButtonPrefixCls, customizePrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = style_default(prefixCls, rootCls);
const groupPrefixCls = `${prefixCls}-group`;
const isMenuMode = trigger && ["click", "hover"].includes(trigger);
const disabled = import_react.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
const [zIndex] = useZIndex("FloatButton", style?.zIndex);
const floatButtonGroupRef = import_react.useRef(null);
import_react.useImperativeHandle(ref, () => ({ nativeElement: floatButtonGroupRef.current }));
const mergedPlacement = [
"top",
"left",
"right",
"bottom"
].includes(placement) ? placement : "top";
const [open, setOpen] = useControlledState(false, customOpen);
const hoverTrigger = trigger === "hover";
const clickTrigger = trigger === "click";
const triggerOpen = useEvent$1((nextOpen) => {
if (mergedDisabled) return;
if (open !== nextOpen) {
setOpen(nextOpen);
onOpenChange?.(nextOpen);
}
});
const onMouseEnter = () => {
if (hoverTrigger) triggerOpen(true);
};
const onMouseLeave = () => {
if (hoverTrigger) triggerOpen(false);
};
const onInternalTriggerButtonClick = (e) => {
if (clickTrigger) triggerOpen(!open);
onTriggerButtonClick?.(e);
};
import_react.useEffect(() => {
if (clickTrigger) {
const onDocClick = (e) => {
if (floatButtonGroupRef.current?.contains(e.target)) return;
triggerOpen(false);
};
document.addEventListener("click", onDocClick, { capture: true });
return () => document.removeEventListener("click", onDocClick, { capture: true });
}
}, [clickTrigger]);
const individual = shape === "circle";
const mergedProps = {
...props,
shape,
type,
placement: mergedPlacement,
disabled: mergedDisabled
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps });
const listContext = import_react.useMemo(() => ({
shape,
individual,
classNames: {
root: mergedClassNames.item,
icon: mergedClassNames.itemIcon,
content: mergedClassNames.itemContent
},
styles: {
root: mergedStyles.item,
icon: mergedStyles.itemIcon,
content: mergedStyles.itemContent
}
}), [
shape,
individual,
mergedClassNames,
mergedStyles
]);
const triggerContext = import_react.useMemo(() => ({
...listContext,
individual: true,
classNames: {
root: mergedClassNames.trigger,
icon: mergedClassNames.triggerIcon,
content: mergedClassNames.triggerContent
},
styles: {
root: mergedStyles.trigger,
icon: mergedStyles.triggerIcon,
content: mergedStyles.triggerContent
}
}), [
listContext,
mergedClassNames,
mergedStyles
]);
let listNode;
const listCls = `${groupPrefixCls}-list`;
const renderList = (motionClassName) => {
const vertical = mergedPlacement === "top" || mergedPlacement === "bottom";
const sharedProps = {
className: clsx(listCls, mergedClassNames.list, motionClassName),
style: mergedStyles.list
};
if (individual) listNode = /*#__PURE__*/ import_react.createElement(Flex, {
vertical,
...sharedProps
}, children);
else listNode = /*#__PURE__*/ import_react.createElement(Space.Compact, {
vertical,
...sharedProps
}, children);
return listNode;
};
return /*#__PURE__*/ import_react.createElement(GroupContext.Provider, { value: listContext }, /*#__PURE__*/ import_react.createElement("div", {
className: clsx(groupPrefixCls, hashId, cssVarCls, rootCls, contextClassName, mergedClassNames.root, className, rootClassName, {
[`${groupPrefixCls}-rtl`]: direction === "rtl",
[`${groupPrefixCls}-individual`]: individual,
[`${groupPrefixCls}-${mergedPlacement}`]: isMenuMode,
[`${groupPrefixCls}-menu-mode`]: isMenuMode
}),
style: {
zIndex,
...mergedStyles.root
},
ref: floatButtonGroupRef,
onMouseEnter,
onMouseLeave
}, isMenuMode ? /*#__PURE__*/ import_react.createElement(es_default$5, {
visible: open,
motionName: `${listCls}-motion`
}, ({ className: motionClassName }) => renderList(motionClassName)) : renderList(), isMenuMode && /*#__PURE__*/ import_react.createElement(GroupContext.Provider, { value: triggerContext }, /*#__PURE__*/ import_react.createElement(FloatButton, {
type,
icon: open ? mergedCloseIcon : icon,
"aria-label": props["aria-label"],
className: `${groupPrefixCls}-trigger`,
onClick: onInternalTriggerButtonClick,
disabled: mergedDisabled,
...floatButtonProps
}))));
};
var FloatButtonGroup = /*#__PURE__*/ import_react.forwardRef(InternalFloatButtonGroup);
var PureFloatButton = ({ backTop, ...props }) => backTop ? /*#__PURE__*/ import_react.createElement(BackTop, {
...props,
visibilityHeight: 0
}) : /*#__PURE__*/ import_react.createElement(FloatButton, { ...props });
/** @private Internal Component. Do not use in your production. */
var PurePanel = ({ className, items, classNames: cls, styles, prefixCls: customizePrefixCls, ...restProps }) => {
const { getPrefixCls } = import_react.useContext(ConfigContext);
const pureCls = `${getPrefixCls(floatButtonPrefixCls, customizePrefixCls)}-pure`;
if (items) return /*#__PURE__*/ import_react.createElement(FloatButtonGroup, {
className: clsx(className, pureCls),
classNames: cls,
styles,
...restProps
}, items.map((item, index) => /*#__PURE__*/ import_react.createElement(PureFloatButton, {
key: index,
...item
})));
return /*#__PURE__*/ import_react.createElement(PureFloatButton, {
className: clsx(className, pureCls),
classNames: cls,
styles,
...restProps
});
};
FloatButton.BackTop = BackTop;
FloatButton.Group = FloatButtonGroup;
FloatButton._InternalPanelDoNotUseOrYouWillBeFired = PurePanel;
var float_button_default = FloatButton;
var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min(), 1);
var import_duration = /* @__PURE__ */ __toESM(require_duration(), 1);
var $windowSize = valtioFactory(() => ({
width: window.innerWidth,
height: window.innerHeight
}), { equals: (prev, next) => prev.width === next.width && prev.height === next.height });
window.addEventListener("resize", $windowSize.updateThrottled);
window.addEventListener("orientationchange", $windowSize.updateThrottled);
document.addEventListener("scroll", throttle($windowSize.update, 1e3), { passive: true });
var floatEntryStore = proxyWithLocalStorage({
align: "right",
top: 100
}, "float-entry");
var C = {
wrapperInner: "flex flex-col items-center gap-y-8px b-1px b-gate-primary rounded-18px b-solid bg-gate-bg p-4px",
buttonIcon: "size-18px"
};
function GateFloatEntry() {
return /* @__PURE__ */ jsx(DndContext, { children: /* @__PURE__ */ jsx(GateFloatEntryInner, {}) });
}
function GateFloatEntryInner() {
const { align, top } = useSnapshot(floatEntryStore, { sync: true });
const tooltipConfig = (0, import_react.useMemo)(() => {
return {
arrow: false,
placement: align === "right" ? "left" : "right",
align: { offset: [10 * (align === "right" ? -1 : 1), 0] }
};
}, [align]);
const gateEntryHref = (0, import_react.useMemo)(getGateEntryHref, []);
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform } = useDraggable({ id: "GateFloatEntry" });
useDndMonitor({ onDragEnd(e) {
if (e.active.id === "GateFloatEntry") {
$windowSize.update();
const rect = wrapperRef.current?.getBoundingClientRect();
if (!rect) return;
const top = minmax(Math.round(rect.top), 0, window.innerHeight - rect.height);
let align = floatEntryStore.align;
if (floatEntryStore.align === "right" && rect.left < window.innerWidth / 4 || floatEntryStore.align === "left" && rect.right > window.innerWidth / 4 * 3) align = floatEntryStore.align === "right" ? "left" : "right";
appLog("new pos", {
align,
top
});
floatEntryStore.align = align;
floatEntryStore.top = top;
wrapperRef.current?.animate([{ transform: align === "right" ? `translateX(-${window.innerWidth - rect.right}px)` : `translateX(${rect.left}px)` }, { transform: "translateX(0)" }], {
duration: 200,
easing: "ease-in-out"
});
}
} });
const wrapperRef = useDelegatedRef(setNodeRef);
const wrapperClassName = r("fixed transition-250 transition-ease-in-out transition-property-right,left", clsZGateFloatEntry, align === "right" && "right--34px pr-10px hover:right--5px", align === "left" && "left--34px pl-10px hover:left--5px");
useMount($windowSize.updateThrottled);
const { height: windowHeight } = $windowSize.use();
const usingTop = (0, import_react.useMemo)(() => minmax(top, 0, windowHeight - 150), [top, windowHeight]);
return /* @__PURE__ */ jsx("div", {
ref: wrapperRef,
className: wrapperClassName,
style: {
top: `${usingTop}px`,
transform: CSS.Transform.toString(transform)
},
children: /* @__PURE__ */ jsxs("div", {
className: C.wrapperInner,
children: [
/* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [APP_NAME, ": 在新窗口打开"] }),
...tooltipConfig,
children: /* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button",
href: gateEntryHref,
target: "_blank",
children: /* @__PURE__ */ jsx(IconForOpenExternalLink, { className: C.buttonIcon })
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [APP_NAME, ": 弹窗打开"] }),
...tooltipConfig,
children: /* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button",
onClick: showModalFeed,
children: /* @__PURE__ */ jsx(ForwardRef$8, { className: C.buttonIcon })
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [APP_NAME, ": 设置"] }),
...tooltipConfig,
children: /* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button",
onClick: toggleModalSettings,
children: /* @__PURE__ */ jsx(IconForConfig, { className: C.buttonIcon })
})
}),
/* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button cursor-move",
...listeners,
...attributes,
ref: setActivatorNodeRef,
children: /* @__PURE__ */ jsx(ForwardRef$b, { className: "size-14px" })
})
]
})
});
}
function useSizeExpression(target, fn, initialValue) {
const _fn = useMemoizedFn(fn);
const box = useRefStateBox(initialValue);
(0, import_react.useEffect)(() => {
const observer = new ResizeObserver((entries) => {
for (const entry of entries) if (entry.target === target && entry.contentRect) {
const val = _fn(entry);
if (!isEqual$1(box.val, val)) box.set(val);
}
});
observer.observe(target);
return () => {
observer.disconnect();
};
}, [target, _fn]);
return box.state;
}
/**
* https://github.com/robinjonsson/react-use-sticky/blob/master/src/index.ts
*/
/**
* Returns a ref, and a stateful value bound to the ref
*/
function useSticky() {
const stickyRef = (0, import_react.useRef)(null);
const [sticky, setSticky] = (0, import_react.useState)(false);
(0, import_react.useEffect)(() => {
const observe = throttle(() => {
if (!stickyRef.current) return;
const stickyActive = Math.trunc(stickyRef.current.getBoundingClientRect().top * 10) / 10 <= Number.parseInt(getComputedStyle(stickyRef.current).top);
setSticky(stickyActive);
}, 16);
observe();
document.addEventListener("scroll", observe, { passive: true });
window.addEventListener("resize", observe);
window.addEventListener("orientationchange", observe);
return () => {
document.removeEventListener("scroll", observe);
window.removeEventListener("resize", observe);
window.removeEventListener("orientationchange", observe);
observe.cancel();
};
}, []);
return [stickyRef, sticky];
}
var iconParkOutlineArrowRight = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
d: "M42 24H6m24-12l12 12l-12 12"
})]
});
var ForwardRef$1 = (0, import_react.forwardRef)(iconParkOutlineArrowRight);
var iconParkOutlineArrowLeft = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
d: "M5.799 24h36m-24 12l-12-12l12-12"
})]
});
var ForwardRef = (0, import_react.forwardRef)(iconParkOutlineArrowLeft);
var debug = baseDebug.extend("RecHeader");
function RecHeader({ tab, leftSlot, rightSlot, shortcutEnabled, ref }) {
const { pureRecommend, showBackForwardButtons, multiSelect: { showIcon: multiSelectShowIcon }, style: { pureRecommend: { useStickyTabbar, stickyTabbarShadow, useWhiteBackground } } } = useSettingsSnapshot();
const onRefresh = useOnRefresh();
const _isRecTab = (0, import_react.useMemo)(() => isRecTab(tab), [tab]);
const recSelf = useRecSelfContext();
const { refreshing } = recSelf.useStore();
const [canGoBack, canGoForward] = recSelf.useTabBackForwardStatus(tab);
const serviceQueueState = recSelf.useTabServiceQueueState(tab);
const serviceQueueStateInfo = serviceQueueState ? /* @__PURE__ */ jsxs(Fragment, { children: [
" ",
"(当前 ",
serviceQueueState.cursor + 1,
"/",
serviceQueueState.len,
")"
] }) : void 0;
useHotkey({
shift: true,
key: "<"
}, toggleModalSettings, { eventType: "keyup" });
useHotkey("Shift+M", () => {
toggleMultiSelecting(false);
antMessage.success({
key: "multi-select-state-changed",
content: `已${multiSelectStore.multiSelecting ? "进入" : "退出"}「多选」`
});
}, { enabled: multiSelectShowIcon });
const [stickyRef, sticky] = useSticky();
const scrollToTop = useMemoizedFn(() => {
if (!pureRecommend) return;
const container = stickyRef.current?.parentElement;
if (!container) return;
const rect = container.getBoundingClientRect();
const headerHeight = $headerHeight.get();
if (rect.top < headerHeight) {
const yOffset = getElementOffset(container).top;
debug("[refresh:scroll] rect.top = %s, headerHeight = %s", rect.top, headerHeight);
document.documentElement.scrollTop = yOffset - headerHeight + 2;
}
});
(0, import_react.useImperativeHandle)(ref, () => ({ scrollToTop }));
const headerHeight = $headerHeight.use();
const showAccessKeyManage = useShouldShowAccessKeyManage(tab);
const usingEvolevdHeader = $usingEvolevdHeader.use();
const dark = useIsDarkMode();
const expandToFullWidthCss = useExpandToFullWidthCss();
const boxShadowCss = (() => {
let val;
if (usingEvolevdHeader) val = dark ? "rgba(0, 0, 0, 26%) 0px 2px 10px 1px" : "rgba(0, 0, 0, 13%) 0 1px 10px 1px;";
else val = `0 2px 4px ${dark ? "rgb(255 255 255 / 5%)" : "rgb(0 0 0 / 8%)"}`;
return /* @__PURE__ */ css("box-shadow:", val, ";");
})();
const compactTagRecSpacing = tab === ETab.TagRecCustom;
const wrapperClassName = r(pureRecommend && useStickyTabbar && [
"sticky b-b-1px b-b-transparent b-b-solid",
compactTagRecSpacing ? "mb-6px" : "mb-10px",
"z-1001",
sticky && ["bg-gate-bg", "b-b-gate-bg-lv1"]
], sticky && "sticky-state-on");
const wrapperCss = (0, import_react.useMemo)(() => {
if (!(pureRecommend && useStickyTabbar)) return;
const arr = [/* @__PURE__ */ css("top:", headerHeight - 1, "px;")];
if (stickyTabbarShadow && sticky) arr.push(boxShadowCss, expandToFullWidthCss);
return arr;
}, [
pureRecommend,
useStickyTabbar,
stickyTabbarShadow,
sticky,
headerHeight,
boxShadowCss,
expandToFullWidthCss
]);
return /* @__PURE__ */ jsx("div", {
ref: stickyRef,
"data-role": "tab-bar-wrapper",
className: wrapperClassName,
css: wrapperCss,
children: /* @__PURE__ */ jsxs("div", {
"data-role": "tab-bar",
className: clsx(APP_CLS_TAB_BAR, "relative mb-0 h-auto flex flex-row items-center justify-between gap-x-15px px-0", compactTagRecSpacing ? "py-4px" : "py-8px"),
children: [/* @__PURE__ */ jsxs("div", {
"data-class-name": "left",
className: "h-full flex flex-wrap items-center gap-x-15px gap-y-8px",
children: [/* @__PURE__ */ jsx(VideoSourceTab, { className: "flex-none" }), leftSlot]
}), /* @__PURE__ */ jsxs("div", {
"data-class-name": "right",
className: "h-full min-w-180px flex flex-row-reverse flex-wrap items-center justify-right gap-x-8px gap-y-8px",
children: [
/* @__PURE__ */ jsx(RefreshButton, {
tab,
refreshHotkeyEnabled: shortcutEnabled
}),
showBackForwardButtons && _isRecTab && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["前进", serviceQueueStateInfo] }),
children: /* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button",
disabled: refreshing || !canGoForward,
onClick: () => onRefresh("forward"),
children: /* @__PURE__ */ jsx(ForwardRef$1, {})
})
}), /* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["后退", serviceQueueStateInfo] }),
children: /* @__PURE__ */ jsx(button_default, {
className: "icon-only-round-button",
disabled: refreshing || !canGoBack,
onClick: () => onRefresh("back"),
children: /* @__PURE__ */ jsx(ForwardRef, {})
})
})] }),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "设置",
children: /* @__PURE__ */ jsxs(button_default, {
onClick: showModalSettings,
className: "icon-only-round-button",
children: [/* @__PURE__ */ jsx(ModalSettingsHotkey, {}), /* @__PURE__ */ jsx(IconForConfig, { className: "size-14px" })]
})
}),
multiSelectShowIcon && /* @__PURE__ */ jsx(MultiSelectButton, {
iconOnly: true,
addCopyActions: true
}),
showAccessKeyManage && /* @__PURE__ */ jsx(AccessKeyManage, { style: { marginLeft: 5 } }),
rightSlot
]
})]
})
});
}
/**
* 使用如 margin-inline: -10px; padding-inline: 10px; 来扩展到全屏宽度
*/
function useExpandToFullWidthCss() {
const { xScrolling, bodyWidth } = useSizeExpression(document.body, (entry) => {
const width = entry.contentRect.width;
const xScrolling = !!(width && Math.round(width) > Math.round(window.innerWidth));
if (!xScrolling) return { xScrolling };
else return {
xScrolling,
bodyWidth: width
};
}, () => ({ xScrolling: false }));
return (0, import_react.useMemo)(() => {
if (!xScrolling) {
const scrollbarWidth = isMac || isSafari ? "0px" : "20px";
return /* @__PURE__ */ css("margin-inline:calc((100% - 100vw + ", scrollbarWidth, ") / 2);padding-inline:calc((100vw - ", scrollbarWidth, " - 100%) / 2);");
} else {
const w = Math.floor(bodyWidth);
return /* @__PURE__ */ css("margin-inline:calc((100% - ", w, "px) / 2);padding-inline:calc((", w, "px - 100%) / 2);");
}
}, [xScrolling, bodyWidth]);
}
function useShouldShowAccessKeyManage(tab) {
const { accessKey } = useSettingsSnapshot();
return !accessKey && [ETab.AppRecommend, ETab.Liked].includes(tab);
}
function PureRecommend() {
const { grid: { twoColumnModeAlign }, sidebarAlign } = useSettingsSnapshot();
const { usingTwoColumnMode } = useGridDisplayModeChecker();
const shortcutEnabled = usePlainShortcutEnabled();
const recSelf = useInitRecSelf();
const { tabbarView, sidebarView } = recSelf.useStore();
const { tab, direction } = useDeferredTab();
const recHeaderRef = (0, import_react.useRef)(null);
const onScrollToTop = useMemoizedFn(() => recHeaderRef.current?.scrollToTop());
const headerHeight = $headerHeight.use();
const sidebarCss = (0, import_react.useMemo)(() => {
return /* @__PURE__ */ css("position:sticky;top:", headerHeight + 55, "px;max-height:calc(100vh - ", headerHeight + 55 + 20, "px);");
}, [headerHeight]);
let clsFlexContainer;
let clsRecGridContainer;
let clsRecGrid;
clsFlexContainer = clsx("flex gap-x-25px", sidebarAlign === ESidebarAlign.Right && "flex-row-reverse");
clsRecGridContainer = "flex-1";
clsRecGrid = clsx(usingTwoColumnMode && ["w-720px", clsForTwoColumnModeAlign(twoColumnModeAlign, "margin")]);
return /* @__PURE__ */ jsxs(RecSelfContext.Provider, {
value: recSelf,
children: [/* @__PURE__ */ jsx(RecHeader, {
ref: recHeaderRef,
tab,
leftSlot: tab === ETab.TagRecCustom ? void 0 : tabbarView,
shortcutEnabled
}), tab === ETab.TagRecCustom ? /* @__PURE__ */ jsx(TagRecDiscoveryLayout, {
controls: tabbarView,
stickyTop: headerHeight + 45,
children: /* @__PURE__ */ jsx(RecGrid, {
tab,
direction,
shortcutEnabled,
infiniteScrollUseWindow: true,
onScrollToTop
}, tab)
}) : /* @__PURE__ */ jsxs("div", {
className: clsFlexContainer,
children: [/* @__PURE__ */ jsx(RecSidebar, {
css: sidebarCss,
tab,
sidebarView
}), /* @__PURE__ */ jsx(RecGrid, {
tab,
direction,
containerClassName: clsRecGridContainer,
className: clsRecGrid,
shortcutEnabled,
infiniteScrollUseWindow: true,
onScrollToTop
}, tab)]
})]
});
}
function initDefaultHomepageLargePreview() {
setupLargePreview({
itemsSelector: ".bili-feed4 .feed-card:has(.bili-video-card__wrap > a),.bili-feed4 .bili-feed-card:has(.bili-video-card__wrap > a)",
observerTarget: document.querySelector(".recommended-container_floor-aside")
});
}
var bewlyEnabledSelector = "html.bewly-design:not(:has(#i_cecream,#app))";
function hasBewlyBewly() {
return !!document.querySelector(bewlyEnabledSelector);
}
function tryDetectBewlyBewly() {
return tryAction(bewlyEnabledSelector, () => {
appWarn(`unmount for using bewly-design`);
root?.unmount();
}, {
pollTimeout: 5e3,
warnOnTimeout: false
});
}
var root;
async function initHomepage() {
tryToRemove(".adblock-tips");
tryToRemove(".vip-login-tip");
tryAction("html.gray", (el) => el.classList.remove("gray"));
registerSettingsGmCommand();
if (hasBewlyBewly()) return appWarn(`quit for using bewly-design`);
initMyRelations();
if (settings.pureRecommend || inGateEntry() || getOnlyTab() || getEntryTab()) {
await initHomepagePureRecommend();
tryDetectBewlyBewly();
} else {
initHomepageGateFloatEntry();
if (settings.videoCard.videoPreview.addTo.defaultHomepage) initDefaultHomepageLargePreview();
}
}
function initHomepageGateFloatEntry() {
const container = document.createElement("section");
container.classList.add(APP_CLS_ROOT);
document.body.append(container);
root = createReactRoot(container);
root.render(/* @__PURE__ */ jsxs(AppRoot, { children: [/* @__PURE__ */ jsx(SetupForPage, {
antd: true,
baseGlobalStyle: true
}), /* @__PURE__ */ jsx(GateFloatEntry, {})] }));
}
function prewarmCurrentTabService() {
try {
prewarmTabService(getCurrentUsingTab());
} catch {}
}
async function initHomepagePureRecommend() {
if (isSafari) await delay$1(500);
prewarmCurrentTabService();
tryToRemove("#i_cecream .bili-feed4-layout, body > #app .bili-feed4-layout");
tryToRemove(".bili-feed4 .header-channel");
tryToRemove(".palette-button-wrap");
const biliLayout = document.createElement("div");
biliLayout.classList.add("bili-feed4-layout", "pure-recommend", APP_CLS_ROOT);
await poll$1(() => document.querySelector(".bili-feed4 .bili-header"), {
interval: 20,
timeout: 2e3
});
document.body.append(biliLayout);
const container = document.createElement("div");
biliLayout.append(container);
perfMark("premount");
root = createReactRoot(container);
root.render(/* @__PURE__ */ jsxs(AppRoot, { children: [
/* @__PURE__ */ jsx(SetupForPage, {
antd: true,
baseGlobalStyle: true,
homePageGlobalStyle: true
}),
/* @__PURE__ */ jsx(PureRecommend, {}),
/* @__PURE__ */ jsx(float_button_default.BackTop, { style: { insetInlineEnd: "var(--back-top-right, 24px)" } })
] }));
requestAnimationFrame(() => {
perfMark("mounted");
perfMark("first-frame");
});
}
import_dayjs_min.default.extend(import_duration.default);
(function main() {
if (location.hostname === "www.tagrec.cn") {
document.documentElement.dataset.tagrecInstalled = "1";
window.dispatchEvent(new CustomEvent("tagrec-installed"));
return;
}
perfMark("main-enter");
if (IN_BILIBILI_HOMEPAGE) return initHomepage();
if (IN_BILIBILI_VIDEO_PLAY_PAGE) return module.import('./video-play-page-JZeYpXRO-CUekkLRQ.js').then((m) => m.initVideoPlayPage());
if (IN_BILIBILI_SPACE_PAGE) return module.import('./space-page-dGOxOoCW-DvcBeGod.js').then((m) => m.initSpacePage());
if (IN_BILIBILI_SEARCH_PAGE) return module.import('./search-page-Cw6IXwjk-DBfiWMoB.js').then((m) => m.initSearchPage());
})();})}}));
System.register("./ModalSettings-ColynSbg-T0qgWNTi.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./___monkey.entry.js','./routes-B0HXSp2g-8gD5jzg6.js','./discovery-policy-BBjOB0CZ-BWQIVxg7.js','./share-tokens-DwS1MNAq-C8u87gZL.js','./share-import-DgpdTju8-Dey7mX2T.js','./share-import-modal-CeNu8LoU-N2aE6hH8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var APP_NAME,BiliBrandColor,__toESM,require_react,pmap,Tabs,AccessKeyManage,TabIcon,borderCycleListLabels,usePrevious,useSortedTabKeys,favoritesL1,favorites,tagRecSharedSession,Typography,SEEN_CAP_MAX,clampSeenCap,useSensors,useSensor,arrayMove,DndContext,closestCenter,SortableContext,verticalListSortingStrategy,CONFIGURABLE_TAB_KEYS,viewScope,favoritesStore,favoritesL1Store,themesStore,viewScopeStore,tagRecViewScopeLegacyActiveTheme,videoSourceTabState,themes,seen,sediment,blacklist,blacklistStore,tagRecCombinationBlocks,PointerSensor,TabConfig,useSortable,CSS,ForwardRef$8,buildTagRecFavDeepLink,buildTagRecCustomDeepLink,TAG_REC_POOL_KEYS,TAG_REC_POOL_LABELS,Segmented,tagRecFavSession,TAG_REC_DROP_DIM_LABELS,handleManualRefreshRelationsCache,useSnapshot,jsxs,BaseModalClassNames,jsx,IconForConfig,ModalClose,BaseModal,css,useSettingsSnapshot,useMount,useMemoizedFn,useHover_default,Fragment,ETab,HelpInfo,IconForHelp,PrimaryBgContentDivider,CheckboxSettingItem,explainForFlag,AntdTooltip,UseCustomGridSwitcher,GridTemplateColumnsConfig,GridRowGapSwitcher,GridDisplayModeSwitcher,clsx,button_default,Tag,IconForCopy,EVideoLinkOpenMode,VideoLinkOpenModeConfig,Select,updateSettings,SwitchSettingItem,HotkeyDisplay,settings,Slider,Divider,Space,useCurrentTheme,ThemeGroups,IconAnimatedChecked,EXTRA_TOOLTIP,WatchlaterContinuePlaySettings,FollowGroupMechanismNote,collapse_default,appRecShowContentFromOtherTabEl,appRecAnonymousFetchEl,pcRecAnonymousFetchEl,useRecSelfContext,computeTagRecFilterSignature,Input,ForwardRef$16,Checkbox,Modal,TypedInputNumber,useRequest_default,Popconfirm,IconForOpenExternalLink,IconForCloudStorage,IconForReload,internalBooleanPaths,buttonOpenCss,r,Radio,ESidebarAlign,ForwardRef$20,ForwardRef$18,antMessage,isNil,IconForInfo,IconForDelete,loadSettingsFromGmStorage,get$2,pickSettings,toast,isEqual$1,difference,uniq,useComponentConfig,DisabledContext,useMergedArrow,useCompactItemContext,useSize,useSemanticRootStyle,useMergeSemantic,useControlledState,getColorAlpha,FormItemInputContext,getStatusClassNames,Popover,ContextIsolator,updateSettingsInnerArray,dfStore,Empty,hasTagRecCombination,ForwardRef$33,hideModalSettings,antModal,ForwardRef$7$1,parseTagRecConfig,mergeTagRecConfigExports,planTagRecImport,serializeTagRecConfig,stringifyTagRecConfig,normalizeTagRecFilterSettings,proxy,cloneDeep,resetSettings,toastAndReload,allowedLeafSettingsPaths,articleDraft,runSettingsMigration,restoreOmitPaths,TOOLTIP_BLACK_BG_COLOR,parseUpRepresent,getUserNickname,useSettingsInnerArray,getNewestValueOfSettingsInnerArray,useLocale,useEvent,generateColor,genStyleHooks,merge,useCSSVarCls,isFunction,genAlphaColor,AggregationColor,ColorBlock,pickAttrs,DF_SELECTED_KEY_PREFIX_GROUP,IconForUp,IconForGroup,formatSpaceUrl,formatFollowGroupUrl,groupNegativeTagIds,Dropdown,nameTagRecCombination,Switch,Badge,hasTagRecGroupConfig,groupState,TagGroupStateLabel,theme,applyTagRecScopeEntriesToGroupOverrides,resolveTagRecEffective,stripTagRecGroupSlot,applyTagRecScopeEntriesToL1Overrides,require_dayjs_min,ForwardRef$35,unit$1,genCompactItemStyle,ColorPresets,useLayoutEffect$2,useForceUpdate,es_default$2,normalizeTagName,resolveTagRecEffectiveProvenance,TAG_REC_PATCHABLE_DIM_KEYS,tagRecL1ChainGroup,fetchTagInfoById,fetchTagInfoByName,upsertTagRecPreset,cloneTagRecFilterCore,ae,findTagRecCondition,removeTagRecCondition,applyTagRecPreset,restoreTagRecBuiltinPreset,removeTagRecPreset,renameTagRecPreset,require_ms,getGradientPercentColor,SliderInternalContext,UnstableContext,_toConsumableArray,toHexFormat,Input$1,getRoundNumber,TAG_REC_CONDITION_FIELDS,tagRecConditionAtom,tagRecConditionAtoms,PARTITION_NAME_TO_PARENT,excludeTagRecCondition,addTagRecCondition,genPurePanel,set_HAS_RESTORED_SETTINGS,subscribeKey,delay$1,$evolvedThemeColor,modalSettingsStore,TabPaneKey,sharedClassNames,SettingsGroup,ResetPartialSettingsButton,TagRecFilterForm,unitsFingerprint,withTagRecTempEnables,withTagRecGroupScope,CandidateResolvePicker,table_default,clearTagRecSeenRecords,clearTagRecSedimentRecords,Alert,tagRecDiscoveryPolicy,TagRecCandidatePickList,pickTagRecScopePatch,TagRecScopeConfigEditor,DimsSection,AdvancedSection,resolveCandidateRows,TagRecPoolSlotsBlock,TAG_REC_SHARE_SITE_BASE,extractShareImportFromText,expandShareSelection,buildMyShareRows,readShareTokens,normalizeShareTokenEntries,getShareDetail,parseSharePkgForEdit,createShareFactoryFilterConfig,buildSharePageUrl,buildShareEditUrl,SHARE_GUARD_LIMITS,fetchShareUploader,formatShareUpdatedAgo,describeShareField,describeShareCategory,probeOutcomeToRemote,deleteShare,buildSharePatchBody,patchShare,rememberShareToken,hasSharePkgGroupContent,buildSharePkgPayload,buildSharePayload,preflightShareIssues,checkDupShare,collectSharePositiveTagIds,forgetShareToken,uploadShare,requestShareImportPkg,requestShareImport,TagRecShareImportModal,applyTagRecImport,TagRecShareGroupContents,TagRecConditionTreeView;return{setters:[function(module){APP_NAME=module.A;BiliBrandColor=module.B;__toESM=module._;require_react=module.r;pmap=module.p;},function(module){Tabs=module.T;AccessKeyManage=module.A;TabIcon=module.a;borderCycleListLabels=module.b;usePrevious=module.u;useSortedTabKeys=module.c;favoritesL1=module.f;favorites=module.d;tagRecSharedSession=module.t;Typography=module.e;SEEN_CAP_MAX=module.S;clampSeenCap=module.g;useSensors=module.h;useSensor=module.i;arrayMove=module.j;DndContext=module.D;closestCenter=module.k;SortableContext=module.l;verticalListSortingStrategy=module.v;CONFIGURABLE_TAB_KEYS=module.C;viewScope=module.m;favoritesStore=module.n;favoritesL1Store=module.o;themesStore=module.p;viewScopeStore=module.q;tagRecViewScopeLegacyActiveTheme=module.r;videoSourceTabState=module.s;themes=module.w;seen=module.x;sediment=module.y;blacklist=module.z;blacklistStore=module.B;tagRecCombinationBlocks=module.E;PointerSensor=module.P;TabConfig=module.F;useSortable=module.G;CSS=module.H;ForwardRef$8=module.I;buildTagRecFavDeepLink=module.J;buildTagRecCustomDeepLink=module.K;TAG_REC_POOL_KEYS=module.L;TAG_REC_POOL_LABELS=module.M;Segmented=module.N;tagRecFavSession=module.O;TAG_REC_DROP_DIM_LABELS=module.Q;handleManualRefreshRelationsCache=module.R;},function(module){useSnapshot=module.u;jsxs=module.j;BaseModalClassNames=module.B;jsx=module.a;IconForConfig=module.I;ModalClose=module.M;BaseModal=module.b;css=module.c;useSettingsSnapshot=module.d;useMount=module.e;useMemoizedFn=module.f;useHover_default=module.g;Fragment=module.F;ETab=module.E;HelpInfo=module.H;IconForHelp=module.h;PrimaryBgContentDivider=module.P;CheckboxSettingItem=module.C;explainForFlag=module.i;AntdTooltip=module.A;UseCustomGridSwitcher=module.U;GridTemplateColumnsConfig=module.G;GridRowGapSwitcher=module.k;GridDisplayModeSwitcher=module.l;clsx=module.m;button_default=module.n;Tag=module.T;IconForCopy=module.o;EVideoLinkOpenMode=module.p;VideoLinkOpenModeConfig=module.V;Select=module.S;updateSettings=module.q;SwitchSettingItem=module.r;HotkeyDisplay=module.s;settings=module.t;Slider=module.v;Divider=module.D;Space=module.w;useCurrentTheme=module.x;ThemeGroups=module.y;IconAnimatedChecked=module.z;EXTRA_TOOLTIP=module.J;WatchlaterContinuePlaySettings=module.W;FollowGroupMechanismNote=module.K;collapse_default=module.L;appRecShowContentFromOtherTabEl=module.N;appRecAnonymousFetchEl=module.O;pcRecAnonymousFetchEl=module.Q;useRecSelfContext=module.R;computeTagRecFilterSignature=module.X;Input=module.Y;ForwardRef$16=module.Z;Checkbox=module._;Modal=module.$;TypedInputNumber=module.a0;useRequest_default=module.a1;Popconfirm=module.a2;IconForOpenExternalLink=module.a3;IconForCloudStorage=module.a4;IconForReload=module.a5;internalBooleanPaths=module.a6;buttonOpenCss=module.a7;r=module.a8;Radio=module.a9;ESidebarAlign=module.aa;ForwardRef$20=module.ab;ForwardRef$18=module.ac;antMessage=module.ad;isNil=module.ae;IconForInfo=module.af;IconForDelete=module.ag;loadSettingsFromGmStorage=module.ah;get$2=module.ai;pickSettings=module.aj;toast=module.ak;isEqual$1=module.al;difference=module.am;uniq=module.an;useComponentConfig=module.ao;DisabledContext=module.ap;useMergedArrow=module.aq;useCompactItemContext=module.ar;useSize=module.as;useSemanticRootStyle=module.at;useMergeSemantic=module.au;useControlledState=module.av;getColorAlpha=module.aw;FormItemInputContext=module.ax;getStatusClassNames=module.ay;Popover=module.az;ContextIsolator=module.aA;updateSettingsInnerArray=module.aB;dfStore=module.aC;Empty=module.aD;hasTagRecCombination=module.aE;ForwardRef$33=module.aF;hideModalSettings=module.aG;antModal=module.aH;ForwardRef$7$1=module.aI;parseTagRecConfig=module.aJ;mergeTagRecConfigExports=module.aK;planTagRecImport=module.aL;serializeTagRecConfig=module.aM;stringifyTagRecConfig=module.aN;normalizeTagRecFilterSettings=module.aO;proxy=module.aP;cloneDeep=module.aQ;resetSettings=module.aR;toastAndReload=module.aS;allowedLeafSettingsPaths=module.aT;articleDraft=module.aU;runSettingsMigration=module.aV;restoreOmitPaths=module.aW;TOOLTIP_BLACK_BG_COLOR=module.aX;parseUpRepresent=module.aY;getUserNickname=module.aZ;useSettingsInnerArray=module.a_;getNewestValueOfSettingsInnerArray=module.a$;useLocale=module.b0;useEvent=module.b1;generateColor=module.b2;genStyleHooks=module.b3;merge=module.b4;useCSSVarCls=module.b5;isFunction=module.b6;genAlphaColor=module.b7;AggregationColor=module.b8;ColorBlock=module.b9;pickAttrs=module.ba;DF_SELECTED_KEY_PREFIX_GROUP=module.bb;IconForUp=module.bc;IconForGroup=module.bd;formatSpaceUrl=module.be;formatFollowGroupUrl=module.bf;groupNegativeTagIds=module.bg;Dropdown=module.bh;nameTagRecCombination=module.bi;Switch=module.bj;Badge=module.bk;hasTagRecGroupConfig=module.bl;groupState=module.bm;TagGroupStateLabel=module.bn;theme=module.bo;applyTagRecScopeEntriesToGroupOverrides=module.bp;resolveTagRecEffective=module.bq;stripTagRecGroupSlot=module.br;applyTagRecScopeEntriesToL1Overrides=module.bs;require_dayjs_min=module.bt;ForwardRef$35=module.bu;unit$1=module.bv;genCompactItemStyle=module.bw;ColorPresets=module.bx;useLayoutEffect$2=module.by;useForceUpdate=module.bz;es_default$2=module.bA;normalizeTagName=module.bB;resolveTagRecEffectiveProvenance=module.bC;TAG_REC_PATCHABLE_DIM_KEYS=module.bD;tagRecL1ChainGroup=module.bE;fetchTagInfoById=module.bF;fetchTagInfoByName=module.bG;upsertTagRecPreset=module.bH;cloneTagRecFilterCore=module.bI;ae=module.bJ;findTagRecCondition=module.bK;removeTagRecCondition=module.bL;applyTagRecPreset=module.bM;restoreTagRecBuiltinPreset=module.bN;removeTagRecPreset=module.bO;renameTagRecPreset=module.bP;require_ms=module.bQ;getGradientPercentColor=module.bR;SliderInternalContext=module.bS;UnstableContext=module.bT;_toConsumableArray=module.bU;toHexFormat=module.bV;Input$1=module.bW;getRoundNumber=module.bX;TAG_REC_CONDITION_FIELDS=module.bY;tagRecConditionAtom=module.bZ;tagRecConditionAtoms=module.b_;PARTITION_NAME_TO_PARENT=module.b$;excludeTagRecCondition=module.c0;addTagRecCondition=module.c1;genPurePanel=module.c2;set_HAS_RESTORED_SETTINGS=module.c3;subscribeKey=module.c4;delay$1=module.c5;$evolvedThemeColor=module.c6;},function(module){modalSettingsStore=module.m;TabPaneKey=module.T;sharedClassNames=module.s;SettingsGroup=module.S;ResetPartialSettingsButton=module.R;TagRecFilterForm=module.a;unitsFingerprint=module.u;withTagRecTempEnables=module.w;withTagRecGroupScope=module.b;CandidateResolvePicker=module.C;table_default=module.t;clearTagRecSeenRecords=module.c;clearTagRecSedimentRecords=module.d;Alert=module.A;tagRecDiscoveryPolicy=module.e;TagRecCandidatePickList=module.f;pickTagRecScopePatch=module.p;TagRecScopeConfigEditor=module.g;DimsSection=module.D;AdvancedSection=module.h;resolveCandidateRows=module.r;TagRecPoolSlotsBlock=module.i;},function(module){TAG_REC_SHARE_SITE_BASE=module.T;extractShareImportFromText=module.e;expandShareSelection=module.a;buildMyShareRows=module.b;readShareTokens=module.r;normalizeShareTokenEntries=module.n;getShareDetail=module.g;parseSharePkgForEdit=module.p;createShareFactoryFilterConfig=module.c;buildSharePageUrl=module.d;buildShareEditUrl=module.f;SHARE_GUARD_LIMITS=module.S;fetchShareUploader=module.h;formatShareUpdatedAgo=module.i;describeShareField=module.j;describeShareCategory=module.k;probeOutcomeToRemote=module.l;deleteShare=module.m;buildSharePatchBody=module.o;patchShare=module.q;rememberShareToken=module.s;hasSharePkgGroupContent=module.t;buildSharePkgPayload=module.u;buildSharePayload=module.v;preflightShareIssues=module.w;checkDupShare=module.x;collectSharePositiveTagIds=module.y;forgetShareToken=module.z;uploadShare=module.A;},function(module){requestShareImportPkg=module.r;requestShareImport=module.a;},function(module){TagRecShareImportModal=module.T;applyTagRecImport=module.a;TagRecShareGroupContents=module.b;TagRecConditionTreeView=module.c;},null,null,null,null,null],execute:(function(){exports("ModalSettings",ModalSettings);/**
* Returns the input value unchanged.
*
* @template T - The type of the input value.
* @param x - The value to be returned.
* @returns The input value.
*
* @example
* // Returns 5
* identity(5);
*
* @example
* // Returns 'hello'
* identity('hello');
*
* @example
* // Returns { key: 'value' }
* identity({ key: 'value' });
*/
function identity(x) {
return x;
}
/**
* Regular expression pattern to split strings into words for various case conversions
*
* This pattern matches sequences of characters in a string, considering the following cases:
* - Sequences of two or more uppercase letters followed by an uppercase letter and lowercase letters or digits (for acronyms)
* - Sequences of one uppercase letter optionally followed by lowercase letters and digits
* - Single uppercase letters
* - Sequences of digits
* - Emojis and other Unicode characters
*
* The resulting match can be used to convert camelCase, snake_case, kebab-case, and other mixed formats into
* a consistent format like snake case. It also supports emojis and other Unicode characters.
*
* @example
* const matches = 'camelCaseHTTPRequest🚀'.match(CASE_SPLIT_PATTERN);
* // matches: ['camel', 'Case', 'HTTP', 'Request', '🚀']
*/
var CASE_SPLIT_PATTERN = /\p{Lu}?\p{Ll}+|[0-9]+|\p{Lu}+(?!\p{Ll})|\p{Emoji_Presentation}|\p{Extended_Pictographic}|\p{L}+/gu;
/**
* Splits `string` into an array of its words, treating spaces and punctuation marks as separators.
*
* @param str The string to inspect.
* @param [pattern] The pattern to match words.
* @returns Returns the words of `string`.
*
* @example
* words('fred, barney, & pebbles');
* // => ['fred', 'barney', 'pebbles']
*
* words('camelCaseHTTPRequest🚀');
* // => ['camel', 'Case', 'HTTP', 'Request', '🚀']
*
* words('Lunedì 18 Set')
* // => ['Lunedì', '18', 'Set']
*/
function words(str) {
return Array.from(str.match(CASE_SPLIT_PATTERN) ?? []);
}
/**
* Converts the first character of each word in a string to uppercase and the remaining characters to lowercase.
*
* Start case is the naming convention in which each word is written with an initial capital letter.
* @param str - The string to convert.
* @returns The converted string.
*
* @example
* const result1 = startCase('hello world'); // result will be 'Hello World'
* const result2 = startCase('HELLO WORLD'); // result will be 'Hello World'
* const result3 = startCase('hello-world'); // result will be 'Hello World'
* const result4 = startCase('hello_world'); // result will be 'Hello World'
*/
function startCase(str) {
const words$1 = words(str.trim());
let result = "";
for (let i = 0; i < words$1.length; i++) {
const word = words$1[i];
if (result) result += " ";
result += word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return result;
}
/**
* Removes trailing whitespace or specified characters from a string.
*
* If `chars` is a string, it should be a single character. To trim a string with multiple characters,
* provide an array instead.
*
* @param str - The string from which trailing characters will be trimmed.
* @param chars - The character(s) to remove from the end of the string.
* @returns The resulting string after the specified trailing character has been removed.
*
* @example
* const trimmedStr1 = trimEnd('hello---', '-') // returns 'hello'
* const trimmedStr2 = trimEnd('123000', '0') // returns '123'
* const trimmedStr3 = trimEnd('abcabcabc', 'c') // returns 'abcabcab'
* const trimmedStr4 = trimEnd('trimmedxxx', 'x') // returns 'trimmed'
*/
function trimEnd(str, chars) {
if (chars === void 0) return str.trimEnd();
let endIndex = str.length;
switch (typeof chars) {
case "string":
if (chars.length !== 1) throw new Error(`The 'chars' parameter should be a single character string.`);
while (endIndex > 0 && str[endIndex - 1] === chars) endIndex--;
break;
case "object": while (endIndex > 0 && chars.includes(str[endIndex - 1])) endIndex--;
}
return str.substring(0, endIndex);
}
/**
* Removes leading whitespace or specified characters from a string.
*
* If `chars` is a string, it should be a single character. To trim a string with multiple characters,
* provide an array instead.
*
* @param str - The string from which leading characters will be trimmed.
* @param chars - The character(s) to remove from the start of the string.
* @returns The resulting string after the specified leading character has been removed.
*
* @example
* const trimmedStr1 = trimStart('---hello', '-') // returns 'hello'
* const trimmedStr2 = trimStart('000123', '0') // returns '123'
* const trimmedStr3 = trimStart('abcabcabc', 'a') // returns 'bcabcabc'
* const trimmedStr4 = trimStart('xxxtrimmed', 'x') // returns 'trimmed'
*/
function trimStart(str, chars) {
if (chars === void 0) return str.trimStart();
let startIndex = 0;
switch (typeof chars) {
case "string":
if (chars.length !== 1) throw new Error(`The 'chars' parameter should be a single character string.`);
while (startIndex < str.length && str[startIndex] === chars) startIndex++;
break;
case "object": while (startIndex < str.length && chars.includes(str[startIndex])) startIndex++;
}
return str.substring(startIndex);
}
/**
* Removes leading and trailing whitespace or specified characters from a string.
*
* @param str - The string from which characters will be trimmed.
* @param chars - The character(s) to remove from the string. Can be a single character or an array of characters.
* @returns The resulting string after the specified characters have been removed.
*
* @example
* trim(" hello "); // "hello"
* trim("--hello--", "-"); // "hello"
* trim("##hello##", ["#", "o"]); // "hell"
*/
function trim(str, chars) {
if (chars === void 0) return str.trim();
return trimStart(trimEnd(str, chars), chars);
}
var import_react = /* @__PURE__ */ __toESM(require_react());
var PanelPickerContext = /*#__PURE__*/ import_react.createContext({});
var PanelPresetsContext = /*#__PURE__*/ import_react.createContext({});
var ColorClear = ({ prefixCls, value, onChange, className, style, disabled }) => {
const onClick = () => {
if (disabled) return;
if (onChange && value && !value.cleared) {
const hsba = value.toHsb();
hsba.a = 0;
const genColor = generateColor(hsba);
genColor.cleared = true;
onChange(genColor);
}
};
const onKeyDown = (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
onClick();
}
};
return /*#__PURE__*/ import_react.createElement("div", {
role: "button",
"aria-label": "Clear color",
"aria-disabled": disabled || void 0,
tabIndex: disabled ? -1 : 0,
className: clsx(`${prefixCls}-clear`, className, { [`${prefixCls}-clear-disabled`]: disabled }),
style,
onClick,
onKeyDown
});
};
var ColorSteppers = ({ prefixCls, min = 0, max = 100, value, onChange, className, formatter }) => {
const colorSteppersPrefixCls = `${prefixCls}-steppers`;
const [internalValue, setInternalValue] = (0, import_react.useState)(0);
const stepValue = !Number.isNaN(value) ? value : internalValue;
return /*#__PURE__*/ import_react.createElement(TypedInputNumber, {
className: clsx(colorSteppersPrefixCls, className),
min,
max,
value: stepValue,
formatter,
size: "small",
onChange: (step) => {
setInternalValue(step || 0);
onChange?.(step);
}
});
};
var ColorAlphaInput = ({ prefixCls, value, onChange }) => {
const colorAlphaInputPrefixCls = `${prefixCls}-alpha-input`;
const [internalValue, setInternalValue] = (0, import_react.useState)(() => generateColor(value || "#000"));
const alphaValue = value || internalValue;
const handleAlphaChange = (step) => {
const hsba = alphaValue.toHsb();
hsba.a = (step || 0) / 100;
const genColor = generateColor(hsba);
setInternalValue(genColor);
onChange?.(genColor);
};
return /*#__PURE__*/ import_react.createElement(ColorSteppers, {
value: getColorAlpha(alphaValue),
prefixCls,
formatter: (step) => `${step}%`,
className: colorAlphaInputPrefixCls,
onChange: handleAlphaChange
});
};
var hexReg = /(^#[\da-f]{6}$)|(^#[\da-f]{8}$)/i;
var isHexString = (hex) => hexReg.test(`#${hex}`);
var ColorHexInput = ({ prefixCls, value, onChange }) => {
const colorHexInputPrefixCls = `${prefixCls}-hex-input`;
const [hexValue, setHexValue] = (0, import_react.useState)(() => value ? toHexFormat(value.toHexString()) : void 0);
(0, import_react.useEffect)(() => {
if (value) setHexValue(toHexFormat(value.toHexString()));
}, [value]);
const handleHexChange = (e) => {
const originValue = e.target.value;
setHexValue(toHexFormat(originValue));
if (isHexString(toHexFormat(originValue, true))) onChange?.(generateColor(originValue));
};
return /*#__PURE__*/ import_react.createElement(Input$1, {
className: colorHexInputPrefixCls,
value: hexValue,
prefix: "#",
onChange: handleHexChange,
size: "small"
});
};
var ColorHsbInput = ({ prefixCls, value, onChange }) => {
const colorHsbInputPrefixCls = `${prefixCls}-hsb-input`;
const [internalValue, setInternalValue] = (0, import_react.useState)(() => generateColor(value || "#000"));
const hsbValue = value || internalValue;
const handleHsbChange = (step, type) => {
const hsb = hsbValue.toHsb();
hsb[type] = type === "h" ? step : (step || 0) / 100;
const genColor = generateColor(hsb);
setInternalValue(genColor);
onChange?.(genColor);
};
return /*#__PURE__*/ import_react.createElement("div", { className: colorHsbInputPrefixCls }, /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 360,
min: 0,
value: Number(hsbValue.toHsb().h),
prefixCls,
className: colorHsbInputPrefixCls,
formatter: (step) => getRoundNumber(step || 0).toString(),
onChange: (step) => handleHsbChange(Number(step), "h")
}), /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 100,
min: 0,
value: Number(hsbValue.toHsb().s) * 100,
prefixCls,
className: colorHsbInputPrefixCls,
formatter: (step) => `${getRoundNumber(step || 0)}%`,
onChange: (step) => handleHsbChange(Number(step), "s")
}), /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 100,
min: 0,
value: Number(hsbValue.toHsb().b) * 100,
prefixCls,
className: colorHsbInputPrefixCls,
formatter: (step) => `${getRoundNumber(step || 0)}%`,
onChange: (step) => handleHsbChange(Number(step), "b")
}));
};
var ColorRgbInput = ({ prefixCls, value, onChange }) => {
const colorRgbInputPrefixCls = `${prefixCls}-rgb-input`;
const [internalValue, setInternalValue] = (0, import_react.useState)(() => generateColor(value || "#000"));
const rgbValue = value || internalValue;
const handleRgbChange = (step, type) => {
const rgb = rgbValue.toRgb();
rgb[type] = step || 0;
const genColor = generateColor(rgb);
setInternalValue(genColor);
onChange?.(genColor);
};
return /*#__PURE__*/ import_react.createElement("div", { className: colorRgbInputPrefixCls }, /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 255,
min: 0,
value: Number(rgbValue.toRgb().r),
prefixCls,
className: colorRgbInputPrefixCls,
onChange: (step) => handleRgbChange(Number(step), "r")
}), /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 255,
min: 0,
value: Number(rgbValue.toRgb().g),
prefixCls,
className: colorRgbInputPrefixCls,
onChange: (step) => handleRgbChange(Number(step), "g")
}), /*#__PURE__*/ import_react.createElement(ColorSteppers, {
max: 255,
min: 0,
value: Number(rgbValue.toRgb().b),
prefixCls,
className: colorRgbInputPrefixCls,
onChange: (step) => handleRgbChange(Number(step), "b")
}));
};
var selectOptions = [
"hex",
"hsb",
"rgb"
].map((format) => ({
value: format,
label: format.toUpperCase()
}));
var ColorInput = (props) => {
const { prefixCls, format, value, disabledAlpha, onFormatChange, onChange, disabledFormat } = props;
const [colorFormat, setColorFormat] = useControlledState("hex", format);
const colorInputPrefixCls = `${prefixCls}-input`;
const triggerFormatChange = (newFormat) => {
setColorFormat(newFormat);
onFormatChange?.(newFormat);
};
const steppersNode = (0, import_react.useMemo)(() => {
const inputProps = {
value,
prefixCls,
onChange
};
switch (colorFormat) {
case "hsb": return /*#__PURE__*/ import_react.createElement(ColorHsbInput, { ...inputProps });
case "rgb": return /*#__PURE__*/ import_react.createElement(ColorRgbInput, { ...inputProps });
default: return /*#__PURE__*/ import_react.createElement(ColorHexInput, { ...inputProps });
}
}, [
colorFormat,
prefixCls,
value,
onChange
]);
return /*#__PURE__*/ import_react.createElement("div", { className: `${colorInputPrefixCls}-container` }, !disabledFormat && /*#__PURE__*/ import_react.createElement(Select, {
value: colorFormat,
variant: "borderless",
getPopupContainer: (current) => current,
popupMatchSelectWidth: 68,
placement: "bottomRight",
onChange: triggerFormatChange,
className: `${prefixCls}-format-select`,
size: "small",
options: selectOptions
}), /*#__PURE__*/ import_react.createElement("div", { className: colorInputPrefixCls }, steppersNode), !disabledAlpha && /*#__PURE__*/ import_react.createElement(ColorAlphaInput, {
prefixCls,
value,
onChange
}));
};
var GradientColorSlider = (props) => {
const { prefixCls, colors, type, color, range = false, className, activeIndex, onActive, onDragStart, onDragChange, onKeyDelete, ...restProps } = props;
const sliderProps = {
...restProps,
track: false
};
const linearCss = import_react.useMemo(() => {
return `linear-gradient(90deg, ${colors.map((c) => `${c.color} ${c.percent}%`).join(", ")})`;
}, [colors]);
const pointColor = import_react.useMemo(() => {
if (!color || !type) return null;
if (type === "alpha") return color.toRgbString();
return `hsl(${color.toHsb().h}, 100%, 50%)`;
}, [color, type]);
const onInternalDragStart = useEvent(onDragStart);
const onInternalDragChange = useEvent(onDragChange);
const unstableContext = import_react.useMemo(() => ({
onDragStart: onInternalDragStart,
onDragChange: onInternalDragChange
}), []);
const handleRender = useEvent((ori, info) => {
const { onFocus, style, className: handleCls, onKeyDown } = ori.props;
const mergedStyle = { ...style };
if (type === "gradient") mergedStyle.background = getGradientPercentColor(colors, info.value);
return /*#__PURE__*/ import_react.cloneElement(ori, {
onFocus: (e) => {
onActive?.(info.index);
onFocus?.(e);
},
style: mergedStyle,
className: clsx(handleCls, { [`${prefixCls}-slider-handle-active`]: activeIndex === info.index }),
onKeyDown: (e) => {
if ((e.key === "Delete" || e.key === "Backspace") && onKeyDelete) onKeyDelete(info.index);
onKeyDown?.(e);
}
});
});
const sliderContext = import_react.useMemo(() => ({
direction: "ltr",
handleRender
}), []);
return /*#__PURE__*/ import_react.createElement(SliderInternalContext.Provider, { value: sliderContext }, /*#__PURE__*/ import_react.createElement(UnstableContext.Provider, { value: unstableContext }, /*#__PURE__*/ import_react.createElement(Slider, {
...sliderProps,
className: clsx(className, `${prefixCls}-slider`),
tooltip: { open: false },
range: {
editable: range,
minCount: 2
},
styles: {
rail: { background: linearCss },
handle: pointColor ? { background: pointColor } : {}
},
classNames: {
rail: `${prefixCls}-slider-rail`,
handle: `${prefixCls}-slider-handle`
}
})));
};
var SingleColorSlider = (props) => {
const { value, onChange, onChangeComplete } = props;
const singleOnChange = (v) => onChange(v[0]);
const singleOnChangeComplete = (v) => onChangeComplete(v[0]);
return /*#__PURE__*/ import_react.createElement(GradientColorSlider, {
...props,
value: [value],
onChange: singleOnChange,
onChangeComplete: singleOnChangeComplete
});
};
function sortColors(colors) {
return _toConsumableArray(colors).sort((a, b) => a.percent - b.percent);
}
/**
* GradientColorBar will auto show when the mode is `gradient`.
*/
var GradientColorBar = (props) => {
const { prefixCls, mode, onChange, onChangeComplete, onActive, activeIndex, onGradientDragging, colors } = props;
const isGradient = mode === "gradient";
const colorList = import_react.useMemo(() => colors.map((info) => ({
percent: info.percent,
color: info.color.toRgbString()
})), [colors]);
const values = import_react.useMemo(() => colorList.map((info) => info.percent), [colorList]);
const colorsRef = import_react.useRef(colorList);
const onDragStart = ({ rawValues, draggingIndex, draggingValue }) => {
if (rawValues.length > colorList.length) {
const newPointColor = getGradientPercentColor(colorList, draggingValue);
const nextColors = _toConsumableArray(colorList);
nextColors.splice(draggingIndex, 0, {
percent: draggingValue,
color: newPointColor
});
colorsRef.current = nextColors;
} else colorsRef.current = colorList;
onGradientDragging(true);
onChange(new AggregationColor(sortColors(colorsRef.current)), true);
};
const onDragChange = ({ deleteIndex, draggingIndex, draggingValue }) => {
let nextColors = _toConsumableArray(colorsRef.current);
if (deleteIndex !== -1) nextColors.splice(deleteIndex, 1);
else {
nextColors[draggingIndex] = {
...nextColors[draggingIndex],
percent: draggingValue
};
nextColors = sortColors(nextColors);
}
onChange(new AggregationColor(nextColors), true);
};
const onKeyDelete = (index) => {
const nextColors = _toConsumableArray(colorList);
nextColors.splice(index, 1);
const nextColor = new AggregationColor(nextColors);
onChange(nextColor);
onChangeComplete(nextColor);
};
const onInternalChangeComplete = (nextValues) => {
onChangeComplete(new AggregationColor(colorList));
if (activeIndex >= nextValues.length) onActive(nextValues.length - 1);
onGradientDragging(false);
};
if (!isGradient) return null;
return /*#__PURE__*/ import_react.createElement(GradientColorSlider, {
min: 0,
max: 100,
prefixCls,
className: `${prefixCls}-gradient-slider`,
colors: colorList,
color: null,
value: values,
range: true,
onChangeComplete: onInternalChangeComplete,
disabled: false,
type: "gradient",
activeIndex,
onActive,
onDragStart,
onDragChange,
onKeyDelete
});
};
var GradientColorBar_default = /*#__PURE__*/ import_react.memo(GradientColorBar);
var components = { slider: SingleColorSlider };
var PanelPicker = () => {
const panelPickerContext = (0, import_react.useContext)(PanelPickerContext);
const { mode, onModeChange, modeOptions, prefixCls, allowClear, value, disabledAlpha, onChange, onClear, onChangeComplete, activeIndex, gradientDragging, ...injectProps } = panelPickerContext;
const colors = import_react.useMemo(() => {
if (!value.cleared) return value.getColors();
return [{
percent: 0,
color: new AggregationColor("")
}, {
percent: 100,
color: new AggregationColor("")
}];
}, [value]);
const isSingle = !value.isGradient();
const [lockedColor, setLockedColor] = import_react.useState(value);
useLayoutEffect$2(() => {
if (!isSingle) setLockedColor(colors[activeIndex]?.color);
}, [
isSingle,
colors,
gradientDragging,
activeIndex
]);
const activeColor = import_react.useMemo(() => {
if (isSingle) return value;
if (gradientDragging) return lockedColor;
return colors[activeIndex]?.color;
}, [
colors,
value,
activeIndex,
isSingle,
lockedColor,
gradientDragging
]);
const [pickerColor, setPickerColor] = import_react.useState(activeColor);
const [forceSync, setForceSync] = useForceUpdate();
const mergedPickerColor = pickerColor?.equals(activeColor) ? activeColor : pickerColor;
useLayoutEffect$2(() => {
setPickerColor(activeColor);
}, [forceSync, activeColor?.toHexString()]);
const fillColor = (nextColor, info) => {
let submitColor = generateColor(nextColor);
if (value.cleared) {
const rgb = submitColor.toRgb();
if (!rgb.r && !rgb.g && !rgb.b && info) {
const { type: infoType, value: infoValue = 0 } = info;
submitColor = new AggregationColor({
h: infoType === "hue" ? infoValue : 0,
s: 1,
b: 1,
a: infoType === "alpha" ? infoValue / 100 : 1
});
} else submitColor = genAlphaColor(submitColor);
}
if (mode === "single") return submitColor;
const nextColors = _toConsumableArray(colors);
nextColors[activeIndex] = {
...nextColors[activeIndex],
color: submitColor
};
return new AggregationColor(nextColors);
};
const onPickerChange = (colorValue, fromPicker, info) => {
const nextColor = fillColor(colorValue, info);
setPickerColor(nextColor.isGradient() ? nextColor.getColors()[activeIndex].color : nextColor);
onChange(nextColor, fromPicker);
};
const onInternalChangeComplete = (nextColor, info) => {
onChangeComplete(fillColor(nextColor, info));
setForceSync();
};
const onInputChange = (colorValue) => {
onChange(fillColor(colorValue));
};
let operationNode = null;
const showMode = modeOptions.length > 1;
if (allowClear || showMode) operationNode = /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-operation` }, showMode && /*#__PURE__*/ import_react.createElement(Segmented, {
size: "small",
options: modeOptions,
value: mode,
onChange: onModeChange
}), /*#__PURE__*/ import_react.createElement(ColorClear, {
prefixCls,
value,
onChange: (clearColor) => {
onChange(clearColor);
onClear?.();
},
...injectProps
}));
return /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, operationNode, /*#__PURE__*/ import_react.createElement(GradientColorBar_default, {
...panelPickerContext,
colors
}), /*#__PURE__*/ import_react.createElement(es_default$2, {
prefixCls,
value: mergedPickerColor?.toHsb(),
disabledAlpha,
onChange: (colorValue, info) => {
onPickerChange(colorValue, true, info);
},
onChangeComplete: (colorValue, info) => {
onInternalChangeComplete(colorValue, info);
},
components
}), /*#__PURE__*/ import_react.createElement(ColorInput, {
value: activeColor,
onChange: onInputChange,
prefixCls,
disabledAlpha,
...injectProps
}));
};
var PanelPresets = () => {
const { prefixCls, value, presets, onChange } = (0, import_react.useContext)(PanelPresetsContext);
return Array.isArray(presets) ? /*#__PURE__*/ import_react.createElement(ColorPresets, {
value,
presets,
prefixCls,
onChange
}) : null;
};
var ColorPickerPanel = (props) => {
const { prefixCls, presets, panelRender, value, onChange, onClear, allowClear, disabled, disabledAlpha, mode, onModeChange, modeOptions, onChangeComplete, activeIndex, onActive, format, onFormatChange, gradientDragging, onGradientDragging, disabledFormat } = props;
const colorPickerPanelPrefixCls = `${prefixCls}-inner`;
const panelContext = import_react.useMemo(() => ({
prefixCls,
value,
onChange,
onClear,
allowClear,
disabled,
disabledAlpha,
mode,
onModeChange,
modeOptions,
onChangeComplete,
activeIndex,
onActive,
format,
onFormatChange,
gradientDragging,
onGradientDragging,
disabledFormat
}), [
prefixCls,
value,
onChange,
onClear,
allowClear,
disabled,
disabledAlpha,
mode,
onModeChange,
modeOptions,
onChangeComplete,
activeIndex,
onActive,
format,
onFormatChange,
gradientDragging,
onGradientDragging,
disabledFormat
]);
const presetContext = import_react.useMemo(() => ({
prefixCls,
value,
presets,
onChange
}), [
prefixCls,
value,
presets,
onChange
]);
const innerPanel = /*#__PURE__*/ import_react.createElement("div", { className: `${colorPickerPanelPrefixCls}-content` }, /*#__PURE__*/ import_react.createElement(PanelPicker, null), Array.isArray(presets) && /*#__PURE__*/ import_react.createElement(Divider, null), /*#__PURE__*/ import_react.createElement(PanelPresets, null));
return /*#__PURE__*/ import_react.createElement(PanelPickerContext.Provider, { value: panelContext }, /*#__PURE__*/ import_react.createElement(PanelPresetsContext.Provider, { value: presetContext }, /*#__PURE__*/ import_react.createElement("div", { className: colorPickerPanelPrefixCls }, isFunction(panelRender) ? panelRender(innerPanel, { components: {
Picker: PanelPicker,
Presets: PanelPresets
} }) : innerPanel)));
};
var ColorTrigger = /*#__PURE__*/ (0, import_react.forwardRef)((props, ref) => {
const { color, prefixCls, open, disabled, format, className, style, classNames, styles, showText, activeIndex, ...rest } = props;
const colorTriggerPrefixCls = `${prefixCls}-trigger`;
const colorTextPrefixCls = `${colorTriggerPrefixCls}-text`;
const colorTextCellPrefixCls = `${colorTextPrefixCls}-cell`;
const [locale] = useLocale("ColorPicker");
const desc = import_react.useMemo(() => {
if (!showText) return "";
if (isFunction(showText)) return showText(color);
if (color.cleared) return locale.transparent;
if (color.isGradient()) return color.getColors().map((c, index) => {
const inactive = activeIndex !== -1 && activeIndex !== index;
return /*#__PURE__*/ import_react.createElement("span", {
key: index,
className: clsx(colorTextCellPrefixCls, inactive && `${colorTextCellPrefixCls}-inactive`)
}, c.color.toRgbString(), " ", c.percent, "%");
});
const hexString = color.toHexString().toUpperCase();
const alpha = getColorAlpha(color);
switch (format) {
case "rgb": return color.toRgbString();
case "hsb": return color.toHsbString();
default: return alpha < 100 ? `${hexString.slice(0, 7)},${alpha}%` : hexString;
}
}, [
color,
format,
showText,
activeIndex,
locale.transparent,
colorTextCellPrefixCls
]);
const containerNode = (0, import_react.useMemo)(() => color.cleared ? /*#__PURE__*/ import_react.createElement(ColorClear, {
prefixCls,
className: classNames.body,
style: styles.body,
disabled
}) : /*#__PURE__*/ import_react.createElement(ColorBlock, {
prefixCls,
color: color.toCssString(),
className: classNames.body,
innerClassName: classNames.content,
style: styles.body,
innerStyle: styles.content
}), [
color,
prefixCls,
disabled,
classNames.body,
classNames.content,
styles.body,
styles.content
]);
return /*#__PURE__*/ import_react.createElement("div", {
ref,
className: clsx(colorTriggerPrefixCls, className, classNames.root, {
[`${colorTriggerPrefixCls}-active`]: open,
[`${colorTriggerPrefixCls}-disabled`]: disabled
}),
style: {
...styles.root,
...style
},
...pickAttrs(rest)
}, containerNode, showText && /*#__PURE__*/ import_react.createElement("div", {
className: clsx(colorTextPrefixCls, classNames.description),
style: styles.description
}, desc));
});
/**
* Combine the `color` and `mode` to make sure sync of state.
*/
function useModeColor(defaultValue, value, mode) {
const [locale] = useLocale("ColorPicker");
const [mergedColor, setMergedColor] = useControlledState(defaultValue, value);
const [modeState, setModeState] = import_react.useState("single");
const [modeOptionList, modeSet] = import_react.useMemo(() => {
const list = (Array.isArray(mode) ? mode : [mode]).filter((m) => m);
if (!list.length) list.push("single");
const modes = new Set(list);
const optionList = [];
const pushOption = (modeType, localeTxt) => {
if (modes.has(modeType)) optionList.push({
label: localeTxt,
value: modeType
});
};
pushOption("single", locale.singleColor);
pushOption("gradient", locale.gradientColor);
return [optionList, modes];
}, [
mode,
locale.singleColor,
locale.gradientColor
]);
const [cacheColor, setCacheColor] = import_react.useState(null);
const setColor = useEvent((nextColor) => {
setCacheColor(nextColor);
setMergedColor(nextColor);
});
const postColor = import_react.useMemo(() => {
const colorObj = generateColor(mergedColor || "");
return colorObj.equals(cacheColor) ? cacheColor : colorObj;
}, [mergedColor, cacheColor]);
const postMode = import_react.useMemo(() => {
if (modeSet.has(modeState)) return modeState;
return modeOptionList[0]?.value;
}, [
modeSet,
modeState,
modeOptionList
]);
import_react.useEffect(() => {
setModeState(postColor.isGradient() ? "gradient" : "single");
}, [postColor]);
return [
postColor,
setColor,
postMode,
setModeState,
modeOptionList
];
}
/**
* @private Internal usage only
* see: https://developer.mozilla.org/en-US/docs/Web/CSS/gradient/conic-gradient#checkerboard
*/
var getTransBg = (size, colorFill) => ({
backgroundImage: `conic-gradient(${colorFill} 25%, transparent 25% 50%, ${colorFill} 50% 75%, transparent 75% 100%)`,
backgroundSize: `${size} ${size}`
});
var genColorBlockStyle = (token, size) => {
const { componentCls, borderRadiusSM, colorPickerInsetShadow, lineWidth, colorFillSecondary } = token;
return { [`${componentCls}-color-block`]: {
position: "relative",
borderRadius: borderRadiusSM,
width: size,
height: size,
boxShadow: colorPickerInsetShadow,
flex: "none",
...getTransBg("50%", token.colorFillSecondary),
[`${componentCls}-color-block-inner`]: {
width: "100%",
height: "100%",
boxShadow: `inset 0 0 0 ${unit$1(lineWidth)} ${colorFillSecondary}`,
borderRadius: "inherit"
}
} };
};
var genInputStyle = (token) => {
const { componentCls, antCls, fontSizeSM, lineHeightSM, colorPickerAlphaInputWidth, marginXXS, paddingXXS, controlHeightSM, marginXS, fontSizeIcon, paddingXS, colorTextPlaceholder, colorPickerInputNumberHandleWidth, lineWidth } = token;
return { [`${componentCls}-input-container`]: {
display: "flex",
[`${componentCls}-steppers${antCls}-input-number`]: {
fontSize: fontSizeSM,
lineHeight: lineHeightSM,
padding: 0,
[`${antCls}-input-number-input`]: {
paddingInlineStart: paddingXXS,
paddingInlineEnd: 0
},
[`${antCls}-input-number-handler-wrap`]: { width: colorPickerInputNumberHandleWidth }
},
[`${componentCls}-steppers${componentCls}-alpha-input`]: {
flex: `0 0 ${unit$1(colorPickerAlphaInputWidth)}`,
marginInlineStart: marginXXS
},
[`${componentCls}-format-select${antCls}-select`]: {
marginInlineEnd: marginXS,
width: "auto",
"&-single": {
[`${antCls}-select-selector`]: {
padding: 0,
border: 0
},
[`${antCls}-select-arrow`]: { insetInlineEnd: 0 },
[`${antCls}-select-selection-item`]: {
paddingInlineEnd: token.calc(fontSizeIcon).add(marginXXS).equal(),
fontSize: fontSizeSM,
lineHeight: unit$1(controlHeightSM)
},
[`${antCls}-select-item-option-content`]: {
fontSize: fontSizeSM,
lineHeight: lineHeightSM
},
[`${antCls}-select-dropdown`]: { [`${antCls}-select-item`]: { minHeight: "auto" } }
}
},
[`${componentCls}-input`]: {
gap: marginXXS,
alignItems: "center",
flex: 1,
width: 0,
[`${componentCls}-hsb-input,${componentCls}-rgb-input`]: {
height: controlHeightSM,
display: "flex",
gap: marginXXS,
alignItems: "center"
},
[`${componentCls}-steppers`]: { flex: 1 },
[`${componentCls}-hex-input${antCls}-input-affix-wrapper`]: {
flex: 1,
padding: `0 ${unit$1(paddingXS)}`,
[`${antCls}-input`]: {
fontSize: fontSizeSM,
textTransform: "uppercase",
lineHeight: unit$1(token.calc(controlHeightSM).sub(token.calc(lineWidth).mul(2)).equal())
},
[`${antCls}-input-prefix`]: { color: colorTextPlaceholder }
}
}
} };
};
var genPickerStyle = (token) => {
const { componentCls, controlHeightLG, borderRadiusSM, colorPickerInsetShadow, marginSM, colorBgElevated, colorFillSecondary, lineWidthBold, colorPickerHandlerSize } = token;
return {
userSelect: "none",
[`${componentCls}-select`]: {
[`${componentCls}-palette`]: {
minHeight: token.calc(controlHeightLG).mul(4).equal(),
overflow: "hidden",
borderRadius: borderRadiusSM
},
[`${componentCls}-saturation`]: {
position: "absolute",
borderRadius: "inherit",
boxShadow: colorPickerInsetShadow,
inset: 0
},
marginBottom: marginSM
},
[`${componentCls}-handler`]: {
width: colorPickerHandlerSize,
height: colorPickerHandlerSize,
border: `${unit$1(lineWidthBold)} solid ${colorBgElevated}`,
position: "relative",
borderRadius: "50%",
cursor: "pointer",
boxShadow: `${colorPickerInsetShadow}, 0 0 0 1px ${colorFillSecondary}`
}
};
};
var genPresetsStyle = (token) => {
const { componentCls, antCls, colorTextQuaternary, paddingXXS, colorPickerPresetColorSize, fontSizeSM, colorText, lineHeightSM, lineWidth, borderRadius, colorFill, colorWhite, marginXXS, paddingXS, fontHeightSM } = token;
return { [`${componentCls}-presets`]: {
[`${antCls}-collapse-item > ${antCls}-collapse-header`]: {
padding: 0,
[`${antCls}-collapse-expand-icon`]: {
height: fontHeightSM,
color: colorTextQuaternary,
paddingInlineEnd: paddingXXS
}
},
[`${antCls}-collapse`]: {
display: "flex",
flexDirection: "column",
gap: marginXXS
},
[`${antCls}-collapse-item > ${antCls}-collapse-panel > ${antCls}-collapse-body`]: { padding: `${unit$1(paddingXS)} 0` },
"&-label": {
fontSize: fontSizeSM,
color: colorText,
lineHeight: lineHeightSM
},
"&-items": {
display: "flex",
flexWrap: "wrap",
gap: token.calc(marginXXS).mul(1.5).equal(),
[`${componentCls}-presets-color`]: {
position: "relative",
cursor: "pointer",
width: colorPickerPresetColorSize,
height: colorPickerPresetColorSize,
"&::before": {
content: "\"\"",
pointerEvents: "none",
width: token.calc(colorPickerPresetColorSize).add(token.calc(lineWidth).mul(4)).equal(),
height: token.calc(colorPickerPresetColorSize).add(token.calc(lineWidth).mul(4)).equal(),
position: "absolute",
top: token.calc(lineWidth).mul(-2).equal(),
insetInlineStart: token.calc(lineWidth).mul(-2).equal(),
borderRadius,
border: `${unit$1(lineWidth)} solid transparent`,
transition: `border-color ${token.motionDurationMid} ${token.motionEaseInBack}`
},
"&:hover::before": { borderColor: colorFill },
"&::after": {
boxSizing: "border-box",
position: "absolute",
top: "50%",
insetInlineStart: "21.5%",
display: "table",
width: token.calc(colorPickerPresetColorSize).div(13).mul(5).equal(),
height: token.calc(colorPickerPresetColorSize).div(13).mul(8).equal(),
border: `${unit$1(token.lineWidthBold)} solid ${token.colorWhite}`,
borderTop: 0,
borderInlineStart: 0,
transform: "rotate(45deg) scale(0) translate(-50%,-50%)",
opacity: 0,
content: "\"\"",
transition: `all ${token.motionDurationFast} ${token.motionEaseInBack}, opacity ${token.motionDurationFast}`
},
[`&${componentCls}-presets-color-checked`]: {
"&::after": {
opacity: 1,
borderColor: colorWhite,
transform: "rotate(45deg) scale(1) translate(-50%,-50%)",
transition: `transform ${token.motionDurationMid} ${token.motionEaseOutBack} ${token.motionDurationFast}`
},
[`&${componentCls}-presets-color-bright`]: { "&::after": { borderColor: "rgba(0, 0, 0, 0.45)" } }
}
}
},
"&-empty": {
fontSize: fontSizeSM,
color: colorTextQuaternary
}
} };
};
var genSliderStyle = (token) => {
const { componentCls, colorPickerInsetShadow, colorBgElevated, colorFillSecondary, lineWidthBold, colorPickerHandlerSizeSM, colorPickerSliderHeight, marginSM, marginXS } = token;
const handleInnerSize = token.calc(colorPickerHandlerSizeSM).sub(token.calc(lineWidthBold).mul(2).equal()).equal();
const handleHoverSize = token.calc(colorPickerHandlerSizeSM).add(token.calc(lineWidthBold).mul(2).equal()).equal();
const activeHandleStyle = { "&:after": {
transform: "scale(1)",
boxShadow: `${colorPickerInsetShadow}, 0 0 0 1px ${token.colorPrimaryActive}`
} };
return {
[`${componentCls}-slider`]: [getTransBg(unit$1(colorPickerSliderHeight), token.colorFillSecondary), {
margin: 0,
padding: 0,
height: colorPickerSliderHeight,
borderRadius: token.calc(colorPickerSliderHeight).div(2).equal(),
"&-rail": {
height: colorPickerSliderHeight,
borderRadius: token.calc(colorPickerSliderHeight).div(2).equal(),
boxShadow: colorPickerInsetShadow
},
[`& ${componentCls}-slider-handle`]: {
width: handleInnerSize,
height: handleInnerSize,
top: 0,
borderRadius: "100%",
"&:before": {
display: "block",
position: "absolute",
background: "transparent",
left: {
_skip_check_: true,
value: "50%"
},
top: "50%",
transform: "translate(-50%, -50%)",
width: handleHoverSize,
height: handleHoverSize,
borderRadius: "100%"
},
"&:after": {
width: colorPickerHandlerSizeSM,
height: colorPickerHandlerSizeSM,
border: `${unit$1(lineWidthBold)} solid ${colorBgElevated}`,
boxShadow: `${colorPickerInsetShadow}, 0 0 0 1px ${colorFillSecondary}`,
outline: "none",
insetInlineStart: token.calc(lineWidthBold).mul(-1).equal(),
top: token.calc(lineWidthBold).mul(-1).equal(),
background: "transparent",
transition: "none"
},
"&:focus": activeHandleStyle
}
}],
[`${componentCls}-slider-container`]: {
display: "flex",
gap: marginSM,
marginBottom: marginSM,
[`${componentCls}-slider-group`]: {
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
display: "flex",
"&-disabled-alpha": { justifyContent: "center" }
}
},
[`${componentCls}-gradient-slider`]: {
marginBottom: marginXS,
[`& ${componentCls}-slider-handle`]: {
"&:after": { transform: "scale(0.8)" },
"&-active, &:focus": activeHandleStyle
}
}
};
};
var genActiveStyle = (token, borderColor, outlineColor) => ({
borderInlineEndWidth: token.lineWidth,
borderColor,
boxShadow: `0 0 0 ${unit$1(token.controlOutlineWidth)} ${outlineColor}`,
outline: 0
});
var genRtlStyle = (token) => {
const { componentCls } = token;
return { "&-rtl": {
[`${componentCls}-presets-color`]: { "&::after": { direction: "ltr" } },
[`${componentCls}-clear`]: { "&::after": { direction: "ltr" } }
} };
};
var genClearStyle = (token, size, extraStyle) => {
const { componentCls, borderRadiusSM, lineWidth, lineType, colorSplit, colorBorder, red6 } = token;
return { [`${componentCls}-clear`]: {
width: size,
height: size,
borderRadius: borderRadiusSM,
border: `${unit$1(lineWidth)} ${lineType} ${colorSplit}`,
position: "relative",
overflow: "hidden",
cursor: "inherit",
userSelect: "none",
transition: `all ${token.motionDurationFast}`,
...extraStyle,
"&::after": {
content: "\"\"",
position: "absolute",
insetInlineEnd: token.calc(lineWidth).mul(-1).equal(),
top: token.calc(lineWidth).mul(-1).equal(),
display: "block",
width: 40,
height: 2,
transformOrigin: `calc(100% - 1px) 1px`,
transform: "rotate(-45deg)",
backgroundColor: red6
},
[`&:not(${componentCls}-clear-disabled):hover`]: { borderColor: colorBorder }
} };
};
var genStatusStyle = (token) => {
const { componentCls, colorError, colorWarning, colorErrorHover, colorWarningHover, colorErrorOutline, colorWarningOutline } = token;
return {
[`&${componentCls}-status-error`]: {
borderColor: colorError,
[`&:not(${componentCls}-trigger-disabled):hover`]: { borderColor: colorErrorHover },
[`&${componentCls}-trigger-active`]: { ...genActiveStyle(token, colorError, colorErrorOutline) }
},
[`&${componentCls}-status-warning`]: {
borderColor: colorWarning,
[`&:not(${componentCls}-trigger-disabled):hover`]: { borderColor: colorWarningHover },
[`&${componentCls}-trigger-active`]: { ...genActiveStyle(token, colorWarning, colorWarningOutline) }
}
};
};
var genSizeStyle = (token) => {
const { componentCls, controlHeightLG, controlHeightSM, controlHeight, controlHeightXS, borderRadius, borderRadiusSM, borderRadiusXS, borderRadiusLG, fontSizeLG } = token;
return {
[`&${componentCls}-lg`]: {
minWidth: controlHeightLG,
minHeight: controlHeightLG,
borderRadius: borderRadiusLG,
[`${componentCls}-color-block, ${componentCls}-clear`]: {
width: controlHeight,
height: controlHeight,
borderRadius
},
[`${componentCls}-trigger-text`]: { fontSize: fontSizeLG }
},
[`&${componentCls}-sm`]: {
minWidth: controlHeightSM,
minHeight: controlHeightSM,
borderRadius: borderRadiusSM,
[`${componentCls}-color-block, ${componentCls}-clear`]: {
width: controlHeightXS,
height: controlHeightXS,
borderRadius: borderRadiusXS
},
[`${componentCls}-trigger-text`]: { lineHeight: unit$1(controlHeightXS) }
}
};
};
var genColorPickerStyle = (token) => {
const { antCls, componentCls, colorPickerWidth, colorPrimary, motionDurationMid, colorBgElevated, colorTextDisabled, colorText, colorBgContainerDisabled, borderRadius, marginXS, marginSM, controlHeight, controlHeightSM, colorPickerPresetColorSize, colorPickerPreviewSize, lineWidth, lineType, colorBorder, paddingXXS, fontSize, colorPrimaryHover, controlOutline } = token;
return [{ [componentCls]: {
[`${componentCls}-inner`]: {
"&-content": {
display: "flex",
flexDirection: "column",
width: colorPickerWidth,
[`& > ${antCls}-divider`]: { margin: `${unit$1(marginSM)} 0 ${unit$1(marginXS)}` }
},
[`${componentCls}-panel`]: { ...genPickerStyle(token) },
...genSliderStyle(token),
...genColorBlockStyle(token, colorPickerPreviewSize),
...genInputStyle(token),
...genPresetsStyle(token),
...genClearStyle(token, colorPickerPresetColorSize, { marginInlineStart: "auto" }),
[`${componentCls}-operation`]: {
display: "flex",
justifyContent: "space-between",
marginBottom: marginXS
}
},
"&-trigger": {
minWidth: controlHeight,
minHeight: controlHeight,
borderRadius,
border: `${unit$1(lineWidth)} ${lineType} ${colorBorder}`,
cursor: "pointer",
display: "inline-flex",
alignItems: "flex-start",
justifyContent: "center",
transition: `all ${motionDurationMid}`,
background: colorBgElevated,
padding: token.calc(paddingXXS).sub(lineWidth).equal(),
[`${componentCls}-trigger-text`]: {
marginInlineStart: marginXS,
marginInlineEnd: token.calc(marginXS).sub(token.calc(paddingXXS).sub(lineWidth)).equal(),
fontSize,
color: colorText,
alignSelf: "center",
"&-cell": {
"&:not(:last-child):after": { content: "\", \"" },
"&-inactive": { color: colorTextDisabled }
}
},
[`&:not(${componentCls}-trigger-disabled):hover`]: { borderColor: colorPrimaryHover },
[`&${componentCls}-trigger-active`]: { ...genActiveStyle(token, colorPrimary, controlOutline) },
"&-disabled": {
color: colorTextDisabled,
background: colorBgContainerDisabled,
cursor: "not-allowed",
[`${componentCls}-trigger-text`]: { color: colorTextDisabled }
},
...genClearStyle(token, controlHeightSM),
...genColorBlockStyle(token, controlHeightSM),
...genStatusStyle(token),
...genSizeStyle(token)
},
...genRtlStyle(token)
} }, genCompactItemStyle(token, { focusElCls: `${componentCls}-trigger-active` })];
};
var style_default = genStyleHooks("ColorPicker", (token) => {
const { colorTextQuaternary, marginSM } = token;
const colorPickerSliderHeight = 8;
return genColorPickerStyle(merge(token, {
colorPickerWidth: 234,
colorPickerHandlerSize: 16,
colorPickerHandlerSizeSM: 12,
colorPickerAlphaInputWidth: 44,
colorPickerInputNumberHandleWidth: 16,
colorPickerPresetColorSize: 24,
colorPickerInsetShadow: `inset 0 0 1px 0 ${colorTextQuaternary}`,
colorPickerSliderHeight,
colorPickerPreviewSize: token.calc(colorPickerSliderHeight).mul(2).add(marginSM).equal()
}));
});
var ColorPicker = (props) => {
const { mode, value, defaultValue, format, defaultFormat, allowClear = false, presets, children, trigger = "click", open, disabled, placement = "bottomLeft", arrow, panelRender, showText, style, className, size: customizeSize, rootClassName, prefixCls: customizePrefixCls, styles, classNames, disabledAlpha = false, onFormatChange, onChange, onClear, onOpenChange, onChangeComplete, getPopupContainer, autoAdjustOverflow = true, destroyTooltipOnHide, destroyOnHidden, disabledFormat, ...rest } = props;
const { getPrefixCls, direction, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles, arrow: contextArrow } = useComponentConfig("colorPicker");
const contextDisabled = (0, import_react.useContext)(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled;
const prefixCls = getPrefixCls("color-picker", customizePrefixCls);
const mergedArrow = useMergedArrow(arrow, contextArrow);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
const mergedSize = useSize((ctx) => customizeSize ?? compactSize ?? ctx);
const mergedProps = {
...props,
trigger,
allowClear,
autoAdjustOverflow,
disabledAlpha,
arrow: mergedArrow,
placement,
disabled: mergedDisabled,
size: mergedSize
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps }, { popup: { _default: "root" } });
const [internalPopupOpen, setPopupOpen] = useControlledState(false, open);
const popupOpen = !mergedDisabled && internalPopupOpen;
const [formatValue, setFormatValue] = useControlledState(defaultFormat, format);
const triggerFormatChange = (newFormat) => {
setFormatValue(newFormat);
if (formatValue !== newFormat) onFormatChange?.(newFormat);
};
const triggerOpenChange = (open) => {
if (!open || !mergedDisabled) {
setPopupOpen(open);
onOpenChange?.(open);
}
};
const [mergedColor, setColor, modeState, setModeState, modeOptions] = useModeColor(defaultValue, value, mode);
const isAlphaColor = (0, import_react.useMemo)(() => getColorAlpha(mergedColor) < 100, [mergedColor]);
const [cachedGradientColor, setCachedGradientColor] = import_react.useState(null);
const onInternalChangeComplete = (color) => {
if (onChangeComplete) {
let changeColor = generateColor(color);
if (disabledAlpha && isAlphaColor) changeColor = genAlphaColor(color);
onChangeComplete(changeColor);
}
};
const onInternalChange = (data, changeFromPickerDrag) => {
let color = generateColor(data);
if (disabledAlpha && isAlphaColor) color = genAlphaColor(color);
setColor(color);
setCachedGradientColor(null);
if (onChange) onChange(color, color.toCssString());
if (!changeFromPickerDrag) onInternalChangeComplete(color);
};
const [activeIndex, setActiveIndex] = import_react.useState(0);
const [gradientDragging, setGradientDragging] = import_react.useState(false);
const onInternalModeChange = (newMode) => {
setModeState(newMode);
if (newMode === "single" && mergedColor.isGradient()) {
setActiveIndex(0);
onInternalChange(new AggregationColor(mergedColor.getColors()[0].color));
setCachedGradientColor(mergedColor);
} else if (newMode === "gradient" && !mergedColor.isGradient()) {
const baseColor = isAlphaColor ? genAlphaColor(mergedColor) : mergedColor;
onInternalChange(new AggregationColor(cachedGradientColor || [{
percent: 0,
color: baseColor
}, {
percent: 100,
color: baseColor
}]));
}
};
const { status: contextStatus } = import_react.useContext(FormItemInputContext);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = style_default(prefixCls, rootCls);
const rtlCls = { [`${prefixCls}-rtl`]: direction };
const mergedRootCls = clsx(rootClassName, cssVarCls, rootCls, rtlCls);
const mergedCls = clsx(getStatusClassNames(prefixCls, contextStatus), {
[`${prefixCls}-sm`]: mergedSize === "small",
[`${prefixCls}-lg`]: mergedSize === "large"
}, compactItemClassnames, contextClassName, mergedRootCls, className, hashId);
const mergedPopupCls = clsx(prefixCls, mergedRootCls, mergedClassNames.popup?.root);
const popoverProps = {
open: popupOpen,
trigger,
placement,
arrow: mergedArrow,
rootClassName,
getPopupContainer,
autoAdjustOverflow,
destroyOnHidden: destroyOnHidden ?? !!destroyTooltipOnHide
};
return /*#__PURE__*/ import_react.createElement(Popover, {
classNames: { root: mergedPopupCls },
styles: {
root: mergedStyles.popup?.root,
container: styles?.popupOverlayInner
},
onOpenChange: triggerOpenChange,
content: /*#__PURE__*/ import_react.createElement(ContextIsolator, { form: true }, /*#__PURE__*/ import_react.createElement(ColorPickerPanel, {
mode: modeState,
onModeChange: onInternalModeChange,
modeOptions,
prefixCls,
value: mergedColor,
allowClear,
disabled: mergedDisabled,
disabledAlpha,
presets,
panelRender,
format: formatValue,
onFormatChange: triggerFormatChange,
onChange: onInternalChange,
onChangeComplete: onInternalChangeComplete,
onClear,
activeIndex,
onActive: setActiveIndex,
gradientDragging,
onGradientDragging: setGradientDragging,
disabledFormat
})),
...popoverProps
}, children || /*#__PURE__*/ import_react.createElement(ColorTrigger, {
activeIndex: popupOpen ? activeIndex : -1,
open: popupOpen,
className: mergedCls,
classNames: mergedClassNames,
styles: mergedStyles,
prefixCls,
disabled: mergedDisabled,
showText,
format: formatValue,
...rest,
color: mergedColor
}));
};
ColorPicker._InternalPanelDoNotUseOrYouWillBeFired = genPurePanel(ColorPicker, void 0, (props) => ({
...props,
placement: "bottom",
autoAdjustOverflow: false
}), "color-picker", (prefixCls) => prefixCls);
var color_picker_default = ColorPicker;
var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min(), 1);
var lucideFolderOpen = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "m6 14l1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"
})]
});
var ForwardRef$7 = (0, import_react.forwardRef)(lucideFolderOpen);
var tablerBuildingStore = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M3 21h18M3 7v1a3 3 0 0 0 6 0V7m0 1a3 3 0 0 0 6 0V7m0 1a3 3 0 0 0 6 0V7H3l2-4h14l2 4M5 21V10.85M19 21V10.85M9 21v-4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v4"
})]
});
var ForwardRef$6 = (0, import_react.forwardRef)(tablerBuildingStore);
/**
* 「组现状与配置」顶部三件套 (wayfinding 001/004/007) —— 只挂 L2:
* ① 条件树 (含基础编辑: 删/切 all↔any/加原子与排除 —— 直写 group.combination);
* ② 生效值表 (键存在性回放归因, 默认只显「有声明的行」, 会话层只读);
* ③ 抽牌状态 (开关/权重/视野/临时开启/交集收窄)。
* 「改」一律写 group.overrides (Q12 决议); 嵌套重组仍走「编辑条件 ↗」工作台。
*/
var LAYER_BADGE = {
global: {
text: "全局",
fg: "#595959",
bg: "#f5f5f5"
},
pool: {
text: "池",
fg: "#0958d9",
bg: "#e6f4ff"
},
l1: {
text: "一级组",
fg: "#389e0d",
bg: "#f6ffed"
},
l2: {
text: "本组",
fg: "#d4380d",
bg: "#fff2e8"
},
cell: {
text: "格",
fg: "#d46b08",
bg: "#fff7e6"
},
combination: {
text: "组合",
fg: "#531dab",
bg: "#f9f0ff"
},
session: {
text: "会话",
fg: "#cf1322",
bg: "#fff1f0"
},
sessionCell: {
text: "会话·格",
fg: "#cf1322",
bg: "#fff1f0"
},
derived: {
text: "交集·收窄",
fg: "#595959",
bg: "#f5f5f5"
}
};
var LayerBadge = ({ id }) => {
const c = LAYER_BADGE[id];
return /* @__PURE__ */ jsx("span", {
className: "rounded-4px px-4px text-11px",
style: {
color: c.fg,
background: c.bg
},
children: c.text
});
};
var fmtNum = (v) => v >= 1e4 ? `${(v / 1e4).toFixed(v % 1e4 ? 1 : 0)}w` : String(v);
var fmtRate = (v) => `${+(v * 100).toFixed(2)}%`;
function formatDimValue(key, d) {
const v = d;
if (v.enabled === false) return "已声明 · 关";
if (key === "duration") return `时长 ${v.min || 0}s ~ ${v.max || "∞"}`;
if (key === "pubdate") return `发布时间 ${[v.win ? String(v.win) : void 0, [v.from ? `≥${(/* @__PURE__ */ new Date(Number(v.from) * 1e3)).toLocaleDateString()}` : "", v.to ? `≤${(/* @__PURE__ */ new Date(Number(v.to) * 1e3)).toLocaleDateString()}` : ""].filter(Boolean).join(" ")].filter(Boolean).join(" ") || "已开启"}`;
if ("list" in v) return `${v.list.length} 项`;
if (typeof v.value === "number") return key.endsWith("Rate") ? fmtRate(v.value) : fmtNum(v.value);
return "已开启";
}
function writeGroupDim(group, key, value) {
const o = group.overrides ??= {};
const dims = o.dims ??= {};
dims[key] = cloneDeep(value);
}
function clearGroupDim(group, key) {
const o = group.overrides;
if (!o?.dims) return;
delete o.dims[key];
if (!Object.keys(o.dims).length) delete o.dims;
if (!Object.keys(o).length) delete group.overrides;
}
/** {enabled,value} 家族的「改」气泡 —— 其余复杂维 (duration/pubdate/名单) 引导去下方槽位编辑器 */
function DimEditPopover({ group, dimKey, eff, children }) {
const cur = eff[dimKey];
const isRate = dimKey.endsWith("Rate");
const patch = (next) => writeGroupDim(group, dimKey, {
...cloneDeep(cur),
...next
});
return /* @__PURE__ */ jsx(Popover, {
trigger: "click",
content: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-6px text-12px",
children: [
/* @__PURE__ */ jsx("div", {
className: "opacity-60",
children: "写入位置: 本组 overrides(上方生效值即时重解)"
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: cur.enabled,
onChange: (v) => patch({ enabled: v })
}),
/* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
min: 0,
step: isRate ? .01 : 100,
value: cur.value,
onChange: (v) => typeof v === "number" && patch({ value: v })
}),
/* @__PURE__ */ jsx("span", {
className: "opacity-60",
children: isRate ? "(比率 0.02=2%)" : ""
})
]
}),
group.overrides?.dims?.[dimKey] !== void 0 && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto self-start px-0",
onClick: () => clearGroupDim(group, dimKey),
children: "清除本组声明(回继承)"
})
]
}),
children
});
}
function ConditionAdder({ kind, group, targetGroupId, onDone }) {
const [field, setField] = (0, import_react.useState)("tag");
const push = (atom) => {
let applied = false;
if (favorites.editGroupCombination(group.id, (combo) => {
applied = kind === "exclude" ? excludeTagRecCondition(combo, atom) : addTagRecCondition(combo, atom, targetGroupId);
}) && !applied) toast(kind === "exclude" ? "该条件已在排除名单中,未重复加入" : "该条件已存在或组合已达上限,未加入");
onDone?.();
};
const textInput = (ph, build) => /* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "w-220px",
placeholder: ph,
enterButton: "加入",
onSearch: (raw) => {
const atom = build(raw.trim());
if (atom) push(atom);
}
});
return /* @__PURE__ */ jsxs("div", {
className: "w-320px flex flex-col gap-y-6px",
children: [
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-140px",
value: field,
onChange: setField,
options: Object.entries(TAG_REC_CONDITION_FIELDS).map(([value, label]) => ({
value,
label
}))
}),
field === "tag" && /* @__PURE__ */ jsx(CandidateResolvePicker, {
confirmLabel: kind === "exclude" ? "加入排除" : "加入条件",
existingTagIds: tagRecConditionAtoms(group.combination?.include ?? {
kind: "group",
children: []
}).flatMap((a) => a.field === "tag" ? [a.value.id] : []),
existingLabel: "已在条件树",
onConfirm: (entries) => {
for (const e of entries) push(tagRecConditionAtom("tag", e));
}
}),
(field === "title" || field === "desc") && textInput(`${TAG_REC_CONDITION_FIELDS[field]}…`, (raw) => raw ? tagRecConditionAtom(field, raw) : void 0),
field === "up" && textInput("UP 主名…", (raw) => raw ? tagRecConditionAtom("up", { name: raw }) : void 0),
field === "partition" && /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-220px",
showSearch: true,
placeholder: "搜分区名加入",
value: null,
options: Object.entries(PARTITION_NAME_TO_PARENT).map(([name, ids]) => ({
value: name,
label: `${name}${ids.v1 !== void 0 ? ` (v1:${ids.v1})` : ""}${ids.v2 !== void 0 ? ` (v2:${ids.v2})` : ""}`
})),
filterOption: (input, opt) => !!opt?.label?.toString().includes(input.trim()),
onChange: (name) => {
const ids = PARTITION_NAME_TO_PARENT[name];
const entry = {
name,
...ids?.v2 !== void 0 ? { v2: ids.v2 } : { v1: ids?.v1 },
level: "parent"
};
push(tagRecConditionAtom("partition", entry));
}
})
]
});
}
function TagRecGroupStatusPanel({ groupId, getPopupContainer }) {
useSnapshot(favoritesStore);
useSnapshot(favoritesL1Store);
useSnapshot(viewScopeStore);
useSnapshot(tagRecFavSession);
useSnapshot(tagRecSharedSession);
const [pool, setPool] = (0, import_react.useState)("allV2");
const [showAll, setShowAll] = (0, import_react.useState)(false);
const [adderOpen, setAdderOpen] = (0, import_react.useState)();
const group = favorites.list.find((g) => g.id === groupId);
if (!group) return null;
const l1 = group.l1Id ? favoritesL1.list.find((l) => l.id === group.l1Id) : void 0;
const core = settings.tagRec.filter;
const sessionCore = tagRecFavSession.filterOverride;
const resolved = resolveTagRecEffectiveProvenance(core, pool, group, l1, sessionCore);
const { prov } = resolved;
const viewed = viewScope.viewedGroupIds();
const inView = !viewed || viewed.has(groupId);
const tempOn = tagRecSharedSession.tempEnabledGroupIds.includes(groupId);
const l1Off = l1 && !l1.enabled && !tagRecSharedSession.tempEnabledL1Ids.includes(l1.id);
const deckState = !(!!group.enabled || tempOn) ? {
text: "未入抽",
fg: "#cf1322"
} : l1Off || !inView ? {
text: "暂不入抽",
fg: "#d46b08"
} : {
text: "在抽",
fg: "#389e0d"
};
const deckWhy = [
!group.enabled && tempOn ? "临时开启" : !group.enabled ? "组开关关" : "",
l1Off ? `一级组「${l1.name}」未启用` : "",
!inView ? "不在当前观看范围" : ""
].filter(Boolean).join(" · ");
const treeEdit = {
onRemoveNode: (id) => favorites.editGroupCombination(groupId, (c) => removeTagRecCondition(c, id)),
onRemoveExclude: (id) => favorites.editGroupCombination(groupId, (c) => removeTagRecCondition(c, id)),
onSetMode: (id, mode) => favorites.editGroupCombination(groupId, (c) => {
const node = findTagRecCondition(c.include, id);
if (node?.kind === "group") node.mode = mode;
}),
renderAdder: (target) => {
const key = target.kind === "exclude" ? "exclude" : `group:${target.id}`;
return /* @__PURE__ */ jsx(Popover, {
open: adderOpen === key,
onOpenChange: (v) => setAdderOpen(v ? key : void 0),
trigger: "click",
getPopupContainer,
content: /* @__PURE__ */ jsx(ConditionAdder, {
kind: target.kind === "exclude" ? "exclude" : "group",
group,
targetGroupId: target.kind === "group" ? target.id : void 0,
onDone: () => setAdderOpen(void 0)
}),
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "dashed",
className: "h-20px px-6px text-11px",
children: ["+", target.kind === "exclude" ? "排除" : "条件"]
})
}, key);
}
};
const declaredDimKeys = TAG_REC_PATCHABLE_DIM_KEYS.filter((k) => prov.dims[k] !== void 0);
const dimRows = showAll ? [...TAG_REC_PATCHABLE_DIM_KEYS] : declaredDimKeys;
const groupDeclares = (key) => {
if (group.overrides?.pools?.[pool]?.dims?.[key] !== void 0) return "格";
if (group.overrides?.dims?.[key] !== void 0) return "本组";
if (group.combination?.filters?.dims?.[key] !== void 0) return "组合.filters";
return "—";
};
const scoreDeclared = Object.keys(prov.scoreWeights).length + Object.keys(prov.scoreAnchors).length;
const sessionRowProv = (id) => id === "session" || id === "sessionCell";
const neg = resolved.neg;
const blockBits = [
neg.tagIds.length && `tag ${neg.tagIds.length} 项`,
neg.up.length && `UP ${neg.up.length} 项`,
neg.title.length && `标题词 ${neg.title.length}`,
neg.desc.length && `简介词 ${neg.desc.length}`,
neg.partitionBlock.length && `分区 ${neg.partitionBlock.length}`,
...(neg.tagBlock ?? []).map((l) => `tag 屏蔽 ${l.list.length}(${l.scope}${l.strict ? "·严格" : ""})`)
].filter(Boolean);
const blockScopes = [...new Set([
...neg.tagIds,
...neg.up,
...neg.title,
...neg.desc,
...neg.partitionBlock,
...neg.tagBlock ?? []
].map((e) => e.scope))];
const allowBits = [
neg.tagAllow && `tag ${neg.tagAllow.list.length}(${neg.tagAllow.scope})`,
neg.upAllow && `UP ${neg.upAllow.list.length}(${neg.upAllow.scope})`,
neg.titleAllow && `标题词 ${neg.titleAllow.list.length}(${neg.titleAllow.scope})`,
neg.descAllow && `简介词 ${neg.descAllow.list.length}(${neg.descAllow.scope})`,
neg.partitionAllow && `分区 ${neg.partitionAllow.list.length}(${neg.partitionAllow.scope})`
].filter((b) => !!b);
const allowScopes = [...new Set([
neg.tagAllow,
neg.upAllow,
neg.titleAllow,
neg.descAllow,
neg.partitionAllow
].flatMap((x) => x ? [x.scope] : []))];
const negDecl = group.overrides?.negative;
const cfListDims = group.combination?.filters?.dims;
/** 声明探测 = 键存在且容器非空 (稀疏语义: 键在=声明, 空壳不算) */
const negDeclBits = (keys, dimsKeys) => [negDecl && keys.some((k) => {
const v = negDecl[k];
if (v === void 0) return false;
if (Array.isArray(v)) return v.length > 0;
if (v && typeof v === "object") {
const l = v;
return Array.isArray(l.list) ? l.list.length > 0 : l.enabled !== void 0;
}
return v === true;
}) ? "本组" : "", cfListDims && dimsKeys.some((k) => {
const r = cfListDims[k];
return !!(r && Array.isArray(r.list) && r.list.length > 0);
}) ? "组合.filters" : ""].filter(Boolean).join(" + ") || "—";
const blockDecl = negDeclBits([
"tagIds",
"strict",
"tagBlock",
"up",
"title",
"desc",
"partitionBlock",
"partition"
], [
"upBlacklist",
"titleKeywords",
"descKeywords",
"partitionBlock",
"tagBlock"
]);
const allowDecl = negDeclBits([
"tagAllow",
"upAllow",
"titleAllow",
"descAllow",
"partitionAllow"
], [
"tagAllow",
"upAllow",
"titleAllow",
"descAllow",
"partitionAllow"
]);
return /* @__PURE__ */ jsxs("div", {
className: "mb-10px b-1px b-gate-border rounded-6px b-solid p-10px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex items-center justify-between",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-13px",
children: "组现状"
}), /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(Badge, {
color: deckState.fg,
text: /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: deckState.text
})
}),
!!deckWhy && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: deckWhy
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-11px",
children: ["权重 ", group.weight]
}),
resolved.intersection && /* @__PURE__ */ jsx(Tag, {
className: "mr-0 text-10px",
children: "交集 · 收窄"
})
]
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "mb-8px",
children: [/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "条件树"
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "(删原子/切命中模式/加条件直接落库;嵌套重组去工作台「编辑条件 ↗」)"
})]
}), /* @__PURE__ */ jsx(TagRecConditionTreeView, {
combination: hasTagRecCombination(group.combination) ? group.combination : void 0,
tagIds: group.tag_ids,
edit: treeEdit
})]
}),
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex flex-wrap items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "生效值"
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-190px",
value: pool,
onChange: setPool,
getPopupContainer,
options: TAG_REC_POOL_KEYS.map((p) => ({
value: p,
label: TAG_REC_POOL_LABELS[p]
}))
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-11px",
onClick: () => setShowAll((v) => !v),
children: showAll ? "只看有声明的" : "显示全部字段"
})
]
}), /* @__PURE__ */ jsxs("table", {
className: "b-collapse w-full text-12px",
children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", {
className: "text-left opacity-60",
children: [
/* @__PURE__ */ jsx("th", {
className: "py-2px font-normal",
children: "字段"
}),
/* @__PURE__ */ jsx("th", {
className: "py-2px font-normal",
children: "生效值"
}),
/* @__PURE__ */ jsx("th", {
className: "py-2px font-normal",
children: "来自"
}),
/* @__PURE__ */ jsx("th", {
className: "py-2px font-normal",
children: "本组声明"
}),
/* @__PURE__ */ jsx("th", { className: "py-2px font-normal" })
]
}) }), /* @__PURE__ */ jsxs("tbody", { children: [
dimRows.map((k) => {
const layer = prov.dims[k] ?? "global";
const sessionOwned = sessionRowProv(layer);
const simple = "value" in resolved.cfg.dims[k];
return /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: TAG_REC_DROP_DIM_LABELS[k] ?? k
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: formatDimValue(k, resolved.cfg.dims[k])
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: /* @__PURE__ */ jsx(LayerBadge, { id: layer })
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: groupDeclares(k)
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: sessionOwned ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "会话改动 · 去过滤弹层"
}) : simple ? /* @__PURE__ */ jsx(DimEditPopover, {
group,
dimKey: k,
eff: resolved.cfg.dims,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-11px",
children: "改"
})
}) : /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "下方槽位编辑"
})
})
]
}, k);
}),
!!scoreDeclared && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: "算分参数"
}),
/* @__PURE__ */ jsxs("td", {
className: "py-3px opacity-80",
children: [
"权重 ",
Object.keys(prov.scoreWeights).length,
" 项声明 · 锚点 ",
Object.keys(prov.scoreAnchors).length,
" ",
"项声明"
]
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: /* @__PURE__ */ jsx(LayerBadge, { id: prov.scoreWeights[Object.keys(prov.scoreWeights)[0]] ?? "global" })
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: group.overrides?.score ? "本组" : "—"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "下方槽位编辑"
})
})
]
}),
(showAll || prov.smallPoolThreshold !== "global") && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: "小池阈值"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: resolved.cfg.smallPoolThreshold
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: /* @__PURE__ */ jsx(LayerBadge, { id: prov.smallPoolThreshold })
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: group.overrides?.pools?.[pool]?.smallPoolThreshold !== void 0 ? "格" : group.overrides?.smallPoolThreshold !== void 0 ? "本组" : "—"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: sessionRowProv(prov.smallPoolThreshold) ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "会话改动 · 去过滤弹层"
}) : /* @__PURE__ */ jsx(Popover, {
trigger: "click",
content: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-6px text-12px",
children: [/* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
min: 0,
value: resolved.cfg.smallPoolThreshold,
onChange: (v) => {
if (typeof v !== "number") return;
const o = group.overrides ??= {};
o.smallPoolThreshold = v;
}
}), /* @__PURE__ */ jsx("span", {
className: "opacity-60",
children: "写入本组 overrides"
})]
}),
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-11px",
children: "改"
})
})
})
]
}),
(showAll || prov.poolsEnabled[pool] !== "derived" || !resolved.poolsEnabled[pool]) && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsxs("td", {
className: "py-3px",
children: [
"本池入抽 (",
TAG_REC_POOL_LABELS[pool],
")"
]
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: resolved.poolsEnabled[pool] ? "开" : "关"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: /* @__PURE__ */ jsx(LayerBadge, { id: prov.poolsEnabled[pool] })
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: group.overrides?.poolsEnabled?.[pool] !== void 0 ? "本组" : "—"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: sessionRowProv(prov.poolsEnabled[pool]) ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "会话改动 · 去过滤弹层"
}) : /* @__PURE__ */ jsx(Switch, {
size: "small",
checked: group.overrides?.poolsEnabled?.[pool] ?? resolved.poolsEnabled[pool],
onChange: (v) => {
const o = group.overrides ??= {};
const pe = o.poolsEnabled ??= {};
pe[pool] = v;
}
})
})
]
}),
(prov.enabled !== "global" || prov.strictMode !== "global") && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: "过滤总开关 / 严格模式"
}),
/* @__PURE__ */ jsxs("td", {
className: "py-3px",
children: [
resolved.cfg.enabled ? "开" : "关",
" / ",
resolved.cfg.strictMode ? "开" : "关"
]
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: /* @__PURE__ */ jsx(LayerBadge, { id: sessionRowProv(prov.enabled) ? prov.enabled : prov.strictMode })
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: "—"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "会话/全局层字段 · 组层无槽位"
})
})
]
}),
!!blockBits.length && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: "屏蔽名单"
}),
/* @__PURE__ */ jsxs("td", {
className: "py-3px",
children: [blockBits.join(" · "), neg.strict && /* @__PURE__ */ jsx(Tag, {
className: "ml-4px mr-0 text-10px",
children: "严格"
})]
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: blockScopes.join(" / ")
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: blockDecl
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "下方槽位/负向管理"
})
})
]
}),
!!allowBits.length && /* @__PURE__ */ jsxs("tr", {
className: "b-t-1px b-t-gate-border b-t-solid",
children: [
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: "仅看名单"
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px",
children: allowBits.join(" · ")
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: allowScopes.join(" / ")
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px opacity-80",
children: allowDecl
}),
/* @__PURE__ */ jsx("td", {
className: "py-3px text-right",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "下方槽位/负向管理"
})
})
]
}),
!dimRows.length && !scoreDeclared && prov.smallPoolThreshold === "global" && !blockBits.length && !allowBits.length && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", {
colSpan: 5,
className: "py-6px opacity-60",
children: "本组及上层均无声明 —— 全部继承出厂全局。"
}) })
] })]
})] })
]
});
}
function TagRecGroupConfigModal({ open, target, onClose, getPopupContainer }) {
if (!open || !target) return null;
const core = settings.tagRec.filter;
if (target.kind === "l2") {
const group = favorites.list.find((g) => g.id === target.groupId);
if (!group) return null;
const l1 = group.l1Id ? favoritesL1.list.find((l) => l.id === group.l1Id) : void 0;
return /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `标签组「${group.name}」现状与配置`,
hint: hasTagRecCombination(group.combination) ? "上方为组现状(条件树 + 生效值 + 抽牌状态);本弹窗下方槽位编辑器管理本组覆盖与辅助名单。条件树的嵌套重组在工作台「编辑条件 ↗」。" : "上方为组现状(条件树 + 生效值 + 抽牌状态);下方槽位编辑器管理本组覆盖与辅助名单。",
statusContent: /* @__PURE__ */ jsx(TagRecGroupStatusPanel, {
groupId: group.id,
getPopupContainer
}),
core,
presets: settings.tagRec.filter.presets,
slots: [{
key: "group",
label: "整组 (各池共用)"
}, ...TAG_REC_POOL_KEYS.map((p) => ({
key: `cell:${p}`,
label: `格 · ${TAG_REC_POOL_LABELS[p]}`,
cell: true
}))],
resolve: (key) => resolveTagRecEffective(core, key === "group" ? "tagTop" : key.slice(5), group, l1).cfg,
resolveBase: (key) => resolveTagRecEffective(core, key === "group" ? "tagTop" : key.slice(5), stripTagRecGroupSlot(group, key), l1).cfg,
storedOverrideOf: (key) => pickTagRecScopePatch(key === "group" ? group.overrides : group.overrides?.pools?.[key.slice(5)]),
negativeOf: (key) => key === "group" ? group.overrides?.negative : void 0,
storedPoolsEnabledOf: (key) => key === "group" ? group.overrides?.poolsEnabled : void 0,
resolvePoolsEnabled: (key) => resolveTagRecEffective(core, key === "group" ? "tagTop" : key.slice(5), group, l1).poolsEnabled,
basePoolsEnabled: (key) => resolveTagRecEffective(core, key === "group" ? "tagTop" : key.slice(5), stripTagRecGroupSlot(group, key), l1).poolsEnabled,
onSave: (entries) => {
const next = applyTagRecScopeEntriesToGroupOverrides(group.overrides, entries);
if (next) favorites.updateGroup(group.id, { overrides: next });
else delete group.overrides;
toast("已保存组配置");
},
onClose,
getPopupContainer
});
}
const l1 = favoritesL1.list.find((l) => l.id === target.l1Id);
if (!l1) return null;
const chainGroup = () => tagRecL1ChainGroup(l1, favorites.list);
const strippedL1 = {
...l1,
overrides: void 0
};
return /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `一级组「${l1.name}」配置`,
core,
presets: settings.tagRec.filter.presets,
slots: [{
key: "l1",
label: "一级组"
}],
resolve: () => resolveTagRecEffective(core, "tagTop", chainGroup(), l1).cfg,
resolveBase: () => resolveTagRecEffective(core, "tagTop", chainGroup(), strippedL1).cfg,
storedOverrideOf: () => pickTagRecScopePatch(l1.overrides),
negativeOf: () => l1.overrides?.negative,
storedPoolsEnabledOf: () => l1.overrides?.poolsEnabled,
resolvePoolsEnabled: () => resolveTagRecEffective(core, "tagTop", chainGroup(), l1).poolsEnabled,
basePoolsEnabled: () => resolveTagRecEffective(core, "tagTop", chainGroup(), strippedL1).poolsEnabled,
onSave: (entries) => {
const next = applyTagRecScopeEntriesToL1Overrides(l1.overrides, entries);
if (next) favoritesL1.updateL1(l1.id, { overrides: next });
else delete l1.overrides;
toast("已保存组配置");
},
onClose,
getPopupContainer
});
}
/**
* [tag-rec] 分享包 filterConfig 编辑页签 (tag-share-platform 票26) —— 「我的分享 → 改包内容」内
* - 绑定 = 包内 filterConfig 副本的 valtio proxy (my-shares-modal openEditor 时归一化 + 克隆
* 脱源, 与用户全局 settings.tagRec.filter 解耦); 保存时整档 cloneDeep 写回 payload.filterConfig
* (PATCH 换 payload => 服务端全量重审)
* - 页签 = 维度阈值 (DimsSection) / 算分与小池 (AdvancedSection 含严格模式) / 池级覆盖
* (R6: 池层槽位块 —— 摘要 + ⚙深编弹同款 ScopeConfigEditor + 清空) / 预设 (整套嵌套编辑)
* - 预设应用走 applyTagRecPreset —— 其内部 assignTagRecFilterCore 先 cloneDeep 再逐键写,
* 天然断 proxy 别名 (core.dims 与 preset.config.dims 不共享底层对象)
* - 写入面约束 (服务端防护档): 预设名/名单词串长 ≤200, preset.config 最深嵌套 ≤11 层
*/
/** 预设整套覆盖包内当前配置 + activePreset 跟随 (直用全局档同一引擎 —— assign 内 cloneDeep 断别名) */
var applySharePkgPreset = applyTagRecPreset;
function TagRecShareFilterEditor({ filter, disabled: outerDisabled }) {
const snap = useSnapshot(filter);
const disabled = outerDisabled || !snap.enabled;
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "总开关"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: snap.enabled,
disabled: outerDisabled,
onChange: (v) => filter.enabled = v
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "关闭 = 完全不过滤 (等同预设「全放」, 负向过滤同失效)"
})
]
}), /* @__PURE__ */ jsx(Tabs, {
size: "small",
items: [
{
key: "dims",
label: "维度阈值",
children: /* @__PURE__ */ jsx(DimsSection, {
core: filter,
snap,
disabled
})
},
{
key: "score",
label: "算分与高级",
children: /* @__PURE__ */ jsx(AdvancedSection, {
core: filter,
snap,
disabled
})
},
{
key: "pools",
label: "池级覆盖",
children: /* @__PURE__ */ jsx(PoolsPane, {
filter,
disabled
})
},
{
key: "presets",
label: `预设 (${snap.presets.length})`,
children: /* @__PURE__ */ jsx(PresetsPane, {
filter,
disabled
})
}
]
})]
});
}
/** 任意 core 形状的编辑面 (包内顶层档 / 预设内嵌档共用): 总开关 + 维度 + 算分 */
function CoreEditor({ core, disabled }) {
const snap = useSnapshot(core);
const dimDisabled = disabled || !snap.enabled;
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-6px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "text-12px font-bold",
children: "过滤开关"
}), /* @__PURE__ */ jsx(Switch, {
size: "small",
checked: snap.enabled,
disabled,
onChange: (v) => core.enabled = v
})]
}),
/* @__PURE__ */ jsx(DimsSection, {
core,
snap,
disabled: dimDisabled
}),
/* @__PURE__ */ jsx(AdvancedSection, {
core,
snap,
disabled: dimDisabled
}),
/* @__PURE__ */ jsx(TagRecPoolSlotsBlock, {
core,
disabled: dimDisabled
})
]
});
}
/** 池级覆盖 tab: 复用池层槽位块 (摘要 + ⚙ 深编弹 ScopeConfigEditor + 清空回继承) —— R6 起包内可逐槽细编 */
function PoolsPane({ filter, disabled }) {
return /* @__PURE__ */ jsx(TagRecPoolSlotsBlock, {
core: filter,
disabled
});
}
var PRESET_CUSTOM_VALUE = "__custom__";
/** 预设 tab: 激活档选择 (应用=整套覆盖) + 每条预设的改名/删除/恢复出厂/嵌套配置编辑 + 存当前为新预设 */
function PresetsPane({ filter, disabled }) {
const snap = useSnapshot(filter);
const activeIsPreset = snap.presets.some((p) => p.name === snap.activePreset);
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-6px",
children: [
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "激活预设"
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-170px",
disabled,
options: [...snap.presets.map((p) => ({
value: p.name,
label: p.builtin ? p.name : `${p.name} (自定义)`
})), {
value: PRESET_CUSTOM_VALUE,
label: "自定义 (无归属)"
}],
value: activeIsPreset ? snap.activePreset : PRESET_CUSTOM_VALUE,
onChange: (v) => {
if (v === PRESET_CUSTOM_VALUE) filter.activePreset = "";
else if (applySharePkgPreset(filter, v)) toast(`已按预设「${v}」覆盖当前包内配置`);
}
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "选预设 = 整套覆盖当前维度/算分; 「自定义」只摘归属不改配置"
})
]
}),
snap.presets.map((p, i) => /* @__PURE__ */ jsx(PresetCard, {
filter,
index: i,
active: snap.activePreset === p.name,
disabled
}, `${i}:${p.name}`)),
!snap.presets.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "包内没有预设 —— 可用下方「存为预设」把当前配置沉淀一条"
}),
/* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "w-280px",
placeholder: "存当前配置为预设 (同名覆盖; 内置档保内置标)",
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
disabled,
enterButton: "存为预设",
onSearch: (raw) => {
const name = raw.trim();
if (!name) return;
const existing = filter.presets.find((p) => p.name === name);
upsertTagRecPreset(filter, {
name,
builtin: existing?.builtin,
config: cloneTagRecFilterCore(filter)
});
filter.activePreset = name;
toast(`已存为预设「${name}」`);
}
})
]
});
}
/** 单条预设卡: 名 + 内置/激活标 + 应用/存当前覆盖/改名(自定义)/恢复出厂(内置)/删除(自定义)/展开配置编辑 */
function PresetCard({ filter, index, active, disabled }) {
const preset = filter.presets[index];
const snap = useSnapshot(preset);
const [editing, setEditing] = (0, import_react.useState)(false);
const [renaming, setRenaming] = (0, import_react.useState)(false);
return /* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-dashed px-8px py-6px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px gap-y-4px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-13px",
children: snap.name
}),
snap.builtin && /* @__PURE__ */ jsx(Tag, {
className: "mr-0",
children: "内置"
}),
active && /* @__PURE__ */ jsx(Tag, {
color: "green",
className: "mr-0",
children: "激活"
}),
/* @__PURE__ */ jsxs("span", {
className: "ml-auto inline-flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => {
if (applySharePkgPreset(filter, snap.name)) toast(`已按预设「${snap.name}」覆盖当前包内配置`);
},
children: "应用"
}),
/* @__PURE__ */ jsx(Popconfirm, {
title: `把当前包内配置存入预设「${snap.name}」?`,
description: "预设快照被覆写 (同名覆盖语义)",
onConfirm: () => {
upsertTagRecPreset(filter, {
name: snap.name,
builtin: snap.builtin || void 0,
config: cloneTagRecFilterCore(filter)
});
toast(`已把当前配置存入预设「${snap.name}」`);
},
children: /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
disabled,
children: "存当前覆盖"
})
}),
!snap.builtin && /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
disabled,
onClick: () => setRenaming((v) => !v),
children: renaming ? "收起改名" : "改名"
}),
snap.builtin && /* @__PURE__ */ jsx(Popconfirm, {
title: `恢复内置预设「${snap.name}」为出厂配置?`,
onConfirm: () => {
if (restoreTagRecBuiltinPreset(filter, snap.name)) toast(`已恢复出厂「${snap.name}」`);
},
children: /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
disabled,
children: "恢复出厂"
})
}),
!snap.builtin && /* @__PURE__ */ jsx(Popconfirm, {
title: `删除预设「${snap.name}」?`,
onConfirm: () => removeTagRecPreset(filter, snap.name),
children: /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
danger: true,
className: "h-auto px-0 text-12px",
disabled,
children: "删"
})
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => setEditing((v) => !v),
children: editing ? "收起配置" : "编辑配置"
})
]
})
]
}),
renaming && !snap.builtin && /* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "mt-4px w-260px",
defaultValue: snap.name,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
enterButton: "改名",
onSearch: (v) => {
if (renameTagRecPreset(filter, snap.name, v)) {
toast(`已改名为「${v.trim()}」`);
setRenaming(false);
} else toast("改名失败: 空名 / 与既有预设重名");
}
}),
editing && /* @__PURE__ */ jsx("div", {
className: "mt-6px",
children: /* @__PURE__ */ jsx(CoreEditor, {
core: preset.config,
disabled
})
})
]
});
}
/**
* [tag-rec] 「我的分享」本地记账管理 Modal (tag-share-platform spec §5 / 票 17)
* - 数据源 = GM `tag-rec:share-tokens` 账本 (shareId → {token,title,createdAt}); 「我的分享」无服务端端点
* - 每条操作: 打开分享页 (网站详情链接) / 改标题描述 (行内编辑 → PATCH X-Mgmt-Token) / 删除 (二次确认 → DELETE)
* - reconcile 轻量档: 打开时对每条带 token 的账目 getShareDetail 探 status (并发 ≤3, 失败静默不阻塞);
* removed => 整行标灰「已下架」, 404 => 「服务端查无」标 (不存在或 token 失效且非 published 同态)
* - 账本畸形条目 (token 缺失) => 「失权」标, 只可读 (打开分享页) 不可 PATCH/DELETE
* - 401/403 => 「管理 token 失效」提示; PATCH 404/409 / DELETE 404 => 询问是否摘账 (forgetShareToken)
* - 底部常显「清空插件数据将同时失去全部管理 token, 不可找回」(spec §9 GM 清空 = 管理权同失)
* - 票31 (tag-share): focusShareId = 「更新原分享」深链位 (分享上传 dup-check 命中行入口);
* 打开时该 shareId 在账本且持 token => 自动展开其「改包内容」编辑器定位; 查无/失权 => 只开列表
*/
function TagRecMySharesModal({ open, onClose, focusShareId }) {
/** GM 账本原图 (undefined = 读取中); 本地操作成功后同步改此图避免整表重读 */
const [tokenMap, setTokenMap] = (0, import_react.useState)(void 0);
/** reconcile 探测表 shareId => remote (unprobed 不落表 —— 失败静默) */
const [probes, setProbes] = (0, import_react.useState)({});
const [editId, setEditId] = (0, import_react.useState)();
const [editMode, setEditMode] = (0, import_react.useState)("text");
const [editTitle, setEditTitle] = (0, import_react.useState)("");
const [editDesc, setEditDesc] = (0, import_react.useState)("");
const [editDescKnown, setEditDescKnown] = (0, import_react.useState)(false);
const [editPkg, setEditPkg] = (0, import_react.useState)(void 0);
/** R6: 包内组/L1 深编弹层目标 (复用收藏夹同款 ScopeConfigEditor, 草稿写回 raw.overrides) */
const [deepEdit, setDeepEdit] = (0, import_react.useState)(null);
const [editPkgFailed, setEditPkgFailed] = (0, import_react.useState)(false);
/** 票26 过滤配置编辑态: cfg=归一化后 proxy 副本 (编辑面绑定); base=归一化原值
* (保存时 isEqual 比 cfg vs base —— 没动过就不写回, 原包内字段 (含未知键) 原样透传) */
const [editFilter, setEditFilter] = (0, import_react.useState)();
const [editBusy, setEditBusy] = (0, import_react.useState)(false);
const [editIssues, setEditIssues] = (0, import_react.useState)([]);
const [busyIds, setBusyIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
const editIdRef = (0, import_react.useRef)(void 0);
editIdRef.current = editId;
const focusIdRef = (0, import_react.useRef)(void 0);
focusIdRef.current = focusShareId;
const rows = tokenMap === void 0 ? void 0 : buildMyShareRows(tokenMap, probes);
/** 包编辑态派生: 被组引用中的 L1 id 集 (「删」按钮阻塞判据 + 与 buildSharePkgPayload 空壳裁剪同口径) */
const editUsedL1Ids = new Set((editPkg?.groups ?? []).map((g) => g.l1Id).filter((x) => !!x));
const closeEditor = () => {
setEditId(void 0);
setEditIssues([]);
setEditBusy(false);
setEditPkg(void 0);
setEditPkgFailed(false);
setEditFilter(void 0);
};
/** 本地摘账 (GM + 视图同步); 正在编辑该条 => 收起编辑器 */
const dropRow = (shareId) => {
forgetShareToken(shareId);
setTokenMap((prev) => {
if (!prev || !(shareId in prev)) return prev;
const next = { ...prev };
delete next[shareId];
return next;
});
setProbes((prev) => {
if (!(shareId in prev)) return prev;
const next = { ...prev };
delete next[shareId];
return next;
});
if (editIdRef.current === shareId) closeEditor();
};
(0, import_react.useEffect)(() => {
if (!open) return;
setTokenMap(void 0);
setProbes({});
setBusyIds(/* @__PURE__ */ new Set());
closeEditor();
let cancelled = false;
(async () => {
const map = await readShareTokens();
if (cancelled) return;
setTokenMap(map);
const focusId = focusIdRef.current;
if (focusId) {
const row = buildMyShareRows(map, {}).find((r) => r.shareId === focusId);
if (row?.token) openEditor(row, "payload");
}
const probees = normalizeShareTokenEntries(map).filter((e) => e.token);
let idx = 0;
const run = async () => {
for (;;) {
const e = probees[idx++];
if (!e || cancelled || !e.token) return;
const res = await getShareDetail(e.shareId, { mgmtToken: e.token });
if (cancelled) return;
const remote = probeOutcomeToRemote(res.kind, res.kind === "ok" ? res.detail.status : void 0);
if (remote === "unprobed") continue;
setProbes((prev) => ({
...prev,
[e.shareId]: remote
}));
}
};
await Promise.all([
run(),
run(),
run()
]);
})();
return () => {
cancelled = true;
};
}, [open]);
/** 行内编辑展开: 预填本地记账标题; 远端 title/description/payload best-effort 回填 (成功 => 所见即所得) */
const openEditor = (row, mode) => {
setEditId(row.shareId);
setEditMode(mode);
setEditTitle(row.title);
setEditDesc("");
setEditDescKnown(false);
setEditPkg(void 0);
setEditPkgFailed(false);
setEditFilter(void 0);
setEditIssues([]);
if (!row.token) return;
getShareDetail(row.shareId, { mgmtToken: row.token }).then((res) => {
if (editIdRef.current !== row.shareId) return;
if (res.kind !== "ok") {
if (mode === "payload") setEditPkgFailed(true);
return;
}
setEditTitle(res.detail.title);
setEditDesc(res.detail.description ?? "");
setEditDescKnown(true);
const pkg = parseSharePkgForEdit(res.detail.payload) ?? null;
setEditPkg(pkg);
if (pkg) {
const base = normalizeTagRecFilterSettings(pkg.base.filterConfig) ?? createShareFactoryFilterConfig();
setEditFilter({
cfg: proxy(cloneDeep(base)),
base
});
} else setEditFilter(void 0);
});
};
/** PATCH 保存: 成功后本地记账标题同步 (rememberShareToken 覆写); 401/404/409 分态提示 */
const saveEdit = async (row) => {
if (!row.token || editBusy) return;
const body = buildSharePatchBody({
title: editTitle,
description: editDescKnown || editDesc.trim() ? editDesc : void 0
});
if (!body) {
toast(`标题必填且不超过 ${SHARE_GUARD_LIMITS.MAX_STRING_LEN} 字`, 4e3);
return;
}
setEditBusy(true);
setEditIssues([]);
try {
const res = await patchShare(row.shareId, row.token, body);
if (res.kind === "ok") {
const title = res.title ?? body.title ?? row.title;
rememberShareToken(row.shareId, {
token: row.token,
title,
createdAt: row.createdAt
});
setTokenMap((prev) => prev && {
...prev,
[row.shareId]: {
token: row.token,
title,
createdAt: row.createdAt
}
});
closeEditor();
toast(`已更新「${title}」`);
return;
}
if (res.kind === "rejected") {
setEditIssues(res.issues);
return;
}
if (res.kind === "unauthorized") {
toast("管理 token 失效 (服务端记录已删或插件数据被清), 无法修改", 5e3);
return;
}
if (res.kind === "not-found" || res.kind === "conflict") {
if (await antModal.confirm({
centered: true,
title: res.kind === "not-found" ? "服务端已不存在该分享" : "分享已在服务端下架",
content: "本地记账只剩管理凭据, 是否移除这条记录?",
okText: "移除记录"
})) dropRow(row.shareId);
return;
}
toast(`修改失败: ${res.message}`, 5e3);
} finally {
setEditBusy(false);
}
};
/** PATCH 包内容: 仅携 {payload} —— 服务端防护档校验 + 全量重审;
* 只走结构化档 (buildSharePkgPayload 重建, l1/themes 按剩余组引用精简) —— 不开手改 JSON 口子;
* 保存前拦: 每组 ≥1 正向 tag (服务端语义档同拒) / L1·主题名非空 (空名会让包过不了导入侧校验);
* ok.status==='pending' => 审核降级属正常 (文案提示); rejected 逐条列; 401/404/409 同 saveEdit 分态 */
const savePayload = async (row) => {
if (!row.token || editBusy || !editPkg) return;
if (!editPkg.groups.length) {
toast("包内至少要留 1 个标签组 (想全删请用「删除」)", 4e3);
return;
}
const emptyGroup = editPkg.groups.find((g) => !hasSharePkgGroupContent(g));
if (emptyGroup) {
toast(`组「${emptyGroup.name || "未命名"}」没有内容条件,请添加条件或删掉该组`, 4e3);
return;
}
if (editPkg.l1.some((l) => !l.name.trim())) {
toast("一级组名不能为空", 4e3);
return;
}
if (editPkg.themes.some((t) => !t.name.trim())) {
toast("主题名不能为空", 4e3);
return;
}
let payload;
try {
payload = buildSharePkgPayload(editPkg);
} catch (error) {
toast(error instanceof Error ? error.message : "内容条件不合法,请检查后再保存", 4e3);
return;
}
if (editFilter) {
const edited = cloneDeep(editFilter.cfg);
if (!isEqual$1(edited, editFilter.base)) payload.filterConfig = edited;
}
setEditBusy(true);
setEditIssues([]);
try {
const res = await patchShare(row.shareId, row.token, { payload });
if (res.kind === "ok") {
closeEditor();
toast(res.status === "pending" ? "已更新, 正重走审核 (pending), 稍后自动上架" : "已更新包内容");
return;
}
if (res.kind === "rejected") {
setEditIssues(res.issues);
return;
}
if (res.kind === "unauthorized") {
toast("管理 token 失效 (服务端记录已删或插件数据被清), 无法修改", 5e3);
return;
}
if (res.kind === "not-found" || res.kind === "conflict") {
if (await antModal.confirm({
centered: true,
title: res.kind === "not-found" ? "服务端已不存在该分享" : "分享已在服务端下架",
content: "本地记账只剩管理凭据, 是否移除这条记录?",
okText: "移除记录"
})) dropRow(row.shareId);
return;
}
toast(`修改失败: ${res.message}`, 5e3);
} finally {
setEditBusy(false);
}
};
/** DELETE: 二次确认 → 软删 + 摘账; 404 (服务端已被下架/不存在) 同样摘账并提示 */
const onDelete = async (row) => {
if (!row.token || busyIds.has(row.shareId)) return;
if (!await antModal.confirm({
centered: true,
title: `删除分享「${row.title || row.shareId}」?`,
content: "服务端下架该分享 (不可恢复), 并从本地记账移除",
okText: "删除",
okButtonProps: { danger: true }
})) return;
setBusyIds((prev) => new Set(prev).add(row.shareId));
try {
const res = await deleteShare(row.shareId, row.token);
if (res.kind === "ok") {
dropRow(row.shareId);
toast("已删除分享");
return;
}
if (res.kind === "not-found") {
dropRow(row.shareId);
toast("服务端已下架/不存在, 已摘账", 4e3);
return;
}
if (res.kind === "unauthorized") {
toast("管理 token 失效 (服务端记录已删或插件数据被清), 无法删除", 5e3);
return;
}
toast(`删除失败: ${res.message}`, 5e3);
} finally {
setBusyIds((prev) => {
const next = new Set(prev);
next.delete(row.shareId);
return next;
});
}
};
/** 结构化包编辑的小动作 (不可变更新 editPkg) */
const patchGroup = (gi, patch) => setEditPkg((prev) => prev && {
...prev,
groups: prev.groups.map((g, i) => i === gi ? {
...g,
...patch
} : g)
});
const removeGroup = (gi) => setEditPkg((prev) => prev && {
...prev,
groups: prev.groups.filter((_, i) => i !== gi)
});
const removeTag = (gi, key, id) => setEditPkg((prev) => prev && {
...prev,
groups: prev.groups.map((g, i) => i === gi ? {
...g,
[key]: g[key].filter((t) => t.id !== id)
} : g)
});
/** 加 tag: 正向/负向互斥 (构造器同口径 —— 正向优先); 已在同列去重 */
const addTag = (gi, key, entry) => {
setEditPkg((prev) => prev && {
...prev,
groups: prev.groups.map((g, i) => {
if (i !== gi || g[key].some((t) => t.id === entry.id)) return g;
const next = {
...g,
[key]: [...g[key], entry]
};
if (key === "tagIds") next.negIds = next.negIds.filter((t) => t.id !== entry.id);
if (key === "negIds" && next.tagIds.some((t) => t.id === entry.id)) return g;
return next;
})
});
};
const addGroup = () => setEditPkg((prev) => prev && {
...prev,
groups: [...prev.groups, {
id: crypto.randomUUID(),
name: "",
tagIds: [],
negIds: [],
negStrict: false,
weight: 1,
raw: {}
}]
});
const patchL1 = (li, patch) => setEditPkg((prev) => prev && {
...prev,
l1: prev.l1.map((l, i) => i === li ? {
...l,
...patch
} : l)
});
const removeL1 = (li) => setEditPkg((prev) => prev && {
...prev,
l1: prev.l1.filter((_, i) => i !== li)
});
const addL1 = () => setEditPkg((prev) => prev && {
...prev,
l1: [...prev.l1, {
id: crypto.randomUUID(),
name: "",
weight: 1,
raw: {}
}]
});
const patchTheme = (ti, patch) => setEditPkg((prev) => prev && {
...prev,
themes: prev.themes.map((t, i) => i === ti ? {
...t,
...patch
} : t)
});
const removeTheme = (ti) => setEditPkg((prev) => prev && {
...prev,
themes: prev.themes.filter((_, i) => i !== ti),
l1: prev.l1.map((l) => l.themeId === prev.themes[ti]?.id ? {
...l,
themeId: void 0
} : l)
});
const addTheme = () => setEditPkg((prev) => prev && {
...prev,
themes: [...prev.themes, {
id: crypto.randomUUID(),
name: "",
raw: {}
}]
});
const removeBlacklist = (id) => setEditPkg((prev) => prev && {
...prev,
blacklist: prev.blacklist.filter((b) => b.id !== id)
});
const addBlacklist = (entry) => setEditPkg((prev) => prev && {
...prev,
blacklist: prev.blacklist.some((b) => b.id === entry.id) ? prev.blacklist : [...prev.blacklist, entry]
});
const remoteTag = (row) => row.remote === "removed" ? /* @__PURE__ */ jsx(Tag, { children: "已下架" }) : row.remote === "pending" ? /* @__PURE__ */ jsx(Tag, {
color: "gold",
children: "审核中"
}) : row.remote === "gone" ? /* @__PURE__ */ jsx(Tag, {
color: "warning",
children: "服务端查无"
}) : null;
return /* @__PURE__ */ jsxs(Modal, {
title: "我的分享",
open,
onCancel: onClose,
footer: null,
width: 560,
children: [
rows === void 0 ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-13px",
children: "读取本地记账中…"
}) : rows.length === 0 ? /* @__PURE__ */ jsx(Alert, {
type: "info",
showIcon: true,
title: "还没有分享",
description: "去「标签收藏表」勾选标签组, 点吸顶操作条上的「分享选中」即可上传"
}) : /* @__PURE__ */ jsx("div", {
className: "max-h-60vh overflow-y-auto",
children: rows.map((row) => {
const manageable = !!row.token;
const displayTitle = row.title || "(未命名分享)";
return /* @__PURE__ */ jsxs("div", {
className: `mb-8px b-1px b-gate-border rounded-6px b-solid px-10px py-8px ${row.remote === "removed" ? "opacity-50" : ""}`,
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-baseline justify-between gap-x-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
ellipsis: { tooltip: displayTitle },
className: "text-14px",
children: displayTitle
}), /* @__PURE__ */ jsx(Typography.Link, {
className: "shrink-0",
title: "打开分享详情页 (不携管理 token —— W6-7b: #key= 明文上会进浏览器历史/代理日志)",
href: buildSharePageUrl(TAG_REC_SHARE_SITE_BASE, row.shareId),
target: "_blank",
children: "打开分享页"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-2px flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px font-mono",
children: row.shareId
}),
row.createdAt > 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: (0, import_dayjs_min.default)(row.createdAt).format("YYYY-MM-DD HH:mm")
}),
remoteTag(row),
/* @__PURE__ */ jsx("span", {
className: "ml-auto inline-flex items-center gap-x-10px",
children: manageable ? /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => {
if (editId === row.shareId && editMode === "text") closeEditor();
else openEditor(row, "text");
},
children: editId === row.shareId && editMode === "text" ? "收起" : "改标题/描述"
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => {
if (editId === row.shareId && editMode === "payload") closeEditor();
else openEditor(row, "payload");
},
children: editId === row.shareId && editMode === "payload" ? "收起" : "改包内容"
}),
/* @__PURE__ */ jsx(Typography.Link, {
className: "text-12px",
title: "网页版编辑: 可改/删字段,暂不能新增 (tag 解析在插件内)",
href: buildShareEditUrl(TAG_REC_SHARE_SITE_BASE, row.shareId, row.token),
target: "_blank",
children: "网页编辑"
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
title: "复制管理 token —— 入群验证/网页编辑手动粘贴用 (凭此 token 可改可删该分享,勿外泄)",
onClick: () => {
GM.setClipboard(row.token);
toast("已复制管理 token");
},
children: "复制 token"
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
danger: true,
className: "h-auto px-0 text-12px",
disabled: busyIds.has(row.shareId),
onClick: () => void onDelete(row),
children: "删除"
})
] }) : /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
title: "账本缺少管理 token (畸形记录), 仅可查看",
children: "失权"
})
})
]
}),
editId === row.shareId && /* @__PURE__ */ jsxs("div", {
className: "mt-8px",
children: [
editMode === "text" ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Input, {
size: "small",
placeholder: "分享标题 (必填)",
value: editTitle,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
showCount: true,
onChange: (e) => setEditTitle(e.target.value)
}), /* @__PURE__ */ jsx(Input.TextArea, {
className: "mt-6px",
size: "small",
placeholder: editDescKnown ? "描述 (留空 = 清空服务端描述)" : "描述 (远端现值未取到, 留空则不动)",
value: editDesc,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
showCount: true,
autoSize: {
minRows: 2,
maxRows: 3
},
onChange: (e) => setEditDesc(e.target.value)
})] }) : /* @__PURE__ */ jsxs(Fragment, { children: [editPkgFailed ? /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "block text-12px",
children: [
"远端包内容拉取失败 (网络/服务错误)",
" ",
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => openEditor(row, "payload"),
children: "重试"
})
]
}) : editPkg === void 0 ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "block text-12px",
children: "远端包内容加载中…"
}) : editPkg === null ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "block text-12px",
children: "远端包内容无法按导出格式 v2 解析, 不支持编辑"
}) : /* @__PURE__ */ jsx(Tabs, {
size: "small",
items: [{
key: "pkg",
label: "分组与黑名单",
children: /* @__PURE__ */ jsxs("div", {
className: "max-h-300px flex flex-col gap-y-6px overflow-y-auto",
children: [
editPkg.groups.map((g, gi) => {
return /* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-solid px-8px py-6px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx(Input, {
size: "small",
value: g.name,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
placeholder: "组名 (空=回落首个 tag 名)",
onChange: (e) => patchGroup(gi, { name: e.target.value })
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "shrink-0 text-12px",
children: [
"正",
g.tagIds.length,
"·负",
g.negIds.length
]
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
danger: true,
className: "h-auto shrink-0 px-0 text-12px",
onClick: () => removeGroup(gi),
children: "删组"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-4px flex items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-110px",
placeholder: "归属一级组",
value: g.l1Id ?? "",
onChange: (v) => patchGroup(gi, { l1Id: v || void 0 }),
options: [{
value: "",
label: "未归属"
}, ...editPkg.l1.map((l) => ({
value: l.id,
label: l.name || "(未命名一级组)"
}))]
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-64px",
title: "权重 (0=挂起)",
value: g.weight,
onChange: (v) => patchGroup(gi, { weight: v }),
options: [
0,
1,
2,
3,
4,
5
].map((w) => ({
value: w,
label: `w${w}`
}))
}),
/* @__PURE__ */ jsx(Checkbox, {
checked: g.negStrict,
onChange: (e) => patchGroup(gi, { negStrict: e.target.checked }),
children: /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "负向严格"
})
}),
/* @__PURE__ */ jsx(Input, {
size: "small",
className: "flex-1",
placeholder: "备注 (可空)",
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
value: g.note ?? "",
onChange: (e) => patchGroup(gi, { note: e.target.value || void 0 })
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
className: "shrink-0",
disabled: !editFilter,
title: "阈值/算分/池格/名单 (同收藏夹组配置)",
onClick: () => setDeepEdit({
kind: "group",
gi
}),
children: "⚙ 配置"
})
]
}),
g.tagIds.length + g.negIds.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-4px flex flex-wrap gap-4px",
children: [g.tagIds.map((t) => /* @__PURE__ */ jsx(Tag, {
closable: true,
onClose: (e) => {
e.preventDefault();
removeTag(gi, "tagIds", t.id);
},
children: t.name || `#${t.id}`
}, `p${t.id}`)), g.negIds.map((t) => /* @__PURE__ */ jsx(Tag, {
closable: true,
color: "error",
onClose: (e) => {
e.preventDefault();
removeTag(gi, "negIds", t.id);
},
children: t.name || `#${t.id}`
}, `n${t.id}`))]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-4px flex items-center gap-x-6px",
children: [/* @__PURE__ */ jsx(TagEntryAdder, {
placeholder: "加正向 tag (名或 #id)",
onAdd: (e) => addTag(gi, "tagIds", e)
}), /* @__PURE__ */ jsx(TagEntryAdder, {
negative: true,
placeholder: "加负向 tag",
onAdd: (e) => addTag(gi, "negIds", e)
})]
})
]
}, g.id);
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
className: "self-start",
onClick: addGroup,
children: "+ 新增标签组"
}),
editPkg.groups.length === 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "组已删空 —— 若想整包下架请改用「删除」"
}),
/* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-dashed px-8px py-6px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"一级组 ",
editPkg.l1.length,
" 个 (仅被组引用才进包; enabled 开关位不携带)"
]
}),
editPkg.l1.map((l, li) => /* @__PURE__ */ jsxs("div", {
className: "mt-4px flex items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx(Input, {
size: "small",
className: "flex-1",
placeholder: "一级组名",
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
value: l.name,
onChange: (e) => patchL1(li, { name: e.target.value })
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-100px",
placeholder: "所属主题",
value: l.themeId ?? "",
onChange: (v) => patchL1(li, { themeId: v || void 0 }),
options: [{
value: "",
label: "未归属"
}, ...editPkg.themes.map((t) => ({
value: t.id,
label: t.name || "(未命名主题)"
}))]
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-58px",
title: "权重",
value: l.weight,
onChange: (v) => patchL1(li, { weight: v }),
options: [
0,
1,
2,
3,
4,
5
].map((w) => ({
value: w,
label: `w${w}`
}))
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
className: "shrink-0",
disabled: !editFilter,
title: "阈值/算分/名单 (同收藏夹一级组配置)",
onClick: () => setDeepEdit({
kind: "l1",
li
}),
children: "⚙"
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
danger: !editUsedL1Ids.has(l.id),
className: "h-auto shrink-0 px-0 text-12px",
disabled: editUsedL1Ids.has(l.id),
title: editUsedL1Ids.has(l.id) ? "被组引用中的一级组不可删 (先把组改挂/删)" : void 0,
onClick: () => removeL1(li),
children: "删"
})
]
}, l.id)),
/* @__PURE__ */ jsx(button_default, {
size: "small",
className: "mt-4px",
onClick: addL1,
children: "+ 新增一级组"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-dashed px-8px py-6px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"主题 ",
editPkg.themes.length,
" 个 (仅被一级组引用才进包)"
]
}),
editPkg.themes.map((t, ti) => /* @__PURE__ */ jsxs("div", {
className: "mt-4px flex items-center gap-x-6px",
children: [/* @__PURE__ */ jsx(Input, {
size: "small",
className: "flex-1",
placeholder: "主题名",
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
value: t.name,
onChange: (e) => patchTheme(ti, { name: e.target.value })
}), /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
danger: true,
className: "h-auto shrink-0 px-0 text-12px",
onClick: () => removeTheme(ti),
children: "删"
})]
}, t.id)),
/* @__PURE__ */ jsx(button_default, {
size: "small",
className: "mt-4px",
onClick: addTheme,
children: "+ 新增主题"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-dashed px-8px py-6px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"附黑名单 ",
editPkg.blacklist.length,
" 条 (点 × 摘除)"
]
}),
editPkg.blacklist.length > 0 && /* @__PURE__ */ jsx("div", {
className: "mt-4px flex flex-wrap gap-4px",
children: editPkg.blacklist.map((b) => /* @__PURE__ */ jsx(Tag, {
closable: true,
onClose: (e) => {
e.preventDefault();
removeBlacklist(b.id);
},
children: b.name || `#${b.id}`
}, `b${b.id}`))
}),
/* @__PURE__ */ jsx(TagEntryAdder, {
placeholder: "加黑名单 tag (名或 #id)",
onAdd: addBlacklist
})
]
})
]
})
}, {
key: "filter",
label: "过滤配置",
children: editFilter ? /* @__PURE__ */ jsx("div", {
className: "max-h-300px overflow-y-auto pr-4px",
children: /* @__PURE__ */ jsx(TagRecShareFilterEditor, { filter: editFilter.cfg })
}) : /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "包内容未载入"
})
}]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-4px block text-12px",
children: "组/L1/主题/黑名单全可增删改; 未引用的空壳自动裁; 过滤配置页签可整套编辑; 保存 = 整包重审"
})] }),
editIssues.length > 0 && /* @__PURE__ */ jsx(Alert, {
type: "error",
className: "mt-6px",
title: "未通过, 请修改后重试",
description: /* @__PURE__ */ jsx("ul", {
className: "m-0 list-disc pl-18px text-12px",
children: editIssues.map((it, i) => /* @__PURE__ */ jsxs("li", { children: [
it.field ? `${it.field}: ` : "",
it.reason,
it.word ? ` (命中词: ${it.word})` : "",
it.category ? ` (类目: ${it.category})` : ""
] }, i))
})
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-6px flex justify-end gap-x-8px",
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: closeEditor,
disabled: editBusy,
children: "取消"
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
loading: editBusy,
disabled: editMode === "text" ? !editTitle.trim() : !editPkg || !editPkg.groups.length,
onClick: () => void (editMode === "text" ? saveEdit(row) : savePayload(row)),
children: "保存"
})]
})
]
})
]
}, row.shareId);
})
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "warning",
className: "mt-10px block text-12px",
children: "清空插件数据将同时失去全部管理 token, 不可找回"
}),
deepEdit && editPkg && editFilter && /* @__PURE__ */ jsx(PkgScopeEditor, {
editPkg,
pkgCore: editFilter.cfg,
deepEdit,
setEditPkg,
onClose: () => setDeepEdit(null)
})
]
});
}
/**
* tag 录入小组件 (包编辑「加正向/负向/黑名单」共用):
* 名 → fetchTagInfoByName; `#123`/纯数字 → fetchTagInfoById; 未命中 toast, 命中回传 {id,name}
*/
function TagEntryAdder({ onAdd, placeholder, negative }) {
const [text, setText] = (0, import_react.useState)("");
const [busy, setBusy] = (0, import_react.useState)(false);
const submit = async () => {
const t = text.trim();
if (!t || busy) return;
setBusy(true);
try {
const m = t.match(/^#?(\d+)$/);
const info = m ? await fetchTagInfoById(Number(m[1])) : await fetchTagInfoByName(t);
if (!info || typeof info.id !== "number") {
toast(`未找到标签「${t}」`, 3e3);
return;
}
onAdd({
id: info.id,
name: info.name
});
setText("");
} catch {
toast("请求失败(可能是网络/风控),请稍后重试", 4e3);
} finally {
setBusy(false);
}
};
return /* @__PURE__ */ jsxs("span", {
className: "inline-flex flex-1 items-center gap-x-4px",
children: [/* @__PURE__ */ jsx(Input, {
size: "small",
placeholder: placeholder ?? "加 tag (名或 #id)",
value: text,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
disabled: busy,
onChange: (e) => setText(e.target.value),
onPressEnter: () => void submit()
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
className: "shrink-0",
loading: busy,
danger: negative,
onClick: () => void submit(),
children: negative ? "加负向" : "加"
})]
});
}
/**
* [tag-rec] R6: 包内组/L1 深编适配器 —— 复用收藏夹同款 ScopeConfigEditor (真·同款控件)
* - 基底 core = 包内 filterConfig 编辑副本 (editFilter.cfg); L1 链 = 包内一级组
* - 生效解/差分基准: raw.overrides 合成 TagGroup/TagL1Group 走 resolveTagRecEffective 同一链
* (包内未知键随 raw 保留; tagIds 不合成 —— 顶层 negative_tag_ids 喂链, 写回折叠口径不变)
* - 存盘写回 editPkg 草稿的 raw.overrides: 负向 tagIds/strict 折叠编辑模型 (与 buildSharePkgPayload 同口径);
* L1 名单容器直存 overrides (L1 无顶层负向别名)
* - 纯内存草稿, 零请求面; 实际持久化在「保存」(PATCH) 时整包提交
*/
function PkgScopeEditor({ editPkg, pkgCore, deepEdit, setEditPkg, onClose }) {
/** 包组 → TagGroup 形 (生效链求解用): 负向双源合成 strict; tagIds 恒走顶层 negative_tag_ids */
const toTagGroup = (g) => {
const src = typeof g.raw.overrides === "object" && g.raw.overrides !== null ? g.raw.overrides : {};
const overrides = cloneDeep(src);
if (overrides.negative) {
const n = { ...overrides.negative };
delete n.tagIds;
n.strict = g.negStrict || void 0;
if (Object.keys(n).length) overrides.negative = n;
else delete overrides.negative;
} else if (g.negStrict) overrides.negative = { strict: true };
const toEntry = (t) => ({
id: t.id,
name: t.name ?? ""
});
return {
id: g.id,
name: g.name,
l1Id: g.l1Id,
tag_ids: g.tagIds.map(toEntry),
negative_tag_ids: g.negIds.map(toEntry),
negative_strict: g.negStrict || void 0,
overrides: Object.keys(overrides).length ? overrides : void 0,
weight: g.weight,
enabled: true,
addedAt: 0
};
};
const toTagL1 = (l) => {
const src = typeof l.raw.overrides === "object" && l.raw.overrides !== null ? l.raw.overrides : void 0;
return {
id: l.id,
name: l.name,
themeId: l.themeId,
weight: l.weight,
enabled: true,
addedAt: 0,
overrides: src ? cloneDeep(src) : void 0
};
};
const pkgGroups = editPkg.groups.map(toTagGroup);
const saveGroupEntries = (gi, entries) => setEditPkg((prev) => {
if (!prev) return prev;
return {
...prev,
groups: prev.groups.map((g, i) => {
if (i !== gi) return g;
const prevOvr = typeof g.raw.overrides === "object" && g.raw.overrides !== null ? g.raw.overrides : void 0;
let next = applyTagRecScopeEntriesToGroupOverrides(prevOvr, entries);
const negEntry = entries.find((e) => e.key === "group")?.negative;
const negStrict = negEntry ? !!negEntry.strict : g.negStrict;
if (next?.negative) {
const n = { ...next.negative };
delete n.tagIds;
delete n.strict;
next = { ...next };
if (Object.keys(n).length) next.negative = n;
else delete next.negative;
if (!Object.keys(next).length) next = void 0;
}
const raw = { ...g.raw };
if (next) raw.overrides = next;
else delete raw.overrides;
return {
...g,
negStrict,
raw
};
})
};
});
const saveL1Entries = (li, entries) => setEditPkg((prev) => {
if (!prev) return prev;
return {
...prev,
l1: prev.l1.map((l, i) => {
if (i !== li) return l;
const prevOvr = typeof l.raw.overrides === "object" && l.raw.overrides !== null ? l.raw.overrides : void 0;
const next = applyTagRecScopeEntriesToL1Overrides(prevOvr, entries);
const raw = { ...l.raw };
if (next) raw.overrides = next;
else delete raw.overrides;
return {
...l,
raw
};
})
};
});
if (deepEdit.kind === "group") {
const g = editPkg.groups[deepEdit.gi];
if (!g) return null;
const tg = toTagGroup(g);
const tl = g.l1Id ? editPkg.l1.find((l) => l.id === g.l1Id) : void 0;
const tagL1 = tl ? toTagL1(tl) : void 0;
const poolOf = (key) => key === "group" ? "tagTop" : key.slice(5);
return /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `包内组「${g.name}」配置`,
core: pkgCore,
presets: pkgCore.presets,
slots: [{
key: "group",
label: "整组 (各池共用)"
}, ...TAG_REC_POOL_KEYS.map((p) => ({
key: `cell:${p}`,
label: `格 · ${TAG_REC_POOL_LABELS[p]}`,
cell: true
}))],
resolve: (key) => resolveTagRecEffective(pkgCore, poolOf(key), tg, tagL1).cfg,
resolveBase: (key) => resolveTagRecEffective(pkgCore, poolOf(key), stripTagRecGroupSlot(tg, key), tagL1).cfg,
storedOverrideOf: (key) => pickTagRecScopePatch(key === "group" ? tg.overrides : tg.overrides?.pools?.[key.slice(5)]),
negativeOf: (key) => key === "group" ? tg.overrides?.negative : void 0,
storedPoolsEnabledOf: (key) => key === "group" ? tg.overrides?.poolsEnabled : void 0,
resolvePoolsEnabled: (key) => resolveTagRecEffective(pkgCore, key === "group" ? "tagTop" : key.slice(5), tg, tagL1).poolsEnabled,
basePoolsEnabled: (key) => resolveTagRecEffective(pkgCore, key === "group" ? "tagTop" : key.slice(5), stripTagRecGroupSlot(tg, key), tagL1).poolsEnabled,
onSave: (entries) => {
saveGroupEntries(deepEdit.gi, entries);
toast("已保存组配置 (点「保存」提交后生效)");
},
onClose
});
}
const l = editPkg.l1[deepEdit.li];
if (!l) return null;
const tl = toTagL1(l);
const chain = () => tagRecL1ChainGroup(tl, pkgGroups);
const strippedL1 = {
...tl,
overrides: void 0
};
return /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `包内一级组「${l.name}」配置`,
core: pkgCore,
presets: pkgCore.presets,
slots: [{
key: "l1",
label: "一级组"
}],
resolve: () => resolveTagRecEffective(pkgCore, "tagTop", chain(), tl).cfg,
resolveBase: () => resolveTagRecEffective(pkgCore, "tagTop", chain(), strippedL1).cfg,
storedOverrideOf: () => pickTagRecScopePatch(tl.overrides),
negativeOf: () => tl.overrides?.negative,
storedPoolsEnabledOf: () => tl.overrides?.poolsEnabled,
resolvePoolsEnabled: () => resolveTagRecEffective(pkgCore, "tagTop", chain(), tl).poolsEnabled,
basePoolsEnabled: () => resolveTagRecEffective(pkgCore, "tagTop", chain(), strippedL1).poolsEnabled,
onSave: (entries) => {
saveL1Entries(deepEdit.li, entries);
toast("已保存一级组配置 (点「保存」提交后生效)");
},
onClose
});
}
/** 可重建的推荐记录,与删除收藏的危险操作分开。 */
function TagRecRecoverySettings() {
const { recSharedEmitter } = useRecSelfContext();
const seenCount = useSnapshot(seen.set).size;
const sedimentCount = useSnapshot(sediment.map).size;
return /* @__PURE__ */ jsxs(SettingsGroup, {
title: "推荐记录",
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
clearTagRecSeenRecords();
recSharedEmitter.emit("refresh", "refresh");
toast("已清空已看记录,可以重新推荐浏览过的内容");
},
children: "清空已看"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
clearTagRecSedimentRecords();
recSharedEmitter.emit("refresh", "refresh");
toast("已清空沉淀,标签关系与发掘进度将重新建立");
},
children: "清空沉淀"
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"已看 ",
seenCount,
" 条 · 沉淀 ",
sedimentCount,
" 个标签"
]
})
]
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "mt-6px block text-12px",
children: [
"已看按卡片浏览记录:一半进入屏幕就记入,未必打开或播放过。清空后可重新推荐这些内容。",
/* @__PURE__ */ jsx("br", {}),
"沉淀保存标签关系与发掘进度;清空后会重新建立。收藏组、负向标签和过滤设置均保留。"
]
})]
});
}
/**
* [tag-rec] 分享上传 Modal (tag-share-platform spec §5 / 票 16)
* - 勾选组清单确认 + 标题(必填 ≤200)/描述(≤200) + 黑名单勾选 (默认带, 措辞正向「包含黑名单」;
* spec 反向表述「本次不含黑名单」同义, 正向写防双否定误读) + 风险告知勾选 (每次打开重新勾, 不记忆)
* - 票26「包含过滤配置」opt-in (默认不勾): 勾上则 payload.filterConfig = 真实私配随包
* (serialize 内 cloneDeep 脱 valtio proxy); 不勾维持出厂占位
* - 006 缺口2「随包携带全局排除」opt-in (默认不勾): 勾上则 payload.combinationExclusions =
* tagRecCombinationBlocks.list (升 v3, 收件侧落「以后都不看」); 屏蔽项是隐私面由用户主动选
* - 身份 = nav 登录态 mid+uname (fetchShareUploader); UIUX-U1-8 三态分流: user=就绪 /
* not-logged-in=确认未登录(警示+拒传) / error=拉取失败(Alert+「重试」钮, nonce 重拉)
* - 上传前本地预检 (preflightShareIssues = 服务端防护档同口径); 400 结构化拒绝逐条列不清表单;
* status=pending => 「已提交,审核中稍后可见」; published => toast 带「复制链接/打开分享页」
* - 成功后 mgmtToken 落 GM `tag-rec:share-tokens` (rememberShareToken)
* - 票31「重复分享软提醒」: 上传前先 POST /shares/dup-check ({mid,title,tagIds=正向 id 集});
* 命中 => Modal 内 warning Alert 列相似条目 (「同名」/相似 NN% + 更新于 X 前),
* 行右「更新原分享」仅对本机账本持 token 的命中项出 (readShareTokens), 点击 = 关本弹层 +
* onOpenMyShares(shareId) 开「我的分享」定位该条; Alert action 区「仍然发布新分享」续传 /「取消」回表单;
* 接口失败/无命中 => 零打扰直接走原上传流程 (dup-check 挂了不挡上传)
*/
function TagRecShareModal({ open, groups, onClose, onOpenMyShares }) {
const { token } = theme.useToken();
const formId = (0, import_react.useId)();
const { list: blacklistList } = useSnapshot(blacklistStore);
const blacklistCount = blacklistList.length;
const { list: combinationBlocksList } = useSnapshot(tagRecCombinationBlocks);
const combinationBlocksCount = combinationBlocksList.length;
const [title, setTitle] = (0, import_react.useState)("");
const [description, setDescription] = (0, import_react.useState)("");
const [includeBlacklist, setIncludeBlacklist] = (0, import_react.useState)(true);
const [includeFilter, setIncludeFilter] = (0, import_react.useState)(false);
const [includeExclusions, setIncludeExclusions] = (0, import_react.useState)(false);
const [riskAck, setRiskAck] = (0, import_react.useState)(false);
const [busy, setBusy] = (0, import_react.useState)(false);
const [issues, setIssues] = (0, import_react.useState)([]);
/** 「包内声明」展开集 (wayfinding 005) */
const [expandedIds, setExpandedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
/** 票40: 最后一次提交的 body —— 拒绝渲染时把 field 路径反查成人话定位+实文本 */
const lastBody = (0, import_react.useRef)(void 0);
/**
* 票31 dup-check 软提醒态: 命中时挂出 warning Alert 待用户裁量;
* body = 已构建待发的原样上传体 (「仍然发布新分享」续传用它, 防等待期间表单被改后串味);
* ownedIds = 命中项里本机 GM 账本持管理 token 的 shareId 集 (「更新原分享」钮显隐判据)
*/
const [dupWarn, setDupWarn] = (0, import_react.useState)(void 0);
/**
* 上传者身份三态 (fetchShareUploader):
* undefined = 读取中; not-logged-in = 确认未登录 (警示+拒传);
* error = 拉取失败/响应畸形 (出「重试」钮, nonce 重拉); user = 就绪
*/
const [uploaderRes, setUploaderRes] = (0, import_react.useState)(void 0);
const [uploaderNonce, setUploaderNonce] = (0, import_react.useState)(0);
(0, import_react.useEffect)(() => {
if (!open) return;
setTitle("");
setDescription("");
setIncludeBlacklist(true);
setIncludeFilter(false);
setIncludeExclusions(false);
setRiskAck(false);
setIssues([]);
setExpandedIds(/* @__PURE__ */ new Set());
setDupWarn(void 0);
}, [open]);
(0, import_react.useEffect)(() => {
if (!open) return;
setUploaderRes(void 0);
let cancelled = false;
fetchShareUploader().then((r) => {
if (!cancelled) setUploaderRes(r);
});
return () => {
cancelled = true;
};
}, [open, uploaderNonce]);
const uploader = uploaderRes?.kind === "user" ? uploaderRes.user : void 0;
const totalIds = groups.reduce((s, g) => s + g.tag_ids.length, 0);
const canUpload = !!uploader && !busy && groups.length > 0 && !!title.trim() && title.length <= SHARE_GUARD_LIMITS.MAX_STRING_LEN && description.length <= SHARE_GUARD_LIMITS.MAX_STRING_LEN && riskAck;
/** 实际上传段 (onUpload dup-check 放行后 / dup Alert「仍然发布新分享」续传 共用) */
const runUpload = async (body) => {
const res = await uploadShare(body);
if (res.kind === "rejected") {
setIssues(res.issues);
return;
}
if (res.kind === "failed") {
toast(`分享上传失败: ${res.message}`, 5e3);
return;
}
rememberShareToken(res.id, {
token: res.mgmtToken,
title: body.title,
createdAt: Date.now()
});
const url = buildSharePageUrl(TAG_REC_SHARE_SITE_BASE, res.id);
const editUrl = buildShareEditUrl(TAG_REC_SHARE_SITE_BASE, res.id, res.mgmtToken);
onClose();
if (res.status === "pending") {
antMessage.success({
content: `已提交「${body.title}」, 审核中稍后可见`,
duration: 6
});
return;
}
antMessage.success({
duration: 8,
content: /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-10px",
children: [
"已分享「",
body.title,
"」",
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0",
onClick: () => {
GM.setClipboard(url);
toast("已复制分享链接");
},
children: "复制链接"
}),
/* @__PURE__ */ jsx(Typography.Link, {
href: editUrl,
target: "_blank",
children: "打开分享页"
})
]
})
});
};
const onUpload = async () => {
const u = uploader;
if (!u || busy || !groups.length || dupWarn) return;
const body = {
mid: u.mid,
nickname: u.nickname,
avatar: u.avatar,
title: title.trim(),
description: description.trim() || void 0,
payload: buildSharePayload({
groups,
l1: favoritesL1.list,
themes: themes.list,
blacklist: blacklist.list,
includeBlacklist: includeBlacklist && blacklistCount > 0,
filterConfig: includeFilter ? settings.tagRec.filter : void 0,
combinationExclusions: includeExclusions ? tagRecCombinationBlocks.list : void 0
})
};
lastBody.current = body;
const localIssues = preflightShareIssues(body);
if (localIssues.length) {
setIssues(localIssues);
return;
}
setBusy(true);
setIssues([]);
try {
const dupRes = await checkDupShare({
mid: body.mid,
title: body.title,
tagIds: collectSharePositiveTagIds(body.payload.favorites)
});
if (dupRes.kind === "ok" && dupRes.dups.length) {
const tokens = await readShareTokens();
const ownedIds = new Set(dupRes.dups.filter((d) => !!tokens[d.id]?.token).map((d) => d.id));
setDupWarn({
body,
dups: dupRes.dups,
ownedIds
});
return;
}
await runUpload(body);
} finally {
setBusy(false);
}
};
/** 票31 Alert「仍然发布新分享」: 沿用触发时的原样 body 续传 (不再走 dup-check —— 用户已裁量过) */
const resumeUpload = async () => {
const w = dupWarn;
setDupWarn(void 0);
if (!w) return;
setBusy(true);
try {
await runUpload(w.body);
} finally {
setBusy(false);
}
};
/** 票31 Alert 行内「更新原分享」: 关本弹层 → pane 侧开「我的分享」Modal 定位该分享 */
const gotoMyShare = (shareId) => {
setDupWarn(void 0);
onClose();
onOpenMyShares?.(shareId);
};
return /* @__PURE__ */ jsxs(Modal, {
title: `分享 ${groups.length} 个标签组`,
open,
onCancel: () => busy ? void 0 : onClose(),
onOk: () => void onUpload(),
okText: "发布分享",
confirmLoading: busy,
okButtonProps: { disabled: !canUpload || !!dupWarn },
footer: (originNode) => /* @__PURE__ */ jsxs(Fragment, { children: [busy && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mr-8px text-12px",
children: "上传中,完成后自动关闭"
}), originNode] }),
width: 520,
children: [
/* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-8px b-solid px-12px py-10px",
style: { background: token.colorFillQuaternary },
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex items-center justify-between gap-x-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-12px",
children: "分享内容"
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "shrink-0 text-12px",
children: [
groups.length,
" 组 · ",
totalIds,
" 个标签"
]
})]
}),
/* @__PURE__ */ jsx("div", {
className: "max-h-260px overflow-y-auto",
children: groups.map((g) => /* @__PURE__ */ jsxs("div", {
className: "py-2px",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-baseline gap-x-6px",
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "h-auto w-16px shrink-0 px-0 text-11px opacity-70",
title: "展开包内声明(条件树 / 覆盖 / 负向)",
onClick: () => setExpandedIds((prev) => {
const next = new Set(prev);
if (next.has(g.id)) next.delete(g.id);
else next.add(g.id);
return next;
}),
children: expandedIds.has(g.id) ? "▾" : "▸"
}),
/* @__PURE__ */ jsx(Typography.Text, {
ellipsis: { tooltip: g.name },
className: "min-w-0 flex-1 text-13px",
children: g.name
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "shrink-0 text-12px",
children: [g.tag_ids.length, " 个标签"]
})
]
}), expandedIds.has(g.id) ? /* @__PURE__ */ jsx(TagRecShareGroupContents, { group: g }) : g.combination && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
ellipsis: { tooltip: nameTagRecCombination(g.combination) },
className: "ml-18px block text-12px",
children: ["条件: ", nameTagRecCombination(g.combination)]
})]
}, g.id))
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-6px flex items-center justify-between gap-x-8px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: ["保留所属一级组与主题结构", (groups.some((g) => hasTagRecCombination(g.combination)) || includeExclusions) && " · 组合结构完整携带 (v3)"]
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto shrink-0 px-0 text-12px",
onClick: () => setExpandedIds((prev) => prev.size === groups.length ? /* @__PURE__ */ new Set() : new Set(groups.map((g) => g.id))),
children: expandedIds.size === groups.length ? "全部收起" : "全部展开"
})]
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-14px",
children: [/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex items-center justify-between gap-x-8px text-12px",
children: [/* @__PURE__ */ jsxs("label", {
htmlFor: `${formId}-title`,
children: [
"分享标题",
" ",
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "必填"
})
]
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "shrink-0 text-12px",
children: [
title.length,
" / ",
SHARE_GUARD_LIMITS.MAX_STRING_LEN
]
})]
}), /* @__PURE__ */ jsx(Input, {
id: `${formId}-title`,
placeholder: "给这份分享起个名字",
value: title,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
onChange: (e) => {
setTitle(e.target.value);
setDupWarn(void 0);
}
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-12px",
children: [/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex items-center justify-between gap-x-8px text-12px",
children: [/* @__PURE__ */ jsxs("label", {
htmlFor: `${formId}-description`,
children: [
"描述",
" ",
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "选填"
})
]
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "shrink-0 text-12px",
children: [
description.length,
" / ",
SHARE_GUARD_LIMITS.MAX_STRING_LEN
]
})]
}), /* @__PURE__ */ jsx(Input.TextArea, {
id: `${formId}-description`,
placeholder: "介绍内容或推荐理由",
value: description,
maxLength: SHARE_GUARD_LIMITS.MAX_STRING_LEN,
autoSize: {
minRows: 2,
maxRows: 4
},
onChange: (e) => setDescription(e.target.value)
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-14px overflow-hidden b-1px b-gate-border rounded-8px b-solid",
children: [
/* @__PURE__ */ jsx("div", {
className: "px-12px py-9px",
children: /* @__PURE__ */ jsxs(Checkbox, {
checked: includeBlacklist,
disabled: !blacklistCount,
onChange: (e) => setIncludeBlacklist(e.target.checked),
children: ["包含标签黑名单", blacklistCount ? `(${blacklistCount} 项)` : "(空)"]
})
}),
/* @__PURE__ */ jsxs("div", {
className: "b-t-1px b-t-gate-border b-t-solid px-12px py-9px",
children: [/* @__PURE__ */ jsx(Checkbox, {
checked: includeFilter,
onChange: (e) => setIncludeFilter(e.target.checked),
children: "包含过滤配置"
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-2px block pl-24px text-12px",
style: { textWrap: "pretty" },
children: includeFilter ? "屏蔽词、UP 主名单等个人设置将随分享公开" : "默认不分享屏蔽词、UP 主名单等个人设置"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "b-t-1px b-t-gate-border b-t-solid px-12px py-9px",
children: [/* @__PURE__ */ jsxs(Checkbox, {
checked: includeExclusions,
disabled: !combinationBlocksCount,
onChange: (e) => setIncludeExclusions(e.target.checked),
children: ["随包携带全局排除", combinationBlocksCount ? `(${combinationBlocksCount} 项)` : "(空)"]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-2px block pl-24px text-12px",
style: { textWrap: "pretty" },
children: includeExclusions ? "你屏蔽的标签、UP 主等排除条件将随分享公开" : "默认不分享你的全局排除条件"
})]
})
]
}),
/* @__PURE__ */ jsx(Alert, {
type: "warning",
showIcon: true,
className: "mt-12px",
title: "发布后公开展示",
style: {
padding: "12px 14px",
borderRadius: 8
},
description: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-4px text-12px leading-19px",
style: { textWrap: "pretty" },
children: [
/* @__PURE__ */ jsxs("div", { children: [
"标签组",
includeBlacklist && blacklistCount > 0 ? "、黑名单" : "",
includeFilter ? "、过滤配置" : "",
includeExclusions && combinationBlocksCount > 0 ? "、全局排除条件" : "",
"上传到第三方平台并公开。"
] }),
/* @__PURE__ */ jsx("div", { children: "同时公开你的 B 站昵称与 UID。" }),
/* @__PURE__ */ jsx("div", { children: "自动审核不保证合规,请对发布内容负责。" }),
/* @__PURE__ */ jsx("div", { children: "修改、删除需管理凭证;仅发放一次,丢失无法找回。" })
]
})
}),
/* @__PURE__ */ jsx(Checkbox, {
className: "mt-10px",
checked: riskAck,
onChange: (e) => setRiskAck(e.target.checked),
children: /* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-13px",
children: "我已了解并同意公开发布"
})
}),
uploaderRes?.kind === "not-logged-in" && /* @__PURE__ */ jsx(Alert, {
type: "error",
className: "mt-8px",
title: "请先登录 B 站",
description: /* @__PURE__ */ jsxs(Fragment, { children: [
"分享需要读取你的 B 站昵称与 UID。",
" ",
/* @__PURE__ */ jsx(Typography.Link, {
href: "https://passport.bilibili.com/login",
target: "_blank",
children: "去登录"
})
] })
}),
uploaderRes?.kind === "error" && /* @__PURE__ */ jsx(Alert, {
type: "error",
className: "mt-8px",
title: "读取登录态失败",
description: "可能是网络/风控原因 —— 登录态取不到则无法上传",
action: /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setUploaderNonce((n) => n + 1),
children: "重试"
})
}),
dupWarn && /* @__PURE__ */ jsx(Alert, {
type: "warning",
className: "mt-8px",
title: "你可能发过相似的分享:",
description: /* @__PURE__ */ jsx("ul", {
className: "m-0 list-none pl-0 text-12px",
children: dupWarn.dups.map((d) => /* @__PURE__ */ jsxs("li", {
className: "flex items-center gap-x-8px py-1px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
className: "min-w-0 flex-1 text-12px",
ellipsis: { tooltip: `「${d.title}」 · 更新于 ${formatShareUpdatedAgo(d.updatedAt)}` },
children: [
"『",
d.title,
"』 · 更新于 ",
formatShareUpdatedAgo(d.updatedAt),
" ·",
" ",
d.titleMatch ? "同名" : `相似 ${Math.min(100, Math.max(0, Math.round(d.similarity * 100)))}%`
]
}), dupWarn.ownedIds.has(d.id) && /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto shrink-0 px-0 text-12px",
onClick: () => gotoMyShare(d.id),
children: "更新原分享"
})]
}, d.id))
}),
action: /* @__PURE__ */ jsxs("div", {
className: "flex gap-x-8px",
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: () => void resumeUpload(),
children: "仍然发布新分享"
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setDupWarn(void 0),
children: "取消"
})]
})
}),
issues.length > 0 && /* @__PURE__ */ jsx(Alert, {
type: "error",
className: "mt-8px",
title: "未通过, 请修改后重传",
description: /* @__PURE__ */ jsxs("ul", {
className: "m-0 list-disc pl-18px text-12px",
children: [issues.map((it, i) => /* @__PURE__ */ jsxs("li", { children: [
it.field ? `${describeShareField(it.field, lastBody.current)}: ` : "",
it.reason,
it.word ? ` (命中词: ${it.word})` : "",
it.category ? ` (类目: ${describeShareCategory(it.category)})` : ""
] }, i)), issues.some((it) => it.category) && /* @__PURE__ */ jsx("li", {
className: "list-none text-11px opacity-70",
children: "AI 审核存在误判可能 —— 若为误判,微调该处文案后重传即可"
})]
})
}),
uploaderRes === void 0 && !issues.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: "读取登录态中…"
})
]
});
}
var tablerFileImport = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
children: [/* @__PURE__ */ jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }), /* @__PURE__ */ jsx("path", { d: "M5 13V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2h-5.5M2 19h7m-3-3l3 3l-3 3" })]
})]
});
var ForwardRef$5 = (0, import_react.forwardRef)(tablerFileImport);
var tablerFileExport = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
children: [/* @__PURE__ */ jsx("path", { d: "M14 3v4a1 1 0 0 0 1 1h4" }), /* @__PURE__ */ jsx("path", { d: "M11.5 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v5m-5 6h7m-3-3l3 3l-3 3" })]
})]
});
var ForwardRef$4 = (0, import_react.forwardRef)(tablerFileExport);
/**
* [tag-rec] 「标签收藏」pane (spec §5.1) —— 本特性管理中枢
* - 收藏表 = antd Table 分组行 (票 17): L1 标题行(折叠/改名/weight/enabled 批量入堆开关/麾下聚合小字)
* 票 25: L1 行 tag_id 数/所属一级组两列语义不适用 => colSpan 并进组名列(长名不竖排), 行右端显「N 组 · M 个 id · K 负向」
* 票 26: L1 行另有「所属主题」列 (主题 = L1 归属筛子, tab1 一键切换工作集; L2 行本列留空)
* + 其下 L2 组行原样: [勾选] 组名行内编辑 | tag_id 数徽标 | 所属一级组指认 | weight 0~5 步进 | enabled 开关 | 操作
* - 三态呈现: enabled=false 整行置灰; weight=0 行尾「挂起」徽标; 正常无标; L1 停用 => 麾下子树一并灰化
* - 展开行 = 组内 tag_id 芯片 (name 即深链跳 tab2, 不渲 #id/tag_type 徽标; 每 id「移到…」下拉)
* + 尾部「添加变体」(解析管线→候选清单勾进本组) /「重新挖掘变体」(disabled 占位·票 08)
* + 票 29「批量选择」开关: 开启后点 chip 切选中态, 选中>0 出「移到组…/复制到组…/删除」(Modal+Select 选目标 L2)
* - 票 31: L1/L2 行统一复选 (L1 勾选 = 整棵子树, 麾下 L2 复选 disabled 灰显; 混选 = 并入/移动禁+提示)
* + 吸顶操作条 sticky 于面板滚动容器 (非全局 fixed): 「并入…」/「移到一级组…」/「启用/停用」/「删除」
* - 集市入口常显;推荐记录可直接清空;JSON 备份折叠;危险区仅保留清空收藏表。
* - JSON 导入 = 预览 Modal 先行: 冲突组 (共享正向 id / 组 id 撞车) 逐条交用户裁量
* (并入宿主/保留两份/跳过), filterConfig 覆盖改为勾选确认 (feat: 不再静默出双份)
* - 面板路径与 tab2 同管线同写沉淀 (§3.5); 不变式皆在 store 门面层强制执行
*/
function TabPaneTagFavorites() {
/** 导入预览草稿 (feat4): plan 算好后不直接落表, Modal 列冲突 + 裁量 + filterConfig 勾选;
批量导入时 data = mergeTagRecConfigExports 合成件, fileCount/failed 供预览播报 */
const [importDraft, setImportDraft] = (0, import_react.useState)();
/** 冲突逐条裁量: group.id => merge=并入 hits[0] / keep=保留两份 / skip=跳过 */
const [importChoices, setImportChoices] = (0, import_react.useState)({});
const [importApplyFilter, setImportApplyFilter] = (0, import_react.useState)(true);
/**
* 票 43: 资格指纹比对法 refresh —— pane 随设置 modal 卸载时, 挂载期指纹 vs 卸载期指纹不同
* (主题归属/层级/三态/tag_ids/导入 任一变更) => emit refresh 一次性重抽 feed;
* 比对法而非动作打点: 任何编辑入口自动覆盖, 名/负向等不入指纹的编辑不触发重抽
* (pane 在 RecHeader=>RecSelf 树内, emitter 与 RecGrid 监听同根)
*/
const { recSharedEmitter } = useRecSelfContext();
const effectiveFingerprint = () => unitsFingerprint(withTagRecGroupScope(favorites.list, viewScope.viewedGroupIds(), tagRecSharedSession.tempEnabledGroupIds), withTagRecTempEnables(favoritesL1.list, tagRecSharedSession.tempEnabledL1Ids), viewScope.viewedL1Ids());
const [fpOnMount] = (0, import_react.useState)(effectiveFingerprint);
const [fsOnMount] = (0, import_react.useState)(() => computeTagRecFilterSignature({
core: settings.tagRec.filter,
groups: favorites.list,
l1s: favoritesL1.list
}));
(0, import_react.useEffect)(() => () => {
const fpNow = effectiveFingerprint();
const fsNow = computeTagRecFilterSignature({
core: settings.tagRec.filter,
groups: favorites.list,
l1s: favoritesL1.list
});
if (fpNow !== fpOnMount || fsNow !== fsOnMount) recSharedEmitter.emit("refresh", "refresh");
}, [
recSharedEmitter,
fpOnMount,
fsOnMount
]);
/**
* 「导入 JSON」: 解析+计划后不直接落表 —— 弹「导入预览」Modal (feat: 冲突交用户裁量);
* 默认裁量: 同源实体 (组 id 撞车) 或正向 id 被宿主全覆盖 => 「并入宿主」, 部分重叠 => 「保留两份」;
* 支持多选批量: 逐个解析 (坏件列名跳过不拦好件), 合成单件后走同一预览管线 (mergeTagRecConfigExports)
*/
const onImportJson = async () => {
const files = await chooseFiles(".json");
if (!files.length) return;
const datas = [];
const failed = [];
for (const f of files) {
const res = parseTagRecConfig(await f.text());
if (res.ok) datas.push(res.data);
else failed.push(`${f.name}: ${res.reason}`);
}
if (!datas.length) return toast(`导入失败: ${failed[0] ?? "未选择有效文件"}`, 5e3);
const data = mergeTagRecConfigExports(datas);
const plan = planTagRecImport(data, {
favorites: favorites.list,
blacklist: blacklist.list,
l1: favoritesL1Store.list,
themes: themesStore.list
});
const choices = {};
for (const c of plan.conflicts) {
const covered = c.group.tag_ids.every((e) => c.hits[0].shared.includes(e.id));
choices[c.group.id] = c.group.combination || favorites.list.find((g) => g.id === c.hits[0].id)?.combination ? "keep" : c.hits[0].sameEntity || covered ? "merge" : "keep";
}
setImportChoices(choices);
setImportApplyFilter(true);
setImportDraft({
data,
plan,
fileCount: files.length,
failed
});
};
/** 「导入预览」确认: 按逐条裁量落表 —— 写侧收口 store/apply-import.ts (票 18: 与分享导入共用) */
const confirmImport = () => {
const d = importDraft;
setImportDraft(void 0);
if (!d) return;
const res = applyTagRecImport({
plan: d.plan,
filterConfig: d.data.filterConfig,
choices: importChoices,
applyFilterConfig: importApplyFilter
});
const invalid = d.plan.invalidGroups.length ? `; ${d.plan.invalidGroups.length} 组无效未入` : "";
const synthesized = d.plan.synthesizedThemes ? `, 其中 ${d.plan.synthesizedThemes} 个为悬挂引用合成` : "";
toast(`已导入 ${res.themesAdded} 主题${synthesized} / ${res.l1Added} 一级组 / ${res.added} 组新入${res.merged ? ` / ${res.merged} 组并入既有` : ""}${res.skipped ? ` / ${res.skipped} 组跳过` : ""} / 黑名单 ${res.blacklistAdded} 项${res.filterApplied ? " / 过滤配置已覆盖" : ""}${invalid}`, 5e3);
};
/**
* 票 18/27 (tag-share)「粘贴分享链接/id/导入码」: 自由文本 extractShareImportFromText 判别 —
* share = shareId(+groups 子集) 走 requestShareImport; pkg = 试改版导入码走 requestShareImportPkg;
* 同一管线汇流进预览 Modal (origin='paste' 挂载点)
*/
const [shareLinkText, setShareLinkText] = (0, import_react.useState)("");
const shareImportSi = useSnapshot(tagRecSharedSession).shareImport;
const onImportShareLink = () => {
const found = extractShareImportFromText(shareLinkText);
if (!found) return toast("未识别到分享链接、分享 ID 或导入码", 4e3);
setShareLinkText("");
if (found.kind === "pkg") requestShareImportPkg(found.code, "paste");
else requestShareImport(found.shareId, found.groupIds, "paste");
};
const [mySharesOpen, setMySharesOpen] = (0, import_react.useState)(false);
const [mySharesFocus, setMySharesFocus] = (0, import_react.useState)(void 0);
/** 「导出 JSON」: {version:1, themes, activeThemeId, l1, favorites, blacklist, filterConfig} 打包; seen/沉淀不进导出 (§6.1) */
const onExportJson = () => {
const data = serializeTagRecConfig({
combinationExclusions: tagRecCombinationBlocks.list,
themes: themesStore.list,
activeThemeId: viewScope.legacyActiveThemeId(),
l1: favoritesL1Store.list,
favorites: favoritesStore.list,
blacklist: blacklistStore.list,
filterConfig: settings.tagRec.filter
});
downloadTextFile(`${APP_NAME}-tag-rec-config ${(0, import_dayjs_min.default)().format("YYYY-MM-DD HH-mm-ss")}.json`, stringifyTagRecConfig(data), "application/json");
};
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsxs(SettingsGroup, {
title: "标签组集市",
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(AntdTooltip, {
title: "浏览/导入别人分享的标签组",
children: /* @__PURE__ */ jsxs(button_default, {
type: "primary",
href: TAG_REC_SHARE_SITE_BASE,
target: "_blank",
children: [/* @__PURE__ */ jsx(ForwardRef$6, {}), "去逛集市"]
})
}),
/* @__PURE__ */ jsx(Input, {
className: "max-w-320px",
placeholder: "粘贴分享链接、分享 ID 或试改版导入码",
value: shareLinkText,
allowClear: true,
onChange: (e) => setShareLinkText(e.target.value),
onPressEnter: onImportShareLink
}),
/* @__PURE__ */ jsx(button_default, {
onClick: onImportShareLink,
loading: shareImportSi.loading && shareImportSi.origin === "paste",
children: "导入分享"
}),
/* @__PURE__ */ jsxs(button_default, {
onClick: () => setMySharesOpen(true),
children: [/* @__PURE__ */ jsx(ForwardRef$16, {}), "我的分享"]
})
]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "「标签组集市」= 分享平台 (tagrec.cn), 挑中的分享复制链接/ID 回右侧粘贴即可; 收整段深链/分享页链接/裸分享 ID (动态里贴的链接也可), 含 tag-rec-groups 子集参时只列指定组"
})]
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["标签收藏表", /* @__PURE__ */ jsxs(HelpInfo, { children: [
"行=标签组: 一个收藏项可挂多个 tag_id 覆盖同义变体; 跨组 id 重复合法。",
/* @__PURE__ */ jsx("br", {}),
"一级组启用后,按二级组各自的开关与权重参与推荐。一级组旁的「+ 二级组」可先建空组,再添加标签。",
/* @__PURE__ */ jsx("br", {}),
"主题与一级组归属可直接切换或新建;权重和简介在行内编辑。勾选后可移动、合并、分享和批量启停。",
/* @__PURE__ */ jsx("br", {}),
"主题筛选只查找表格行;「用此筛选看推荐」才应用临时范围。临时选择不会改保存开关,显式保存开关优先。",
/* @__PURE__ */ jsx("br", {}),
"展开行含「负向成员」位: 负向 tag 经池交集/名匹配过滤本组流, 严格校验开关另逐条核查 (默认关)。",
/* @__PURE__ */ jsx("br", {}),
"三态: 正常(weight≥1 入抽) / 挂起(weight=0, 仅「标签发掘」手动可选) / 停用(enabled=false, 两 tab 皆不可用)"
] })] }),
children: /* @__PURE__ */ jsx(FavoritesSection, { onOpenMyShares: (shareId) => {
setMySharesFocus(shareId);
setMySharesOpen(true);
} })
}),
/* @__PURE__ */ jsx(TagRecRecoverySettings, {}),
/* @__PURE__ */ jsxs("details", {
className: "mt-16px b-1px b-gate-border rounded-8px b-solid p-12px",
children: [/* @__PURE__ */ jsx("summary", {
className: "cursor-pointer text-13px",
children: "备份与导入 · JSON"
}), /* @__PURE__ */ jsxs(SettingsGroup, {
title: "工具",
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsxs(button_default, {
onClick: onImportJson,
children: [/* @__PURE__ */ jsx(ForwardRef$5, {}), "导入 JSON"]
}), /* @__PURE__ */ jsxs(button_default, {
onClick: onExportJson,
children: [/* @__PURE__ */ jsx(ForwardRef$4, {}), "导出 JSON"]
})]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "JSON = 收藏表+黑名单+过滤配置三件打包, 支持多选批量导入 (跨文件同组自动去重); 导入先出预览: 冲突组可选并入宿主/保留两份/跳过, 过滤配置勾选覆盖 (多文件取最后一份)"
})]
})]
}),
/* @__PURE__ */ jsxs("details", {
className: "mt-10px b-1px b-gate-border rounded-8px b-solid p-12px",
children: [/* @__PURE__ */ jsx("summary", {
className: "cursor-pointer text-13px",
children: "删除收藏数据"
}), /* @__PURE__ */ jsx(SettingsGroup, {
title: "危险区",
children: /* @__PURE__ */ jsx(DangerZoneSection, {})
})]
}),
/* @__PURE__ */ jsx(Modal, {
title: "导入预览",
open: !!importDraft,
onCancel: () => setImportDraft(void 0),
onOk: confirmImport,
okText: "应用导入",
width: 560,
children: importDraft && /* @__PURE__ */ jsxs("div", {
className: "max-h-60vh overflow-y-auto",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
className: "text-12px",
children: [
(importDraft.fileCount ?? 1) > 1 ? `已选 ${importDraft.fileCount} 个文件; ` : "",
"将新增 ",
importDraft.plan.themesToAdd.length,
" 主题 / ",
importDraft.plan.l1ToAdd.length,
" 一级组 /",
" ",
importDraft.plan.favoritesToAdd.length,
" 标签组 / 黑名单 ",
importDraft.plan.blacklistToAdd.length,
" 项",
importDraft.plan.synthesizedThemes ? ` (其中 ${importDraft.plan.synthesizedThemes} 个主题为悬挂引用合成)` : "",
importDraft.plan.invalidGroups.length ? `; ${importDraft.plan.invalidGroups.length} 组无效未入` : ""
]
}),
!!importDraft.failed?.length && /* @__PURE__ */ jsxs(Typography.Text, {
type: "warning",
className: "mt-6px block text-12px",
children: [
importDraft.failed.length,
" 个文件解析失败已跳过: ",
importDraft.failed.join(";")
]
}),
importDraft.plan.conflicts.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-10px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
strong: true,
className: "text-13px",
children: [importDraft.plan.conflicts.length, " 个组与既有收藏冲突"]
}), /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-128px",
placeholder: "批量设为…",
value: null,
onChange: (v) => {
if (!v) return;
setImportChoices(Object.fromEntries(importDraft.plan.conflicts.map((c) => [c.group.id, v])));
},
options: [
{
value: "merge",
label: "全部并入宿主"
},
{
value: "keep",
label: "全部保留两份"
},
{
value: "skip",
label: "全部跳过"
}
]
})]
}),
importDraft.plan.conflicts.map((c) => /* @__PURE__ */ jsxs("div", {
className: "mt-6px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsxs("span", {
className: "min-w-0 flex-1",
children: [/* @__PURE__ */ jsx(Typography.Text, {
ellipsis: { tooltip: c.group.name },
children: c.group.name
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "block text-12px",
children: [
c.hits[0].sameEntity ? "组 id 同源撞车 · " : "",
"与「",
c.hits[0].name,
"」共享",
" ",
c.hits[0].shared.length,
" 个 tag_id",
c.hits.length > 1 ? ` (另命中 ${c.hits.length - 1} 组)` : ""
]
})]
}), /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-128px shrink-0",
value: importChoices[c.group.id] ?? "keep",
onChange: (v) => setImportChoices((prev) => ({
...prev,
[c.group.id]: v
})),
options: [
{
value: "merge",
label: "并入宿主"
},
{
value: "keep",
label: "保留两份"
},
{
value: "skip",
label: "跳过不入"
}
]
})]
}, c.group.id)),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-6px block text-12px",
children: "并入宿主 = 正/负向成员与名单并入命中组 (宿主已有配置保留, 缺的槽位领养入档值); 保留两份 = 照常入表 (跨组共享 tag_id 本就合法); 跳过 = 该组不入"
})
]
}),
/* @__PURE__ */ jsx(Checkbox, {
className: "mt-10px",
checked: importApplyFilter,
onChange: (e) => setImportApplyFilter(e.target.checked),
children: "覆盖当前过滤配置 (文件含 filterConfig: 阈值/名单/预设整套; 多文件取末份)"
})
]
})
}),
/* @__PURE__ */ jsx(TagRecShareImportModal, { origin: "paste" }),
/* @__PURE__ */ jsx(TagRecMySharesModal, {
open: mySharesOpen,
focusShareId: mySharesFocus,
onClose: () => {
setMySharesOpen(false);
setMySharesFocus(void 0);
}
})
]
});
}
/** 文件选择: input[type=file] 一次性 pick (file-backup.ts chooseFile 同形), multiple 支持批量导入 */
function chooseFiles(accept) {
return new Promise((resolve) => {
const input = document.createElement("input");
input.type = "file";
input.accept = accept;
input.multiple = true;
input.addEventListener("change", () => {
resolve(Array.from(input.files ?? []));
});
input.addEventListener("cancel", () => {
resolve([]);
});
input.click();
});
}
/**
* 文本下载: Blob + a[download] —— 照 file-backup.ts usePlainLink;
* GM_download 在 ViolentMonkey mv3 下有 bug 无响应, 不走 (裁量与仓内结论一致)
*/
function downloadTextFile(filename, content, mime) {
const blob = new Blob([content], { type: mime });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
setTimeout(() => URL.revokeObjectURL(url), 6e4);
}
var ORPHAN_L1_ROW_ID = "__orphan__";
var NEW_L1_OPTION = "__new_l1__";
var NEW_L2_OPTION = "__new_l2__";
var NEW_THEME_OPTION = "__new_theme__";
var THEME_FILTER_UNASSIGNED = "__theme_unassigned__";
var L1_FILTER_ALL = "__l1_all__";
var L1_FILTER_UNASSIGNED = "__l1_unassigned__";
var THEME_ASSIGN_CLEAR = "__theme_clear__";
/** L1 下拉项 (停用/挂起带态后缀) —— 行内「所属一级组」/「并入…」/吸顶条「移到一级组…」三处同款 */
var l1Option = (l) => {
const state = groupState(l);
return {
value: l.id,
label: state === "normal" ? l.name : `${l.name} (${TagGroupStateLabel[state]})`
};
};
function FavoritesSection({ onOpenMyShares }) {
const { recSharedEmitter } = useRecSelfContext();
const { list } = useSnapshot(favoritesStore);
const { list: l1List } = useSnapshot(favoritesL1Store);
const { list: themeList } = useSnapshot(themesStore);
const { scope: viewScopeSnap } = useSnapshot(viewScopeStore);
const soloViewedThemeId = tagRecViewScopeLegacyActiveTheme(viewScopeSnap, themeList.map((t) => t.id));
const [selectedKeys, setSelectedKeys] = (0, import_react.useState)([]);
const [creating, setCreating] = (0, import_react.useState)(false);
const [search, setSearch] = (0, import_react.useState)("");
const [newChild, setNewChild] = (0, import_react.useState)();
const [expandedGroups, setExpandedGroups] = (0, import_react.useState)([]);
const [collapsedL1, setCollapsedL1] = (0, import_react.useState)(/* @__PURE__ */ new Set());
const [mergeOpen, setMergeOpen] = (0, import_react.useState)(false);
const [mergeTarget, setMergeTarget] = (0, import_react.useState)(void 0);
const [moveL2Open, setMoveL2Open] = (0, import_react.useState)(false);
const [moveL2Target, setMoveL2Target] = (0, import_react.useState)(void 0);
const [themeFilterSet, setThemeFilterSet] = (0, import_react.useState)(/* @__PURE__ */ new Set());
const [themeMergeOpen, setThemeMergeOpen] = (0, import_react.useState)(false);
const [themeMergeTarget, setThemeMergeTarget] = (0, import_react.useState)(void 0);
const [assignThemeOpen, setAssignThemeOpen] = (0, import_react.useState)(false);
const [assignThemeTarget, setAssignThemeTarget] = (0, import_react.useState)(void 0);
const [l1Filter, setL1Filter] = (0, import_react.useState)(void 0);
const [configTarget, setConfigTarget] = (0, import_react.useState)(void 0);
const groups = [...list].sort((a, b) => b.addedAt - a.addedAt);
const l1s = [...l1List].sort((a, b) => b.addedAt - a.addedAt);
const themeIds = new Set(themeList.map((t) => t.id));
const effThemeSet = new Set([...themeFilterSet].filter((k) => k === THEME_FILTER_UNASSIGNED || themeIds.has(k)));
const checkedThemeIds = [...effThemeSet].filter((k) => k !== THEME_FILTER_UNASSIGNED);
const unassignedL1Count = l1s.filter((l) => !l.themeId).length;
const orphansVisible = !effThemeSet.size || effThemeSet.has(THEME_FILTER_UNASSIGNED);
const scopedL1s = effThemeSet.size ? l1s.filter((l) => l.themeId ? effThemeSet.has(l.themeId) : effThemeSet.has(THEME_FILTER_UNASSIGNED)) : l1s;
const effL1Filter = l1Filter === L1_FILTER_UNASSIGNED ? orphansVisible ? L1_FILTER_UNASSIGNED : void 0 : scopedL1s.some((l) => l.id === l1Filter) ? l1Filter : void 0;
const visibleL1s = scopedL1s.filter((l) => effL1Filter === void 0 || effL1Filter !== L1_FILTER_UNASSIGNED && l.id === effL1Filter);
const showOrphans = orphansVisible && (effL1Filter === void 0 || effL1Filter === L1_FILTER_UNASSIGNED);
const l1Ids = new Set(l1s.map((l) => l.id));
const childrenByL1 = /* @__PURE__ */ new Map();
const orphans = [];
for (const g of groups) if (g.l1Id && l1Ids.has(g.l1Id)) {
const arr = childrenByL1.get(g.l1Id) ?? [];
arr.push(g);
childrenByL1.set(g.l1Id, arr);
} else orphans.push(g);
const rows = [];
/** 麾下子树合计 (票 25): 渲染期内存算零请求, L1 永不自持 tag_ids/负向故直求和即可 */
const l1Agg = (kids) => ({
childCount: kids.length,
idCount: kids.reduce((s, g) => s + g.tag_ids.length, 0),
negCount: kids.reduce((s, g) => s + groupNegativeTagIds(g).length, 0)
});
for (const l1 of visibleL1s) {
const children = childrenByL1.get(l1.id) ?? [];
const q = search.trim().toLowerCase();
const ancestorMatch = !q || l1.name.toLowerCase().includes(q) || (themeList.find((t) => t.id === l1.themeId)?.name ?? "未归属").toLowerCase().includes(q);
const matches = ancestorMatch ? children : children.filter((g) => g.name.toLowerCase().includes(q) || g.tag_ids.some((t) => t.name.toLowerCase().includes(q)));
if (!ancestorMatch && !matches.length) continue;
rows.push({
kind: "l1",
id: l1.id,
l1,
title: l1.name,
contextOnly: !ancestorMatch,
...l1Agg(children)
});
if (q || !collapsedL1.has(l1.id)) for (const g of matches) rows.push({
kind: "l2",
id: g.id,
group: g,
l1Enabled: l1.enabled
});
}
const visibleOrphans = search.trim() ? orphans.filter((g) => g.name.toLowerCase().includes(search.trim().toLowerCase()) || g.tag_ids.some((t) => t.name.toLowerCase().includes(search.trim().toLowerCase()))) : orphans;
if (showOrphans && visibleOrphans.length) {
rows.push({
kind: "l1",
id: ORPHAN_L1_ROW_ID,
title: "未归属 (缺一级组)",
...l1Agg(orphans)
});
if (search.trim() || !collapsedL1.has(ORPHAN_L1_ROW_ID)) for (const g of visibleOrphans) rows.push({
kind: "l2",
id: g.id,
group: g,
l1Enabled: true
});
}
const l2Ids = new Set(groups.map((g) => g.id));
const l1IdOfGroup = new Map(groups.map((g) => [g.id, g.l1Id]));
const selKeys = selectedKeys.map(String).filter((k) => l1Ids.has(k) || l2Ids.has(k));
const selL1Ids = selKeys.filter((k) => l1Ids.has(k));
const selL2Ids = selKeys.filter((k) => l2Ids.has(k));
const selL1Set = new Set(selL1Ids);
const mixedPick = selL1Ids.length > 0 && selL2Ids.length > 0;
const mergeKind = mixedPick ? void 0 : selL1Ids.length > 0 ? "l1" : selL2Ids.length > 0 ? "l2" : void 0;
const canMerge = mergeKind !== void 0;
const canMoveL2 = !mixedPick && selL1Ids.length === 0 && selL2Ids.length >= 1;
const canAssignTheme = !mixedPick && selL1Ids.length >= 1;
const selL1ChildCount = selL1Ids.reduce((s, id) => s + (childrenByL1.get(id)?.length ?? 0), 0);
const shareSelection = expandShareSelection({
l1Ids: selL1Ids,
groupIds: selL2Ids
}, groups);
const shareGroups = shareSelection.filter((g) => g.tag_ids.length > 0 || hasTagRecCombination(g.combination));
const emptyShareCount = shareSelection.length - shareGroups.length;
const [shareOpen, setShareOpen] = (0, import_react.useState)(false);
const deleteSelected = () => {
const removedL2 = favorites.removeGroups(selL2Ids);
let removedL1 = 0;
for (const id of selL1Ids) if (favoritesL1.removeL1Tree(id)) removedL1++;
setSelectedKeys([]);
toast(`已删除 ${[removedL1 ? `${removedL1} 个一级组${selL1ChildCount ? ` (连麾下 ${selL1ChildCount} 个标签组)` : ""}` : "", removedL2 ? `${removedL2} 个标签组` : ""].filter(Boolean).join(" + ") || "0 项"}`);
};
const toggleL1Collapse = (id) => setCollapsedL1((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
const toggleThemeFilter = (key, checked) => setThemeFilterSet((prev) => {
const next = new Set(prev);
if (checked) next.add(key);
else next.delete(key);
return next;
});
/**
* 主题「并入…」确认 (feat: 复选驱动): 勾选主题麾下 L1 全改挂 target, 源主题删;
* 目标含已选主题自身 (并入它=其余并入它) 或「新建主题…」弹窗取名;
* 确认后把已删源 id 剔出筛选集 (目标留在集内继续筛)
*/
const confirmThemeMerge = async () => {
const target = themeMergeTarget;
setThemeMergeOpen(false);
setThemeMergeTarget(void 0);
if (!target) return;
let targetId = target;
if (target === NEW_THEME_OPTION) {
const draft = await promptText("新建主题并并入", "");
if (draft === void 0) return;
const t = themes.addTheme({ name: draft });
if (!t) return;
targetId = t.id;
}
if (checkedThemeIds.length === 1 && checkedThemeIds[0] === targetId) return toast("目标就是勾选的唯一主题, 无需并入");
const n = themes.mergeThemesInto(checkedThemeIds, targetId);
if (n === void 0) return;
setThemeFilterSet((prev) => {
const next = new Set(prev);
for (const id of checkedThemeIds) if (id !== targetId) next.delete(id);
return next;
});
toast(`已把 ${n} 个主题并入「${themes.list.find((t) => t.id === targetId)?.name ?? targetId}」`);
};
/** 批量启/停 (feat4): 逐类门面 update —— L1 勾选连带语义只管呈现, 此处按显式勾选项写; 混选同样可用 */
const setSelectedEnabled = (enabled) => {
let n = 0;
for (const id of selL1Ids) if (favoritesL1.updateL1(id, { enabled })) n++;
for (const id of selL2Ids) if (favorites.updateGroup(id, { enabled })) n++;
toast(`已${enabled ? "启用" : "停用"} ${n} 项`);
};
/**
* 统一「并入…」确认 (feat4): kind 由勾选集定 (混选禁入)
* L1 档 —— 目标 = 既有 L1 (含已选自身 = 其余并入它): 逐源 mergeL1IntoL1, 源壳删;
* 目标 = 新建: 弹窗取名 addL1 后同路径
* L2 档 —— 目标 = 既有组 (含已选): mergeGroupsInto 统一归并成员与规则;
* 目标 = 新建: 最早组为基准,mergeGroupsToNew 校验创建后删除源组
*/
const confirmMerge = async () => {
const target = mergeTarget;
const kind = mergeKind;
setMergeOpen(false);
setMergeTarget(void 0);
if (!kind || !target) return;
if (kind === "l1") {
let targetId = target;
if (target === NEW_L1_OPTION) {
const draft = await promptText("新建一级组并并入", l1s.find((l) => l.id === selL1Ids[0])?.name ?? "");
if (draft === void 0) return;
const l1 = favoritesL1.addL1({ name: draft });
if (!l1) return;
targetId = l1.id;
}
if (!favoritesL1.list.some((l) => l.id === targetId)) return toast("目标一级组不存在");
if (selL1Ids.length === 1 && selL1Ids[0] === targetId) return toast("目标就是勾选的唯一组, 无需并入");
for (const sid of selL1Ids) if (sid !== targetId) favoritesL1.mergeL1IntoL1(sid, targetId);
setSelectedKeys([]);
return;
}
if (target === NEW_L2_OPTION) {
const draft = await promptText("新建标签组并并入", groups.find((g) => g.id === selL2Ids[0])?.name ?? "");
if (draft === void 0) return;
const created = favorites.mergeGroupsToNew(selL2Ids, draft);
if (!created) return;
toast(`已建标签组「${created.name}」并并入 ${selL2Ids.length} 个组`);
setSelectedKeys([]);
return;
}
if (selL2Ids.length === 1 && selL2Ids[0] === target) return toast("目标就是勾选的唯一组, 无需并入");
const merged = favorites.mergeGroupsInto(selL2Ids, target);
if (merged !== void 0) {
toast(`已把 ${merged} 个标签组并入「${groups.find((g) => g.id === target)?.name ?? target}」`);
setSelectedKeys([]);
}
};
/** 吸顶条「挂到主题…」确认 (feat3+): 逐 L1 指认 themeId (批量 assignL1ToTheme);
目标 = 既有主题 / 「新建主题…」弹窗取名 / 「未归属」清回 */
const confirmAssignTheme = async () => {
const target = assignThemeTarget;
setAssignThemeOpen(false);
setAssignThemeTarget(void 0);
if (!target) return;
let themeId;
if (target === NEW_THEME_OPTION) {
const draft = await promptText("新建主题, 并把勾选的一级组归入", "");
if (draft === void 0) return;
const t = themes.addTheme({ name: draft });
if (!t) return;
themeId = t.id;
} else if (target !== THEME_ASSIGN_CLEAR) themeId = target;
let n = 0;
for (const id of selL1Ids) if (themes.assignL1ToTheme(id, themeId)) n++;
toast(`已把 ${n} 个一级组归入${themeId ? `「${themes.list.find((t) => t.id === themeId)?.name ?? themeId}」` : "「未归属」"}`);
setSelectedKeys([]);
};
/** 新建目标先取名并确认;与选择既有目标一样,经批量移动门面归入并清理移空的源组。 */
const confirmMoveL2 = async () => {
const target = moveL2Target;
setMoveL2Open(false);
if (!target || !selL2Ids.length) return;
const res = target === NEW_L1_OPTION ? await createL1AndMoveGroups(selL2Ids) : favoritesL1.moveGroupsToL1(selL2Ids, target);
if (!res) return;
const shells = res.removedShells.length ? `; 源空壳一级组已删: ${res.removedShells.join("、")}` : "";
toast(target === NEW_L1_OPTION ? `已建一级组「${res.l1.name}」并把 ${res.moved} 组归入 (默认停用)${shells}` : `已将 ${res.moved} 个标签组移到「${res.l1.name}」${shells}`);
setSelectedKeys([]);
};
/** 「+ 一级组」: 默认停用 (spec 重新开张语义) —— L1 行 enabled 开关是「逐个挑谁上岗」主路径 */
const addL1 = async () => {
const name = await promptText("新建一级组");
if (name === void 0) return;
const l1 = favoritesL1.addL1({ name });
if (l1) toast(`已新建一级组「${l1.name}」(默认停用, 行内启用后麾下组才入抽)`);
};
const noteEditor = (entity, onChange) => /* @__PURE__ */ jsx(Typography.Paragraph, {
type: entity.note?.trim() ? void 0 : "secondary",
className: "mt-3px text-12px mb-0!",
"aria-label": `简介:${entity.name}`,
ellipsis: {
rows: 1,
tooltip: entity.note
},
editable: {
text: entity.note ?? "",
tooltip: "编辑简介",
triggerType: ["text"],
autoSize: {
minRows: 1,
maxRows: 3
},
onChange
},
children: entity.note?.trim() ? entity.note : "+ 简介"
});
const toggleGroupDetails = (id) => setExpandedGroups((prev) => prev.includes(id) ? prev.filter((k) => k !== id) : [...prev, id]);
const startNewChild = (l1Id) => {
setNewChild({
l1Id,
name: ""
});
setCollapsedL1((prev) => new Set([...prev].filter((id) => id !== l1Id)));
};
const createChild = () => {
if (!newChild?.name.trim()) return;
if (!favoritesL1.list.some((l) => l.id === newChild.l1Id)) return toast("一级组已不存在");
const created = favorites.addGroup({
name: newChild.name,
l1Id: newChild.l1Id,
tag_ids: []
});
if (!created) return;
setExpandedGroups((prev) => [...prev, created.group.id]);
setSearch("");
setNewChild(void 0);
toast("已建空二级组,可在下方添加标签");
};
const columns = [
{
key: "name",
title: "组名与简介",
render: (_, r) => r.kind === "l1" ? /* @__PURE__ */ jsxs("div", {
className: "min-w-0",
children: [/* @__PURE__ */ jsxs("div", {
className: "min-h-24px flex flex-wrap items-center gap-x-7px gap-y-3px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "break-words text-14px",
editable: r.l1 && !r.contextOnly ? {
tooltip: "编辑一级组名",
triggerType: ["text", "icon"],
onChange: (v) => favoritesL1.updateL1(r.id, { name: v })
} : void 0,
children: r.title
}),
r.l1 && !r.contextOnly && /* @__PURE__ */ jsx(Typography.Link, {
className: "text-12px",
href: buildTagRecFavDeepLink(location.origin),
target: "_blank",
title: "打开主题推荐",
onClick: (e) => e.stopPropagation(),
children: "↗"
}),
r.l1 && !r.contextOnly && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-4px",
onClick: () => startNewChild(r.id),
children: "+ 二级组"
}),
r.contextOnly && /* @__PURE__ */ jsx("span", {
className: "text-10px opacity-50",
children: "路径 · 仅下方二级组匹配"
}),
/* @__PURE__ */ jsxs("span", {
className: "text-11px opacity-60",
children: [
r.childCount,
" 组 · ",
r.idCount,
" 个标签",
r.negCount ? ` · ${r.negCount} 个负向` : ""
]
})
]
}), r.l1 && !r.contextOnly && noteEditor(r.l1, (v) => favoritesL1.updateL1(r.id, { note: v }))]
}) : /* @__PURE__ */ jsxs("div", {
className: "min-w-0 pl-18px",
children: [/* @__PURE__ */ jsxs("div", {
className: "min-h-24px flex flex-wrap items-center gap-x-7px gap-y-3px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
className: "break-words",
editable: {
tooltip: "编辑二级组名",
triggerType: ["text", "icon"],
onChange: (v) => favorites.updateGroup(r.id, { name: v })
},
children: r.group.name
}),
hasTagRecCombination(r.group.combination) ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tag, {
className: "mr-0 text-10px",
title: nameTagRecCombination(r.group.combination),
children: "组合"
}), /* @__PURE__ */ jsx(Typography.Link, {
className: "text-12px",
href: buildTagRecCustomDeepLink(location.origin, { group: r.id }),
target: "_blank",
title: "在「标签发掘」中编辑子组 / 全部命中·任一命中 / 标签·标题·简介等条件",
onClick: (e) => e.stopPropagation(),
children: "编辑条件 ↗"
})] }) : /* @__PURE__ */ jsx(Typography.Link, {
className: "text-12px",
href: buildTagRecCustomDeepLink(location.origin, { group: r.id }),
target: "_blank",
title: "打开标签发掘",
onClick: (e) => e.stopPropagation(),
children: "↗"
}),
!r.group.tag_ids.length && !hasTagRecCombination(r.group.combination) && /* @__PURE__ */ jsx(Tag, {
className: "mr-0 text-10px",
children: "待添加标签"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "h-auto px-4px text-11px color-gate-primary",
onClick: () => toggleGroupDetails(r.id),
children: !r.group.tag_ids.length ? hasTagRecCombination(r.group.combination) ? expandedGroups.includes(r.id) ? "收起条件" : "条件组" : "添加标签" : expandedGroups.includes(r.id) ? "收起标签" : `${r.group.tag_ids.length} 个标签`
})
]
}), noteEditor(r.group, (v) => favorites.updateGroup(r.id, { note: v }))]
})
},
{
key: "assignment",
title: "所属主题 / 一级组",
width: 185,
render: (_, r) => {
if (r.kind === "l1") {
if (!r.l1) return null;
if (r.contextOnly) return /* @__PURE__ */ jsx(Tag, { children: themeList.find((t) => t.id === r.l1?.themeId)?.name ?? "未归属主题" });
return /* @__PURE__ */ jsx(ThemeAssignSelect, { l1: r.l1 });
}
return /* @__PURE__ */ jsx(L1AssignSelect, { group: r.group });
}
},
{
key: "weight",
title: "抽取权重",
width: 90,
render: (_, r) => {
if (r.kind === "l1" && r.contextOnly) return null;
const entity = r.kind === "l1" ? r.l1 : r.group;
if (!entity) return null;
return /* @__PURE__ */ jsx(AntdTooltip, {
title: "0 挂起,不参与自动推荐;1–5 越大越常抽到",
children: /* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
className: "w-72px",
styles: { actions: {
opacity: 1,
width: 22
} },
"aria-label": `抽取权重:${entity.name}`,
min: 0,
max: 5,
precision: 0,
value: entity.weight,
onChange: (v) => {
if (v === null) return;
if (r.kind === "l1") favoritesL1.updateL1(r.id, { weight: v });
else favorites.updateGroup(r.id, { weight: v });
}
})
});
}
},
{
key: "enabled",
title: "保存开关",
width: 100,
align: "center",
render: (_, r) => {
if (r.kind === "l1" && r.contextOnly) return null;
const entity = r.kind === "l1" ? r.l1 : r.group;
if (!entity) return null;
const empty = r.kind === "l2" && !r.group.tag_ids.length && !hasTagRecCombination(r.group.combination);
const label = empty ? "待添加" : entity.weight === 0 ? "挂起" : !entity.enabled ? "已关闭" : r.kind === "l2" && !r.l1Enabled ? "父组关闭" : "已启用";
return /* @__PURE__ */ jsx(AntdTooltip, {
title: empty ? "先添加标签,再启用二级组" : r.kind === "l1" ? "保存开关优先于临时范围;打开后按各二级组自己的开关参与推荐" : !r.l1Enabled ? "二级组开关独立保存;一级组打开后才参与推荐" : "保存开关优先于该二级组的临时选择",
children: /* @__PURE__ */ jsxs("span", {
className: "min-h-24px inline-flex items-center justify-center gap-4px",
children: [/* @__PURE__ */ jsx(Switch, {
size: "small",
"aria-label": (r.kind === "l1" ? "启用一级组:" : "启用二级组:") + entity.name,
checked: entity.enabled,
disabled: empty,
onChange: (v) => r.kind === "l1" ? favoritesL1.updateL1(r.id, { enabled: v }) : favorites.updateGroup(r.id, { enabled: v })
}), /* @__PURE__ */ jsx("span", {
className: "text-10px opacity-55",
children: label
})]
})
});
}
},
{
key: "actions",
title: "操作",
width: 100,
align: "right",
render: (_, r) => {
if (r.kind === "l1" && r.contextOnly) return null;
const entity = r.kind === "l1" ? r.l1 : r.group;
if (!entity) return null;
return /* @__PURE__ */ jsxs("div", {
className: "min-h-24px inline-flex items-center gap-8px",
children: [/* @__PURE__ */ jsx(AntdTooltip, {
title: r.kind === "l1" ? "过滤与算分配置" : "组现状与配置",
children: /* @__PURE__ */ jsx(Badge, {
dot: hasTagRecGroupConfig(entity.overrides),
size: "small",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "px-0",
"aria-label": `${r.kind === "l1" ? "过滤与算分配置" : "组现状与配置"}:${entity.name}`,
icon: /* @__PURE__ */ jsx(IconForConfig, { className: "size-14px" }),
onClick: () => setConfigTarget(r.kind === "l1" ? {
kind: "l1",
l1Id: r.id
} : {
kind: "l2",
groupId: r.id
})
})
})
}), /* @__PURE__ */ jsx(Popconfirm, {
title: "删除" + (r.kind === "l1" ? "一级组" : "二级组"),
description: r.kind === "l1" ? `删除「${entity.name}」及其 ${r.childCount} 个二级组?` : `删除「${entity.name}」?`,
okText: "删除",
okButtonProps: { danger: true },
onConfirm: () => r.kind === "l1" ? favoritesL1.removeL1Tree(r.id) : favorites.removeGroup(r.id),
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
danger: true,
className: "px-0",
"aria-label": `删除:${entity.name}`,
children: "删除"
})
})]
});
}
}
];
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-12px flex flex-wrap items-center gap-8px",
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: () => setCreating((v) => !v),
children: creating ? "收起" : "新建标签组"
}),
/* @__PURE__ */ jsxs(button_default, {
size: "small",
onClick: addL1,
children: [/* @__PURE__ */ jsx(ForwardRef$33, {}), "一级组"]
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"共 ",
l1s.length,
" 个一级组 · ",
list.length,
" 组"
]
})
]
}),
/* @__PURE__ */ jsx(Input, {
size: "small",
className: "mb-10px max-w-400px",
allowClear: true,
value: search,
placeholder: "搜一级组、二级组或标签",
"aria-label": "搜索收藏表",
onChange: (e) => {
setSearch(e.target.value);
setSelectedKeys([]);
},
onKeyDown: (e) => {
if (e.key === "Escape" && search) {
e.stopPropagation();
setSearch("");
}
}
}),
themeList.length > 0 || unassignedL1Count > 0 ? /* @__PURE__ */ jsx("div", {
className: "mb-10px",
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px gap-y-6px",
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: !effThemeSet.size ? "primary" : "default",
onClick: () => {
setThemeFilterSet(/* @__PURE__ */ new Set());
setSelectedKeys([]);
},
children: "全部主题"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "主题:"
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "没有归属主题的一级组 (可经行内「所属主题」列或吸顶「挂到主题…」归置)",
children: /* @__PURE__ */ jsx(Checkbox, {
checked: effThemeSet.has(THEME_FILTER_UNASSIGNED),
onChange: (e) => {
toggleThemeFilter(THEME_FILTER_UNASSIGNED, e.target.checked);
setSelectedKeys([]);
},
children: /* @__PURE__ */ jsxs("span", {
className: `text-13px ${unassignedL1Count ? "color-gate-primary font-600" : "opacity-60"}`,
children: ["未归属", unassignedL1Count ? ` ⚠${unassignedL1Count}` : " · 0"]
})
})
}),
themeList.map((t) => /* @__PURE__ */ jsx(ThemeFilterCheck, {
theme: t,
l1Count: l1s.filter((l) => l.themeId === t.id).length,
active: soloViewedThemeId === t.id,
checked: effThemeSet.has(t.id),
onCheck: (v) => {
toggleThemeFilter(t.id, v);
setSelectedKeys([]);
}
}, t.id)),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "只看勾选主题,停用的一级组本次临时开启;刷新网页还原,收藏表启用状态不变。未勾选则恢复当前启用范围",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
viewScope.previewThemes(effThemeSet);
videoSourceTabState.value = ETab.TagRecFav;
hideModalSettings();
recSharedEmitter.emit("refresh", "refresh");
},
children: "用此筛选看推荐"
})
}),
checkedThemeIds.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"已勾 ",
checkedThemeIds.length,
" 个"
]
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "勾选主题麾下的一级组全部改挂到目标主题, 源主题删除; 目标可选已勾主题自身或新建",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
setThemeMergeTarget(void 0);
setThemeMergeOpen(true);
},
children: "并入…"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "删除勾选主题; 其麾下一级组落回「未归属」(组与启用状态不动)",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
onClick: async () => {
const orphanN = l1s.filter((l) => l.themeId && checkedThemeIds.includes(l.themeId)).length;
if (!await antModal.confirm({
centered: true,
title: `删除 ${checkedThemeIds.length} 个主题?`,
content: orphanN ? `其麾下 ${orphanN} 个一级组将落回「未归属」(组与启用状态不动)` : "勾选的主题下没有归属的一级组",
okText: "删除",
okButtonProps: { danger: true }
})) return;
const n = themes.removeThemes(checkedThemeIds);
toast(`已删除 ${n} 个主题`);
},
children: "删除"
})
})
] })
]
})
}) : null,
(l1s.length > 0 || themeList.length > 0) && /* @__PURE__ */ jsx("div", {
className: "mb-6px flex items-center gap-x-8px",
children: /* @__PURE__ */ jsx(AntdTooltip, {
title: effThemeSet.size ? "按一级组筛选表格行 (纯视图); 选项已收窄到勾选主题麾下" : "按一级组筛选表格行 (纯视图, 不动归属); 「未归属」= 无一级组的裸奔组伪节",
children: /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-200px",
showSearch: true,
optionFilterProp: "label",
value: effL1Filter ?? L1_FILTER_ALL,
options: [
{
value: L1_FILTER_ALL,
label: effThemeSet.size ? "勾选主题内全部一级组" : "全部一级组"
},
...orphansVisible ? [{
value: L1_FILTER_UNASSIGNED,
label: "未归属"
}] : [],
...scopedL1s.map((l) => ({
value: l.id,
label: `仅「${l.name}」`
}))
],
onChange: (v) => {
setL1Filter(v === L1_FILTER_ALL ? void 0 : v);
setSelectedKeys([]);
}
})
})
}),
creating && /* @__PURE__ */ jsx(CandidateResolvePicker, {
confirmLabel: "存为一项",
onConfirm: (entries) => {
const g = favorites.addGroup({
name: entries[0]?.name,
tag_ids: entries
})?.group;
if (g) {
toast(`已存为标签组「${g.name}」`);
setCreating(false);
}
}
}),
selKeys.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "sticky top-0 z-10 mb-6px flex flex-wrap items-center gap-8px b-b-1px b-gate-border b-solid bg-gate-bg py-6px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"已选",
" ",
[selL1Ids.length ? `${selL1Ids.length} 个一级组` : "", selL2Ids.length ? `${selL2Ids.length} 个标签组` : ""].filter(Boolean).join(" + ")
]
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "把勾选项并入一个目标 (可选已选项自身 = 其余并入它, 或新建组); 一级组=麾下二级组过继后源壳删, 标签组=成员并入后源组删",
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !canMerge,
onClick: () => {
setMergeTarget(void 0);
setMergeOpen(true);
},
children: "并入…"
}) })
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "勾选的标签组改挂到另一级组 (可新建); 源一级组变空壳将自动删除",
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !canMoveL2,
onClick: () => {
setMoveL2Target(void 0);
setMoveL2Open(true);
},
children: "移到一级组…"
}) })
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "勾选的一级组改挂到目标主题 (可新建, 或「未归属」清回)",
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !canAssignTheme,
onClick: () => {
setAssignThemeTarget(void 0);
setAssignThemeOpen(true);
},
children: "挂到主题…"
}) })
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setSelectedEnabled(true),
children: "启用"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setSelectedEnabled(false),
children: "停用"
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "删除勾选项; 一级组连麾下标签组一并删除",
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
onClick: async () => {
if (await antModal.confirm({
centered: true,
title: "删除勾选项",
content: `将删除 ${[selL1Ids.length ? `${selL1Ids.length} 个一级组${selL1ChildCount ? ` (连麾下 ${selL1ChildCount} 个标签组)` : ""}` : "", selL2Ids.length ? `${selL2Ids.length} 个标签组` : ""].filter(Boolean).join(" + ")}`,
okText: "删除",
okButtonProps: { danger: true }
})) deleteSelected();
},
children: "删除"
}) })
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "把勾选的标签组上传到分享平台; 勾选一级组 = 麾下整棵子树",
children: /* @__PURE__ */ jsxs("span", { children: [/* @__PURE__ */ jsxs(button_default, {
size: "small",
disabled: !shareGroups.length,
onClick: () => setShareOpen(true),
children: [
"分享选中 (",
shareGroups.length,
")"
]
}), !!emptyShareCount && /* @__PURE__ */ jsxs("span", {
className: "ml-4px text-11px opacity-60",
children: [emptyShareCount, " 个空组暂不分享,可导出备份"]
})] })
}),
mixedPick && /* @__PURE__ */ jsx(Typography.Text, {
type: "warning",
className: "text-12px",
children: "并入/移动不支持混选 (启用/停用/删除可用)"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "ml-auto px-0",
onClick: () => setSelectedKeys([]),
children: "取消选择"
})
]
}),
/* @__PURE__ */ jsx(table_default, {
size: "small",
rowKey: "id",
columns: columns.map((column) => ({
...column,
onCell: () => ({ style: { verticalAlign: "top" } })
})),
dataSource: rows,
pagination: false,
tableLayout: "fixed",
virtual: true,
scroll: {
x: 820,
y: 480
},
onRow: (r) => r.kind === "l1" ? { style: { background: "color-mix(in oklab, var(--bilibili-gate--primary-color) 8%, transparent)" } } : {},
rowSelection: {
columnWidth: 44,
selectedRowKeys: [...selKeys, ...rows.filter((r) => r.kind === "l2" && !!r.group.l1Id && selL1Set.has(r.group.l1Id)).map((r) => r.id)],
renderCell: (_checked, r, _index, node) => r.kind === "l1" && r.contextOnly ? /* @__PURE__ */ jsx("span", {
className: "text-11px opacity-40",
children: "›"
}) : node,
getCheckboxProps: (r) => ({ disabled: r.kind === "l1" ? !r.l1 || r.contextOnly : !!r.group.l1Id && selL1Set.has(r.group.l1Id) }),
onSelect: (r, on) => {
const next = new Set(selKeys);
if (on) next.add(r.id);
else next.delete(r.id);
if (r.kind === "l1") for (const g of childrenByL1.get(r.id) ?? []) next.delete(g.id);
setSelectedKeys([...next]);
},
onSelectAll: (on, _selected, changed) => {
const next = new Set(selKeys);
for (const r of changed) {
if (r.kind === "l1" && (!r.l1 || r.contextOnly)) continue;
if (on) next.add(r.id);
else next.delete(r.id);
}
const parentKeys = new Set([...next].filter((id) => l1Ids.has(id)));
setSelectedKeys([...next].filter((id) => !l2Ids.has(id) || !parentKeys.has(l1IdOfGroup.get(id) ?? "")));
}
},
expandable: {
columnWidth: 44,
expandedRowKeys: expandedGroups,
onExpandedRowsChange: (keys) => setExpandedGroups([...keys]),
rowExpandable: () => true,
expandedRowRender: (r) => r.kind === "l2" ? /* @__PURE__ */ jsx(GroupExpandedArea, {
group: r.group,
onOpenStatus: () => setConfigTarget({
kind: "l2",
groupId: r.group.id
})
}) : null,
expandIcon: ({ record: r, expanded, onExpand }) => r.kind === "l1" ? /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "h-22px w-22px shrink-0 px-0",
title: collapsedL1.has(r.id) ? "展开" : "折叠",
onClick: (e) => {
e.stopPropagation();
toggleL1Collapse(r.id);
},
children: collapsedL1.has(r.id) ? /* @__PURE__ */ jsx(ForwardRef$7$1, { className: "text-15px" }) : /* @__PURE__ */ jsx(ForwardRef$7, { className: "text-15px" })
}) : /* @__PURE__ */ jsx("button", {
type: "button",
className: `ant-table-row-expand-icon ${expanded ? "ant-table-row-expand-icon-expanded" : "ant-table-row-expand-icon-collapsed"}`,
"aria-label": expanded ? "收起" : "展开",
onClick: (e) => onExpand(r, e)
})
},
locale: { emptyText: search.trim() ? "没有匹配的组或标签" : "新建一级组后,点击「+ 二级组」创建空组;也可从标签新建" }
}),
/* @__PURE__ */ jsxs(Modal, {
title: "新建二级组",
open: !!newChild,
onCancel: () => setNewChild(void 0),
onOk: createChild,
okText: "创建空组",
okButtonProps: { disabled: !newChild?.name.trim() },
width: 360,
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-10px text-12px opacity-65",
children: [
themeList.find((t) => t.id === l1s.find((l) => l.id === newChild?.l1Id)?.themeId)?.name ?? "未归属",
" ›",
" ",
l1s.find((l) => l.id === newChild?.l1Id)?.name,
" › 新二级组"
]
}),
/* @__PURE__ */ jsx(Input, {
autoFocus: true,
maxLength: 120,
placeholder: "输入二级组名",
"aria-label": "二级组名",
value: newChild?.name ?? "",
onChange: (e) => setNewChild((prev) => prev ? {
...prev,
name: e.target.value
} : prev),
onPressEnter: createChild
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: "先建组,再添加标签。空组保留名称和配置,暂不参与推荐。"
})
]
}),
/* @__PURE__ */ jsxs(Modal, {
title: mergeKind === "l1" ? `并入 ${selL1Ids.length} 个一级组` : `并入 ${selL2Ids.length} 个标签组`,
open: mergeOpen,
onCancel: () => setMergeOpen(false),
onOk: () => void confirmMerge(),
okText: "并入",
okButtonProps: { disabled: !mergeTarget },
width: 400,
children: [/* @__PURE__ */ jsx(Select, {
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: mergeKind === "l1" ? "选择目标一级组" : "选择目标标签组",
value: mergeTarget,
onChange: setMergeTarget,
options: mergeKind === "l1" ? [{
value: NEW_L1_OPTION,
label: "+ 新建一级组并并入…"
}, ...l1s.map((l) => ({
...l1Option(l),
label: `${l1Option(l).label}${selL1Set.has(l.id) ? " (已选)" : ""}`
}))] : [{
value: NEW_L2_OPTION,
label: "+ 新建标签组并并入…"
}, ...groups.map((g) => ({
value: g.id,
label: `${g.name} (${g.tag_ids.length} 个 id)${selL2Ids.includes(g.id) ? " (已选)" : ""}`
}))]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: mergeKind === "l1" ? `勾选的一级组麾下标签组全部过继到目标, 源一级组删除${mergeTarget === NEW_L1_OPTION ? "; 目标=新建: 确认后弹窗取名" : ""}` : `成员与屏蔽名单合并; 其他设置冲突保留目标、缺项补齐, 合并后沿用目标组的设置与归属。源组删除${mergeTarget === NEW_L2_OPTION ? "; 新组以最早组为基准, 确认后取名" : ""}`
})]
}),
/* @__PURE__ */ jsxs(Modal, {
title: `并入 ${checkedThemeIds.length} 个主题`,
open: themeMergeOpen,
onCancel: () => setThemeMergeOpen(false),
onOk: () => void confirmThemeMerge(),
okText: "并入",
okButtonProps: { disabled: !themeMergeTarget },
width: 400,
children: [/* @__PURE__ */ jsx(Select, {
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: "选择目标主题",
value: themeMergeTarget,
onChange: setThemeMergeTarget,
options: [{
value: NEW_THEME_OPTION,
label: "+ 新建主题并并入…"
}, ...themeList.slice().sort((a, b) => Number(checkedThemeIds.includes(b.id)) - Number(checkedThemeIds.includes(a.id))).map((t) => {
const dup = themeList.filter((x) => x.name === t.name).length > 1;
const check = checkedThemeIds.includes(t.id) ? " (已勾)" : "";
return {
value: t.id,
label: `${t.name}${dup ? `·#${t.id.slice(0, 4)}` : ""} (${l1s.filter((l) => l.themeId === t.id).length} 个一级组)${check}`
};
})]
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: ["勾选主题麾下的一级组全部改挂到目标主题, 源主题删除", themeMergeTarget === NEW_THEME_OPTION ? "; 目标=新建: 确认后弹窗取名" : ""]
})]
}),
/* @__PURE__ */ jsxs(Modal, {
title: `把 ${selL1Ids.length} 个一级组挂到主题`,
open: assignThemeOpen,
onCancel: () => setAssignThemeOpen(false),
onOk: () => void confirmAssignTheme(),
okText: "归入",
okButtonProps: { disabled: !assignThemeTarget },
width: 400,
children: [/* @__PURE__ */ jsx(Select, {
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: "选择目标主题",
value: assignThemeTarget,
onChange: setAssignThemeTarget,
options: [
{
value: NEW_THEME_OPTION,
label: "+ 新建主题…"
},
{
value: THEME_ASSIGN_CLEAR,
label: "未归属 (清回)"
},
...themeList.map((t) => ({
value: t.id,
label: `${t.name} (${l1s.filter((l) => l.themeId === t.id).length} 个一级组)`
}))
]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: "主题是筛子不是容器: 改挂不动 enabled/weight, 只在 tab1 主题档参与过滤"
})]
}),
/* @__PURE__ */ jsxs(Modal, {
title: `移动 ${selL2Ids.length} 个标签组到一级组`,
open: moveL2Open,
onCancel: () => setMoveL2Open(false),
onOk: confirmMoveL2,
okText: "移动",
okButtonProps: { disabled: !moveL2Target },
width: 400,
children: [/* @__PURE__ */ jsx(Select, {
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: "选择目标一级组",
value: moveL2Target,
onChange: setMoveL2Target,
options: [{
value: NEW_L1_OPTION,
label: "+ 新建一级组…"
}, ...l1s.map(l1Option)]
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: "改挂后源一级组若变空壳将提示并自动删除"
})]
}),
/* @__PURE__ */ jsx(TagRecShareModal, {
open: shareOpen,
groups: shareGroups,
onClose: () => setShareOpen(false),
onOpenMyShares
}),
/* @__PURE__ */ jsx(LegacyBlacklistImportRow, {}),
/* @__PURE__ */ jsx(TagRecGroupConfigModal, {
open: !!configTarget,
target: configTarget,
onClose: () => setConfigTarget(void 0)
})
] });
}
/**
* L2 行「所属一级组」指认 (票 17): 下拉列全部 L1 (停用/挂起带态后缀, 指入停用 L1 合法但暂不入抽)
* +「新建一级组…」入口 —— 先取名,确认后才创建和归入;取消保持原归属。
*/
function L1AssignSelect({ group }) {
const { list: l1List } = useSnapshot(favoritesL1Store);
const l1s = l1List;
const value = group.l1Id && l1s.some((l) => l.id === group.l1Id) ? group.l1Id : void 0;
return /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-full",
"aria-label": `所属一级组:${group.name}`,
placeholder: "未归属",
showSearch: true,
optionFilterProp: "label",
getPopupContainer: (trigger) => trigger.parentElement,
value: value ?? null,
options: [{
value: NEW_L1_OPTION,
label: "+ 新建一级组…"
}, ...l1s.map(l1Option)],
onChange: (v) => {
if (v === NEW_L1_OPTION) createL1AndMoveGroups([group.id]).then((res) => {
if (res) toast(`已建一级组「${res.l1.name}」并归入 (默认停用)`);
});
else favoritesL1.assignGroupToL1(group.id, v);
}
});
}
/**
* L1 行「所属主题」指认 (票 26): 下拉列全部主题 + allowClear(清空=未归属, 落「全部」档)
* +「新建主题…」入口 —— 弹窗取名 => addTheme + 自动指认本 L1;
* 主题是筛子不是容器: 指认不改 enabled/weight, 只在 tab1 主题档参与过滤
*/
function ThemeAssignSelect({ l1 }) {
const { list: themeList } = useSnapshot(themesStore);
const value = l1.themeId && themeList.some((t) => t.id === l1.themeId) ? l1.themeId : void 0;
return /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-full font-bold",
styles: { root: { borderColor: "var(--bilibili-gate--primary-color)" } },
"aria-label": `所属主题:${l1.name}`,
placeholder: "选择或新建主题",
allowClear: true,
showSearch: true,
optionFilterProp: "label",
getPopupContainer: (trigger) => trigger.parentElement,
value: value ?? null,
options: [{
value: NEW_THEME_OPTION,
label: "+ 新建主题…"
}, ...themeList.map((t) => ({
value: t.id,
label: t.name
}))],
onChange: (v) => {
if (v === NEW_THEME_OPTION) createThemeAndAssign(l1);
else themes.assignL1ToTheme(l1.id, v);
}
});
}
/**
* feat3+: 主题复选框 (chip 撤) —— 勾选 = 表格只展该主题麾下 (多选并集, 空集=全部);
* 右键出菜单 [改名…/删除]; 当前生效主题名高亮 (active=主题推荐正在抽的主题);
* 0-L1 空壳主题灰显 (并入素材一目了然)
*/
function ThemeFilterCheck({ theme, l1Count, active, checked, onCheck }) {
const rename = async () => {
let draft = theme.name;
if (!await antModal.confirm({
centered: true,
title: "主题改名",
content: /* @__PURE__ */ jsx(Input, {
autoFocus: true,
defaultValue: theme.name,
onChange: (e) => draft = e.target.value
}),
okText: "改名"
}) || draft.trim() === theme.name) return;
themes.updateTheme(theme.id, { name: draft });
};
const remove = async () => {
if (!await antModal.confirm({
centered: true,
title: `删除主题「${theme.name}」?`,
content: l1Count ? `麾下 ${l1Count} 个一级组将落回「未归属」(组与启用状态不动)` : "该主题下没有归属的一级组",
okText: "删除",
okButtonProps: { danger: true }
})) return;
themes.removeTheme(theme.id);
};
return /* @__PURE__ */ jsx(Dropdown, {
trigger: ["contextMenu"],
menu: {
items: [{
key: "rename",
label: "改名…"
}, {
key: "remove",
label: "删除",
danger: true
}],
onClick: ({ key }) => key === "rename" ? void rename() : void remove()
},
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(AntdTooltip, {
title: "勾选筛选本主题的一级组 · 右键改名/删除 · ↗ 以本主题打开「主题推荐」",
children: /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center",
children: [/* @__PURE__ */ jsx(Checkbox, {
checked,
onChange: (e) => onCheck(e.target.checked),
children: /* @__PURE__ */ jsxs("span", {
className: `text-12px ${active ? "color-gate-primary font-600" : l1Count ? "" : "opacity-50"}`,
children: [
theme.name,
" · ",
l1Count
]
})
}), /* @__PURE__ */ jsx(Typography.Link, {
className: "ml-3px text-12px",
title: `以主题「${theme.name}」打开「主题推荐」`,
href: buildTagRecFavDeepLink(location.origin),
target: "_blank",
onClick: (ev) => {
ev.stopPropagation();
themes.setActive(theme.id);
},
children: "↗"
})]
})
}) })
});
}
/** 弹窗取名 (feat4「并入…」新建档): 确认 => 输入串 (空串门面自拒), 取消 => undefined */
async function promptText(title, defaultValue = "") {
let draft = defaultValue;
return await antModal.confirm({
centered: true,
title,
content: /* @__PURE__ */ jsx(Input, {
autoFocus: true,
"aria-label": "名称",
placeholder: "输入名称",
defaultValue,
onChange: (e) => draft = e.target.value
}),
okText: "确定"
}) ? draft : void 0;
}
/** 行内与批量「新建一级组」共用:取消/空名/源组失效均不迁移,失败时不留下新空壳。 */
async function createL1AndMoveGroups(groupIds) {
const name = await promptText("新建一级组并归入");
if (name === void 0) return;
if (!groupIds.length || groupIds.some((id) => !favorites.list.some((g) => g.id === id))) {
toast("标签组已不存在");
return;
}
const l1 = favoritesL1.addL1({ name });
if (!l1) return;
const res = favoritesL1.moveGroupsToL1(groupIds, l1.id);
if (!res) favoritesL1.removeL1(l1.id);
return res;
}
/** 「新建主题…」(票 26): 弹窗取名 => addTheme (空名门面自拒) => 自动指认当前 L1 */
async function createThemeAndAssign(l1) {
let draft = "";
if (!await antModal.confirm({
centered: true,
title: `新建主题, 并把一级组「${l1.name}」归入`,
content: /* @__PURE__ */ jsx(Input, {
autoFocus: true,
placeholder: "主题名 (如: 游戏)",
onChange: (e) => draft = e.target.value
}),
okText: "新建并归入"
})) return;
const t = themes.addTheme({ name: draft });
if (!t) return;
themes.assignL1ToTheme(l1.id, t.id);
toast(`已建主题「${t.name}」并把「${l1.name}」归入`);
}
/** 展开行 = 组内 tag_id 芯片列表 + 负向成员位 (票 14) + 尾部「添加变体」/「重新挖掘变体」 + chip 批量移/复 (票 29) */
function GroupExpandedArea({ group, onOpenStatus }) {
const { list: allGroups } = useSnapshot(favoritesStore);
const [addingVariant, setAddingVariant] = (0, import_react.useState)(!group.tag_ids.length);
const [enableAfterAdding, setEnableAfterAdding] = (0, import_react.useState)(true);
const [addingNegative, setAddingNegative] = (0, import_react.useState)(false);
const [batchPick, setBatchPick] = (0, import_react.useState)(false);
const [pickedIds, setPickedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
const [batchOp, setBatchOp] = (0, import_react.useState)(void 0);
const [batchTargetId, setBatchTargetId] = (0, import_react.useState)(void 0);
const [opIds, setOpIds] = (0, import_react.useState)([]);
const [cooccurOpen, setCooccurOpen] = (0, import_react.useState)(false);
const [cooccurRows, setCooccurRows] = (0, import_react.useState)([]);
const [cooccurBusy, setCooccurBusy] = (0, import_react.useState)(false);
const negatives = groupNegativeTagIds(group);
const togglePick = (id) => setPickedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
const deletePicked = () => {
let removed = 0;
for (const id of pickedIds) if (favorites.removeTagIdFromGroup(group.id, id)) removed++;
if (removed) toast(`已从本组移出 ${removed} 个标签`);
setPickedIds(/* @__PURE__ */ new Set());
};
/**
* 批量确认 (票 29): 移 = addTagIdsToGroup(目标) + 逐 id removeTagIdFromGroup(本组);
* 复 = 仅 addTagIdsToGroup (tag 多宿主本就支持, 票 12); 去重/skipped/负向撞车转正走门面内建汇报;
* 末位保底拒移 (组恒 ≥1 正向) 由 removeTagIdFromGroup 自 toast; 负向成员不在本批量范围
*/
const confirmBatchOp = () => {
const op = batchOp;
const targetId = batchTargetId;
const ids = new Set(opIds);
setBatchOp(void 0);
if (!op || !targetId) return;
if (!favorites.list.some((g) => g.id === targetId)) {
toast("目标标签组不存在");
return;
}
const entries = group.tag_ids.filter((e) => ids.has(e.id)).map((e) => ({ ...e }));
if (!entries.length) return;
favorites.addTagIdsToGroup(targetId, entries);
if (op === "move") {
let removed = 0;
for (const e of entries) if (favorites.removeTagIdFromGroup(group.id, e.id)) removed++;
toast(`已从本组移出 ${removed} 个标签`);
}
setPickedIds(/* @__PURE__ */ new Set());
};
/** 「共现变体表」开合: 开=从沉淀池聚合本组各成员的共现邻域 (剔除已在组内正/负向), 频次跨成员累加 */
const toggleCooccur = () => {
if (cooccurOpen) {
setCooccurOpen(false);
return;
}
const memberNorms = new Set(group.tag_ids.map((e) => normalizeTagName(e.name)));
const negNorms = new Set(negatives.map((e) => normalizeTagName(e.name)));
const acc = /* @__PURE__ */ new Map();
for (const norm of memberNorms) {
const node = sediment.map.get(norm);
if (!node) continue;
for (const [other, edge] of Object.entries(node.cooccur)) {
if (memberNorms.has(other) || negNorms.has(other)) continue;
const cur = acc.get(other);
if (cur) cur.count += edge.count;
else acc.set(other, {
name: sediment.map.get(other)?.variants[0] ?? other,
count: edge.count
});
}
}
setCooccurRows([...acc].sort((a, b) => b[1].count - a[1].count).slice(0, 50).map(([norm, x]) => ({
key: `n:${norm}`,
tag_name: x.name,
cooccur: x.count,
badge: "cooccur",
checked: false
})));
setCooccurOpen(true);
};
/** 「共现变体表」确认: 勾选行现场 tag/info 解析 (票 18 惰性) => 正向入本组; 解析失败的留表可重勾重试 */
const confirmCooccur = async (checked) => {
setCooccurBusy(true);
try {
const resolved = await resolveCandidateRows(checked);
const entries = resolved.filter((r) => r.tag_id !== void 0).map((r) => ({
id: r.tag_id,
name: r.tag_name
}));
const failed = resolved.length - entries.length;
if (entries.length) {
favorites.addTagIdsToGroup(group.id, entries);
setCooccurOpen(false);
}
if (failed) toast(`${failed} 个候选解析失败, 可重试`);
} finally {
setCooccurBusy(false);
}
};
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex flex-wrap items-center gap-x-7px gap-y-3px text-11px",
children: [hasTagRecCombination(group.combination) && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
className: "break-words opacity-75",
children: ["条件: ", nameTagRecCombination(group.combination)]
}), /* @__PURE__ */ jsx(Typography.Link, {
href: buildTagRecCustomDeepLink(location.origin, { group: group.id }),
target: "_blank",
title: "在「标签发掘」中编辑子组 / 全部命中·任一命中 / 标签·标题·简介等条件",
onClick: (e) => e.stopPropagation(),
children: "编辑条件 ↗"
})] }), /* @__PURE__ */ jsx(Typography.Link, {
title: "条件树 / 生效值与来源 / 抽牌状态",
onClick: (e) => {
e.stopPropagation();
onOpenStatus();
},
children: "查看现状"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
type: batchPick ? "primary" : "default",
onClick: () => {
setBatchPick((v) => !v);
setPickedIds(/* @__PURE__ */ new Set());
},
children: batchPick ? `退出批量 (${pickedIds.size})` : "批量选择"
}), group.tag_ids.map((e) => {
const picked = pickedIds.has(e.id);
return /* @__PURE__ */ jsxs("span", {
className: `inline-flex items-center gap-x-4px b-1px rounded-6px b-solid px-6px py-2px ${batchPick ? `cursor-pointer select-none ${picked ? "b-gate-primary color-gate-primary" : "b-gate-border"}` : "b-gate-border"}`,
style: batchPick && picked ? { backgroundColor: "color-mix(in srgb, var(--bilibili-gate--primary-color) 12%, transparent)" } : void 0,
onClick: batchPick ? () => togglePick(e.id) : void 0,
children: [
batchPick && picked && /* @__PURE__ */ jsx("span", {
className: "text-12px color-gate-primary",
children: "✓"
}),
batchPick ? /* @__PURE__ */ jsx("span", { children: e.name }) : /* @__PURE__ */ jsx(Typography.Link, {
href: buildTagRecCustomDeepLink(location.origin, { input: e.name }),
target: "_blank",
title: `在「标签发掘」页解析「${e.name}」(#${e.id})`,
onClick: (ev) => ev.stopPropagation(),
children: e.name
}),
e.tag_type && e.tag_type !== "old_channel" && /* @__PURE__ */ jsx(Tag, {
className: "mr-0",
children: e.tag_type
}),
!batchPick && /* @__PURE__ */ jsx(Dropdown, {
trigger: ["click"],
menu: {
items: [{
key: "move",
label: "移到组…"
}, {
key: "trash",
label: "移到垃圾桶 (移出本组)",
danger: true
}],
onClick: ({ key, domEvent }) => {
domEvent.stopPropagation();
if (key === "trash") favorites.removeTagIdFromGroup(group.id, e.id);
else {
setOpIds([e.id]);
setBatchTargetId(void 0);
setBatchOp("move");
}
}
},
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-2px text-12px",
onClick: (ev) => ev.stopPropagation(),
children: "移到…"
})
})
]
}, e.id);
})]
}),
batchPick && pickedIds.size > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-6px flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"已选 ",
pickedIds.size,
" 个标签"
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
setOpIds([...pickedIds]);
setBatchTargetId(void 0);
setBatchOp("move");
},
children: "移到组…"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
setOpIds([...pickedIds]);
setBatchTargetId(void 0);
setBatchOp("copy");
},
children: "复制到组…"
}),
/* @__PURE__ */ jsx(Popconfirm, {
title: "确定",
description: `把勾选的 ${pickedIds.size} 个标签移出本组?`,
onConfirm: deletePicked,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
children: "删除"
})
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-10px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mb-4px block text-12px",
children: "负向成员 (本组流内剔除; all/v2 名匹配 + 负向池交集近似, 严检见右)"
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
children: [
negatives.map((e) => /* @__PURE__ */ jsxs(Tag, {
color: "red",
closable: true,
onClose: (ev) => {
ev.preventDefault();
favorites.removeNegativeFromGroup(group.id, e.id);
},
children: ["⛔ ", e.name]
}, e.id)),
!negatives.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "无负向成员"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setAddingNegative((v) => !v),
children: addingNegative ? "收起" : "添加负向"
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "严格校验: 出队切片对本组来源逐条拉取视频标签核查负向命中 (每上屏视频至多 1 次请求; 风控可疑期自动停扣)",
children: /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-4px text-12px",
children: ["严格校验", /* @__PURE__ */ jsx(Switch, {
size: "small",
checked: group.negative_strict ?? false,
onChange: (v) => favorites.updateGroup(group.id, { negative_strict: v })
})]
})
})
]
}),
addingNegative && /* @__PURE__ */ jsx(CandidateResolvePicker, {
confirmLabel: "加入本组负向",
onConfirm: (entries) => {
const res = favorites.addNegativeToGroup(group.id, entries);
if (res.added || res.moved) setAddingNegative(false);
if (res.refusedSole.length) toast(`「${res.refusedSole[0].name}」是组内唯一正向, 未转负向 (组至少留 1 个正向)`);
}
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-8px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setAddingVariant((v) => !v),
children: addingVariant ? "收起" : "添加变体"
}), /* @__PURE__ */ jsx(AntdTooltip, {
title: "沉淀池汇总本组各成员标签的共现邻域 (离线读存量, 零请求); 勾选后经 tag/info 解析入组",
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: cooccurOpen ? "primary" : "default",
onClick: toggleCooccur,
children: cooccurOpen ? "收起" : "共现变体表"
})
})]
}),
addingVariant && /* @__PURE__ */ jsxs(Fragment, { children: [!group.tag_ids.length && /* @__PURE__ */ jsxs("div", {
className: "my-8px text-12px",
children: [/* @__PURE__ */ jsx(Checkbox, {
checked: enableAfterAdding,
onChange: (e) => setEnableAfterAdding(e.target.checked),
children: "添加后启用二级组"
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-4px block text-11px",
children: "一级组开关保持不变;一级组关闭时,这个组仍不会参与推荐。"
})]
}), /* @__PURE__ */ jsx(CandidateResolvePicker, {
confirmLabel: "加入本组",
onConfirm: (entries) => {
const wasEmpty = !group.tag_ids.length;
if (favorites.addTagIdsToGroup(group.id, entries).added) {
if (wasEmpty && enableAfterAdding) favorites.updateGroup(group.id, { enabled: true });
setAddingVariant(false);
}
}
})] }),
cooccurOpen && /* @__PURE__ */ jsx(TagRecCandidatePickList, {
rows: cooccurRows,
onToggleRow: (key, checked) => setCooccurRows((prev) => prev.map((r) => r.key === key ? {
...r,
checked
} : r)),
confirmLabel: "加入本组",
statusText: cooccurRows.length ? `沉淀池共现 ${cooccurRows.length} 条 (本组各成员邻域并集, 频次跨成员累加)` : "沉淀池暂无本组共现记录 (先跑几轮解析/推荐积累语料)",
busy: cooccurBusy,
onConfirm: (checked) => void confirmCooccur(checked)
}),
/* @__PURE__ */ jsxs(Modal, {
title: batchOp === "move" ? `移动 ${opIds.length} 个标签到组` : `复制 ${opIds.length} 个标签到组`,
open: batchOp !== void 0,
onCancel: () => setBatchOp(void 0),
onOk: confirmBatchOp,
okText: batchOp === "move" ? "移动" : "复制",
okButtonProps: { disabled: !batchTargetId },
width: 360,
children: [/* @__PURE__ */ jsx(Select, {
showSearch: true,
optionFilterProp: "label",
className: "mt-12px w-full",
placeholder: "选择目标标签组",
value: batchTargetId,
onChange: setBatchTargetId,
options: allGroups.filter((g) => g.id !== group.id).map((g) => ({
value: g.id,
label: `${g.name} (${g.tag_ids.length} 个 id)`
}))
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: batchOp === "move" ? "移动 = 加入目标组并从本组移出;移出最后一个标签后保留空组和配置,并关闭该组" : "复制 = 仅加入目标组, 本组保留 (tag 多宿主本就支持)"
})]
})
] });
}
function LegacyBlacklistImportRow() {
const { list: groups } = useSnapshot(favoritesStore);
const { list: oldBlacklist } = useSnapshot(blacklistStore);
const [targetId, setTargetId] = (0, import_react.useState)(void 0);
if (!oldBlacklist.length) return null;
return /* @__PURE__ */ jsxs("div", {
className: "mt-8px flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
"旧全局黑名单 ",
oldBlacklist.length,
" 项 (已休眠, 不参与过滤)"
]
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "min-w-160px",
placeholder: "导入为哪组的负向",
value: targetId,
onChange: setTargetId,
options: groups.map((g) => ({
value: g.id,
label: g.name
}))
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !targetId,
onClick: () => {
if (!targetId) return;
const res = favorites.addNegativeToGroup(targetId, oldBlacklist.map((e) => ({
id: e.id,
name: e.name
})));
toast(`已导入负向 ${res.added + res.moved} 项${res.skipped ? ` (${res.skipped} 项已在负向跳过)` : ""}`);
},
children: "导入为负向"
})
]
});
}
function DangerZoneSection() {
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("div", {
className: "flex items-center gap-x-8px",
children: /* @__PURE__ */ jsx(Popconfirm, {
title: "清空收藏表",
description: "全部标签组/一级组/主题名册将整体删除 (黑名单/沉淀/已看不动)。确定?",
okText: "清空",
okButtonProps: { danger: true },
onConfirm: () => {
const g = favorites.clearAll();
const l = favoritesL1.clearAll();
const t = themes.clearAll();
toast(`已清空收藏表 (${l} 个一级组 / ${g} 个标签组${t ? ` / ${t} 个主题` : ""})`);
},
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
children: "清空收藏表"
})
})
}) });
}
function TagRecDiscoverySettings() {
const ladder = useSettingsSnapshot().tagRec.ladder;
const policy = tagRecDiscoveryPolicy(ladder);
const fields = [
{
key: "triggerYield",
label: "预挖触发线(条/轮)",
fallback: 10,
min: 1,
max: 50
},
...policy.mode === "fixed" ? [{
key: "autoDigMax",
label: "累计自动轮次上限",
fallback: 3,
min: 1,
max: 20
}] : [{
key: "lowYield",
label: "低产线(条/轮)",
fallback: 3,
min: 1,
max: 50
}, {
key: "lowYieldStreakLimit",
label: "连续低产停止轮数",
fallback: 2,
min: 1,
max: 20
}],
{
key: "digStreamWindow",
label: "每轮搜索词窗口",
fallback: 4,
min: 1,
max: 8
},
{
key: "emptyTtlMinutes",
label: "空搜索复探冷却(分钟)",
fallback: 60,
min: 5,
max: 1440
},
{
key: "seedPoolMax",
label: "推荐相关选种扫描窗口",
fallback: 12,
min: 10,
max: 200
},
{
key: "seedCount",
label: "每轮推荐相关种子数",
fallback: 3,
min: 1,
max: 10
},
{
key: "seedMinScore",
label: "种子质量门槛(0 为不开)",
fallback: 0,
min: 0,
max: 100
},
{
key: "fallbackPage",
label: "相关库存放行页长",
fallback: 20,
min: 5,
max: 50
}
];
return /* @__PURE__ */ jsxs(SettingsGroup, {
title: "发现策略(全局高级参数)",
children: [
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx("span", { children: "自动搜索策略" }), /* @__PURE__ */ jsx(Select, {
size: "small",
value: policy.mode,
options: [{
value: "adaptive",
label: "自适应:按低产停止"
}, {
value: "fixed",
label: "固定:按累计轮数停止"
}],
onChange: (mode) => {
settings.tagRec.ladder.mode = mode;
}
})]
}),
fields.map((field) => /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx("span", { children: field.label }), /* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
min: field.min,
max: field.max,
value: ladder[field.key] ?? field.fallback,
onChange: (value) => {
if (typeof value === "number" && Number.isFinite(value)) settings.tagRec.ladder[field.key] = Math.min(field.max, Math.max(field.min, Math.round(value)));
}
})]
}, field.key)),
/* @__PURE__ */ jsx(Typography.Paragraph, {
type: "secondary",
className: "mt-8px text-12px",
children: "下一次调度生效,保留已付轮次与分页。空搜索冷却到期后,仅缺库存且来源允许时探测第一页;页上限、失败、暂停和缺种子分别处理。定向重查入口在推荐页。"
})
]
});
}
/**
* [tag-rec] 「标签过滤」pane (spec §5.1) —— FilterForm 绑全局 tagRec.filter configPath 段
* - 预设/总开关/维度/高级区/K 旋钮全部在 FilterForm (scope='global') 内, 持久化+迁移白拿
* - 「恢复出厂」/自定义预设增删改名走预设引擎 (model/filter-engine.ts)
* - 附加行为参数 (归因徽章/多样性护栏/已看容量) 走声明式 configPath 绑定
*/
function TabPaneTagFilter() {
const { seenCap, diversityGuard } = useSettingsSnapshot().tagRec;
const { recSharedEmitter } = useRecSelfContext();
const [fsOnMount] = (0, import_react.useState)(() => computeTagRecFilterSignature({
core: settings.tagRec.filter,
groups: favorites.list,
l1s: favoritesL1.list
}) + `|dg:${JSON.stringify(settings.tagRec.diversityGuard)}|ladder:${JSON.stringify(settings.tagRec.ladder)}`);
(0, import_react.useEffect)(() => () => {
if (computeTagRecFilterSignature({
core: settings.tagRec.filter,
groups: favorites.list,
l1s: favoritesL1.list
}) + `|dg:${JSON.stringify(settings.tagRec.diversityGuard)}|ladder:${JSON.stringify(settings.tagRec.ladder)}` !== fsOnMount) recSharedEmitter.emit("refresh", "refresh");
}, [recSharedEmitter, fsOnMount]);
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["标签过滤", /* @__PURE__ */ jsxs(HelpInfo, { children: [
"作用于「收藏推荐」「指定标签」两 tab;",
/* @__PURE__ */ jsx("br", {}),
"「指定标签」tabbar 的「过滤」按钮可在会话内临时覆盖 (刷新即丢)"
] })] }),
children: /* @__PURE__ */ jsx(TagRecFilterForm, {
core: settings.tagRec.filter,
scope: "global"
})
}),
/* @__PURE__ */ jsx(TagRecRecoverySettings, {}),
/* @__PURE__ */ jsx(TagRecDiscoverySettings, {}),
/* @__PURE__ */ jsxs(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["其它参数", /* @__PURE__ */ jsx(HelpInfo, { children: "「收藏推荐」展示与抽样行为 (spec §3.3)" })] }),
children: [
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", { children: "已看记录容量" }),
/* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
className: "w-100px",
min: 1,
max: SEEN_CAP_MAX,
step: 100,
value: seenCap,
onChange: (v) => {
if (typeof v === "number" && Number.isFinite(v)) settings.tagRec.seenCap = clampSeenCap(v);
}
}),
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"卡片一半进入屏幕就记录为已看,用于避免重复推荐。两种标签推荐共享记录,超过容量后移除最旧记录。 上限",
" ",
SEEN_CAP_MAX,
" 条。"
] })
]
}),
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", { children: "归因徽章" }),
/* @__PURE__ */ jsx(SwitchSettingItem, { configPath: "tagRec.showAttributionBadge" }),
/* @__PURE__ */ jsx(HelpInfo, { children: "开 = 卡片展示来源组名 (recommendReason 槽位)" })
]
}),
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", { children: "护栏·单组占比" }),
/* @__PURE__ */ jsx(SwitchSettingItem, { configPath: "tagRec.diversityGuard.group.enabled" }),
/* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
className: "w-80px",
min: 10,
max: 100,
step: 5,
disabled: !diversityGuard.group.enabled,
value: Math.round(diversityGuard.group.maxShare * 100),
onChange: (v) => {
if (typeof v === "number" && Number.isFinite(v)) settings.tagRec.diversityGuard.group.maxShare = Math.min(1, Math.max(.1, v / 100));
}
}),
/* @__PURE__ */ jsx("span", {
className: "ml-4px text-12px",
children: "%/屏"
}),
/* @__PURE__ */ jsx(HelpInfo, { children: "默认开启,单组每批推荐最多占 N%;超出部分顺延到后续推荐。" })
]
}),
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", { children: "护栏·单 UP" }),
/* @__PURE__ */ jsx(SwitchSettingItem, { configPath: "tagRec.diversityGuard.up.enabled" }),
/* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
className: "w-80px",
min: 1,
max: 10,
disabled: !diversityGuard.up.enabled,
value: diversityGuard.up.max,
onChange: (v) => {
if (typeof v === "number" && Number.isFinite(v)) settings.tagRec.diversityGuard.up.max = Math.min(10, Math.max(1, Math.round(v)));
}
}),
/* @__PURE__ */ jsx("span", {
className: "ml-4px text-12px",
children: "条/屏"
}),
/* @__PURE__ */ jsx(HelpInfo, { children: "开 = 同一 UP 每切片 ≤N 条, 超额顺延 (票09: owner.mid 计数, 缺 mid 豁免; 默认 4)" })
]
})
]
})
]
});
}
/**
* https://keithjgrant.com/posts/2023/04/transitioning-to-height-auto/
* https://www.youtube.com/watch?v=B_n4YONte5A
* https://css-tricks.com/css-grid-can-do-auto-height-transitions/
*/
function CollapsePanel({ expanded, children, ...props }) {
return /* @__PURE__ */ jsx("div", {
...props,
"data-classname": "wrapper",
css: /* @__PURE__ */ css("transition:grid-template-rows 0.2s ease-out;display:grid;grid-template-rows:", expanded ? 1 : 0, "fr;"),
children: /* @__PURE__ */ jsx("div", {
"data-classname": "inner",
className: "overflow-hidden",
children
})
});
}
var import_ms = /* @__PURE__ */ __toESM(require_ms(), 1);
var ObjectUrlWrapper = class {
url;
revokeTimer;
constructor(obj, delay = (0, import_ms.default)("10min")) {
this.url = URL.createObjectURL(obj);
this.revokeTimer = setTimeout(this.revoke, delay);
}
revoke = () => {
if (this.revokeTimer !== void 0) clearTimeout(this.revokeTimer);
this.revokeTimer = void 0;
if (this.url) ae.try(() => URL.revokeObjectURL(this.url));
this.url = void 0;
};
};
var lastObjectUrl;
async function genUrl(paths) {
const fullSettings = await loadSettingsFromGmStorage();
const val = paths?.length ? pickSettings(fullSettings, paths).pickedSettings : fullSettings;
const json = JSON.stringify(val, null, 2);
const blob = new Blob([json], { type: "application/json" });
lastObjectUrl?.revoke();
lastObjectUrl = new ObjectUrlWrapper(blob);
return lastObjectUrl.url;
}
async function exportSettings(paths, moduleLabel) {
const url = await genUrl(paths);
const filename = `${[
APP_NAME,
trim(moduleLabel || "", ["-", " "]),
"settings"
].filter(Boolean).join("-")} ${(0, import_dayjs_min.default)().format("YYYY-MM-DD HH:mm:ss")}.json`;
const usePlainLink = () => {
const link = document.createElement("a");
link.href = url;
link.download = filename;
link.click();
};
usePlainLink();
}
async function chooseFileForImportSettings() {
const file = await chooseSingleJsonFile();
if (!file) return;
const text = await file.text();
if (!text) return toast("文件内容为空!");
let settingsFromFile;
try {
settingsFromFile = JSON.parse(text);
} catch {
return toast("无法解析文件内容!");
}
runSettingsMigration(settingsFromFile);
return settingsFromFile;
}
async function importSettings() {
const settingsFromFile = await chooseFileForImportSettings();
if (!settingsFromFile) return;
const { pickedPaths, pickedSettings: importedSettings } = pickSettings(settingsFromFile, allowedLeafSettingsPaths);
if (!pickedPaths.length) return toast("没有有效的设置!");
{
const keys = Object.keys(importedSettings);
if (keys.length === 1) {
if (keys[0] === "filter") return toast("过滤相关数据请使用「内容过滤」内独立的导入导出功能!", 5e3);
return toast("异常数据", 5e3);
}
}
set_HAS_RESTORED_SETTINGS(true);
updateSettings(importedSettings);
antMessage.success("导入成功!");
return toastAndReload();
}
function chooseSingleJsonFile() {
return chooseFile({
accept: ".json",
multiple: false
});
}
function chooseFile(options) {
return new Promise((resolve) => {
const input = document.createElement("input");
input.type = "file";
Object.assign(input, options);
input.addEventListener("change", () => {
resolve(input.files?.[0] || null);
});
input.click();
});
}
var tablerRestore = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
children: [/* @__PURE__ */ jsx("path", { d: "M3.06 13a9 9 0 1 0 .49-4.087" }), /* @__PURE__ */ jsx("path", { d: "M3 4.001v5h5M11 12a1 1 0 1 0 2 0a1 1 0 1 0-2 0" })]
})]
});
var ForwardRef$3 = (0, import_react.forwardRef)(tablerRestore);
var iconParkOutlineDownC = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinejoin: "round",
strokeWidth: 4,
children: [/* @__PURE__ */ jsx("path", { d: "M24 44c11.046 0 20-8.954 20-20S35.046 4 24 4S4 12.954 4 24s8.954 20 20 20Z" }), /* @__PURE__ */ jsx("path", {
strokeLinecap: "round",
d: "m33 21l-9 9l-9-9"
})]
})]
});
var ForwardRef$2 = (0, import_react.forwardRef)(iconParkOutlineDownC);
function onResetSettings() {
resetSettings();
return toastAndReload();
}
async function onRestoreSettings() {
const remoteSettings = await articleDraft.getData();
runSettingsMigration(remoteSettings);
const { pickedPaths, pickedSettings } = pickSettings(remoteSettings, difference(allowedLeafSettingsPaths, restoreOmitPaths));
if (!pickedPaths.length) return antMessage.error("备份不存在或没有有效的配置");
set_HAS_RESTORED_SETTINGS(true);
updateSettings(pickedSettings);
return toastAndReload();
}
function TabPaneAdvance() {
const [internalKeysExpanded, setInternalKeysExpanded] = (0, import_react.useState)(false);
const manualRefreshRelationsCacheReq = useRequest_default(handleManualRefreshRelationsCache, { manual: true });
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconForConfig, { className: "mr-4px size-28px" }), "设置"] }),
children: /* @__PURE__ */ jsxs("div", {
className: clsx(sharedClassNames.settingsLine, "gap-x-20px"),
children: [
/* @__PURE__ */ jsx(Popconfirm, {
title: "确定",
description: "确定恢复默认设置? 此操作不可逆!",
onConfirm: onResetSettings,
children: /* @__PURE__ */ jsxs(button_default, {
danger: true,
type: "primary",
children: [/* @__PURE__ */ jsx(ForwardRef$3, {}), "恢复默认设置"]
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "导出所有设置项到文件中, 包含 access_key 等数据, 请妥善保存",
children: /* @__PURE__ */ jsxs(button_default, {
onClick: () => exportSettings(),
children: [/* @__PURE__ */ jsx(ForwardRef$4, {}), "导出设置"]
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "从文件中导入设置项, 将覆盖当前设置, 此操作不可逆!",
children: /* @__PURE__ */ jsxs(button_default, {
onClick: () => importSettings(),
children: [/* @__PURE__ */ jsx(ForwardRef$5, {}), "导入设置"]
})
})
]
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(IconForCloudStorage, { className: "mr-4px size-28px" }),
"设置备份",
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"使用专栏草稿箱作为云存储 ",
/* @__PURE__ */ jsx("br", {}),
"勾选后可自动备份, 手动恢复"
] })
] }),
children: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-20px",
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "backupSettingsToArticleDraft",
label: "备份设置到专栏草稿箱中",
tooltip: `专栏 - 草稿箱 - ${APP_NAME}`
}),
/* @__PURE__ */ jsxs("a", {
className: "inline-flex items-center",
href: "https://member.bilibili.com/platform/upload/text/draft",
target: "_blank",
children: [/* @__PURE__ */ jsx(IconForOpenExternalLink, { className: "mr-4px size-16px" }), "去草稿箱浏览"]
}),
/* @__PURE__ */ jsx(Popconfirm, {
title: "确定",
description: "将覆盖本地设置? 此操作不可逆!",
onConfirm: onRestoreSettings,
children: /* @__PURE__ */ jsxs(button_default, {
danger: true,
type: "primary",
children: [/* @__PURE__ */ jsx(ForwardRef$3, {}), "从专栏草稿箱中恢复"]
})
})
]
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(IconForReload, { className: "mr-4px size-28px" }),
"缓存",
/* @__PURE__ */ jsx(HelpInfo, { children: "手动刷新缓存数据" })
] }),
children: /* @__PURE__ */ jsx("div", {
className: "flex items-center gap-x-20px",
children: /* @__PURE__ */ jsxs(button_default, {
onClick: () => manualRefreshRelationsCacheReq.run(),
loading: manualRefreshRelationsCacheReq.loading,
className: "gap-x-1",
children: [!manualRefreshRelationsCacheReq.loading && /* @__PURE__ */ jsx(IconForReload, { className: "size-1em" }), "刷新黑名单/关注列表缓存"]
})
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
"其他",
/* @__PURE__ */ jsx(HelpInfo, { children: "这里是一些实验性设置项, 可能随时会变更或删除" }),
/* @__PURE__ */ jsx(button_default, {
onClick: () => setInternalKeysExpanded((v) => !v),
className: "ml-10px inline-icon-only-round-button",
css: internalKeysExpanded && buttonOpenCss,
children: /* @__PURE__ */ jsx(ForwardRef$2, { className: clsx("size-16px transition-300 transition-transform", internalKeysExpanded ? "rotate-180" : "rotate-0") })
})
] }),
children: /* @__PURE__ */ jsx(CollapsePanel, {
expanded: internalKeysExpanded,
children: /* @__PURE__ */ jsxs("div", {
className: "w-full flex gap-x-20px b-1px b-gate-border rounded-6px b-solid p-10px",
children: [/* @__PURE__ */ jsx(ResetPartialSettingsButton, {
paths: internalBooleanPaths,
className: "flex-none"
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-1 flex-wrap items-start gap-x-20px gap-y-10px",
children: internalBooleanPaths.map((k) => /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: k,
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [k, INTERNAL_BOOLEAN_TOOLTIPS[k] && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("br", {}), INTERNAL_BOOLEAN_TOOLTIPS[k]] })] }),
label: startCase(k.startsWith("__internal") ? k.slice(10) : k.replaceAll("__internal.", ""))
}, k))
})]
})
})
})
]
});
}
var INTERNAL_BOOLEAN_TOOLTIPS = {};
var clsRadioWrapper = "[&_.ant-radio-wrapper]:mx-0 [&_.ant-radio-wrapper]:inline-flex [&_.ant-radio-wrapper]:items-center";
var clsRadioLabel = "[&_.ant-radio-label]:inline-flex [&_.ant-radio-label]:items-center ![&_.ant-radio-label]:pl-4px";
function SidebarSwitcher({ className }) {
const { enableSidebar, sidebarAlign } = useSettingsSnapshot();
const _className = r("flex items-center gap-x-15px", clsRadioWrapper, clsRadioLabel, className);
return /* @__PURE__ */ jsx(Radio.Group, {
size: "large",
className: _className,
value: enableSidebar,
onChange: (e) => {
settings.enableSidebar = e.target.value;
},
options: [{
value: true,
label: /* @__PURE__ */ jsxs("span", {
className: "inline-flex-center gap-x-1",
children: [/* @__PURE__ */ jsx(AntdTooltip, {
title: "使用侧边栏(如动态 分组/UP 选择)",
children: /* @__PURE__ */ jsx("span", { children: "开启" })
}), /* @__PURE__ */ jsxs(Radio.Group, {
disabled: !enableSidebar,
buttonStyle: "solid",
size: "small",
value: sidebarAlign,
onChange: (e) => void (settings.sidebarAlign = e.target.value),
children: [/* @__PURE__ */ jsx(Radio.Button, {
value: ESidebarAlign.Left,
className: "inline-flex-center",
children: /* @__PURE__ */ jsx(ForwardRef$20, { className: "size-16px" })
}), /* @__PURE__ */ jsx(Radio.Button, {
value: ESidebarAlign.Right,
className: "inline-flex-center",
children: /* @__PURE__ */ jsx(ForwardRef$18, { className: "size-16px" })
})]
})]
})
}, {
value: false,
label: /* @__PURE__ */ jsx(AntdTooltip, {
title: "使用下拉面板",
children: /* @__PURE__ */ jsx("span", {
className: "inline-flex-center",
children: "禁用"
})
})
}]
});
}
var tablerEyeSearch = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 24 24",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsxs("g", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
children: [/* @__PURE__ */ jsx("path", { d: "M10 12a2 2 0 1 0 4 0a2 2 0 0 0-4 0" }), /* @__PURE__ */ jsx("path", { d: "M12 18q-.492 0-.97-.05Q6.271 17.452 3 12q3.6-6 9-6q5.197 0 8.727 5.558M15 18a3 3 0 1 0 6 0a3 3 0 1 0-6 0m5.2 2.2L22 22" })]
})]
});
var ForwardRef$1 = (0, import_react.forwardRef)(tablerEyeSearch);
function TabPaneBasic() {
const { style } = useSettingsSnapshot();
useMount(() => {
return subscribeKey(settings, "pureRecommend", async (v) => {
await delay$1(0);
toastAndReload();
});
});
const handleCopyScriptVersion = useMemoizedFn(() => {
const content = `v1.1.5`;
GM.setClipboard(content);
antMessage.success(`已复制当前版本: ${content}`);
});
const peekIconRef = (0, import_react.useRef)(null);
const peekIconHovering = useHover_default(peekIconRef);
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.AppRecommend,
className: "mr-5px mt-2px size-30px"
}),
"access_key",
/* @__PURE__ */ jsxs(HelpInfo, {
className: "ml-5px mt-6px size-18px",
IconComponent: IconForHelp,
tooltipProps: { classNames: { root: "text-14px" } },
children: [
"App 端登录凭证, 使用情况: ",
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsx(PrimaryBgContentDivider, {}),
/* @__PURE__ */ jsxs("div", {
className: "group flex items-start",
children: [/* @__PURE__ */ jsxs("div", {
className: "w-55px flex flex-none items-center",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.AppRecommend,
className: "mr-1"
}), " 推荐"]
}), /* @__PURE__ */ jsxs("ul", {
className: "flex-1 list-disc pl-20px",
children: [/* @__PURE__ */ jsx("li", {
className: "w-max",
children: "获取推荐"
}), /* @__PURE__ */ jsx("li", {
className: "w-max",
children: "提交不喜欢"
})]
})]
}),
/* @__PURE__ */ jsx(PrimaryBgContentDivider, {}),
/* @__PURE__ */ jsxs("div", {
className: "group flex items-start",
children: [/* @__PURE__ */ jsxs("div", {
className: "w-55px flex flex-none items-center",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.Liked,
className: "mr-1"
}), "赞"]
}), /* @__PURE__ */ jsx("ul", {
className: "w-max flex-1 list-disc pl-20px",
children: /* @__PURE__ */ jsx("li", {
className: "w-max",
children: "获取点赞列表"
})
})]
})
]
})
] }),
children: /* @__PURE__ */ jsx(AccessKeyManage, {})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: "开关",
resetSettingPaths: [
"pureRecommend",
"multiSelect.showIcon",
"showBackForwardButtons"
],
children: /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "pureRecommend",
label: "覆盖默认推荐",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [explainForFlag("覆盖默认推荐内容", `保留默认首页; 你可以从屏幕边缘找到入口面板, 使用 ${APP_NAME} 的功能.`), "P.S 需要刷新网页"] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "multiSelect.showIcon",
label: "「多选」按钮",
tooltip: "是否显示「多选」按钮"
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "showBackForwardButtons",
label: "「后退 / 前进」按钮",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"是否显示「后退 / 前进」按钮 ",
/* @__PURE__ */ jsx("br", {}),
"仅推荐 Tab 支持, 最多保留 ",
5,
" 次刷新历史"
] })
})
]
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["布局", /* @__PURE__ */ jsx(AntdTooltip, {
title: "透视",
children: /* @__PURE__ */ jsx(ForwardRef$1, {
ref: peekIconRef,
title: "透视",
className: clsx("ml-1.5 size-1em cursor-pointer b-1px b-transparent rounded-md b-solid p-2px hover:b-gate-primary", { peeking: peekIconHovering })
})
})] }),
resetSettingPaths: [
"grid.useCustomGrid",
"grid.enableForceColumn",
"grid.cardMinWidth",
"grid.forceColumnCount",
"grid.enableForceRowGap",
"grid.forceRowGap",
"enableSidebar",
"sidebarAlign",
"grid.gridDisplayMode",
"grid.twoColumnModeAlign"
],
children: /* @__PURE__ */ jsxs("div", {
className: "grid grid-cols-[max-content_1fr] items-center gap-x-1 gap-y-1",
children: [
/* @__PURE__ */ jsx(AntdTooltip, {
title: "网格配置指: 网格宽度, 间距, 列数等",
children: /* @__PURE__ */ jsx("span", {
className: "cursor-pointer text-end",
children: "网格配置:"
})
}),
/* @__PURE__ */ jsx(UseCustomGridSwitcher, {}),
/* @__PURE__ */ jsx("span", {
className: "text-end",
children: "网格列数:"
}),
/* @__PURE__ */ jsx(GridTemplateColumnsConfig, {}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "卡片之间上下间距",
children: /* @__PURE__ */ jsx("span", {
className: "cursor-pointer text-end",
children: "行间距:"
})
}),
/* @__PURE__ */ jsx(GridRowGapSwitcher, {}),
/* @__PURE__ */ jsx("span", {
className: "text-end",
children: "侧边栏:"
}),
/* @__PURE__ */ jsx(SidebarSwitcher, {}),
/* @__PURE__ */ jsx("span", {
className: "text-end",
children: "显示模式:"
}),
/* @__PURE__ */ jsx(GridDisplayModeSwitcher, {})
]
})
}),
/* @__PURE__ */ jsxs(SettingsGroup, {
title: "样式",
resetSettingPaths: [
"style.general.popoverBorderColorUseColorPrimary",
"style.pureRecommend.useStickyTabbar",
"style.pureRecommend.stickyTabbarShadow",
"grid.useCustomGrid",
"style.pureRecommend.useWhiteBackground",
"style.pureRecommend.hideTopChannel"
],
children: [/* @__PURE__ */ jsx("div", {
className: sharedClassNames.settingsLine,
children: /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.general.popoverBorderColorUseColorPrimary",
label: "下拉面板使用主题色边框"
})
}), /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
className: "flex items-center text-1.2em",
children: "全屏推荐"
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.pureRecommend.useStickyTabbar",
label: "固定 Tab 栏",
tooltip: explainForFlag("Tab 栏会吸附在顶栏下方", "Tab 栏会随页面一起滚动")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !style.pureRecommend.useStickyTabbar,
configPath: "style.pureRecommend.stickyTabbarShadow",
label: "固定 Tab 栏时添加边框 & 阴影",
tooltip: explainForFlag("Tab 栏在吸附状态下: 加宽, 添加边框 & 阴影", "不改变")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.pureRecommend.useWhiteBackground",
label: "使用纯白背景",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [explainForFlag("纯白背景", "浅灰色背景"), "仅影响白色/浅色模式"] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.pureRecommend.hideTopChannel",
label: "隐藏顶部分区和Banner"
})
]
})] })]
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["帮助", /* @__PURE__ */ jsxs("span", {
className: "relative top-4px ml-8px mr-4px inline-flex items-center text-size-14px",
children: [
"当前版本",
/* @__PURE__ */ jsxs(Tag, {
color: "green",
className: "mx-4px cursor-pointer",
onClick: handleCopyScriptVersion,
children: [
APP_NAME,
" v",
"1.1.5"
]
}),
/* @__PURE__ */ jsx(IconForCopy, {
className: "size-16px cursor-pointer",
onClick: handleCopyScriptVersion
})
]
})] }),
children: /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(AntdTooltip, {
title: "来个 Star 支持一下",
children: /* @__PURE__ */ jsx(button_default, {
href: "https://github.com/yunomega/Bilibili-Gate-TagRec",
target: "_blank",
children: "GitHub 主页"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "上游作者 magicdawn 的捐赠页",
children: /* @__PURE__ */ jsx(button_default, {
href: "https://afdian.com/a/magicdawn",
target: "_blank",
children: "「爱发电」支持"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "脚本安装/分享入口",
children: /* @__PURE__ */ jsx(button_default, {
href: "https://www.tagrec.cn/install",
target: "_blank",
children: "安装页"
})
}),
/* @__PURE__ */ jsx(button_default, {
href: "https://github.com/yunomega/Bilibili-Gate-TagRec/#%E5%BF%AB%E6%8D%B7%E9%94%AE%E8%AF%B4%E6%98%8E",
target: "_blank",
children: "查看可用的快捷键"
}),
/* @__PURE__ */ jsx(button_default, {
href: "https://github.com/yunomega/Bilibili-Gate-TagRec/releases",
target: "_blank",
children: "更新日志"
})
]
})
})
]
});
}
var LIST_FILTER_SETTING_PATHS = [
"filter.byAuthor.keywords",
"filter.byTitle.keywords",
"filter.dfByTitle.keywords",
"filter.dfHideOpusMids.keywords"
];
var ALL_FILTER_SETTING_PATHS = allowedLeafSettingsPaths.filter((p) => p.startsWith("filter."));
var NONE_LIST_FILTER_SETTING_PATHS = difference(ALL_FILTER_SETTING_PATHS, LIST_FILTER_SETTING_PATHS);
async function exportFilterSettings() {
const settings = await loadSettingsFromGmStorage();
if (!LIST_FILTER_SETTING_PATHS.reduce((count, path) => count + (get$2(settings, path)?.length ?? 0), 0)) antMessage.warning("列表数据为空!");
await exportSettings(ALL_FILTER_SETTING_PATHS, "filter");
}
async function importFilterSettings() {
const settingsFromFile = await chooseFileForImportSettings();
if (!settingsFromFile) return;
const { pickedPaths, pickedSettings: importedSettings } = pickSettings(settingsFromFile, ALL_FILTER_SETTING_PATHS);
if (!pickedPaths.length) return toast("没有有效的设置!");
const existingSettings = await loadSettingsFromGmStorage();
if (!(() => {
if (NONE_LIST_FILTER_SETTING_PATHS.some((path) => {
const imported = get$2(importedSettings, path);
const existing = get$2(existingSettings, path);
if (isNil(imported) || isEqual$1(imported, existing)) return;
return true;
})) return true;
if (LIST_FILTER_SETTING_PATHS.some((path) => {
const importedArr = get$2(importedSettings, path);
const existingArr = get$2(existingSettings, path) ?? [];
if (!importedArr?.length) return;
if (!difference(importedArr, existingArr).length) return;
return true;
})) return true;
return false;
})()) return toast("没有可导入内容, 导入内容与现有设置相同!", 5e3);
let noneListEntryImported = 0;
let listEntryImported = 0;
NONE_LIST_FILTER_SETTING_PATHS.forEach((path) => {
const imported = get$2(importedSettings, path);
const existing = get$2(existingSettings, path);
if (isNil(imported) || isEqual$1(imported, existing)) return;
updateSettings({ [path]: imported });
noneListEntryImported += 1;
});
LIST_FILTER_SETTING_PATHS.forEach((path) => {
const importedArr = get$2(importedSettings, path);
const existingArr = get$2(existingSettings, path) ?? [];
if (!importedArr?.length) return;
const toAdd = difference(importedArr, existingArr);
if (!toAdd.length) return;
updateSettings({ [path]: uniq([...existingArr, ...toAdd]) });
listEntryImported += toAdd.length;
});
antMessage.success(`已导入 ${noneListEntryImported} 条常规数据, ${listEntryImported} 条列表数据!`);
}
var iconParkOutlineCloseSmall = ({ title, titleId, ...props }, ref) => /* @__PURE__ */ jsxs("svg", {
viewBox: "0 0 48 48",
width: "1.2em",
height: "1.2em",
ref,
"aria-labelledby": titleId,
...props,
children: [title ? /* @__PURE__ */ jsx("title", {
id: titleId,
children: title
}) : null, /* @__PURE__ */ jsx("path", {
fill: "none",
stroke: "currentColor",
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 4,
d: "m14 14l20 20m-20 0l20-20"
})]
});
var ForwardRef = (0, import_react.forwardRef)(iconParkOutlineCloseSmall);
var { Search } = Input;
function EditableListSettingItem({ configPath, searchProps, disabled, className, inputClassName, listClassName, renderHeader = identity }) {
const rawList = useSettingsInnerArray(configPath);
const list = (0, import_react.useMemo)(() => uniq(rawList).toReversed(), [rawList]);
const isUpRepresent = ["filter.byAuthor.keywords", "filter.dfHideOpusMids.keywords"].includes(configPath);
const input = /* @__PURE__ */ jsx(Search, {
className: clsx("my-5px", inputClassName),
enterButton: "添加",
allowClear: true,
disabled,
size: "middle",
...searchProps,
onSearch: async (val, e) => {
if (!val) return;
if (new Set(await getNewestValueOfSettingsInnerArray(configPath)).has(val)) {
antMessage.warning(`${val} 已存在`);
return;
}
await updateSettingsInnerArray(configPath, { add: [val] });
if (e?.target) (e.target.closest(".ant-input-wrapper")?.querySelector(".ant-input-clear-icon"))?.click();
}
});
const hasData = !!list.length;
const isEmpty = !hasData;
const _listClassName = r(isEmpty ? "rounded-lg" : ["max-h-250px flex flex-wrap items-start gap-x-10px gap-y-5px overflow-y-auto pr-10px", listClassName]);
const listEl = /* @__PURE__ */ jsxs("div", {
className: _listClassName,
css: disabled && /* @__PURE__ */ css("color:var(--ant-color-text-disabled);background-color:var(--ant-color-bg-container-disabled);box-shadow:none;opacity:1;pointer-events:none;cursor:not-allowed;"),
children: [isEmpty && /* @__PURE__ */ jsx(Empty, {
image: Empty.PRESENTED_IMAGE_SIMPLE,
description: "空空如也",
className: "[&.ant-empty-normal]:my-1 [&.ant-empty-normal]:py-5px"
}), hasData && list.map((t) => {
return /* @__PURE__ */ jsx(TagItemDisplay, {
tag: t.toString(),
TagComponent: isUpRepresent ? UpTagItemDisplay : void 0,
onDelete: (tag) => updateSettingsInnerArray(configPath, { remove: [tag] })
}, t.toString());
})]
});
return /* @__PURE__ */ jsxs(Fragment, { children: [renderHeader(input), listEl] });
}
var TagItemDisplay = (0, import_react.memo)(function({ tag, renderTag, TagComponent, onDelete, className, ...restProps }) {
const tagEl = (0, import_react.useMemo)(() => {
if (TagComponent) return /* @__PURE__ */ jsx(TagComponent, { tag });
renderTag ??= identity;
return renderTag(tag);
}, [
TagComponent,
renderTag,
tag
]);
return /* @__PURE__ */ jsxs("div", {
...restProps,
className: clsx("relative inline-flex items-center b-1px b-gate-border rounded-5px b-solid px-6px py-2px hover:b-gate-primary hover:color-gate-primary", className),
children: [tagEl, /* @__PURE__ */ jsx(ForwardRef, {
className: "ml-2px size-16px cursor-pointer text-size-12px",
onClick: () => onDelete?.(tag)
})]
});
});
var UpTagItemDisplay = (0, import_react.memo)(function({ tag }) {
const { mid, remark } = (0, import_react.useMemo)(() => parseUpRepresent(tag), [tag]);
const [nicknameByMid, setNicknameByMid] = (0, import_react.useState)(void 0);
(0, import_react.useEffect)(() => {
(async () => {
if (!mid) return;
const nickname = await getUserNickname(mid);
if (nickname) setNicknameByMid(nickname);
})();
}, [mid]);
const label = mid ? nicknameByMid || remark || mid : tag;
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
!!mid && /* @__PURE__ */ jsxs(Fragment, { children: [
"mid: ",
mid,
" ",
/* @__PURE__ */ jsx("br", {})
] }),
!!remark && /* @__PURE__ */ jsxs(Fragment, { children: [
"备注: ",
remark,
" ",
/* @__PURE__ */ jsx("br", {}),
remark === nicknameByMid && /* @__PURE__ */ jsxs(Fragment, { children: ["P.S \"备注\" 与 \"用户昵称\" 相同, 是之前的数据, 现在你只需要填写 mid, 会自动获取昵称 ", /* @__PURE__ */ jsx("br", {})] })
] }),
!mid && /* @__PURE__ */ jsxs(Fragment, { children: ["使用用户名过滤: 用户可能改名, 建议使用 mid 过滤 ", /* @__PURE__ */ jsx("br", {})] })
] }),
children: /* @__PURE__ */ jsxs("span", {
className: clsx("inline-flex items-center justify-center", mid ? "cursor-pointer" : "cursor-[edit]"),
children: [mid && /* @__PURE__ */ jsx(ForwardRef$35, { className: "mr-2px size-12px" }), mid ? /* @__PURE__ */ jsx("a", {
href: formatSpaceUrl(mid),
target: "_blank",
style: { color: "inherit" },
children: label
}) : label]
})
}) });
});
var C = { blockContainer: "b-1px b-gate-border rounded-lg b-solid px-2 py-1 light:hover:b-gate-primary" };
function TabPaneFilter() {
const { filter: { byAuthor, byTitle, dfByTitle, dfHideOpusMids } } = useSettingsSnapshot();
return /* @__PURE__ */ jsx("div", {
className: clsx(sharedClassNames.tabPane, "pr-15px"),
children: /* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("span", { children: "内容过滤" }),
/* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "filter.enabled",
className: "ml-10px"
}),
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
/* @__PURE__ */ jsxs(Space, {
size: 5,
children: [/* @__PURE__ */ jsxs(button_default, {
onClick: exportFilterSettings,
disabled: !(byAuthor.keywords.length || byTitle.keywords.length || dfByTitle.keywords.length || dfHideOpusMids.keywords.length),
children: [/* @__PURE__ */ jsx(ForwardRef$4, {}), "导出"]
}), /* @__PURE__ */ jsxs(button_default, {
onClick: importFilterSettings,
children: [/* @__PURE__ */ jsx(ForwardRef$5, {}), "导入"]
})]
})
] }),
children: /* @__PURE__ */ jsx(Tabs, { items: [{
key: "recommend",
label: /* @__PURE__ */ jsxs("span", {
className: "inline-flex-center gap-x-1",
children: [
/* @__PURE__ */ jsx(TabIcon, { tabKey: ETab.AppRecommend }),
"推荐 / 热门",
/* @__PURE__ */ jsxs(HelpInfo, {
className: "ml-1",
children: [
"生效范围: ",
/* @__PURE__ */ jsx("br", {}),
"视频/图文/影视: 推荐类 Tab ",
/* @__PURE__ */ jsx("br", {}),
"UP/标题: 推荐类、热门 Tab ",
/* @__PURE__ */ jsx("br", {})
]
})
]
}),
children: /* @__PURE__ */ jsx(SubTabFilterForRec, {})
}, {
key: "dynamic-feed",
label: /* @__PURE__ */ jsxs("span", {
className: "inline-flex-center gap-x-1",
children: [
/* @__PURE__ */ jsx(TabIcon, { tabKey: ETab.DynamicFeed }),
"动态",
/* @__PURE__ */ jsxs(HelpInfo, {
className: "ml-1",
children: [
"使用场景: 关注的 UP 发布的部分内容 ",
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsxs("ol", { children: [/* @__PURE__ */ jsx("li", { children: "如果有规律可以按标题关键字过滤" }), /* @__PURE__ */ jsx("li", { children: "没有规律可以屏蔽 UP 发布的全部图文动态" })] })
]
})
]
}),
children: /* @__PURE__ */ jsx(SubTabFilterForDynamicFeed, {})
}] })
})
});
}
function SubTabFilterForRec() {
const { enabled, minDuration, minPlayCount, minDanmakuCount, hideGotoTypeBangumi, hideGotoTypePicture, byAuthor, byTitle } = useSettingsSnapshot().filter;
const getExemptFollowedTooltipProps = (label) => {
return {
label: "「已关注」豁免",
tooltipProps: { color: TOOLTIP_BLACK_BG_COLOR },
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"「已关注」内容不考虑过滤条件, 总是展示",
/* @__PURE__ */ jsx("br", {}),
"\"豁免\" 一词来源",
" ",
/* @__PURE__ */ jsx("a", {
target: "_blank",
href: "https://github.com/magicdawn/Bilibili-Gate/issues/1#issuecomment-2197868587",
children: "pilipala"
})
] })
};
};
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
className: "grid grid-cols-2 gap-15px",
children: [
/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
className: sharedClassNames.settingsGroupSubTitle,
children: "视频"
}), /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-1",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-center",
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "filter.minDuration.enabled",
label: "按视频时长过滤",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "不显示短视频" }),
disabled: !enabled,
className: "min-w-130px"
}), /* @__PURE__ */ jsxs(Space.Compact, { children: [/* @__PURE__ */ jsx(TypedInputNumber, {
className: "w-130px",
size: "small",
min: 1,
step: 10,
value: minDuration.value,
onChange: (val) => !isNil(val) && (settings.filter.minDuration.value = val),
disabled: !enabled || !minDuration.enabled
}), /* @__PURE__ */ jsx(Space.Addon, { children: "秒" })] })]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center",
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !enabled,
configPath: "filter.minPlayCount.enabled",
label: "按播放次数过滤",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "不显示播放次数很少的视频" }),
className: "min-w-130px"
}), /* @__PURE__ */ jsxs(Space.Compact, { children: [/* @__PURE__ */ jsx(TypedInputNumber, {
className: "w-130px",
size: "small",
min: 1,
step: 1e3,
value: minPlayCount.value,
onChange: (val) => !isNil(val) && (settings.filter.minPlayCount.value = val),
disabled: !enabled || !minPlayCount.enabled
}), /* @__PURE__ */ jsx(Space.Addon, { children: "次" })] })]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center",
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !enabled,
configPath: "filter.minDanmakuCount.enabled",
label: "按弹幕条数过滤",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "不显示弹幕条数很少的视频" }),
className: "min-w-130px"
}), /* @__PURE__ */ jsxs(Space.Compact, { children: [/* @__PURE__ */ jsx(TypedInputNumber, {
className: "w-130px",
size: "small",
min: 1,
step: 100,
value: minDanmakuCount.value,
onChange: (val) => !isNil(val) && (settings.filter.minDanmakuCount.value = val),
disabled: !enabled || !minDanmakuCount.enabled
}), /* @__PURE__ */ jsx(Space.Addon, { children: "条" })] })]
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "filter.exemptForFollowed.video",
disabled: !enabled,
...getExemptFollowedTooltipProps()
})
]
})] }),
/* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx("div", {
className: sharedClassNames.settingsGroupSubTitle,
children: "图文"
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "filter.hideGotoTypePicture",
label: "过滤图文类型推荐",
disabled: !enabled,
className: "flex",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"过滤 ",
/* @__PURE__ */ jsx("kbd", { children: "goto = picture" }),
" 的内容: 包括 (动态 & 专栏) 等"
] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
className: "flex",
disabled: !enabled || !hideGotoTypePicture,
configPath: "filter.exemptForFollowed.picture",
...getExemptFollowedTooltipProps()
}),
/* @__PURE__ */ jsx("div", {
className: clsx(sharedClassNames.settingsGroupSubTitle, "mt-2"),
children: "影视"
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "filter.hideGotoTypeBangumi",
label: "过滤影视类型推荐",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"过滤 ",
/* @__PURE__ */ jsx("kbd", { children: "goto = bangumi" }),
" 的内容: 包括 (番剧 / 电影 / 国创 / 纪录片) 等"
] }),
disabled: !enabled
})
] }),
/* @__PURE__ */ jsxs("div", {
className: clsx(C.blockContainer, "col-span-full"),
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsGroupSubTitle,
children: [
"UP",
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"根据 UP 过滤视频 ",
/* @__PURE__ */ jsx("br", {}),
"使用 mid 屏蔽时支持备注, 格式: ",
/* @__PURE__ */ jsx(Tag, {
color: "success",
children: "mid(备注)"
}),
" 如",
" ",
/* @__PURE__ */ jsx(Tag, {
color: "success",
children: "8047632(B站官方)"
}),
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsxs("div", {
className: "mt-4px flex items-start",
children: [/* @__PURE__ */ jsx(IconForInfo, { className: "mt-3px" }), /* @__PURE__ */ jsxs("div", {
className: "ml-8px flex-1",
children: [
"B站官方支持黑名单, 对于不喜欢的 UP 可以直接拉黑 ",
/* @__PURE__ */ jsx("br", {}),
"此脚本会拉取官方黑名单, 并无视此页开关, 总是会过滤掉 ",
/* @__PURE__ */ jsx("br", {}),
"这里是客户端过滤, 与黑名单功能重复, 优先使用黑名单功能 ",
/* @__PURE__ */ jsx("br", {})
]
})]
})
] }),
/* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "filter.byAuthor.enabled",
disabled: !enabled,
className: "ml-10px"
}),
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
/* @__PURE__ */ jsxs(button_default, {
onClick: clear_filterByAuthor_uselessRemarkData,
children: [/* @__PURE__ */ jsx(IconForDelete, {}), "清理无效备注数据"]
})
]
}), /* @__PURE__ */ jsx(EditableListSettingItem, {
configPath: "filter.byAuthor.keywords",
searchProps: { placeholder: "添加UP: 全名 / mid / mid(备注)" },
disabled: !enabled || !byAuthor.enabled,
className: "mt-2",
listClassName: "max-h-130px"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: clsx(C.blockContainer, "col-span-full"),
children: [/* @__PURE__ */ jsxs("div", {
className: clsx(sharedClassNames.settingsGroupSubTitle),
children: [
/* @__PURE__ */ jsx("span", { children: "标题" }),
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"根据标题关键词过滤视频 ",
/* @__PURE__ */ jsx("br", {}),
"支持普通关键字和正则(i), 语法:",
/* @__PURE__ */ jsx(Tag, {
color: "success",
variant: "solid",
children: "/abc|\\d+/"
})
] }),
/* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "filter.byTitle.enabled",
disabled: !enabled,
className: "ml-10px"
})
]
}), /* @__PURE__ */ jsx(EditableListSettingItem, {
configPath: "filter.byTitle.keywords",
searchProps: { placeholder: "添加过滤关键词" },
disabled: !enabled || !byTitle.enabled,
listClassName: "max-h-130px"
})]
})
]
}) });
}
function SubTabFilterForDynamicFeed() {
const { filter: { enabled, dfByTitle, dfHideOpusMids } } = useSettingsSnapshot();
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-15px",
children: [/* @__PURE__ */ jsxs("div", {
className: C.blockContainer,
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsGroupSubTitle,
children: [
/* @__PURE__ */ jsx("span", { children: "标题" }),
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"支持普通关键字和正则(i), 语法:",
/* @__PURE__ */ jsx(Tag, {
color: "success",
variant: "solid",
children: "/abc|\\d+/"
}),
/* @__PURE__ */ jsx("br", {}),
"作用范围:所有支持的动态类型:视频、图文、转发等"
] }),
/* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "filter.dfByTitle.enabled",
disabled: !enabled,
className: "ml-10px"
})
]
}), /* @__PURE__ */ jsx(EditableListSettingItem, {
configPath: "filter.dfByTitle.keywords",
searchProps: { placeholder: "添加过滤关键词" },
disabled: !enabled || !dfByTitle.enabled
})]
}), /* @__PURE__ */ jsxs("div", {
className: C.blockContainer,
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsGroupSubTitle,
children: [
/* @__PURE__ */ jsx("span", { children: "屏蔽 UP 的图文动态" }),
/* @__PURE__ */ jsxs(HelpInfo, { children: [
"支持 mid 或 mid(备注) ",
/* @__PURE__ */ jsx("br", {}),
"右键图文动态可快速添加 ",
/* @__PURE__ */ jsx("br", {}),
"转发也算做图文动态"
] }),
/* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "filter.dfHideOpusMids.enabled",
disabled: !enabled,
className: "ml-10px"
})
]
}), /* @__PURE__ */ jsx(EditableListSettingItem, {
configPath: "filter.dfHideOpusMids.keywords",
searchProps: { placeholder: "添加 UP: mid / mid(备注)" },
disabled: !enabled || !dfHideOpusMids.enabled,
listClassName: "max-h-130px"
})]
})]
});
}
async function clear_filterByAuthor_uselessRemarkData() {
const list = settings.filter.byAuthor.keywords;
const newList = await pmap(list, async (item) => {
const { mid, remark } = parseUpRepresent(item);
if (!mid || !remark) return item;
if (await getUserNickname(mid) !== remark) return item;
return mid;
}, Infinity);
if (isEqual$1(newList, list)) return antMessage.warning("没有「无效备注」数据!");
settings.filter.byAuthor.keywords = newList;
return antMessage.success("已清理「无效备注」数据!");
}
function TabPaneOtherPages() {
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsx("div", {
className: "mb-10px flex justify-start",
children: /* @__PURE__ */ jsx(ResetPartialSettingsButton, { paths: [
"fav.useCustomFavPicker.onPlayPage",
"videoPlayPage.addQuickLinks",
"videoCard.videoPreview.addTo.searchPage",
"videoCard.videoPreview.addTo.defaultHomepage"
] })
}),
/* @__PURE__ */ jsxs(SettingsGroup, {
title: "视频播放页",
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "fav.useCustomFavPicker.onPlayPage",
label: `使用自定义收藏弹窗`,
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"使用「",
APP_NAME,
"」提供的选择收藏夹弹窗 ",
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsxs("ul", {
className: "ml-20px list-circle",
children: [
/* @__PURE__ */ jsx("li", { children: "支持拼音搜索, 帮你快速找到收藏夹" }),
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("span", {
className: "flex-v-center",
children: [
"支持从收藏夹图标 或 快捷键",
/* @__PURE__ */ jsx(HotkeyDisplay, {
k: "E",
className: "mx-1 h-14px py-0 line-height-13px"
}),
"触发"
]
}) }),
/* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("span", {
className: "flex-v-center",
children: [/* @__PURE__ */ jsx(HotkeyDisplay, {
k: "Shift+E",
className: "mx-1 h-14px flex py-0 line-height-13px"
}), "无视此开关, 总是生效"]
}) })
]
})
] })
}), /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoPlayPage.addQuickLinks",
label: `添加快速链接`,
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "添加合集快速链接" })
})]
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: "搜索页",
children: /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.addTo.searchPage",
label: "浮动预览: 添加到「搜索页」",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "在搜索页的视频也添加「浮动预览」" })
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: "默认首页推荐",
children: /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.addTo.defaultHomepage",
label: "浮动预览: 添加到「默认首页推荐」",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "使用默认首页推荐时, 添加「浮动预览」" })
})
})
]
});
}
function restrictToBoundingRect(transform, rect, boundingRect) {
const value = { ...transform };
if (rect.top + transform.y <= boundingRect.top) value.y = boundingRect.top - rect.top;
else if (rect.bottom + transform.y >= boundingRect.top + boundingRect.height) value.y = boundingRect.top + boundingRect.height - rect.bottom;
if (rect.left + transform.x <= boundingRect.left) value.x = boundingRect.left - rect.left;
else if (rect.right + transform.x >= boundingRect.left + boundingRect.width) value.x = boundingRect.left + boundingRect.width - rect.right;
return value;
}
var restrictToParentElement = (_ref) => {
let { containerNodeRect, draggingNodeRect, transform } = _ref;
if (!draggingNodeRect || !containerNodeRect) return transform;
return restrictToBoundingRect(transform, draggingNodeRect, containerNodeRect);
};
var restrictToVerticalAxis = (_ref) => {
let { transform } = _ref;
return {
...transform,
x: 0
};
};
function TabPaneRecTabsConfig() {
const { dynamicFeed } = useSettingsSnapshot();
const sortedTabKeys = useSortedTabKeys();
const getCssOrderStyle = (tab) => {
return { order: sortedTabKeys.indexOf(tab) + 1 };
};
return /* @__PURE__ */ jsx("div", {
className: sharedClassNames.tabPane,
children: /* @__PURE__ */ jsxs("div", {
className: "grid grid-cols-[250px_1fr] gap-x-50px",
children: [/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["Tab 设置", /* @__PURE__ */ jsx(HelpInfo, {
className: "ml-5px",
children: "勾选显示, 拖动排序"
})] }),
resetSettingPaths: ["hidingTabKeys", "customTabKeysOrder"],
children: /* @__PURE__ */ jsx(VideoSourceTabOrder, {})
}), /* @__PURE__ */ jsxs(SettingsGroup, {
title: "更多设置",
contentClassName: "gap-y-10px",
resetSettingPaths: [
"watchlater.enableTabbarQuickSwitch",
"watchlater.addSeparator",
"watchlater.continuePlay",
"watchlater.continuePlayDirection",
"fav.addSeparator",
"dynamicFeed.followGroup.enabled",
"dynamicFeed.showLive",
"dynamicFeed.videoOnly",
"dynamicFeed.whenViewAll.enableHideSomeContents",
"appRecommend.enableTabbarQuickSwitch",
"appRecommend.addOtherTabContents",
"appRecommend.anonymousFetch",
"pcRecommend.enableTabbarQuickSwitch",
"pcRecommend.anonymousFetch",
"history.tabbarViewSmallControlSize",
"history.confirmBeforeDelete"
],
children: [
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.Watchlater),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.Watchlater,
className: "mr-5px mt--1px"
}),
"稍后再看",
/* @__PURE__ */ jsx(SwitchSettingItem, {
className: "ml-30px",
configPath: "watchlater.enableTabbarQuickSwitch",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "在 Tab 栏中显示相关设置以快速切换" })
})
]
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "watchlater.addSeparator",
label: "添加分割线",
tooltip: "添加「近期」「更早」分割线"
}), /* @__PURE__ */ jsx(WatchlaterContinuePlaySettings, {})]
})]
}),
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.Fav),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.Fav,
className: "mr-5px mt--2px"
}), "收藏"]
}), /* @__PURE__ */ jsx("div", {
className: sharedClassNames.settingsLine,
children: /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "fav.addSeparator",
label: "添加分割线",
tooltip: "顺序显示时, 按收藏夹添加分割线"
})
})]
}),
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.DynamicFeed),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.DynamicFeed,
className: "mr-5px mt--2px"
}), "动态"]
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "dynamicFeed.followGroup.enabled",
label: "启用分组筛选",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"动态 Tab 启用分组筛选 ",
/* @__PURE__ */ jsx("br", {}),
/* @__PURE__ */ jsx(FollowGroupMechanismNote, {})
] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "dynamicFeed.showLive",
label: "在动态中显示直播",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"动态里显示正在直播的 UP",
/* @__PURE__ */ jsx("br", {}),
"P.S 仅在选择「全部」时展示"
] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "dynamicFeed.videoOnly",
label: "只显示视频",
tooltip: explainForFlag("只获取视频动态: 投稿视频 / 动态视频", "获取全部动态: 包含视频 / 图片 / 文章等")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "dynamicFeed.whenViewAll.enableHideSomeContents",
label: "「全部」动态过滤",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"查看「全部」动态时 ",
/* @__PURE__ */ jsx("br", {}),
explainForFlag("将添加右键菜单, 点击可添加到「全部」动态的过滤列表", "关闭此功能")
] })
}),
dynamicFeed.whenViewAll.enableHideSomeContents && /* @__PURE__ */ jsx(collapse_default, {
size: "small",
className: "w-full",
items: [{
key: "1",
label: "在「全部」动态中隐藏 UP/分组 的动态",
children: /* @__PURE__ */ jsx(DynamicFeedWhenViewAllHideIdsPanel, {})
}]
})
]
})]
}),
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.AppRecommend),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.AppRecommend,
className: "mr-5px"
}),
"App 推荐",
/* @__PURE__ */ jsx(SwitchSettingItem, {
className: "ml-30px",
configPath: "appRecommend.enableTabbarQuickSwitch",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "在 Tab 栏中显示相关设置以快速切换" })
})
]
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [appRecShowContentFromOtherTabEl(), appRecAnonymousFetchEl()]
})]
}),
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.PcRecommend),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [
/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.PcRecommend,
className: "mr-5px"
}),
"PC 推荐",
/* @__PURE__ */ jsx(SwitchSettingItem, {
className: "ml-30px",
configPath: "pcRecommend.enableTabbarQuickSwitch",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "在 Tab 栏中显示相关设置以快速切换" })
})
]
}), /* @__PURE__ */ jsx("div", {
className: sharedClassNames.settingsLine,
children: pcRecAnonymousFetchEl()
})]
}),
/* @__PURE__ */ jsxs("div", {
style: getCssOrderStyle(ETab.History),
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center text-size-1.3em",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: ETab.History,
className: "mr-5px"
}), "历史"]
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "history.tabbarViewSmallControlSize",
label: "筛选组件使用小尺寸",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: ["Tab 栏中历史记录筛选组件", explainForFlag("使用小尺寸", "使用正常尺寸")] })
}), /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "history.confirmBeforeDelete",
label: "移除时需要确认",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"只影响单个移除, 批量移除时总是需要确认 ",
/* @__PURE__ */ jsx("br", {}),
"历史记录移除后无法找回: ",
/* @__PURE__ */ jsx("br", {}),
explainForFlag("删除前需要确认", "直接删除!")
] })
})]
})]
})
]
})]
})
});
}
function useCurrentShowingTabKeys() {
const { hidingTabKeys } = useSettingsSnapshot();
return (0, import_react.useMemo)(() => CONFIGURABLE_TAB_KEYS.filter((key) => !hidingTabKeys.includes(key)), [hidingTabKeys]);
}
function VideoSourceTabOrder({ className, style }) {
const currentShowingTabKeys = useCurrentShowingTabKeys();
const sortedTabKeys = useSortedTabKeys({ sync: true });
const sensors = useSensors(useSensor(PointerSensor));
const handleDragEnd = useMemoizedFn((e) => {
const { over, active } = e;
if (!over?.id || over.id === active.id) return;
const oldIndex = sortedTabKeys.indexOf(active.id.toString());
const newIndex = sortedTabKeys.indexOf(over.id.toString());
const newList = arrayMove(sortedTabKeys, oldIndex, newIndex);
settings.customTabKeysOrder = newList;
});
return /* @__PURE__ */ jsx("div", {
className,
style,
children: /* @__PURE__ */ jsx(Checkbox.Group, {
className: "block line-height-[unset]",
value: currentShowingTabKeys,
onChange: (newVal) => {
if (!newVal.length) return antMessage.error("至少选择一项!");
updateSettings({ hidingTabKeys: CONFIGURABLE_TAB_KEYS.filter((k) => !newVal.includes(k)) });
},
children: /* @__PURE__ */ jsx(DndContext, {
sensors,
collisionDetection: closestCenter,
onDragEnd: handleDragEnd,
modifiers: [restrictToVerticalAxis, restrictToParentElement],
children: /* @__PURE__ */ jsx(SortableContext, {
items: sortedTabKeys,
strategy: verticalListSortingStrategy,
children: sortedTabKeys.map((key) => /* @__PURE__ */ jsx(VideoSourceTabSortableItem, { id: key }, key))
})
})
})
});
}
function VideoSourceTabSortableItem({ id }) {
const { label, desc } = TabConfig[id];
const { attributes, listeners, setNodeRef, transform, transition, setActivatorNodeRef } = useSortable({ id });
const style = {
transform: CSS.Transform.toString(transform),
transition
};
return /* @__PURE__ */ jsxs("div", {
ref: setNodeRef,
style,
className: "mt-8px h-35px flex items-center justify-between b-1px b-gate-bg-lv-2 rounded-6px b-solid pl-10px pr-6px",
children: [/* @__PURE__ */ jsxs(Checkbox, {
value: id,
className: "inline-flex items-center [&_.ant-checkbox-label]:inline-flex [&_.ant-checkbox-label]:select-none [&_.ant-checkbox-label]:items-center",
children: [/* @__PURE__ */ jsx(TabIcon, {
tabKey: id,
className: "mr-5px"
}), /* @__PURE__ */ jsx(AntdTooltip, {
align: { offset: [0, -6] },
title: desc,
children: label
})]
}), /* @__PURE__ */ jsx("div", {
...listeners,
...attributes,
ref: setActivatorNodeRef,
className: "cursor-grab rounded-5px px-5px py-3px text-size-0 hover:bg-gate-bg-lv-3",
children: /* @__PURE__ */ jsx(ForwardRef$8, { className: "size-18px" })
})]
}, id);
}
function DynamicFeedWhenViewAllHideIdsPanel() {
const { hideIds } = useSnapshot(settings.dynamicFeed.whenViewAll);
const onDelete = useMemoizedFn(async (mid) => {
await updateSettingsInnerArray("dynamicFeed.whenViewAll.hideIds", { remove: [mid] });
});
const { groups } = useSnapshot(dfStore);
useMount(() => {
dfStore.updateGroups();
});
if (!hideIds.length) return /* @__PURE__ */ jsx("div", {
className: "flex items-center justify-center",
children: /* @__PURE__ */ jsx(Empty, {})
});
return /* @__PURE__ */ jsx("div", {
className: "max-h-250px flex flex-wrap gap-10px overflow-y-auto",
children: hideIds.map((tag) => {
return /* @__PURE__ */ jsx(TagItemDisplay, {
tag,
onDelete,
renderTag: (t) => /* @__PURE__ */ jsx(DynamicFeedWhenViewAllHideIdTag, {
tag: t,
followGroups: groups
})
}, tag);
})
});
}
function DynamicFeedWhenViewAllHideIdTag({ tag, followGroups }) {
let mid;
let followGroupId;
if (tag.startsWith("up:")) mid = tag.slice(3);
else if (tag.startsWith("group:")) followGroupId = tag.slice(DF_SELECTED_KEY_PREFIX_GROUP.length);
const [upNickname, setUpNickname] = (0, import_react.useState)(void 0);
useMount(async () => {
if (!mid) return;
const nickname = await getUserNickname(mid);
if (nickname) setUpNickname(nickname);
});
const [followGroupName, setFollowGroupName] = (0, import_react.useState)(void 0);
useMount(() => {
if (!followGroupId) return;
const groupName = followGroups?.find((g) => g.tagid.toString() === followGroupId)?.name;
if (groupName) setFollowGroupName(groupName);
});
const label = (0, import_react.useMemo)(() => mid ? upNickname || mid : followGroupId ? followGroupName || followGroupId : "无效数据", [
mid,
upNickname,
followGroupId,
followGroupName
]);
const tooltip = (0, import_react.useMemo)(() => mid ? `mid: ${mid}` : followGroupId ? `分组: ${followGroupId}` : `Tag: ${tag}`, [
mid,
followGroupId,
tag
]);
const icon = (0, import_react.useMemo)(() => mid ? /* @__PURE__ */ jsx(IconForUp, { className: "mr-2px size-12px" }) : followGroupId ? /* @__PURE__ */ jsx(IconForGroup, { className: "mr-2px size-16px" }) : void 0, [mid, followGroupId]);
const href = (0, import_react.useMemo)(() => mid ? formatSpaceUrl(mid) : followGroupId ? formatFollowGroupUrl(followGroupId) : void 0, [mid, followGroupId]);
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(AntdTooltip, {
title: tooltip,
children: /* @__PURE__ */ jsxs("span", {
className: clsx("inline-flex items-center", mid ? "cursor-pointer" : "cursor-text"),
children: [icon, href ? /* @__PURE__ */ jsx("a", {
href,
target: "_blank",
children: label
}) : label]
})
}) });
}
function TabPaneVideoCard() {
const { videoCard: { actions: { showLargePreview, openInPipWindow }, imgPreview: { enabled: imgPreviewEnabled } }, videoLinkOpenMode, style, autoPreviewUpdateInterval } = useSettingsSnapshot();
const openModeOptions = (0, import_react.useMemo)(() => {
return Object.values(EVideoLinkOpenMode).filter((mode) => VideoLinkOpenModeConfig[mode].enabled ?? true).map((mode) => {
const config = VideoLinkOpenModeConfig[mode];
return {
config,
value: mode,
label: /* @__PURE__ */ jsxs("span", {
className: "flex-v-center gap-x-8px",
children: [config.icon, /* @__PURE__ */ jsx("span", { children: config.label })]
})
};
});
}, []);
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.tabPane,
children: [
/* @__PURE__ */ jsx(SettingsGroup, {
title: "视频链接",
resetSettingPaths: ["videoLinkOpenMode"],
children: /* @__PURE__ */ jsxs("div", {
className: "flex-v-center",
children: [
"打开方式",
/* @__PURE__ */ jsxs(HelpInfo, {
useBlackBg: true,
children: [
"选择点击视频(封面图片 或 标题)时打开的模式 ",
/* @__PURE__ */ jsx("br", {}),
openModeOptions.map(({ value, config }) => {
return !!config.desc && /* @__PURE__ */ jsxs("div", {
className: "mt-10px flex items-start first:mt-0",
children: [/* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center",
children: [config.icon, /* @__PURE__ */ jsx("span", {
className: "ml-4px mr-10px min-w-95px",
children: config.label
})]
}), /* @__PURE__ */ jsx("span", {
className: "desc",
children: config.desc
})]
}, value);
})
]
}),
/* @__PURE__ */ jsx(Select, {
className: "ml-8px w-160px",
options: openModeOptions,
value: videoLinkOpenMode,
onChange: (v) => {
updateSettings({ videoLinkOpenMode: v });
}
})
]
})
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["卡片边框", /* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "style.videoCard.useBorder",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
explainForFlag("使用卡片边框", "禁用"),
/* @__PURE__ */ jsx(PrimaryBgContentDivider, {}),
"开启时, 视频卡片会有边框包裹, 更像是一个卡片! ",
/* @__PURE__ */ jsx("br", {}),
"整个卡片区域可点击 / 可触发预览 / 可使用右键菜单 ",
/* @__PURE__ */ jsx("br", {}),
"否则只是封面区域可以",
/* @__PURE__ */ jsx(PrimaryBgContentDivider, {}),
"使用快捷键 ",
/* @__PURE__ */ jsx(HotkeyDisplay, { k: "Shift+B" }),
" 切换状态 ",
/* @__PURE__ */ jsx("br", {}),
borderCycleListLabels.map((label) => /* @__PURE__ */ jsx(Tag, {
color: "success",
className: "mx-1",
children: label
}, label))
] }),
className: "ml-3"
})] }),
resetSettingPaths: [
"style.videoCard.useBorder",
"style.videoCard.useBorderOnlyOnHover",
"style.videoCard.useBoxShadow"
],
children: /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.videoCard.useBorderOnlyOnHover",
label: "仅在悬浮时显示",
disabled: !style.videoCard.useBorder,
tooltip: explainForFlag("仅在悬浮时显示", "常驻显示")
}), /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "style.videoCard.useBoxShadow",
disabled: !style.videoCard.useBorder,
label: "悬浮卡片时使用发光效果",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "悬浮卡片时使用发光效果, 看起来比较花哨~" })
})]
})
}),
/* @__PURE__ */ jsxs(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["快照预览", /* @__PURE__ */ jsx(SwitchSettingItem, {
configPath: "videoCard.imgPreview.enabled",
tooltip: "关闭此功能",
className: "ml-3"
})] }),
resetSettingPaths: [
"videoCard.imgPreview.enabled",
"videoCard.imgPreview.autoPreviewWhenKeyboardSelect",
"videoCard.imgPreview.autoPreviewWhenHover",
"videoCard.imgPreview.disableWhenMultiSelecting",
"useDelayForHover",
"autoPreviewUpdateInterval"
],
children: [/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !imgPreviewEnabled,
configPath: "videoCard.imgPreview.disableWhenMultiSelecting",
label: "多选时禁用",
tooltip: explainForFlag("多选开启时, 禁用快照预览", "不禁用")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !imgPreviewEnabled,
configPath: "useDelayForHover",
label: "延迟悬浮预览",
tooltip: "延迟悬浮预览"
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !imgPreviewEnabled,
configPath: "videoCard.imgPreview.autoPreviewWhenHover",
label: "自动预览: 鼠标悬浮",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
explainForFlag("鼠标悬浮后自动开始预览, 不跟随鼠标位置", "预览进度跟随鼠标位置(百分比)"),
"切换设置快捷键: ",
/* @__PURE__ */ jsx(HotkeyDisplay, { k: "Shift+P" })
] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
disabled: !imgPreviewEnabled,
configPath: "videoCard.imgPreview.autoPreviewWhenKeyboardSelect",
label: "自动预览: 键盘选中",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"手动预览快捷键: ",
/* @__PURE__ */ jsx(HotkeyDisplay, { k: "." }),
" or ",
/* @__PURE__ */ jsx(HotkeyDisplay, { k: "P" })
] })
})
]
}), /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
"自动预览更新间隔",
/* @__PURE__ */ jsx(Slider, {
disabled: !imgPreviewEnabled,
className: "w-500px",
min: 0,
max: 1e3,
keyboard: true,
onChange: (val) => settings.autoPreviewUpdateInterval = val,
value: autoPreviewUpdateInterval
}),
/* @__PURE__ */ jsxs("span", {
style: { width: "65px" },
children: [
"(",
autoPreviewUpdateInterval,
"ms)"
]
})
]
})]
}),
/* @__PURE__ */ jsx(SettingsGroup, {
title: /* @__PURE__ */ jsxs(Fragment, { children: ["操作按钮", /* @__PURE__ */ jsxs(HelpInfo, { children: [
"视频卡片右上角「稍后再看」按钮旁 ",
/* @__PURE__ */ jsx("br", {}),
"「稍后再看」因其通用性不提供关闭选项"
] })] }),
resetSettingPaths: [
"videoCard.actions.showLargePreview",
"videoCard.actions.openInPipWindow",
"videoCard.videoPreview.useMp4",
"videoCard.videoPreview.usePreferredCdn",
"videoCard.videoPreview.useScale",
"videoCard.videoPreview.useVideoCardAsTrigger",
"pipWindow.defaultLocked",
"pipWindow.autoWebFullscreen"
],
children: /* @__PURE__ */ jsxs("div", {
className: "grid grid-cols-[repeat(2,max-content)_1fr] items-start gap-x-20px",
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.actions.showLargePreview",
label: "浮动预览",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
"创意来源「浮图秀」, 但使用视频预览 ",
/* @__PURE__ */ jsx("br", {}),
"操作说明: ",
/* @__PURE__ */ jsx("br", {}),
"1. 鼠标悬浮打开 「浮动预览」, 离开关闭 ",
/* @__PURE__ */ jsx("br", {}),
"2. 点击固定「浮动预览」, 固定指: 不再随鼠标移出关闭预览 ",
/* @__PURE__ */ jsx("br", {}),
"3. 可使用以下方式关闭固定的「浮动预览」",
/* @__PURE__ */ jsxs("ul", {
className: "ml-30px list-circle",
children: [
/* @__PURE__ */ jsx("li", { children: "再次点击视频卡片按钮" }),
/* @__PURE__ */ jsx("li", { children: "点击预览视频右上方的「关闭」按钮" }),
/* @__PURE__ */ jsx("li", { children: "触发其他卡片的「浮动预览」" }),
/* @__PURE__ */ jsx("li", { children: "Esc键" }),
/* @__PURE__ */ jsx("li", { children: "点击页面空白处" })
]
})
] })
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.actions.openInPipWindow",
label: "小窗打开",
tooltip: /* @__PURE__ */ jsx(Fragment, { children: "仅当「文档画中画」API 可用时, 勾选生效" })
}),
/* @__PURE__ */ jsx(Divider, { className: "grid-col-span-full my-2px py-0" }),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-col",
children: [
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.useMp4",
disabled: !showLargePreview,
label: "浮动预览: 使用 mp4",
tooltip: explainForFlag("使用 mp4, 最高 720p, 有声音", "使用 dash, 最高 1080p, 无声音, 理论上更快")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.usePreferredCdn",
disabled: !showLargePreview,
label: "浮动预览: 使用优选 CDN",
tooltip: explainForFlag("使用优选 CDN (降低 MCDN & PCDN 优先级)", "使用默认 CDN")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.useScale",
disabled: !showLargePreview,
label: "浮动预览: 使用放大效果",
tooltip: explainForFlag("浮动预览面板: 放大展开 (类似浮图秀)", "浮动预览面板: 滑动展开")
}),
/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "videoCard.videoPreview.useVideoCardAsTrigger",
label: "浮动预览: 使用视频卡片作为触发器",
tooltip: /* @__PURE__ */ jsxs(Fragment, { children: [
explainForFlag("使用「视频卡片」作为触发器", "使用「视频卡片右上角按钮」作为触发器, 悬浮视频卡片 1 秒后展开"),
/* @__PURE__ */ jsx(Divider, { className: "my-1" }),
/* @__PURE__ */ jsx("ul", {
className: "ml-25px list-circle",
children: /* @__PURE__ */ jsx("li", { children: "与上面「浮动预览」开关独立" })
})
] })
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-col",
children: [/* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "pipWindow.defaultLocked",
disabled: !openInPipWindow,
label: "小窗: 默认锁定",
tooltip: explainForFlag("小窗打开时: 默认锁定", "小窗打开时: 不锁定")
}), /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "pipWindow.autoWebFullscreen",
disabled: !openInPipWindow,
label: "小窗: 自动网页全屏",
tooltip: explainForFlag("自动网页全屏", "不启用")
})]
})
]
})
})
]
});
}
/**
* https://github.com/lyswhut/lx-music-desktop/blob/v2.2.2/src/common/theme/index.json
*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*/
function ThemesSelect() {
const activeId = useCurrentTheme().id;
const prevActiveId = usePrevious(activeId);
const { colorPickerThemeSelectedColor } = useSettingsSnapshot();
const [customColor, setCustomColor] = (0, import_react.useState)(colorPickerThemeSelectedColor || BiliBrandColor.Pink);
const customColorHex = (0, import_react.useMemo)(() => {
return typeof customColor === "string" ? customColor : customColor.toHexString();
}, [customColor]);
useMount(() => {
$evolvedThemeColor.updateThrottled();
});
return /* @__PURE__ */ jsx("div", { children: ThemeGroups.map(({ name, themes, tooltip }) => {
return /* @__PURE__ */ jsxs(import_react.Fragment, { children: [/* @__PURE__ */ jsxs("div", {
className: "mt-2 flex items-center text-size-1.5em",
children: [name, /* @__PURE__ */ jsx(HelpInfo, {
className: "size-16px",
useBlackBg: true,
children: tooltip
})]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-x-5px gap-y-2px",
children: themes.map((t) => {
const isActive = activeId === t.id;
const isCustom = t.isCustom;
const useAnimation = !!prevActiveId && prevActiveId !== t.id;
let previewWrapper = /* @__PURE__ */ jsx("div", {
"data-role": "preview-wrapper",
style: { width: 36 },
className: clsx("mx-auto my-0 aspect-1 b-2px rounded-full b-solid", isActive ? "b-gate-primary" : "b-transparent", "flex items-center justify-center text-size-0"),
children: /* @__PURE__ */ jsx("div", {
"data-role": "preview",
className: "aspect-1 flex items-center justify-center rounded-full text-white",
css: /* @__PURE__ */ css("width:", 28, "px;background-color:", isCustom ? customColorHex : t.colorPrimary, ";"),
children: isActive && /* @__PURE__ */ jsx(IconAnimatedChecked, {
size: 18,
useAnimation
})
})
});
if (t.isCustom) previewWrapper = /* @__PURE__ */ jsx(color_picker_default, {
value: customColor,
onChange: (c) => setCustomColor(c),
onOpenChange: (open) => {
if (!open) updateSettings({ colorPickerThemeSelectedColor: customColorHex });
},
children: previewWrapper
});
let el = /* @__PURE__ */ jsxs("div", {
className: "min-w-60px cursor-pointer text-center",
onClick: (e) => {
updateSettings({ theme: t.id });
},
children: [previewWrapper, t.name]
});
if (t.tooltip || EXTRA_TOOLTIP[t.id]) el = /* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [t.tooltip, EXTRA_TOOLTIP[t.id]] }),
color: t.colorPrimary,
children: el
});
el = /* @__PURE__ */ jsx(import_react.Fragment, { children: el }, t.id);
return el;
})
})] }, name);
}) });
}
var customTabsCss = /* @__PURE__ */ css("&.ant-tabs{.ant-tabs-tab{justify-content:end;padding-inline:5px 15px;}.ant-tabs-nav-operations{display:none;}}");
function ModalSettings({ show, onHide }) {
const { tab } = useSnapshot(modalSettingsStore);
return /* @__PURE__ */ jsxs(BaseModal, {
show,
onHide,
hideWhenMaskOnClick: true,
hideWhenEsc: true,
width: "calc(100vw - 48px)",
clsModalMask: "[&:has(svg.peeking)]:bg-transparent",
clsModal: "max-h-unset h-[calc(100vh-48px)] [&:has(svg.peeking)]:opacity-10",
children: [/* @__PURE__ */ jsxs("div", {
className: BaseModalClassNames.modalHeader,
children: [/* @__PURE__ */ jsxs("div", {
className: BaseModalClassNames.modalTitle,
children: [/* @__PURE__ */ jsx(IconForConfig, { className: "mr-4px mt--2px size-26px" }), "设置"]
}), /* @__PURE__ */ jsx(ModalClose, { onClick: onHide })]
}), /* @__PURE__ */ jsx("main", {
className: BaseModalClassNames.modalBody,
style: { overflow: "hidden" },
children: /* @__PURE__ */ jsx(Tabs, {
tabPlacement: "start",
size: "middle",
css: customTabsCss,
activeKey: tab,
onChange: (tab) => modalSettingsStore.tab = tab,
items: [
{
label: "常规设置",
key: TabPaneKey.Basic,
children: /* @__PURE__ */ jsx(TabPaneBasic, {})
},
{
label: "视频卡片",
key: TabPaneKey.VideoCard,
children: /* @__PURE__ */ jsx(TabPaneVideoCard, {})
},
{
label: "内容过滤",
key: TabPaneKey.Filter,
children: /* @__PURE__ */ jsx(TabPaneFilter, {})
},
{
label: "主题选择",
key: TabPaneKey.ThemeSelect,
children: /* @__PURE__ */ jsx("div", {
className: sharedClassNames.tabPane,
children: /* @__PURE__ */ jsx(ThemesSelect, {})
})
},
{
label: "Tab 设置",
key: TabPaneKey.VideoSourceTabConfig,
children: /* @__PURE__ */ jsx(TabPaneRecTabsConfig, {})
},
{
label: "其他页面",
key: TabPaneKey.OtherPages,
children: /* @__PURE__ */ jsx(TabPaneOtherPages, {})
},
{
label: "标签收藏",
key: TabPaneKey.TagRecFavorites,
children: /* @__PURE__ */ jsx(TabPaneTagFavorites, {})
},
{
label: "标签过滤",
key: TabPaneKey.TagRecFilter,
children: /* @__PURE__ */ jsx(TabPaneTagFilter, {})
},
{
label: "高级设置",
key: TabPaneKey.Advance,
children: /* @__PURE__ */ jsx(TabPaneAdvance, {})
}
]
})
})]
});
}})}}));
System.register("./ModalFeed-C1S1iIOy-D2DiH1gG.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./___monkey.entry.js','./routes-B0HXSp2g-8gD5jzg6.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var __toESM,require_react,useDeferredTab,RecSidebar,TagRecDiscoveryLayout,RecGrid,VideoSourceTab,RefreshButton,ForwardRef$8,useSettingsSnapshot,clsx,useMemoizedFn,useModalsState,useModalDislikeVisible,useModalMoveFavVisible,useInitRecSelf,jsx,jsxs,ETab,ESidebarAlign,BaseModal,RecSelfContext,gridDisplayModeChecker,EGridDisplayMode,ModalClose,BaseModalClassNames,CheckboxSettingItem,Fragment,useToggle,button_default;return{setters:[function(module){__toESM=module._;require_react=module.r;},function(module){useDeferredTab=module.U;RecSidebar=module.V;TagRecDiscoveryLayout=module.W;RecGrid=module.X;VideoSourceTab=module.Y;RefreshButton=module.Z;ForwardRef$8=module._;},function(module){useSettingsSnapshot=module.d;clsx=module.m;useMemoizedFn=module.f;useModalsState=module.c7;useModalDislikeVisible=module.c8;useModalMoveFavVisible=module.c9;useInitRecSelf=module.ca;jsx=module.a;jsxs=module.j;ETab=module.E;ESidebarAlign=module.aa;BaseModal=module.b;RecSelfContext=module.cb;gridDisplayModeChecker=module.cc;EGridDisplayMode=module.cd;ModalClose=module.M;BaseModalClassNames=module.B;CheckboxSettingItem=module.C;Fragment=module.F;useToggle=module.ce;button_default=module.n;},null,null,null,null,null],execute:(function(){var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
function CollapseBtn({ children, initialOpen = false, ref }) {
const [buttonsExpanded, buttonsExpandedActions] = useToggle(initialOpen);
(0, import_react.useImperativeHandle)(ref, () => buttonsExpandedActions, [buttonsExpandedActions]);
const btn = /* @__PURE__ */ jsx(button_default, {
onClick: buttonsExpandedActions.toggle,
className: "icon-only-round-button",
children: /* @__PURE__ */ jsx(ForwardRef$8, { className: clsx("size-13px", buttonsExpanded ? "rotate-z-0" : "rotate-z-180deg") })
});
return /* @__PURE__ */ jsxs(Fragment, { children: [btn, buttonsExpanded && children] });
}
/**
* 懒得维护了, 太复杂的旧不弄了
* two-column-mode: 基础支持, align 不管了, sidebar 不管了
*/
var ModalFeed = exports("ModalFeed",(0, import_react.memo)(function ModalFeed({ show, onHide }) {
const { grid: { gridDisplayMode }, modalFeedFullScreen, sidebarAlign } = useSettingsSnapshot();
const { usingTwoColumnMode } = gridDisplayModeChecker(gridDisplayMode);
const useFullScreen = !usingTwoColumnMode && modalFeedFullScreen;
const scrollerRef = (0, import_react.useRef)(null);
const modalBorderCls = (0, import_react.useMemo)(() => {
return clsx(useFullScreen ? "b-5px" : "b-1px", "b-gate-primary b-solid");
}, [useFullScreen]);
const onScrollToTop = useMemoizedFn(() => {
if (scrollerRef.current) scrollerRef.current.scrollTop = 0;
});
const { modalSettingsVisible } = useModalsState();
const shortcutEnabled = [
show,
!modalSettingsVisible,
!useModalDislikeVisible(),
!useModalMoveFavVisible()
].every(Boolean);
const recSelf = useInitRecSelf(true);
const { tabbarView, sidebarView } = recSelf.useStore();
const { tab, direction } = useDeferredTab();
const renderHeader = () => {
return /* @__PURE__ */ jsxs("div", {
className: clsx(BaseModalClassNames.modalHeader, "gap-x-15px pr-15px"),
children: [/* @__PURE__ */ jsxs("div", {
className: "left flex flex-shrink-1 flex-wrap items-center gap-x-15px gap-y-4px",
children: [/* @__PURE__ */ jsx(VideoSourceTab, {}), tab === ETab.TagRecCustom ? void 0 : tabbarView]
}), /* @__PURE__ */ jsxs("div", {
className: "right flex flex-shrink-0 items-center gap-x-8px",
children: [
gridDisplayMode === EGridDisplayMode.TwoColumnGrid ? null : /* @__PURE__ */ jsx(CollapseBtn, {
initialOpen: true,
children: /* @__PURE__ */ jsx(ModalFeedConfigChecks, {})
}),
/* @__PURE__ */ jsx(RefreshButton, {
refreshHotkeyEnabled: shortcutEnabled,
tab
}),
/* @__PURE__ */ jsx(ModalClose, {
onClick: onHide,
className: "ml-5px"
})
]
})]
});
};
const clsModalMask = clsx(usingTwoColumnMode && "bg-black/90%");
const clsModal = clsx("h-[calc(100vh-30px)] max-h-unset w-[calc(100vw-30px)] pr-0", useFullScreen && "h-full w-full", usingTwoColumnMode && "w-720px", modalBorderCls);
return /* @__PURE__ */ jsx(RecSelfContext.Provider, {
value: recSelf,
children: /* @__PURE__ */ jsxs(BaseModal, {
show,
onHide,
clsModalMask,
clsModal,
children: [renderHeader(), /* @__PURE__ */ jsxs("div", {
"data-role": "modal-body",
className: clsx("flex flex-1 gap-x-25px overflow-hidden", sidebarAlign === ESidebarAlign.Right && "flex-row-reverse"),
children: [tab !== ETab.TagRecCustom && /* @__PURE__ */ jsx(RecSidebar, {
tab,
sidebarView,
className: "max-h-full"
}), /* @__PURE__ */ jsx("div", {
className: "h-full flex-1 overflow-y-auto pr-15px",
ref: scrollerRef,
children: tab === ETab.TagRecCustom ? /* @__PURE__ */ jsx(TagRecDiscoveryLayout, {
controls: tabbarView,
children: /* @__PURE__ */ jsx(RecGrid, {
tab,
direction,
shortcutEnabled,
onScrollToTop,
infiniteScrollUseWindow: false,
scrollerRef
}, tab)
}) : /* @__PURE__ */ jsx(RecGrid, {
tab,
direction,
shortcutEnabled,
onScrollToTop,
infiniteScrollUseWindow: false,
scrollerRef
}, tab)
})]
})]
})
});
}));
function ModalFeedConfigChecks() {
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(CheckboxSettingItem, {
configPath: "modalFeedFullScreen",
label: "全屏",
tooltip: "世界清净了~"
}) });
}})}}));
System.register("./tag-rec-fav-C-UOPfq3-CY70Ew_1.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./discovery-policy-BBjOB0CZ-BWQIVxg7.js','./share-import-modal-CeNu8LoU-N2aE6hH8.js','./versioned-cache-6Gx4pmyX-Dad7R-K0.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match','./share-tokens-DwS1MNAq-C8u87gZL.js','./share-import-DgpdTju8-Dey7mX2T.js'],(function(exports,module){'use strict';var tagRecBackoff,appWarn,isRiskControlError,__toESM,require_react,require_react_dom,BaseTabService,jsx,deriveGroupCliques,tagRecSearchExtraOf,tagRecRecallScheduler,runTagRecFilter,EApiType,TagRecTidDryTracker,settings,resolveTagRecItemScoreParams,tagRecGroupKey,resolveTagRecSeedMinScore,createTagRecProduceTracker,TAG_REC_ALL_POOL_KEYS,tagRecSourceClaims,runTagRecScoreStage,applyTagRecStages,TagRecSourceFailureError,createTagRecAttributionEnricher,invalidateTagRecCardData,clampTagRecK,makeTagRecExhaustSeparator,tagRecAttributionLabelOfGroup,trackTagRecRoundProduce,toast,computeTagRecFilterSignature,useOnRefresh,useRecSelfContext,usePopupContainer,useSnapshot,useSettingsSnapshot,jsxs,button_default,AntdTooltip,Checkbox,Fragment,Popover,createTagRecScopeResolver,Input,showModalSettings,hasTagRecCombination,Tag,ETab,require_jsx_runtime,favorites,favoritesL1,tagRecFavSession,materializeTagRecSessionFilter,themes,viewScope,tagRecSharedSession,tagRecCombinationBlocks,resetTagRecFavFilter,parseTagRecShareDeepLink,parseTagRecPkgHash,syncTagRecFavFilterScope,favoritesStore,favoritesL1Store,themesStore,viewScopeStore,tagRecViewScopeViewedIds,tagRecViewedGroupIds,Typography,TagRecRecheckButton,createTagRecGroupScopeDraft,tagRecViewBucketOf,TAG_REC_VIEW_UNASSIGNED,materializeTagRecGroupScopeDraft,beginTagRecCustomQuery,pushExpandedIds,videoSourceTabState,TagRecUnitsDeck,pickTagIdsForRound,unitsFingerprint,withTagRecGroupScope,isDeckDrawable,isL1Drawable,withTagRecTempEnables,TabPaneKey,modalSettingsStore,TagRecShareImportModal,TagRecUpSearchPool,TagRecPartitionBudget,planTagRecRecall,tagRecRecheckOptions,TagRecDeferredCandidates,tagRecNegativePools,TagRecFallbackLadder,FALLBACK_LADDER_STRATEGY_AGGRESSIVE,createTagRecSeenStage,refilterTagRecStock,upSearchMids,tagRecSupplyView,trimTagRecStock,resetTagRecLadderState,tagRecPartitionWorkQueue,TagRecFairState,previewTagRecFairSlice,hasTagRecPartitionRules,checkTagRecPartitionSlice,dropTagRecStrictTagListHits,rankTagRecFairSlice,ensureTagRecNegativePools,runTagRecRecallRound,tagRecSourceSignature,tagRecReopenedTopTids,tagRecVersionedArray,FilterPopover,TagRecBanner,versionedSlot,tagRecVersionKey,newTagRecLadderState,createTagRecBvidIndex,tagRecSeenStage,tagRecHeapVersion,TAG_REC_STOCK_TOTAL;return{setters:[function(module){tagRecBackoff=module.t;appWarn=module.a;isRiskControlError=module.i;__toESM=module._;require_react=module.r;require_react_dom=module.b;},function(module){BaseTabService=module.cf;jsx=module.a;deriveGroupCliques=module.cg;tagRecSearchExtraOf=module.ch;tagRecRecallScheduler=module.ci;runTagRecFilter=module.cj;EApiType=module.ck;TagRecTidDryTracker=module.cl;settings=module.t;resolveTagRecItemScoreParams=module.cm;tagRecGroupKey=module.cn;resolveTagRecSeedMinScore=module.co;createTagRecProduceTracker=module.cp;TAG_REC_ALL_POOL_KEYS=module.cq;tagRecSourceClaims=module.cr;runTagRecScoreStage=module.cs;applyTagRecStages=module.ct;TagRecSourceFailureError=module.cu;createTagRecAttributionEnricher=module.cv;invalidateTagRecCardData=module.cw;clampTagRecK=module.cx;makeTagRecExhaustSeparator=module.cy;tagRecAttributionLabelOfGroup=module.cz;trackTagRecRoundProduce=module.cA;toast=module.ak;computeTagRecFilterSignature=module.X;useOnRefresh=module.cB;useRecSelfContext=module.R;usePopupContainer=module.cC;useSnapshot=module.u;useSettingsSnapshot=module.d;jsxs=module.j;button_default=module.n;AntdTooltip=module.A;Checkbox=module._;Fragment=module.F;Popover=module.az;createTagRecScopeResolver=module.cD;Input=module.Y;showModalSettings=module.cE;hasTagRecCombination=module.aE;Tag=module.T;ETab=module.E;require_jsx_runtime=module.cF;},function(module){favorites=module.d;favoritesL1=module.f;tagRecFavSession=module.O;materializeTagRecSessionFilter=module.$;themes=module.w;viewScope=module.m;tagRecSharedSession=module.t;tagRecCombinationBlocks=module.E;resetTagRecFavFilter=module.a0;parseTagRecShareDeepLink=module.a1;parseTagRecPkgHash=module.a2;syncTagRecFavFilterScope=module.a3;favoritesStore=module.n;favoritesL1Store=module.o;themesStore=module.p;viewScopeStore=module.q;tagRecViewScopeViewedIds=module.a4;tagRecViewedGroupIds=module.a5;Typography=module.e;TagRecRecheckButton=module.a6;createTagRecGroupScopeDraft=module.a7;tagRecViewBucketOf=module.a8;TAG_REC_VIEW_UNASSIGNED=module.a9;materializeTagRecGroupScopeDraft=module.aa;beginTagRecCustomQuery=module.ab;pushExpandedIds=module.ac;videoSourceTabState=module.s;},function(module){TagRecUnitsDeck=module.j;pickTagIdsForRound=module.k;unitsFingerprint=module.u;withTagRecGroupScope=module.b;isDeckDrawable=module.l;isL1Drawable=module.n;withTagRecTempEnables=module.w;TabPaneKey=module.T;modalSettingsStore=module.m;},function(module){TagRecShareImportModal=module.T;},function(module){TagRecUpSearchPool=module.T;TagRecPartitionBudget=module.a;planTagRecRecall=module.p;tagRecRecheckOptions=module.t;TagRecDeferredCandidates=module.b;tagRecNegativePools=module.c;TagRecFallbackLadder=module.d;FALLBACK_LADDER_STRATEGY_AGGRESSIVE=module.F;createTagRecSeenStage=module.e;refilterTagRecStock=module.r;upSearchMids=module.u;tagRecSupplyView=module.f;trimTagRecStock=module.g;resetTagRecLadderState=module.h;tagRecPartitionWorkQueue=module.i;TagRecFairState=module.j;previewTagRecFairSlice=module.k;hasTagRecPartitionRules=module.l;checkTagRecPartitionSlice=module.m;dropTagRecStrictTagListHits=module.n;rankTagRecFairSlice=module.o;ensureTagRecNegativePools=module.q;runTagRecRecallRound=module.s;tagRecSourceSignature=module.v;tagRecReopenedTopTids=module.w;tagRecVersionedArray=module.x;FilterPopover=module.y;TagRecBanner=module.z;versionedSlot=module.A;tagRecVersionKey=module.B;newTagRecLadderState=module.C;createTagRecBvidIndex=module.D;tagRecSeenStage=module.E;tagRecHeapVersion=module.G;TAG_REC_STOCK_TOTAL=module.H;},null,null,null,null,null,null,null],execute:(function(){exports({clearTagRecFavSharedSeenBvids:clearTagRecFavSharedSeenBvids,computeTagRecFavContextSignature:computeTagRecFavContextSignature,computeTagRecFavFilterSignature:computeTagRecFavFilterSignature,computeTagRecFavSourceSignature:computeTagRecFavSourceSignature,resetTagRecFavSharedState:resetTagRecFavSharedState});var import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1);
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom(), 1);
var we = 0;
var Wt = 1;
var Xt = 2;
var Pn = 4;
function mn(t) {
return () => t;
}
function Io(t) {
t();
}
function re(t, e) {
return (n) => t(e(n));
}
function pn(t, e) {
return () => t(e);
}
function So(t, e) {
return (n) => t(e, n);
}
function Ne(t) {
return t !== void 0;
}
function xo(...t) {
return () => {
t.map(Io);
};
}
function Jt() {}
function ye(t, e) {
return e(t), t;
}
function vo(t, e) {
return e(t);
}
function it(...t) {
return t;
}
function J(t, e) {
return t(Wt, e);
}
function M(t, e) {
t(we, e);
}
function De(t) {
t(Xt);
}
function ot(t) {
return t(Pn);
}
function L(t, e) {
return J(t, So(e, we));
}
function bt(t, e) {
const n = t(Wt, (o) => {
n(), e(o);
});
return n;
}
function hn(t) {
let e, n;
return (o) => (r) => {
e = r, n && clearTimeout(n), n = setTimeout(() => {
o(e);
}, t);
};
}
function Wn(t, e) {
return t === e;
}
function rt(t = Wn) {
let e;
return (n) => (o) => {
t(e, o) || (e = o, n(o));
};
}
function W(t) {
return (e) => (n) => {
t(n) && e(n);
};
}
function B(t) {
return (e) => re(e, t);
}
function Bt(t) {
return (e) => () => {
e(t);
};
}
function S(t, ...e) {
const n = To(...e);
return ((o, r) => {
switch (o) {
case Xt:
De(t);
return;
case Wt: return J(t, n(r));
}
});
}
function kt(t, e) {
return (n) => (o) => {
n(e = t(e, o));
};
}
function Ut(t) {
return (e) => (n) => {
t > 0 ? t-- : e(n);
};
}
function zt(t) {
let e = null, n;
return (o) => (r) => {
e = r, !n && (n = setTimeout(() => {
n = void 0, o(e);
}, t));
};
}
function D(...t) {
const e = Array.from({ length: t.length });
let n = 0, o = null;
const r = 2 ** t.length - 1;
return t.forEach((s, i) => {
const l = 2 ** i;
J(s, (c) => {
const d = n;
n |= l, e[i] = c, d !== r && n === r && o && (o(), o = null);
});
}), (s) => (i) => {
const l = () => {
s([i].concat(e));
};
n === r ? l() : o = l;
};
}
function To(...t) {
return (e) => t.reduceRight(vo, e);
}
function Co(t) {
let e, n;
const o = () => e?.();
return function(r, s) {
switch (r) {
case Wt: return s ? n === s ? void 0 : (o(), n = s, e = J(t, s), e) : (o(), Jt);
case Xt:
o(), n = null;
return;
}
};
}
function w(t) {
let e = t;
const n = K();
return ((o, r) => {
switch (o) {
case we:
e = r;
break;
case Wt:
r(e);
break;
case Pn: return e;
}
return n(o, r);
});
}
function ht(t, e) {
return ye(w(e), (n) => L(t, n));
}
function K() {
const t = [];
return ((e, n) => {
switch (e) {
case we:
t.slice().forEach((o) => {
o(n);
});
return;
case Xt:
t.splice(0);
return;
case Wt: return t.push(n), () => {
const o = t.indexOf(n);
o > -1 && t.splice(o, 1);
};
}
});
}
function Ct(t) {
return ye(K(), (e) => L(t, e));
}
var wo = { singleton: true };
function Z(t, e = [], n = wo) {
const { singleton: o } = n;
return {
constructor: t,
dependencies: e,
id: yo(),
singleton: o
};
}
var yo = () => /* @__PURE__ */ Symbol("id");
function bo(t) {
const e = /* @__PURE__ */ new Map(), n = ({ constructor: o, dependencies: r, id: s, singleton: i }) => {
if (i && e.has(s)) return e.get(s);
const l = o(r.map((c) => n(c)));
return i && e.set(s, l), l;
};
return n(t);
}
function dt(...t) {
const e = K(), n = Array.from({ length: t.length });
let o = 0;
const r = 2 ** t.length - 1;
return t.forEach((s, i) => {
const l = 2 ** i;
J(s, (c) => {
n[i] = c, o |= l, o === r && M(e, n);
});
}), function(s, i) {
switch (s) {
case Xt:
De(e);
return;
case Wt: return o === r && i(n), J(e, i);
}
};
}
function P(t, e = Wn) {
return S(t, rt(e));
}
function Ve(...t) {
return function(e, n) {
switch (e) {
case Xt: return;
case Wt: return xo(...t.map((o) => J(o, n)));
}
};
}
var mt = {
/** Detailed debugging information including item measurements */
DEBUG: 0,
/** General informational messages */
INFO: 1,
/** Warning messages for potential issues */
WARN: 2,
/** Error messages for failures (default level) */
ERROR: 3
};
var Ro = {
[mt.DEBUG]: "debug",
[mt.ERROR]: "error",
[mt.INFO]: "log",
[mt.WARN]: "warn"
};
var Ho = () => typeof globalThis > "u" ? window : globalThis;
var Gt = Z(() => {
const t = w(mt.ERROR);
return {
log: w((n, o, r = mt.INFO) => {
r >= (Ho().VIRTUOSO_LOG_LEVEL ?? ot(t)) && console[Ro[r]]("%creact-virtuoso: %c%s %o", "color: #0253b3; font-weight: bold", "color: initial", n, o);
}),
logLevel: t
};
}, [], { singleton: true });
function Eo(t) {
return "self" in t ? t.document.documentElement : t;
}
function Gn(t) {
const e = Eo(t);
return e.ownerDocument.defaultView.getComputedStyle(e).direction === "rtl";
}
function _t(t, e) {
return Gn(t) ? -e : e;
}
function gn(t, e) {
return Gn(t) ? -e : e;
}
function Ot(t, e, n) {
return $e(t, e, n).callbackRef;
}
function $e(t, e, n) {
const o = import_react.useRef(null);
let r = (i) => {};
const s = import_react.useMemo(() => typeof ResizeObserver < "u" ? new ResizeObserver((i) => {
const l = () => {
const c = i[0].target;
c.offsetParent !== null && t(c);
};
n ? l() : requestAnimationFrame(l);
}) : null, [t, n]);
return r = (i) => {
i && e ? (s?.observe(i), o.current = i) : (o.current && s?.unobserve(o.current), o.current = null);
}, {
callbackRef: r,
ref: o
};
}
function An(t, e, n, o, r, s, i, l, c) {
return $e(import_react.useCallback((f) => {
const v = Bo(f.children, e, l ? "offsetWidth" : "offsetHeight", r);
let m = f.parentElement;
for (; m.dataset.virtuosoScroller === void 0;) m = m.parentElement;
const g = m.lastElementChild?.dataset.viewportType === "window";
let y;
g && (y = m.ownerDocument.defaultView);
const R = i ? l ? i.scrollWidth : i.scrollHeight : g ? l ? y.document.documentElement.scrollWidth : y.document.documentElement.scrollHeight : l ? m.scrollWidth : m.scrollHeight, I = i ? l ? i.offsetWidth : i.offsetHeight : g ? l ? y.innerWidth : y.innerHeight : l ? m.offsetWidth : m.offsetHeight, h = i ? l ? _t(i, i.scrollLeft) : i.scrollTop : g ? l ? _t(y, y.scrollX || y.document.documentElement.scrollLeft) : y.scrollY || y.document.documentElement.scrollTop : l ? _t(m, m.scrollLeft) : m.scrollTop;
o({
scrollHeight: R,
scrollTop: Math.max(h, 0),
viewportHeight: I
}), s?.(l ? In("column-gap", getComputedStyle(f).columnGap, r) : In("row-gap", getComputedStyle(f).rowGap, r)), v !== null && t(v);
}, [
t,
e,
r,
s,
i,
o,
l
]), n, c);
}
function Bo(t, e, n, o) {
const r = t.length;
if (r === 0) return null;
const s = [];
for (let i = 0; i < r; i++) {
const l = t.item(i);
if (l.dataset.index === void 0) continue;
const c = parseInt(l.dataset.index, 10), d = parseFloat(l.dataset.knownSize), f = e(l, n);
if (f === 0 && o("Zero-sized element, this should not happen", { child: l }, mt.ERROR), f === d) continue;
const v = s[s.length - 1];
s.length === 0 || v.size !== f || v.endIndex !== c - 1 ? s.push({
endIndex: c,
size: f,
startIndex: c
}) : s[s.length - 1].endIndex++;
}
return s;
}
function In(t, e, n) {
return e !== "normal" && e?.endsWith("px") !== true && n(`${t} was not resolved to pixel value correctly`, e, mt.WARN), e === "normal" ? 0 : parseInt(e ?? "0", 10);
}
var be = typeof document > "u" ? import_react.useEffect : import_react.useLayoutEffect;
function Ue(t, e, n) {
const o = import_react.useRef(null), r = import_react.useCallback((c) => {
if (!c?.offsetParent) return;
const d = c.getBoundingClientRect(), f = d.width;
let v, m;
if (e) {
const g = e.getBoundingClientRect(), y = d.top - g.top;
m = g.height - Math.max(0, y), v = y + e.scrollTop;
} else {
const g = i.current.ownerDocument.defaultView;
m = g.innerHeight - Math.max(0, d.top), v = d.top + g.scrollY;
}
o.current = {
listHeight: d.height,
offsetTop: v,
visibleHeight: m,
visibleWidth: f
}, t(o.current);
}, [t, e]), { callbackRef: s, ref: i } = $e(r, true, n), l = import_react.useCallback(() => {
r(i.current);
}, [r, i]);
return import_react.useEffect(() => {
if (e) {
e.addEventListener("scroll", l);
const d = new ResizeObserver(() => {
requestAnimationFrame(l);
});
return d.observe(e), () => {
e.removeEventListener("scroll", l), d.unobserve(e);
};
}
const c = i.current?.ownerDocument.defaultView;
return c?.addEventListener("scroll", l), c?.addEventListener("resize", l), () => {
c?.removeEventListener("scroll", l), c?.removeEventListener("resize", l);
};
}, [
l,
e,
i
]), s;
}
var It = Z(() => {
const t = K(), e = K(), n = w(0), o = K(), r = K(), s = w(0), i = K(), l = K(), c = w(0), d = w(0), f = w(0), v = w(0), m = K(), g = K(), y = w(false), R = w(false), I = w(false);
return L(S(t, B(({ scrollTop: h }) => h)), e), L(S(t, B(({ scrollHeight: h }) => h)), l), L(e, s), {
deviation: n,
deviationCommitted: o,
fixedFooterHeight: f,
fixedHeaderHeight: d,
footerHeight: v,
headerHeight: c,
horizontalDirection: R,
scrollBy: g,
scrollContainerState: t,
scrollHeight: l,
scrollingInProgress: y,
scrollTo: m,
scrollTop: e,
skipAnimationFrameInResizeObserver: I,
smoothScrollTargetReached: r,
statefulScrollTop: s,
viewportHeight: i
};
}, [], { singleton: true });
var se = { lvl: 0 };
function Mn(t, e) {
const n = t.length;
if (n === 0) return [];
let { index: o, value: r } = e(t[0]);
const s = [];
for (let i = 1; i < n; i++) {
const { index: l, value: c } = e(t[i]);
s.push({
end: l - 1,
start: o,
value: r
}), o = l, r = c;
}
return s.push({
end: 1 / 0,
start: o,
value: r
}), s;
}
function tt(t) {
return t === se;
}
function ie(t, e) {
if (!tt(t)) return e === t.k ? t.v : e < t.k ? ie(t.l, e) : ie(t.r, e);
}
function Rt(t, e, n = "k") {
if (tt(t)) return [-1 / 0, void 0];
if (Number(t[n]) === e) return [t.k, t.v];
if (Number(t[n]) < e) {
const o = Rt(t.r, e, n);
return o[0] === -1 / 0 ? [t.k, t.v] : o;
}
return Rt(t.l, e, n);
}
function vt(t, e, n) {
return tt(t) ? Dn(e, n, 1) : e === t.k ? ft(t, {
k: e,
v: n
}) : e < t.k ? Sn(ft(t, { l: vt(t.l, e, n) })) : Sn(ft(t, { r: vt(t.r, e, n) }));
}
function Yt() {
return se;
}
function Zt(t, e, n) {
if (tt(t)) return [];
const o = Rt(t, e)[0];
return ko(We(t, o, n));
}
function Pe(t, e) {
if (tt(t)) return se;
const { k: n, l: o, r } = t;
if (e === n) {
if (tt(o)) return r;
if (tt(r)) return o;
const [s, i] = Nn(o);
return xe(ft(t, {
k: s,
l: _n(o),
v: i
}));
}
return e < n ? xe(ft(t, { l: Pe(o, e) })) : xe(ft(t, { r: Pe(r, e) }));
}
function Nt(t) {
return tt(t) ? [] : [
...Nt(t.l),
{
k: t.k,
v: t.v
},
...Nt(t.r)
];
}
function We(t, e, n) {
if (tt(t)) return [];
const { k: o, l: r, r: s, v: i } = t;
let l = [];
return o > e && (l = l.concat(We(r, e, n))), o >= e && o <= n && l.push({
k: o,
v: i
}), o <= n && (l = l.concat(We(s, e, n))), l;
}
function xe(t) {
const { l: e, lvl: n, r: o } = t;
if (o.lvl >= n - 1 && e.lvl >= n - 1) return t;
if (n > o.lvl + 1) {
if (ke(e)) return $n(ft(t, { lvl: n - 1 }));
if (!tt(e) && !tt(e.r)) return ft(e.r, {
l: ft(e, { r: e.r.l }),
lvl: n,
r: ft(t, {
l: e.r.r,
lvl: n - 1
})
});
throw new Error("Unexpected empty nodes");
}
if (ke(t)) return Ge(ft(t, { lvl: n - 1 }));
if (!tt(o) && !tt(o.l)) {
const r = o.l, s = ke(r) ? o.lvl - 1 : o.lvl;
return ft(r, {
l: ft(t, {
lvl: n - 1,
r: r.l
}),
lvl: r.lvl + 1,
r: Ge(ft(o, {
l: r.r,
lvl: s
}))
});
}
throw new Error("Unexpected empty nodes");
}
function ft(t, e) {
return Dn(e.k === void 0 ? t.k : e.k, e.v === void 0 ? t.v : e.v, e.lvl === void 0 ? t.lvl : e.lvl, e.l === void 0 ? t.l : e.l, e.r === void 0 ? t.r : e.r);
}
function _n(t) {
return tt(t.r) ? t.l : xe(ft(t, { r: _n(t.r) }));
}
function ke(t) {
return tt(t) || t.lvl > t.r.lvl;
}
function Nn(t) {
return tt(t.r) ? [t.k, t.v] : Nn(t.r);
}
function Dn(t, e, n, o = se, r = se) {
return {
k: t,
l: o,
lvl: n,
r,
v: e
};
}
function Sn(t) {
return Ge($n(t));
}
function $n(t) {
const { l: e } = t;
return !tt(e) && e.lvl === t.lvl ? ft(e, { r: ft(t, { l: e.r }) }) : t;
}
function Ge(t) {
const { lvl: e, r: n } = t;
return !tt(n) && !tt(n.r) && n.lvl === e && n.r.lvl === e ? ft(n, {
l: ft(t, { r: n.l }),
lvl: e + 1
}) : t;
}
function ko(t) {
return Mn(t, ({ k: e, v: n }) => ({
index: e,
value: n
}));
}
function Un(t, e) {
return !!(t && t.startIndex === e.startIndex && t.endIndex === e.endIndex);
}
function le(t, e) {
return !!(t && t[0] === e[0] && t[1] === e[1]);
}
var Ke = Z(() => ({ recalcInProgress: w(false) }), [], { singleton: true });
function Kn(t, e, n) {
return t[Te(t, e, n)];
}
function Te(t, e, n, o = 0) {
let r = t.length - 1;
for (; o <= r;) {
const s = Math.floor((o + r) / 2), i = t[s], l = n(i, e);
if (l === 0) return s;
if (l === -1) {
if (r - o < 2) return s - 1;
r = s - 1;
} else {
if (r === o) return s;
o = s + 1;
}
}
throw new Error(`Failed binary finding record in array - ${t.join(",")}, searched for ${e}`);
}
function Oo(t, e, n, o) {
const r = Te(t, e, o), s = Te(t, n, o, r);
return t.slice(r, s + 1);
}
function Ht(t, e) {
return Math.round(t.getBoundingClientRect()[e]);
}
function Re(t) {
return !tt(t.groupOffsetTree);
}
function je({ index: t }, e) {
return e === t ? 0 : e < t ? -1 : 1;
}
function Lo() {
return {
groupIndices: [],
groupOffsetTree: Yt(),
lastIndex: 0,
lastOffset: 0,
lastSize: 0,
offsetTree: [],
sizeTree: Yt()
};
}
function zo(t, e) {
let n = tt(t) ? 0 : 1 / 0;
for (const o of e) {
const { endIndex: r, size: s, startIndex: i } = o;
if (n = Math.min(n, i), tt(t)) {
t = vt(t, 0, s);
continue;
}
const l = Zt(t, i - 1, r + 1);
if (l.some(Mo(o))) continue;
let c = false, d = false;
for (const { end: f, start: v, value: m } of l) c ? (r >= v || s === m) && (t = Pe(t, v)) : (d = m !== s, c = true), f > r && r >= v && m !== s && (t = vt(t, r + 1, m));
d && (t = vt(t, i, s));
}
return [t, n];
}
function Fo(t) {
return t.groupIndex !== void 0;
}
function Vo({ offset: t }, e) {
return e === t ? 0 : e < t ? -1 : 1;
}
function ce(t, e, n) {
if (e.length === 0) return 0;
const { index: o, offset: r, size: s } = Kn(e, t, je), i = t - o, l = s * i + (i - 1) * n + r;
return l > 0 ? l + n : l;
}
function jn(t, e) {
if (!Re(e)) return t;
let n = 0;
for (; e.groupIndices[n] <= t + n;) n++;
return t + n;
}
function qn(t, e, n) {
if (Fo(t)) return e.groupIndices[t.groupIndex] + 1;
let r = jn(t.index === "LAST" ? n : t.index, e);
return r = Math.max(0, Math.min(n, r)), r;
}
function Po(t, e, n, o = 0) {
return o > 0 && (e = Math.max(e, Kn(t, o, je).offset)), Mn(Oo(t, e, n, Vo), Ao);
}
function Wo(t, [e, n, o, r]) {
e.length > 0 && o("received item sizes", e, mt.DEBUG);
const s = t.sizeTree;
let i = s, l = 0;
if (n.length > 0 && tt(s) && e.length === 2) {
const m = e[0].size, g = e[1].size;
i = n.reduce((y, R) => vt(vt(y, R, m), R + 1, g), i);
} else [i, l] = zo(i, e);
if (i === s) return t;
const { lastIndex: c, lastOffset: d, lastSize: f, offsetTree: v } = Ae(t.offsetTree, l, i, r);
return {
groupIndices: n,
groupOffsetTree: n.reduce((m, g) => vt(m, g, ce(g, v, r)), Yt()),
lastIndex: c,
lastOffset: d,
lastSize: f,
offsetTree: v,
sizeTree: i
};
}
function Go(t) {
return Nt(t).map(({ k: e, v: n }, o, r) => {
const s = r[o + 1];
return {
endIndex: s === void 0 ? 1 / 0 : s.k - 1,
size: n,
startIndex: e
};
});
}
function xn(t, e) {
let n = 0, o = 0;
for (; n < t;) n += e[o + 1] - e[o] - 1, o++;
return o - (n === t ? 0 : 1);
}
function Ae(t, e, n, o) {
let r = t, s = 0, i = 0, l = 0, c = 0;
if (e === 0) r = [];
else {
c = Te(r, e - 1, je), l = r[c].offset;
const f = Rt(n, e - 1);
s = f[0], i = f[1], r.length && r[c].size === Rt(n, e)[1] && (c -= 1), r = r.slice(0, c + 1);
}
for (const { start: d, value: f } of Zt(n, e, 1 / 0)) {
const v = d - s, m = v * i + l + v * o;
r.push({
index: d,
offset: m,
size: f
}), s = d, l = m, i = f;
}
return {
lastIndex: s,
lastOffset: l,
lastSize: i,
offsetTree: r
};
}
function Ao(t) {
return {
index: t.index,
value: t
};
}
function Mo(t) {
const { endIndex: e, size: n, startIndex: o } = t;
return (r) => r.start === o && (r.end === e || r.end === 1 / 0) && r.value === n;
}
var _o = {
offsetHeight: "height",
offsetWidth: "width"
};
var Lt = Z(([{ log: t }, { recalcInProgress: e }]) => {
const n = K(), o = K(), r = ht(o, 0), s = K(), i = K(), l = w(0), c = w([]), d = w(void 0), f = w(void 0), v = w(void 0), m = w(void 0), g = w((u, p) => Ht(u, _o[p])), y = w(void 0), R = w(0), I = Lo(), h = ht(S(n, D(c, t, R), kt(Wo, I), rt()), I), a = ht(S(c, rt(), kt((u, p) => ({
current: p,
prev: u.current
}), {
current: [],
prev: []
}), B(({ prev: u }) => u)), []);
L(S(c, W((u) => u.length > 0), D(h, R), B(([u, p, b]) => {
const z = u.reduce((F, O, V) => vt(F, O, ce(O, p.offsetTree, b) || V), Yt());
return {
...p,
groupIndices: u,
groupOffsetTree: z
};
})), h), L(S(o, D(h), W(([u, { lastIndex: p }]) => u < p), B(([u, { lastIndex: p, lastSize: b }]) => [{
endIndex: p,
size: b,
startIndex: u
}])), n), L(d, f);
const T = ht(S(d, B((u) => u === void 0)), true);
L(S(f, W((u) => u !== void 0 && tt(ot(h).sizeTree)), B((u) => {
const p = ot(v), b = ot(c).length > 0;
return p !== void 0 && p !== 0 ? b ? [{
endIndex: 0,
size: p,
startIndex: 0
}, {
endIndex: 1,
size: u,
startIndex: 1
}] : [] : [{
endIndex: 0,
size: u,
startIndex: 0
}];
})), n), L(S(m, W((u) => u !== void 0 && u.length > 0 && tt(ot(h).sizeTree)), B((u) => {
const p = [];
let b = u[0], z = 0;
for (let F = 1; F < u.length; F++) {
const O = u[F];
O !== b && (p.push({
endIndex: F - 1,
size: b,
startIndex: z
}), b = O, z = F);
}
return p.push({
endIndex: u.length - 1,
size: b,
startIndex: z
}), p;
})), n), L(S(c, D(v, f), W(([, u, p]) => u !== void 0 && p !== void 0), B(([u, p, b]) => {
const z = [];
for (let F = 0; F < u.length; F++) {
const O = u[F], V = u[F + 1];
z.push({
startIndex: O,
endIndex: O,
size: p
}), V !== void 0 && z.push({
startIndex: O + 1,
endIndex: V - 1,
size: b
});
}
return z;
})), n);
const x = Ct(S(n, D(h), kt(({ sizes: u }, [p, b]) => ({
changed: b !== u,
sizes: b
}), {
changed: false,
sizes: I
}), B((u) => u.changed)));
J(S(l, kt((u, p) => ({
diff: u.prev - p,
prev: p
}), {
diff: 0,
prev: 0
}), B((u) => u.diff)), (u) => {
const { groupIndices: p } = ot(h);
if (u > 0) M(e, true), M(s, u + xn(u, p));
else if (u < 0) {
const b = ot(a);
b.length > 0 && (u -= xn(-u, b)), M(i, u);
}
}), J(S(l, D(t)), ([u, p]) => {
u < 0 && p("`firstItemIndex` prop should not be set to less than zero. If you don't know the total count, just use a very high value", { firstItemIndex: l }, mt.ERROR);
});
const C = Ct(s);
L(S(s, D(h), B(([u, p]) => {
const b = p.groupIndices.length > 0, z = [], F = p.lastSize;
if (b) {
const O = ie(p.sizeTree, 0);
let V = 0, U = 0;
for (; V < u;) {
const j = p.groupIndices[U], X = p.groupIndices.length === U + 1 ? 1 / 0 : p.groupIndices[U + 1] - j - 1;
z.push({
endIndex: j,
size: O,
startIndex: j
}), z.push({
endIndex: j + 1 + X - 1,
size: F,
startIndex: j + 1
}), U++, V += X + 1;
}
const q = Nt(p.sizeTree);
return V !== u && q.shift(), q.reduce((j, { k: X, v: gt }) => {
let ct = j.ranges;
return j.prevSize !== 0 && (ct = [...j.ranges, {
endIndex: X + u - 1,
size: j.prevSize,
startIndex: j.prevIndex
}]), {
prevIndex: X + u,
prevSize: gt,
ranges: ct
};
}, {
prevIndex: u,
prevSize: 0,
ranges: z
}).ranges;
}
return Nt(p.sizeTree).reduce((O, { k: V, v: U }) => ({
prevIndex: V + u,
prevSize: U,
ranges: [...O.ranges, {
endIndex: V + u - 1,
size: O.prevSize,
startIndex: O.prevIndex
}]
}), {
prevIndex: 0,
prevSize: F,
ranges: []
}).ranges;
})), n);
const k = Ct(S(i, D(h, R), B(([u, { offsetTree: p }, b]) => {
return ce(-u, p, b);
})));
return L(S(i, D(h, R), B(([u, p, b]) => {
if (p.groupIndices.length > 0) {
if (tt(p.sizeTree)) return p;
let O = Yt();
const V = ot(a);
let U = 0, q = 0, $ = 0;
for (; U < -u;) {
$ = V[q];
const X = V[q + 1] - $ - 1;
q++, U += X + 1;
}
if (O = Nt(p.sizeTree).reduce((X, { k: gt, v: ct }) => vt(X, Math.max(0, gt + u), ct), O), U !== -u) {
const X = ie(p.sizeTree, $);
O = vt(O, 0, X);
const gt = Rt(p.sizeTree, -u + 1)[1];
O = vt(O, 1, gt);
}
return {
...p,
sizeTree: O,
...Ae(p.offsetTree, 0, O, b)
};
}
const F = Nt(p.sizeTree).reduce((O, { k: V, v: U }) => vt(O, Math.max(0, V + u), U), Yt());
return {
...p,
sizeTree: F,
...Ae(p.offsetTree, 0, F, b)
};
})), h), {
beforeUnshiftWith: C,
data: y,
defaultItemSize: f,
firstItemIndex: l,
fixedItemSize: d,
fixedGroupSize: v,
gap: R,
groupIndices: c,
heightEstimates: m,
itemSize: g,
listRefresh: x,
shiftWith: i,
shiftWithOffset: k,
sizeRanges: n,
sizes: h,
statefulTotalCount: r,
totalCount: o,
trackItemSizes: T,
unshiftWith: s
};
}, it(Gt, Ke), { singleton: true });
function No(t) {
return t.reduce((e, n) => (e.groupIndices.push(e.totalCount), e.totalCount += n + 1, e), {
groupIndices: [],
totalCount: 0
});
}
var Yn = Z(([{ groupIndices: t, sizes: e, totalCount: n }, { headerHeight: o, scrollTop: r }]) => {
const s = K(), i = K(), l = Ct(S(s, B(No)));
return L(S(l, B((c) => c.totalCount)), n), L(S(l, B((c) => c.groupIndices)), t), L(S(dt(r, e, o), W(([c, d]) => Re(d)), B(([c, d, f]) => Rt(d.groupOffsetTree, Math.max(c - f, 0), "v")[0]), rt(), B((c) => [c])), i), {
groupCounts: s,
topItemsIndexes: i
};
}, it(Lt, It));
var At = Z(([{ log: t }]) => {
const e = w(false), n = Ct(S(e, W((o) => o), rt()));
return J(e, (o) => {
o && ot(t)("props updated", {}, mt.DEBUG);
}), {
didMount: n,
propsReady: e
};
}, it(Gt), { singleton: true });
var Do = typeof document < "u" && "scrollBehavior" in document.documentElement.style;
function Zn(t) {
const e = typeof t == "number" ? { index: t } : { ...t };
return e.align || (e.align = "start"), (!e.behavior || !Do) && (e.behavior = "auto"), e.offset === void 0 && (e.offset = 0), e;
}
var fe = Z(([{ gap: t, listRefresh: e, sizes: n, totalCount: o }, { fixedFooterHeight: r, fixedHeaderHeight: s, footerHeight: i, headerHeight: l, scrollingInProgress: c, scrollTo: d, smoothScrollTargetReached: f, viewportHeight: v }, { log: m }]) => {
const g = K(), y = K(), R = w(0);
let I = null, h = null, a = null;
function T() {
I !== null && (I(), I = null), a !== null && (a(), a = null), h && (clearTimeout(h), h = null), M(c, false);
}
return L(S(g, D(n, v, o, R, l, i, m), D(t, s, r), B(([[x, C, k, u, p, b, z, F], O, V, U]) => {
const q = Zn(x), { align: $, behavior: j, offset: X } = q, gt = u - 1, ct = qn(q, C, gt);
let St = ce(ct, C.offsetTree, O) + b;
$ === "end" ? (St += V + Rt(C.sizeTree, ct)[1] - k + U, ct === gt && (St += z)) : $ === "center" ? St += (V + Rt(C.sizeTree, ct)[1] - k + U) / 2 : St -= p, X !== void 0 && X !== 0 && (St += X);
const Ft = (pt) => {
T(), pt ? (F("retrying to scroll to", { location: x }, mt.DEBUG), M(g, x)) : (M(y, true), F("list did not change, scroll successful", {}, mt.DEBUG));
};
if (T(), j === "smooth") {
let pt = false;
a = J(e, (jt) => {
pt = pt || jt;
}), I = bt(f, () => {
Ft(pt);
});
} else I = bt(S(e, $o(150)), Ft);
return h = setTimeout(() => {
T();
}, 1200), M(c, true), F("scrolling from index to", {
behavior: j,
index: ct,
top: St
}, mt.DEBUG), {
behavior: j,
top: St
};
})), d), {
scrollTargetReached: y,
scrollToIndex: g,
topListHeight: R
};
}, it(Lt, It, Gt), { singleton: true });
function $o(t) {
return (e) => {
const n = setTimeout(() => {
e(false);
}, t);
return (o) => {
o && (e(true), clearTimeout(n));
};
};
}
function qe(t, e) {
t === 0 ? e() : requestAnimationFrame(() => {
qe(t - 1, e);
});
}
function Ye(t, e) {
if (t === void 0) return 0;
const n = e - 1, o = typeof t == "number" ? t : t.index === "LAST" ? n : t.index;
return Math.max(0, Math.min(o, n));
}
function Me(t) {
return t === void 0 ? true : typeof t == "number" ? t === 0 : t.index === 0 && (t.align === void 0 || t.align === "start") && (t.offset === void 0 || t.offset === 0);
}
var me = Z(([{ defaultItemSize: t, listRefresh: e, sizes: n }, { scrollTop: o }, { scrollTargetReached: r, scrollToIndex: s }, { didMount: i }]) => {
const l = w(true), c = w(0), d = w(true);
return L(S(i, D(c), W(([f, v]) => !Me(v)), Bt(false)), l), L(S(i, D(c), W(([f, v]) => !Me(v)), Bt(false)), d), J(S(dt(e, i), D(l, n, t, d), W(([[, f], v, { sizeTree: m }, g, y]) => f && (!tt(m) || Ne(g)) && !v && !y), D(c)), ([, f]) => {
if (f === void 0) {
M(l, true), M(d, true);
return;
}
bt(r, () => {
M(d, true);
}), qe(4, () => {
bt(o, () => {
M(l, true);
}), M(s, f);
});
}), {
initialItemFinalLocationReached: d,
initialTopMostItemIndex: c,
scrolledToInitialItem: l
};
}, it(Lt, It, fe, At), { singleton: true });
function Xn(t, e) {
return Math.abs(t - e) < 1.01;
}
var ue = "up";
var ne = "down";
var Uo = "none";
var Ko = {
atBottom: false,
notAtBottomBecause: "NOT_SHOWING_LAST_ITEM",
state: {
offsetBottom: 0,
scrollHeight: 0,
scrollTop: 0,
viewportHeight: 0
}
};
var jo = 0;
var pe = Z(([{ footerHeight: t, headerHeight: e, scrollBy: n, scrollContainerState: o, scrollTop: r, viewportHeight: s }]) => {
const i = w(false), l = w(true), c = K(), d = K(), f = w(4), v = w(jo), m = ht(S(Ve(S(P(r), Ut(1), Bt(true)), S(P(r), Ut(1), Bt(false), hn(100))), rt()), false), g = ht(S(Ve(S(n, Bt(true)), S(n, Bt(false), hn(200))), rt()), false);
L(S(dt(P(r), P(v)), B(([a, T]) => a <= T), rt()), l), L(S(l, zt(50)), d);
const y = Ct(S(dt(o, P(s), P(e), P(t), P(f)), kt((a, [{ scrollHeight: T, scrollTop: x }, C, k, u, p]) => {
const b = x + C - T > -p, z = {
scrollHeight: T,
scrollTop: x,
viewportHeight: C
};
if (b) {
let O, V;
return x > a.state.scrollTop ? (O = "SCROLLED_DOWN", V = a.state.scrollTop - x) : (O = "SIZE_DECREASED", V = a.state.scrollTop - x || a.scrollTopDelta), {
atBottom: true,
atBottomBecause: O,
scrollTopDelta: V,
state: z
};
}
let F;
return z.scrollHeight > a.state.scrollHeight ? F = "SIZE_INCREASED" : C < a.state.viewportHeight ? F = "VIEWPORT_HEIGHT_DECREASING" : x < a.state.scrollTop ? F = "SCROLLING_UPWARDS" : F = "NOT_FULLY_SCROLLED_TO_LAST_ITEM_BOTTOM", {
atBottom: false,
notAtBottomBecause: F,
state: z
};
}, Ko), rt((a, T) => a !== void 0 && a.atBottom === T.atBottom))), R = ht(S(o, kt((a, { scrollHeight: T, scrollTop: x, viewportHeight: C }) => {
if (!Xn(a.scrollHeight, T)) {
const k = T - (x + C) < 1;
return a.scrollTop !== x && k ? {
changed: true,
jump: a.scrollTop - x,
scrollHeight: T,
scrollTop: x
} : {
changed: true,
jump: 0,
scrollHeight: T,
scrollTop: x
};
}
return {
changed: false,
jump: 0,
scrollHeight: T,
scrollTop: x
};
}, {
changed: false,
jump: 0,
scrollHeight: 0,
scrollTop: 0
}), W((a) => a.changed), B((a) => a.jump)), 0);
L(S(y, B((a) => a.atBottom)), i), L(S(i, zt(50)), c);
const I = w(ne);
L(S(o, B(({ scrollTop: a }) => a), rt(), kt((a, T) => ot(g) ? {
direction: a.direction,
prevScrollTop: T
} : {
direction: T < a.prevScrollTop ? ue : ne,
prevScrollTop: T
}, {
direction: ne,
prevScrollTop: 0
}), B((a) => a.direction)), I), L(S(o, zt(50), Bt(Uo)), I);
const h = w(0);
return L(S(m, W((a) => !a), Bt(0)), h), L(S(r, zt(100), D(m), W(([a, T]) => T), kt(([a, T], [x]) => [T, x], [0, 0]), B(([a, T]) => T - a)), h), {
atBottomState: y,
atBottomStateChange: c,
atBottomThreshold: f,
atTopStateChange: d,
atTopThreshold: v,
isAtBottom: i,
isAtTop: l,
isScrolling: m,
lastJumpDueToItemResize: R,
scrollDirection: I,
scrollVelocity: h
};
}, it(It));
var ae = "top";
var de = "bottom";
var vn = "none";
function Tn(t, e, n) {
return typeof t == "number" ? n === ue && e === ae || n === ne && e === de ? t : 0 : n === ue ? e === ae ? t.main : t.reverse : e === de ? t.main : t.reverse;
}
function Cn(t, e) {
return typeof t == "number" ? t : t[e] ?? 0;
}
var Ze = Z(([{ deviation: t, fixedHeaderHeight: e, headerHeight: n, scrollTop: o, viewportHeight: r }]) => {
const s = K(), i = w(0), l = w(0), c = w(0);
return {
increaseViewportBy: l,
listBoundary: s,
overscan: c,
topListHeight: i,
visibleRange: ht(S(dt(P(o), P(r), P(n), P(s, le), P(c), P(i), P(e), P(t), P(l)), B(([f, v, m, [g, y], R, I, h, a, T]) => {
const x = f - a, C = I + h, k = Math.max(m - x, 0);
let u = vn;
const p = Cn(T, ae), b = Cn(T, de);
return g -= a, g += m + h, y += m + h, y -= a, g > f + C - p && (u = ue), y < f - k + v + b && (u = ne), u !== vn ? [Math.max(x - m - Tn(R, ae, u) - p, 0), x - k - h + v + Tn(R, de, u) + b] : null;
}), W((f) => f !== null), rt(le)), [0, 0])
};
}, it(It), { singleton: true });
function qo(t, e, n) {
if (Re(e)) {
const o = jn(t, e);
return [{
index: Rt(e.groupOffsetTree, o)[0],
offset: 0,
size: 0
}, {
data: n?.[0],
index: o,
offset: 0,
size: 0
}];
}
return [{
data: n?.[0],
index: t,
offset: 0,
size: 0
}];
}
var Oe = {
bottom: 0,
firstItemIndex: 0,
items: [],
offsetBottom: 0,
offsetTop: 0,
top: 0,
topItems: [],
topListHeight: 0,
totalCount: 0
};
function ve(t, e, n, o, r, s) {
const { lastIndex: i, lastOffset: l, lastSize: c } = r;
let d = 0, f = 0;
if (t.length > 0) {
d = t[0].offset;
const R = t[t.length - 1];
f = R.offset + R.size;
}
const v = n - i, m = l + v * c + (v - 1) * o, g = d, y = m - f;
return {
bottom: f,
firstItemIndex: s,
items: wn(t, r, s),
offsetBottom: y,
offsetTop: d,
top: g,
topItems: wn(e, r, s),
topListHeight: e.reduce((R, I) => I.size + R, 0),
totalCount: n
};
}
function Jn(t, e, n, o, r, s) {
let i = 0;
if (n.groupIndices.length > 0) for (const f of n.groupIndices) {
if (f - i >= t) break;
i++;
}
const l = t + i, c = Ye(e, l);
return ve(Array.from({ length: l }).map((f, v) => ({
data: s[v + c],
index: v + c,
offset: 0,
size: 0
})), [], l, r, n, o);
}
function wn(t, e, n) {
if (t.length === 0) return [];
if (!Re(e)) return t.map((d) => ({
...d,
index: d.index + n,
originalIndex: d.index
}));
const o = t[0].index, r = t[t.length - 1].index, s = [], i = Zt(e.groupOffsetTree, o, r);
let l, c = 0;
for (const d of t) {
(!l || l.end < d.index) && (l = i.shift(), c = e.groupIndices.indexOf(l.start));
let f;
d.index === l.start ? f = {
index: c,
type: "group"
} : f = {
groupIndex: c,
index: d.index - (c + 1) + n
}, s.push({
...f,
data: d.data,
offset: d.offset,
originalIndex: d.index,
size: d.size
});
}
return s;
}
function yn(t, e) {
return t === void 0 ? 0 : typeof t == "number" ? t : t[e] ?? 0;
}
var Kt = Z(([{ data: t, firstItemIndex: e, gap: n, sizes: o, totalCount: r }, s, { listBoundary: i, topListHeight: l, visibleRange: c }, { initialTopMostItemIndex: d, scrolledToInitialItem: f }, { topListHeight: v }, m, { didMount: g }, { recalcInProgress: y }]) => {
const R = w([]), I = w(0), h = K(), a = w(0);
L(s.topItemsIndexes, R);
const T = ht(S(dt(g, y, P(c, le), P(r), P(o), P(d), f, P(R), P(e), P(n), P(a), t), W(([u, p, , b, , , , , , , , z]) => {
const F = z !== void 0 && z.length !== b;
return u && !p && !F;
}), B(([, , [u, p], b, z, F, O, V, U, q, $, j]) => {
const X = z, { offsetTree: gt, sizeTree: ct } = X, St = ot(I);
if (b === 0) return {
...Oe,
totalCount: b
};
if (u === 0 && p === 0) return St === 0 ? {
...Oe,
totalCount: b
} : Jn(St, F, z, U, q, j || []);
if (tt(ct)) return St > 0 ? null : ve(qo(Ye(F, b), X, j), [], b, q, X, U);
const Ft = [];
if (V.length > 0) {
const N = V[0], Y = V[V.length - 1];
let lt = 0;
for (const et of Zt(ct, N, Y)) {
const Q = et.value, ut = Math.max(et.start, N), xt = Math.min(et.end, Y);
for (let at = ut; at <= xt; at++) Ft.push({
data: j?.[at],
index: at,
offset: lt,
size: Q
}), lt += Q;
}
}
if (!O) return ve([], Ft, b, q, X, U);
const pt = V.length > 0 ? V[V.length - 1] + 1 : 0, jt = Po(gt, u, p, pt);
if (jt.length === 0) return null;
const Qt = b - 1, Et = ye([], (N) => {
for (const Y of jt) {
const lt = Y.value;
let et = lt.offset, Q = Y.start;
const ut = lt.size;
if (lt.offset < u) {
Q += Math.floor((u - lt.offset + q) / (ut + q));
const at = Q - Y.start;
et += at * ut + at * q;
}
Q < pt && (et += (pt - Q) * ut, Q = pt);
const xt = Math.min(Y.end, Qt);
for (let at = Q; at <= xt && !(et >= p); at++) N.push({
data: j?.[at],
index: at,
offset: et,
size: ut
}), et += ut + q;
}
}), te = yn($, ae), H = yn($, de);
if (Et.length > 0 && (te > 0 || H > 0)) {
const N = Et[0], Y = Et[Et.length - 1];
if (te > 0 && N.index > pt) {
const lt = Math.min(te, N.index - pt), et = [];
let Q = N.offset;
for (let ut = N.index - 1; ut >= N.index - lt; ut--) {
const at = Zt(ct, ut, ut)[0]?.value ?? N.size;
Q -= at + q, et.unshift({
data: j?.[ut],
index: ut,
offset: Q,
size: at
});
}
Et.unshift(...et);
}
if (H > 0 && Y.index < Qt) {
const lt = Math.min(H, Qt - Y.index);
let et = Y.offset + Y.size + q;
for (let Q = Y.index + 1; Q <= Y.index + lt; Q++) {
const xt = Zt(ct, Q, Q)[0]?.value ?? Y.size;
Et.push({
data: j?.[Q],
index: Q,
offset: et,
size: xt
}), et += xt + q;
}
}
}
return ve(Et, Ft, b, q, X, U);
}), W((u) => u !== null), rt()), Oe);
L(S(t, W(Ne), B((u) => u?.length)), r), L(S(T, B((u) => u.topListHeight)), v), L(v, l), L(S(T, B((u) => [u.top, u.bottom])), i), L(S(T, B((u) => u.items)), h);
const x = Ct(S(T, W(({ items: u }) => u.length > 0), D(r, t), W(([{ items: u }, p]) => u[u.length - 1].originalIndex === p - 1), B(([, u, p]) => [u - 1, p]), rt(le), B(([u]) => u))), C = Ct(S(T, zt(200), W(({ items: u, topItems: p }) => u.length > 0 && u[0].originalIndex === p.length), B(({ items: u }) => u[0].index), rt()));
return {
endReached: x,
initialItemCount: I,
itemsRendered: h,
listState: T,
minOverscanItemCount: a,
rangeChanged: Ct(S(T, W(({ items: u }) => u.length > 0), B(({ items: u }) => {
let p = 0, b = u.length - 1;
for (; u[p].type === "group" && p < b;) p++;
for (; u[b].type === "group" && b > p;) b--;
return {
endIndex: u[b].index,
startIndex: u[p].index
};
}), rt(Un))),
startReached: C,
topItemsIndexes: R,
...m
};
}, it(Lt, Yn, Ze, me, fe, pe, At, Ke), { singleton: true });
var Qn = Z(([{ fixedFooterHeight: t, fixedHeaderHeight: e, footerHeight: n, headerHeight: o }, { listState: r }]) => {
const s = K(), i = ht(S(dt(n, t, o, e, r), B(([l, c, d, f, v]) => l + c + d + f + v.offsetBottom + v.bottom)), 0);
return L(P(i), s), {
totalListHeight: i,
totalListHeightChanged: s
};
}, it(It, Kt), { singleton: true });
var Yo = Z(([{ viewportHeight: t }, { totalListHeight: e }]) => {
const n = w(false);
return {
alignToBottom: n,
paddingTopAddition: ht(S(dt(n, t, e), W(([r]) => r), B(([, r, s]) => Math.max(0, r - s)), zt(0), rt()), 0)
};
}, it(It, Qn), { singleton: true });
var to = Z(() => ({ context: w(null) }));
var Zo = ({ itemBottom: t, itemTop: e, locationParams: { align: n, behavior: o, ...r }, viewportBottom: s, viewportTop: i }) => e < i ? {
...r,
align: n ?? "start",
...o === void 0 ? {} : { behavior: o }
} : t > s ? {
...r,
align: n ?? "end",
...o === void 0 ? {} : { behavior: o }
} : null;
var eo = Z(([{ gap: t, sizes: e, totalCount: n }, { fixedFooterHeight: o, fixedHeaderHeight: r, headerHeight: s, scrollingInProgress: i, scrollTop: l, viewportHeight: c }, { scrollToIndex: d }]) => {
const f = K();
return L(S(f, D(e, c, n, s, r, o, l), D(t), B(([[v, m, g, y, R, I, h, a], T]) => {
const { calculateViewLocation: x = Zo, done: C, ...k } = v, u = qn(v, m, y - 1), p = ce(u, m.offsetTree, T) + R + I, b = p + Rt(m.sizeTree, u)[1], z = a + I, O = x({
itemBottom: b,
itemTop: p,
locationParams: k,
viewportBottom: a + g - h,
viewportTop: z
});
return O === null ? C?.() : C && bt(S(i, W((V) => !V), Ut(ot(i) ? 1 : 2)), C), O;
}), W((v) => v !== null)), d), { scrollIntoView: f };
}, it(Lt, It, fe, Kt, Gt), { singleton: true });
function bn(t) {
return t === false ? false : t === "smooth" ? "smooth" : "auto";
}
var Xo = (t, e) => typeof t == "function" ? bn(t(e)) : e && bn(t);
var Jo = Z(([{ listRefresh: t, totalCount: e, fixedItemSize: n, data: o }, { atBottomState: r, isAtBottom: s }, { scrollToIndex: i }, { scrolledToInitialItem: l }, { didMount: c, propsReady: d }, { log: f }, { scrollingInProgress: v }, { context: m }, { scrollIntoView: g }]) => {
const y = w(false), R = K();
let I = null;
function h(C) {
M(i, {
align: "end",
behavior: C,
index: "LAST"
});
}
J(S(dt(S(P(e), Ut(1)), c), D(P(y), s, l, v), B(([[C, k], u, p, b, z]) => {
let F = k && b, O = "auto";
return F && (O = Xo(u, p || z), F = F && O !== false), {
followOutputBehavior: O,
shouldFollow: F,
totalCount: C
};
}), W(({ shouldFollow: C }) => C)), ({ followOutputBehavior: C, totalCount: k }) => {
I !== null && (I(), I = null), ot(n) === void 0 ? I = bt(t, () => {
ot(f)("following output to ", { totalCount: k }, mt.DEBUG), h(C), I = null;
}) : requestAnimationFrame(() => {
ot(f)("following output to ", { totalCount: k }, mt.DEBUG), h(C);
});
});
function a(C) {
const k = bt(r, (u) => {
C && !u.atBottom && u.notAtBottomBecause === "SIZE_INCREASED" && I === null && (ot(f)("scrolling to bottom due to increased size", {}, mt.DEBUG), h("auto"));
});
setTimeout(k, 100);
}
J(S(dt(P(y), e, d), W(([C, , k]) => C !== false && k), kt(({ value: C }, [, k]) => ({
refreshed: C === k,
value: k
}), {
refreshed: false,
value: 0
}), W(({ refreshed: C }) => C), D(y, e)), ([, C]) => {
ot(l) && a(C !== false);
}), J(R, () => {
a(ot(y) !== false);
}), J(dt(P(y), r), ([C, k]) => {
C !== false && !k.atBottom && k.notAtBottomBecause === "VIEWPORT_HEIGHT_DECREASING" && h("auto");
});
const T = w(null), x = K();
return L(Ve(S(P(o), B((C) => C?.length ?? 0)), S(P(e))), x), J(S(dt(S(x, Ut(1)), c), D(P(T), l, v, m), B(([[C, k], u, p, b, z]) => k && p && u?.({
context: z,
totalCount: C,
scrollingInProgress: b
})), W((C) => !!C), zt(0)), (C) => {
I !== null && (I(), I = null), ot(n) === void 0 ? I = bt(t, () => {
ot(f)("scrolling into view", {}), M(g, C), I = null;
}) : requestAnimationFrame(() => {
ot(f)("scrolling into view", {}), M(g, C);
});
}), {
autoscrollToBottom: R,
followOutput: y,
scrollIntoViewOnChange: T
};
}, it(Lt, pe, fe, me, At, Gt, It, to, eo));
var Qo = Z(([{ data: t, firstItemIndex: e, gap: n, sizes: o }, { initialTopMostItemIndex: r }, { initialItemCount: s, listState: i }, { didMount: l }]) => (L(S(l, D(s), W(([, c]) => c !== 0), D(r, o, e, n, t), B(([[, c], d, f, v, m, g = []]) => Jn(c, d, f, v, m, g))), i), {}), it(Lt, me, Kt, At), { singleton: true });
var tr = Z(([{ didMount: t }, { scrollTo: e }, { listState: n }]) => {
const o = w(0);
return J(S(t, D(o), W(([, r]) => r !== 0), B(([, r]) => ({ top: r }))), (r) => {
bt(S(n, Ut(1), W((s) => s.items.length > 1)), () => {
requestAnimationFrame(() => {
M(e, r);
});
});
}), { initialScrollTop: o };
}, it(At, It, Kt), { singleton: true });
var no = Z(([{ scrollVelocity: t }]) => {
const e = w(false), n = K(), o = w(false);
return L(S(t, D(o, e, n), W(([r, s]) => s !== false && s !== void 0), B(([r, s, i, l]) => {
const { enter: c, exit: d } = s;
if (i) {
if (d(r, l)) return false;
} else if (c(r, l)) return true;
return i;
}), rt()), e), J(S(dt(e, t, n), D(o)), ([[r, s, i], l]) => {
r && l !== false && l !== void 0 && l.change && l.change(s, i);
}), {
isSeeking: e,
scrollSeekConfiguration: o,
scrollSeekRangeChanged: n,
scrollVelocity: t
};
}, it(pe), { singleton: true });
var Xe = Z(([{ scrollContainerState: t, scrollTo: e }]) => {
const n = K(), o = K(), r = K(), s = w(false), i = w(void 0);
return L(S(dt(n, o), B(([{ scrollTop: l, viewportHeight: c }, { offsetTop: d, listHeight: f }]) => ({
scrollHeight: f,
scrollTop: Math.max(0, l - d),
viewportHeight: c
}))), t), L(S(e, D(o), B(([l, { offsetTop: c }]) => ({
...l,
top: l.top + c
}))), r), {
customScrollParent: i,
useWindowScroll: s,
windowScrollContainerState: n,
windowScrollTo: r,
windowViewportRect: o
};
}, it(It));
var er = Z(([{ sizeRanges: t, sizes: e }, { headerHeight: n, scrollTop: o }, { initialTopMostItemIndex: r }, { didMount: s }, { useWindowScroll: i, windowScrollContainerState: l, windowViewportRect: c }]) => {
const d = K(), f = w(void 0), v = w(null), m = w(null);
return L(l, v), L(c, m), J(S(d, D(e, o, i, v, m, n)), ([g, y, R, I, h, a, T]) => {
const x = Go(y.sizeTree);
I && h !== null && a !== null && (R = h.scrollTop - a.offsetTop), R -= T, g({
ranges: x,
scrollTop: R
});
}), L(S(f, W(Ne), B(nr)), r), L(S(s, D(f), W(([, g]) => g !== void 0), rt(), B(([, g]) => g.ranges)), t), {
getState: d,
restoreStateFrom: f
};
}, it(Lt, It, me, At, Xe));
function nr(t) {
return {
align: "start",
index: 0,
offset: t.scrollTop
};
}
var or = Z(([{ topItemsIndexes: t }]) => {
const e = w(0);
return L(S(e, W((n) => n >= 0), B((n) => Array.from({ length: n }).map((o, r) => r))), t), { topItemCount: e };
}, it(Kt));
function oo(t) {
let e = false, n;
return (() => (e || (e = true, n = t()), n));
}
var rr = oo(() => /iP(ad|od|hone)/i.test(navigator.userAgent) && /WebKit/i.test(navigator.userAgent));
var ro = Z(([{ data: t, defaultItemSize: e, firstItemIndex: n, fixedItemSize: o, fixedGroupSize: r, gap: s, groupIndices: i, heightEstimates: l, itemSize: c, sizeRanges: d, sizes: f, statefulTotalCount: v, totalCount: m, trackItemSizes: g }, { initialItemFinalLocationReached: y, initialTopMostItemIndex: R, scrolledToInitialItem: I }, h, a, T, x, { scrollToIndex: C }, k, { topItemCount: u }, { groupCounts: p }, b]) => {
const { listState: z, minOverscanItemCount: F, topItemsIndexes: O, rangeChanged: V, ...U } = x;
return L(V, b.scrollSeekRangeChanged), L(S(dt(b.windowViewportRect, h.headerHeight, h.fixedHeaderHeight), B(([q, $, j]) => Math.max($ + j + 1, q.visibleHeight))), h.viewportHeight), {
data: t,
defaultItemHeight: e,
firstItemIndex: n,
fixedItemHeight: o,
fixedGroupHeight: r,
gap: s,
groupCounts: p,
heightEstimates: l,
initialItemFinalLocationReached: y,
initialTopMostItemIndex: R,
scrolledToInitialItem: I,
sizeRanges: d,
topItemCount: u,
topItemsIndexes: O,
totalCount: m,
...T,
groupIndices: i,
itemSize: c,
listState: z,
minOverscanItemCount: F,
scrollToIndex: C,
statefulTotalCount: v,
trackItemSizes: g,
rangeChanged: V,
...U,
...b,
...h,
sizes: f,
...a
};
}, it(Lt, me, It, er, Jo, Kt, fe, Z(([{ deviation: t, deviationCommitted: e, scrollBy: n, scrollingInProgress: o, scrollTop: r }, { isAtBottom: s, isScrolling: i, lastJumpDueToItemResize: l, scrollDirection: c }, { listState: d }, { beforeUnshiftWith: f, gap: v, shiftWithOffset: m, sizes: g }, { log: y }, { recalcInProgress: R }]) => {
const I = Ct(S(d, D(l), kt(([, x, C, k], [{ bottom: u, items: p, offsetBottom: b, totalCount: z }, F]) => {
const O = u + b;
let V = 0;
return C === z && x.length > 0 && p.length > 0 && (p[0].originalIndex === 0 && x[0].originalIndex === 0 || (V = O - k, V !== 0 && (V += F))), [
V,
p,
z,
O
];
}, [
0,
[],
0,
0
]), W(([x]) => x !== 0), D(r, c, o, s, y, R), W(([, x, C, k, , , u]) => !u && !k && x !== 0 && C === ue), B(([[x], , , , , C]) => (C("Upward scrolling compensation", { amount: x }, mt.DEBUG), x))));
function h(x) {
x > 0 ? (M(n, {
behavior: "auto",
top: -x
}), M(t, 0)) : (M(t, 0), M(n, {
behavior: "auto",
top: -x
}));
}
J(S(I, D(t, i)), ([x, C, k]) => {
k && rr() ? M(t, C - x) : h(-x);
}), J(S(dt(ht(i, false), t, R), W(([x, C, k]) => !x && !k && C !== 0), B(([x, C]) => C), zt(1)), h), L(S(m, B((x) => ({ top: -x }))), n);
let a = null;
function T() {
if (a === null) return;
const { offset: x } = a;
a = null, M(n, { top: x }), requestAnimationFrame(() => {
M(t, 0), M(R, false);
});
}
return J(e, (x) => {
a === null || !a.acknowledgeable || x === a.offset && T();
}), J(S(f, D(g, v), B(([x, { groupIndices: C, lastSize: k, sizeTree: u }, p]) => {
function b(U) {
return U * (k + p);
}
if (C.length === 0) return b(x);
let z = 0;
const F = ie(u, 0);
let O = 0, V = 0;
for (; O < x;) {
O++, z += F;
let U = C.length === V + 1 ? 1 / 0 : C[V + 1] - C[V] - 1;
O + U > x && (z -= F, U = x - O + 1), O += U, z += b(U), V++;
}
return z;
})), (x) => {
a = {
acknowledgeable: ot(t) !== x,
offset: x
}, M(t, x), requestAnimationFrame(T);
}), { deviation: t };
}, it(It, pe, Kt, Lt, Gt, Ke)), or, Yn, Z(([t, e, n, o, r, s, i, l, c, d, f]) => ({
...t,
...e,
...n,
...o,
...r,
...s,
...i,
...l,
...c,
...d,
...f
}), it(Ze, Qo, At, no, Qn, tr, Yo, Xe, eo, Gt, to))));
function lr(t, e) {
const n = {}, o = {};
let r = 0;
const s = t.length;
for (; r < s;) o[t[r]] = 1, r += 1;
for (const i in e) Object.hasOwn(o, i) || (n[i] = e[i]);
return n;
}
var Ie = typeof document > "u" ? import_react.useEffect : import_react.useLayoutEffect;
function Je(t, e, n) {
const o = Object.keys(e.required || {}), r = Object.keys(e.optional || {}), s = Object.keys(e.methods || {}), i = Object.keys(e.events || {}), l = import_react.createContext({});
function c(h, a) {
h.propsReady !== void 0 && M(h.propsReady, false);
for (const T of o) {
const x = h[e.required[T]];
M(x, a[T]);
}
for (const T of r) if (T in a) {
const x = h[e.optional[T]];
M(x, a[T]);
}
h.propsReady !== void 0 && M(h.propsReady, true);
}
function d(h) {
return s.reduce((a, T) => (a[T] = (x) => {
const C = h[e.methods[T]];
M(C, x);
}, a), {});
}
function f(h) {
return i.reduce((a, T) => (a[T] = Co(h[e.events[T]]), a), {});
}
const v = import_react.forwardRef(function(a, T) {
const { children: x, ...C } = a, [k] = import_react.useState(() => ye(bo(t), (b) => {
c(b, C);
})), [u] = import_react.useState(pn(f, k));
Ie(() => {
for (const b of i) b in C && J(u[b], C[b]);
return () => {
Object.values(u).map(De);
};
}, [
C,
u,
k
]), Ie(() => {
c(k, C);
}), import_react.useImperativeHandle(T, mn(d(k)));
const p = n;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(l.Provider, {
value: k,
children: n === void 0 ? x : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(p, {
...lr([
...o,
...r,
...i
], C),
children: x
})
});
}), m = (h) => {
const a = import_react.useContext(l);
return import_react.useCallback((T) => {
M(a[h], T);
}, [a, h]);
}, g = (h) => {
const T = import_react.useContext(l)[h], x = import_react.useCallback((C) => J(T, C), [T]);
return import_react.useSyncExternalStore(x, () => ot(T), () => ot(T));
}, y = (h) => {
const T = import_react.useContext(l)[h], [x, C] = import_react.useState(pn(ot, T));
return Ie(() => J(T, (k) => {
k !== x && C(mn(k));
}), [T, x]), x;
};
return {
Component: v,
useEmitter: (h, a) => {
const x = import_react.useContext(l)[h];
Ie(() => J(x, a), [a, x]);
},
useEmitterValue: parseInt("19.3.0", 10) >= 18 ? g : y,
usePublisher: m
};
}
var He = import_react.createContext(void 0);
var so = import_react.createContext(void 0);
var Le = "-webkit-sticky";
var Rn = "sticky";
var Qe = oo(() => {
if (typeof document > "u") return Rn;
const t = document.createElement("div");
return t.style.position = Le, t.style.position === Le ? Le : Rn;
});
function ze(t) {
return "self" in t;
}
function cr(t) {
return "body" in t;
}
function io(t, e, n, o = Jt, r, s) {
const i = import_react.useRef(null), l = import_react.useRef(null), c = import_react.useRef(null), d = import_react.useCallback((m) => {
let g, y, R;
const I = m.target;
if (cr(I) || ze(I)) {
const a = ze(I) ? I : I.defaultView;
R = s === true ? _t(a, a.scrollX) : a.scrollY, g = s === true ? a.document.documentElement.scrollWidth : a.document.documentElement.scrollHeight, y = s === true ? a.innerWidth : a.innerHeight;
} else R = s === true ? _t(I, I.scrollLeft) : I.scrollTop, g = s === true ? I.scrollWidth : I.scrollHeight, y = s === true ? I.offsetWidth : I.offsetHeight;
const h = () => {
t({
scrollHeight: g,
scrollTop: Math.max(R, 0),
viewportHeight: y
});
};
m.suppressFlushSync === true ? h() : import_react_dom.flushSync(h), l.current !== null && (R === l.current || R <= 0 || R === g - y) && (l.current = null, e(true), c.current && (clearTimeout(c.current), c.current = null));
}, [
t,
e,
s
]);
import_react.useEffect(() => {
const m = r ?? i.current;
return o(r ?? i.current), d({
suppressFlushSync: true,
target: m
}), m.addEventListener("scroll", d, { passive: true }), () => {
o(null), m.removeEventListener("scroll", d);
};
}, [
i,
d,
n,
o,
r
]);
function f(m) {
const g = i.current;
if (!g || (s === true ? "offsetWidth" in g && g.offsetWidth === 0 : "offsetHeight" in g && g.offsetHeight === 0)) return;
const y = m.behavior === "smooth";
let R, I, h;
ze(g) ? (I = Math.max(Ht(g.document.documentElement, s === true ? "width" : "height"), s === true ? g.document.documentElement.scrollWidth : g.document.documentElement.scrollHeight), R = s === true ? g.innerWidth : g.innerHeight, h = s === true ? _t(g, g.scrollX) : g.scrollY) : (I = g[s === true ? "scrollWidth" : "scrollHeight"], R = Ht(g, s === true ? "width" : "height"), h = s === true ? _t(g, g.scrollLeft) : g.scrollTop);
const a = I - R;
if (m.top === void 0) {
g.scrollTo(m);
return;
}
const T = Math.ceil(Math.max(Math.min(a, m.top), 0));
if (m.top = T, Xn(R, I) || T === h) {
t({
scrollHeight: I,
scrollTop: h,
viewportHeight: R
}), y && e(true);
return;
}
y ? (l.current = T, c.current && clearTimeout(c.current), c.current = setTimeout(() => {
c.current = null, l.current = null, e(true);
}, 1e3)) : l.current = null, s === true && (m = {
...m.behavior === void 0 ? {} : { behavior: m.behavior },
left: gn(g, T)
}), g.scrollTo(m);
}
return {
scrollByCallback: import_react.useCallback((m) => {
s === true && (m = {
...m.behavior === void 0 ? {} : { behavior: m.behavior },
...m.top === void 0 ? {} : { left: gn(i.current, m.top) }
}), i.current.scrollBy(m);
}, [s]),
scrollerRef: i,
scrollToCallback: f
};
}
function tn(t) {
return t;
}
var ar = /* @__PURE__ */ Z(([t, e]) => ({
...t,
...e
}), it(ro, /* @__PURE__ */ Z(() => {
const t = w((l) => `Item ${l}`), e = w((l) => `Group ${l}`), n = w({}), o = w(tn), r = w("div"), s = w(Jt), i = (l, c = null) => ht(S(n, B((d) => d[l]), rt()), c);
return {
components: n,
computeItemKey: o,
EmptyPlaceholder: i("EmptyPlaceholder"),
FooterComponent: i("Footer"),
GroupComponent: i("Group", "div"),
groupContent: e,
HeaderComponent: i("Header"),
HeaderFooterTag: r,
ItemComponent: i("Item", "div"),
itemContent: t,
ListComponent: i("List", "div"),
ScrollerComponent: i("Scroller", "div"),
scrollerRef: s,
ScrollSeekPlaceholder: i("ScrollSeekPlaceholder"),
TopItemListComponent: i("TopItemList")
};
})));
var dr = ({ height: t }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: t } });
var fr = {
overflowAnchor: "none",
position: Qe(),
zIndex: 1
};
var lo = { overflowAnchor: "none" };
var mr = {
...lo,
display: "inline-block",
height: "100%"
};
var Hn = /* @__PURE__ */ import_react.memo(function({ showTopList: e = false }) {
const n = A("listState"), o = Tt("sizeRanges"), r = A("useWindowScroll"), s = A("customScrollParent"), i = Tt("windowScrollContainerState"), l = Tt("scrollContainerState"), c = s || r ? i : l, d = A("itemContent"), f = A("context"), v = A("groupContent"), m = A("trackItemSizes"), g = A("itemSize"), y = A("log"), R = Tt("gap"), I = A("horizontalDirection"), { callbackRef: h } = An(o, g, m, e ? Jt : c, y, R, s, I, A("skipAnimationFrameInResizeObserver")), [a, T] = import_react.useState(0);
rn("deviation", ($) => {
a !== $ && T($);
});
const x = Tt("deviationCommitted");
be(() => {
x(a);
}, [a, x]);
const C = A("EmptyPlaceholder"), k = A("ScrollSeekPlaceholder") ?? dr, u = A("ListComponent"), p = A("ItemComponent"), b = A("GroupComponent"), z = A("computeItemKey"), F = A("isSeeking"), O = A("groupIndices").length > 0, V = A("alignToBottom"), U = A("initialItemFinalLocationReached"), q = e ? {} : {
boxSizing: "border-box",
...I ? {
display: "inline-block",
height: "100%",
marginInlineStart: a === 0 ? V ? "auto" : 0 : a,
paddingInlineEnd: n.offsetBottom,
paddingInlineStart: n.offsetTop,
whiteSpace: "nowrap"
} : {
marginTop: a === 0 ? V ? "auto" : 0 : a,
paddingBottom: n.offsetBottom,
paddingTop: n.offsetTop
},
...U ? {} : { visibility: "hidden" }
};
return !e && n.totalCount === 0 && C !== null && C !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(C, { ...st(C, f) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(u, {
...st(u, f),
"data-testid": e ? "virtuoso-top-item-list" : "virtuoso-item-list",
ref: h,
style: q,
children: (e ? n.topItems : n.items).map(($) => {
const j = $.originalIndex, X = z(j + n.firstItemIndex, $.data, f);
return F ? /* @__PURE__ */ (0, import_react.createElement)(k, {
...st(k, f),
height: $.size,
index: $.index,
key: X,
type: $.type || "item",
...$.type === "group" ? {} : { groupIndex: $.groupIndex }
}) : $.type === "group" ? /* @__PURE__ */ (0, import_react.createElement)(b, {
...st(b, f),
"data-index": j,
"data-item-index": $.index,
"data-known-size": $.size,
key: X,
style: fr
}, v($.index, f)) : /* @__PURE__ */ (0, import_react.createElement)(p, {
...st(p, f),
...co(p, $.data),
"data-index": j,
"data-item-group-index": $.groupIndex,
"data-item-index": $.index,
"data-known-size": $.size,
key: X,
style: I ? mr : lo
}, O ? d($.index, $.groupIndex, $.data, f) : d($.index, $.data, f));
})
});
});
var pr = {
height: "100%",
outline: "none",
overflowY: "auto",
position: "relative",
WebkitOverflowScrolling: "touch"
};
var hr = {
outline: "none",
overflowX: "auto",
position: "relative"
};
var Ee = (t) => ({
height: "100%",
position: "absolute",
top: 0,
width: "100%",
...t ? {
display: "flex",
flexDirection: "column"
} : void 0
});
var en = (t, e, n = 0) => ({
...Ee(t),
position: e ? "relative" : "absolute",
top: e ? -n : 0
});
var gr = {
position: Qe(),
top: 0,
width: "100%",
zIndex: 1
};
function st(t, e) {
if (typeof t != "string") return { context: e };
}
function co(t, e) {
return { item: typeof t == "string" ? void 0 : e };
}
var Ir = /* @__PURE__ */ import_react.memo(function() {
const e = A("HeaderComponent"), n = Tt("headerHeight"), o = A("HeaderFooterTag"), r = Ot(import_react.useMemo(() => (i) => {
n(Ht(i, "height"));
}, [n]), true, A("skipAnimationFrameInResizeObserver")), s = A("context");
return e != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
ref: r,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, { ...st(e, s) })
}) : null;
});
var Sr = /* @__PURE__ */ import_react.memo(function() {
const e = A("FooterComponent"), n = Tt("footerHeight"), o = A("HeaderFooterTag"), r = Ot(import_react.useMemo(() => (i) => {
n(Ht(i, "height"));
}, [n]), true, A("skipAnimationFrameInResizeObserver")), s = A("context");
return e != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
ref: r,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, { ...st(e, s) })
}) : null;
});
function nn({ useEmitter: t, useEmitterValue: e, usePublisher: n }) {
return import_react.memo(function({ children: s, style: i, context: l, ...c }) {
const d = n("scrollContainerState"), f = e("ScrollerComponent"), v = n("smoothScrollTargetReached"), m = e("scrollerRef"), g = e("horizontalDirection") || false, { scrollByCallback: y, scrollerRef: R, scrollToCallback: I } = io(d, v, f, m, void 0, g);
return t("scrollTo", I), t("scrollBy", y), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f, {
"data-testid": "virtuoso-scroller",
"data-virtuoso-scroller": true,
ref: R,
style: {
...g ? hr : pr,
...i
},
tabIndex: 0,
...c,
...st(f, l),
children: s
});
});
}
function on({ useEmitter: t, useEmitterValue: e, usePublisher: n }) {
return import_react.memo(function({ children: s, style: i, context: l, ...c }) {
const d = n("windowScrollContainerState"), f = e("ScrollerComponent"), v = n("smoothScrollTargetReached"), m = e("totalListHeight"), g = e("deviation"), y = e("customScrollParent"), R = import_react.useRef(null), { scrollByCallback: h, scrollerRef: a, scrollToCallback: T } = io(d, v, f, e("scrollerRef"), y);
return be(() => (a.current = y ?? R.current?.ownerDocument.defaultView, () => {
a.current = null;
}), [a, y]), t("windowScrollTo", T), t("scrollBy", h), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f, {
ref: R,
"data-virtuoso-scroller": true,
style: {
position: "relative",
...i,
...m === 0 ? void 0 : { height: m + g }
},
...c,
...st(f, l),
children: s
});
});
}
var xr = ({ children: t }) => {
const e = import_react.useContext(He), n = Tt("viewportHeight"), o = Tt("fixedItemHeight"), r = A("alignToBottom"), s = A("horizontalDirection"), l = Ot(import_react.useMemo(() => re(n, (c) => Ht(c, s ? "width" : "height")), [n, s]), true, A("skipAnimationFrameInResizeObserver"));
return import_react.useEffect(() => {
e && (n(e.viewportHeight), o(e.itemHeight));
}, [
e,
n,
o
]), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
"data-viewport-type": "element",
ref: l,
style: Ee(r),
children: t
});
}, vr = ({ children: t }) => {
const e = import_react.useContext(He), n = Tt("windowViewportRect"), o = Tt("fixedItemHeight"), r = A("customScrollParent"), s = A("useWindowScroll"), i = A("topListHeight"), l = Ue(n, r, A("skipAnimationFrameInResizeObserver")), c = A("alignToBottom");
return import_react.useEffect(() => {
e && (o(e.itemHeight), n({
listHeight: 0,
offsetTop: 0,
visibleHeight: e.viewportHeight,
visibleWidth: 100
}));
}, [
e,
n,
o
]), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
"data-viewport-type": "window",
ref: l,
style: en(c, s, i),
children: t
});
}, Tr = ({ children: t }) => {
const e = A("TopItemListComponent") ?? "div", n = A("headerHeight"), o = {
...gr,
marginTop: `${n}px`
}, r = A("context");
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, {
style: o,
...st(e, r),
children: t
});
}, { Component: uo, useEmitter: rn, useEmitterValue: A, usePublisher: Tt } = /* @__PURE__ */ Je(ar, {
optional: {
restoreStateFrom: "restoreStateFrom",
context: "context",
followOutput: "followOutput",
scrollIntoViewOnChange: "scrollIntoViewOnChange",
itemContent: "itemContent",
groupContent: "groupContent",
overscan: "overscan",
increaseViewportBy: "increaseViewportBy",
minOverscanItemCount: "minOverscanItemCount",
totalCount: "totalCount",
groupCounts: "groupCounts",
topItemCount: "topItemCount",
firstItemIndex: "firstItemIndex",
initialTopMostItemIndex: "initialTopMostItemIndex",
components: "components",
atBottomThreshold: "atBottomThreshold",
atTopThreshold: "atTopThreshold",
computeItemKey: "computeItemKey",
defaultItemHeight: "defaultItemHeight",
fixedGroupHeight: "fixedGroupHeight",
fixedItemHeight: "fixedItemHeight",
heightEstimates: "heightEstimates",
itemSize: "itemSize",
scrollSeekConfiguration: "scrollSeekConfiguration",
headerFooterTag: "HeaderFooterTag",
data: "data",
initialItemCount: "initialItemCount",
initialScrollTop: "initialScrollTop",
alignToBottom: "alignToBottom",
useWindowScroll: "useWindowScroll",
customScrollParent: "customScrollParent",
scrollerRef: "scrollerRef",
logLevel: "logLevel",
horizontalDirection: "horizontalDirection",
skipAnimationFrameInResizeObserver: "skipAnimationFrameInResizeObserver"
},
methods: {
scrollToIndex: "scrollToIndex",
scrollIntoView: "scrollIntoView",
scrollTo: "scrollTo",
scrollBy: "scrollBy",
autoscrollToBottom: "autoscrollToBottom",
getState: "getState"
},
events: {
isScrolling: "isScrolling",
endReached: "endReached",
startReached: "startReached",
rangeChanged: "rangeChanged",
atBottomStateChange: "atBottomStateChange",
atTopStateChange: "atTopStateChange",
totalListHeightChanged: "totalListHeightChanged",
itemsRendered: "itemsRendered",
groupIndices: "groupIndices"
}
}, /* @__PURE__ */ import_react.memo(function(e) {
const n = A("useWindowScroll"), o = A("topItemsIndexes").length > 0, r = A("customScrollParent"), s = A("context");
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(r || n ? yr : wr, {
...e,
context: s,
children: [o && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tr, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Hn, { showTopList: true }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(r || n ? vr : xr, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Ir, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Hn, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sr, {})
] })]
});
})), wr = /* @__PURE__ */ nn({
useEmitter: rn,
useEmitterValue: A,
usePublisher: Tt
}), yr = /* @__PURE__ */ on({
useEmitter: rn,
useEmitterValue: A,
usePublisher: Tt
}), es = uo; /* @__PURE__ */ Z(([t, e]) => ({
...t,
...e
}), it(ro, /* @__PURE__ */ Z(() => {
const t = w((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { children: ["Item $", d] })), e = w(null), n = w((d) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", {
colSpan: 1e3,
children: ["Group ", d]
})), o = w(null), r = w(null), s = w({}), i = w(tn), l = w(Jt), c = (d, f = null) => ht(S(s, B((v) => v[d]), rt()), f);
return {
components: s,
computeItemKey: i,
context: e,
EmptyPlaceholder: c("EmptyPlaceholder"),
FillerRow: c("FillerRow"),
fixedFooterContent: r,
fixedHeaderContent: o,
itemContent: t,
groupContent: n,
ScrollerComponent: c("Scroller", "div"),
scrollerRef: l,
ScrollSeekPlaceholder: c("ScrollSeekPlaceholder"),
TableBodyComponent: c("TableBody", "tbody"),
TableComponent: c("Table", "table"),
TableFooterComponent: c("TableFoot", "tfoot"),
TableHeadComponent: c("TableHead", "thead"),
TableRowComponent: c("TableRow", "tr"),
GroupComponent: c("Group", "tr")
};
}))); ({
position: Qe()}); var kn = {
bottom: 0,
itemHeight: 0,
items: [],
itemWidth: 0,
offsetBottom: 0,
offsetTop: 0,
top: 0
}, Pr = {
bottom: 0,
itemHeight: 0,
items: [{ index: 0 }],
itemWidth: 0,
offsetBottom: 0,
offsetTop: 0,
top: 0
}, { ceil: On, floor: Ce, max: oe, min: Fe, round: Ln } = Math;
function zn(t, e, n) {
return Array.from({ length: e - t + 1 }).map((o, r) => ({
data: n === null ? null : n[r + t],
index: r + t
}));
}
function Wr(t) {
return {
...Pr,
items: t
};
}
function Se(t, e) {
return t !== void 0 && t.width === e.width && t.height === e.height;
}
function Gr(t, e) {
return t !== void 0 && t.column === e.column && t.row === e.row;
}
var Ar = /* @__PURE__ */ Z(([{ increaseViewportBy: t, listBoundary: e, overscan: n, visibleRange: o }, { footerHeight: r, headerHeight: s, scrollBy: i, scrollContainerState: l, scrollTo: c, scrollTop: d, smoothScrollTargetReached: f, viewportHeight: v }, m, g, { didMount: y, propsReady: R }, { customScrollParent: I, useWindowScroll: h, windowScrollContainerState: a, windowScrollTo: T, windowViewportRect: x }, C]) => {
const k = w(0), u = w(0), p = w(kn), b = w({
height: 0,
width: 0
}), z = w({
height: 0,
width: 0
}), F = K(), O = K(), V = w(0), U = w(null), q = w({
column: 0,
row: 0
}), $ = K(), j = K(), X = w(false), gt = w(0), ct = w(true), St = w(false), Ft = w(false);
J(S(y, D(gt), W(([H, N]) => !Me(N))), () => {
M(ct, false);
}), J(S(dt(y, ct, z, b, gt, St), W(([H, N, Y, lt, , et]) => H && !N && Y.height !== 0 && lt.height !== 0 && !et)), ([, , , , H]) => {
if (H === void 0) {
M(ct, true);
return;
}
M(St, true), qe(1, () => {
M(F, H);
}), bt(S(d), () => {
M(e, [0, 0]), M(ct, true);
});
}), L(S(j, W((H) => H != null && H.scrollTop > 0), Bt(0)), u), J(S(y, D(j), W(([, H]) => H != null)), ([, H]) => {
H && (M(b, H.viewport), M(z, H.item), M(q, H.gap), H.scrollTop > 0 && (M(X, true), bt(S(d, Ut(1)), (N) => {
M(X, false);
}), M(c, { top: H.scrollTop })));
}), L(S(b, B(({ height: H }) => H)), v), L(S(dt(P(b, Se), P(z, Se), P(q, (H, N) => H !== void 0 && H.column === N.column && H.row === N.row), P(d)), B(([H, N, Y, lt]) => ({
gap: Y,
item: N,
scrollTop: lt,
viewport: H
}))), $), L(S(dt(P(k), o, P(q, Gr), P(z, Se), P(b, Se), P(U), P(u), P(X), P(ct), P(gt)), W(([, , , , , , , H]) => !H), B(([H, [N, Y], lt, et, Q, ut, xt, , at, Vt]) => {
const { column: Pt, row: ee } = lt, { height: he, width: Be } = et, { width: ln } = Q;
if (xt === 0 && (H === 0 || ln === 0)) return kn;
if (Be === 0) {
const fn = Ye(Vt, H);
return Wr(zn(fn, fn + Math.max(xt - 1, 0), ut));
}
const ge = fo(ln, Be, Pt);
let qt, Mt;
at ? N === 0 && Y === 0 && xt > 0 ? (qt = 0, Mt = xt - 1) : (qt = ge * Ce((N + ee) / (he + ee)), Mt = ge * On((Y + ee) / (he + ee)) - 1, Mt = Fe(H - 1, oe(Mt, ge - 1)), qt = Fe(Mt, oe(0, qt))) : (qt = 0, Mt = -1);
const cn = zn(qt, Mt, ut), { bottom: un, top: an } = Fn(Q, lt, et, cn), dn = On(H / ge);
return {
bottom: un,
itemHeight: he,
items: cn,
itemWidth: Be,
offsetBottom: dn * he + (dn - 1) * ee - un,
offsetTop: an,
top: an
};
})), p), L(S(U, W((H) => H !== null), B((H) => H.length)), k), L(S(dt(b, z, p, q), W(([H, N, { items: Y }]) => Y.length > 0 && N.height !== 0 && H.height !== 0), B(([H, N, { items: Y }, lt]) => {
const { bottom: et, top: Q } = Fn(H, lt, N, Y);
return [Q, et];
}), rt(le)), e);
const pt = w(false);
L(S(d, D(pt), B(([H, N]) => N || H !== 0)), pt);
const jt = Ct(S(dt(p, k), W(([{ items: H }]) => H.length > 0), D(pt), W(([[H, N], Y]) => {
const et = H.items[H.items.length - 1].index === N - 1;
return (Y || H.bottom > 0 && H.itemHeight > 0 && H.offsetBottom === 0 && H.items.length === N) && et;
}), B(([[, H]]) => H - 1), rt())), Qt = Ct(S(P(p), W(({ items: H }) => H.length > 0 && H[0].index === 0), Bt(0), rt())), Et = Ct(S(P(p), D(X), W(([{ items: H }, N]) => H.length > 0 && !N), B(([{ items: H }]) => ({
endIndex: H[H.length - 1].index,
startIndex: H[0].index
})), rt(Un), zt(0)));
L(Et, g.scrollSeekRangeChanged), L(S(F, D(b, z, k, q), B(([H, N, Y, lt, et]) => {
const Q = Zn(H), { align: ut, behavior: xt, offset: at } = Q;
let Vt = Q.index;
Vt === "LAST" && (Vt = lt - 1), Vt = oe(0, Vt, Fe(lt - 1, Vt));
let Pt = _e(N, et, Y, Vt);
return ut === "end" ? Pt = Ln(Pt - N.height + Y.height) : ut === "center" && (Pt = Ln(Pt - N.height / 2 + Y.height / 2)), at !== void 0 && at !== 0 && (Pt += at), {
behavior: xt,
top: Pt
};
})), c);
const te = ht(S(p, B((H) => H.offsetBottom + H.bottom)), 0);
return L(S(x, B((H) => ({
height: H.visibleHeight,
width: H.visibleWidth
}))), b), {
customScrollParent: I,
data: U,
deviation: V,
footerHeight: r,
gap: q,
headerHeight: s,
increaseViewportBy: t,
initialItemCount: u,
itemDimensions: z,
overscan: n,
restoreStateFrom: j,
scrollBy: i,
scrollContainerState: l,
scrollHeight: O,
scrollTo: c,
scrollToIndex: F,
scrollTop: d,
smoothScrollTargetReached: f,
totalCount: k,
useWindowScroll: h,
viewportDimensions: b,
windowScrollContainerState: a,
windowScrollTo: T,
windowViewportRect: x,
...g,
gridState: p,
horizontalDirection: Ft,
initialTopMostItemIndex: gt,
totalListHeight: te,
...m,
endReached: jt,
propsReady: R,
rangeChanged: Et,
startReached: Qt,
stateChanged: $,
stateRestoreInProgress: X,
...C
};
}, it(Ze, It, pe, no, At, Xe, Gt));
function fo(t, e, n) {
return oe(1, Ce((t + n) / (Ce(e) + n)));
}
function Fn(t, e, n, o) {
const { height: r } = n;
if (r === void 0 || o.length === 0) return {
bottom: 0,
top: 0
};
const s = _e(t, e, n, o[0].index);
return {
bottom: _e(t, e, n, o[o.length - 1].index) + r,
top: s
};
}
function _e(t, e, n, o) {
const s = Ce(o / fo(t.width, n.width, e.column)), i = s * n.height + oe(0, s - 1) * e.row;
return i > 0 ? i + e.row : i;
}
var _r = /* @__PURE__ */ Z(([t, e]) => ({
...t,
...e
}), it(Ar, /* @__PURE__ */ Z(() => {
const t = w((v) => `Item ${v}`), e = w({}), n = w(null), o = w("virtuoso-grid-item"), r = w("virtuoso-grid-list"), s = w(tn), i = w("div"), l = w(Jt), c = (v, m = null) => ht(S(e, B((g) => g[v]), rt()), m), d = w(false), f = w(false);
return L(P(f), d), {
components: e,
computeItemKey: s,
context: n,
FooterComponent: c("Footer"),
HeaderComponent: c("Header"),
headerFooterTag: i,
itemClassName: o,
ItemComponent: c("Item", "div"),
itemContent: t,
listClassName: r,
ListComponent: c("List", "div"),
readyStateChanged: d,
reportReadyState: f,
ScrollerComponent: c("Scroller", "div"),
scrollerRef: l,
ScrollSeekPlaceholder: c("ScrollSeekPlaceholder", "div")
};
}))), Nr = /* @__PURE__ */ import_react.memo(function() {
const e = nt("gridState"), n = nt("listClassName"), o = nt("itemClassName"), r = nt("itemContent"), s = nt("computeItemKey"), i = nt("isSeeking"), l = yt("scrollHeight"), c = nt("ItemComponent"), d = nt("ListComponent"), f = nt("ScrollSeekPlaceholder"), v = nt("context"), m = yt("itemDimensions"), g = yt("gap"), y = nt("log"), R = nt("stateRestoreInProgress"), I = yt("reportReadyState"), h = Ot(import_react.useMemo(() => (a) => {
const T = a.parentElement.parentElement.scrollHeight;
l(T);
const x = a.firstChild;
if (x !== null) {
const { height: C, width: k } = x.getBoundingClientRect();
m({
height: C,
width: k
});
}
g({
column: Vn("column-gap", getComputedStyle(a).columnGap, y),
row: Vn("row-gap", getComputedStyle(a).rowGap, y)
});
}, [
l,
m,
g,
y
]), true, false);
return be(() => {
e.itemHeight > 0 && e.itemWidth > 0 && I(true);
}, [e]), R ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(d, {
className: n,
ref: h,
...st(d, v),
"data-testid": "virtuoso-item-list",
style: {
paddingBottom: e.offsetBottom,
paddingTop: e.offsetTop
},
children: e.items.map((a) => {
const T = s(a.index, a.data, v);
return i ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(f, {
...st(f, v),
height: e.itemHeight,
index: a.index,
width: e.itemWidth
}, T) : /* @__PURE__ */ (0, import_react.createElement)(c, {
...st(c, v),
className: o,
"data-index": a.index,
key: T
}, r(a.index, a.data, v));
})
});
}), Dr = import_react.memo(function() {
const e = nt("HeaderComponent"), n = yt("headerHeight"), o = nt("headerFooterTag"), r = Ot(import_react.useMemo(() => (i) => {
n(Ht(i, "height"));
}, [n]), true, false), s = nt("context");
return e != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
ref: r,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, { ...st(e, s) })
}) : null;
}), $r = import_react.memo(function() {
const e = nt("FooterComponent"), n = yt("footerHeight"), o = nt("headerFooterTag"), r = Ot(import_react.useMemo(() => (i) => {
n(Ht(i, "height"));
}, [n]), true, false), s = nt("context");
return e != null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(o, {
ref: r,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(e, { ...st(e, s) })
}) : null;
}), Ur = ({ children: t }) => {
const e = import_react.useContext(so), n = yt("itemDimensions"), o = yt("viewportDimensions"), r = Ot(import_react.useMemo(() => (s) => {
o(s.getBoundingClientRect());
}, [o]), true, false);
return import_react.useEffect(() => {
e && (o({
height: e.viewportHeight,
width: e.viewportWidth
}), n({
height: e.itemHeight,
width: e.itemWidth
}));
}, [
e,
o,
n
]), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
ref: r,
style: Ee(false),
children: t
});
}, Kr = ({ children: t }) => {
const e = import_react.useContext(so), n = yt("windowViewportRect"), o = yt("itemDimensions"), r = nt("customScrollParent"), s = nt("useWindowScroll"), i = Ue(n, r, false);
return import_react.useEffect(() => {
e && (o({
height: e.itemHeight,
width: e.itemWidth
}), n({
listHeight: 0,
offsetTop: 0,
visibleHeight: e.viewportHeight,
visibleWidth: e.viewportWidth
}));
}, [
e,
n,
o
]), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
ref: i,
style: en(false, s),
children: t
});
}, { useEmitter: mo, useEmitterValue: nt, usePublisher: yt } = /* @__PURE__ */ Je(_r, {
optional: {
context: "context",
totalCount: "totalCount",
overscan: "overscan",
itemContent: "itemContent",
components: "components",
computeItemKey: "computeItemKey",
data: "data",
initialItemCount: "initialItemCount",
scrollSeekConfiguration: "scrollSeekConfiguration",
headerFooterTag: "headerFooterTag",
listClassName: "listClassName",
itemClassName: "itemClassName",
useWindowScroll: "useWindowScroll",
customScrollParent: "customScrollParent",
scrollerRef: "scrollerRef",
logLevel: "logLevel",
restoreStateFrom: "restoreStateFrom",
initialTopMostItemIndex: "initialTopMostItemIndex",
increaseViewportBy: "increaseViewportBy"
},
methods: {
scrollTo: "scrollTo",
scrollBy: "scrollBy",
scrollToIndex: "scrollToIndex"
},
events: {
isScrolling: "isScrolling",
endReached: "endReached",
startReached: "startReached",
rangeChanged: "rangeChanged",
atBottomStateChange: "atBottomStateChange",
atTopStateChange: "atTopStateChange",
stateChanged: "stateChanged",
readyStateChanged: "readyStateChanged"
}
}, /* @__PURE__ */ import_react.memo(function({ ...e }) {
const n = nt("useWindowScroll"), o = nt("customScrollParent"), r = o || n ? Zr : Yr, s = o || n ? Kr : Ur, i = nt("context");
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(r, {
...e,
...st(r, i),
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(s, { children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dr, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nr, {}),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)($r, {})
] })
});
})), Yr = /* @__PURE__ */ nn({
useEmitter: mo,
useEmitterValue: nt,
usePublisher: yt
}), Zr = /* @__PURE__ */ on({
useEmitter: mo,
useEmitterValue: nt,
usePublisher: yt
});
function Vn(t, e, n) {
return e !== "normal" && e?.endsWith("px") !== true && n(`${t} was not resolved to pixel value correctly`, e, mt.WARN), e === "normal" ? 0 : parseInt(e ?? "0", 10);
}
/**
* [tag-rec] 组内标签的查看与勾选(B1:换查询单元,不动 view-scope)
*
* 背景:「只看」选择器的粒度是二级组(`view-scope` 最低到 groupScope),面板也只渲染到组名,
* 所以组内 tag 既看不见、也选不了。本模块只给两件纯逻辑:
* 1) 组内标签 chips 的勾选累积(跨组,按 tag id 去重)
* 2) 「只看选中的标签」的查询计划 —— 首个当锚点(**传名**,走文本解析全管线,同 🏷 弹层 #id 口径),
* 其余进 tab2 的扩选暂存位(`pushExpandedIds`)
*
* 语义边界:这条路是**会话级换查询单元**(tab2 临时查询,用完即弃),
* 不写收藏表、不落 view-scope、不构成长期画像 —— 与「查看范围」是两件事。
*/
/** 勾选态切换(跨组累积;按 tag id 去重,保序) */
function toggleTagSelection(prev, entry) {
return prev.some((x) => x.id === entry.id) ? prev.filter((x) => x.id !== entry.id) : [...prev, entry];
}
/** 查询计划:空选 => undefined;否则首个当锚点、其余进扩选 */
function buildTagQueryPlan(picked) {
const [anchor, ...expanded] = picked;
return anchor ? {
anchor,
expanded
} : void 0;
}
function tagPeekRows(group, picked, q = "") {
const on = new Set(picked.map((x) => x.id));
const rows = group.tag_ids.map((e) => ({
id: e.id,
name: e.name,
picked: on.has(e.id)
}));
const k = q.trim().toLowerCase();
if (!k) return rows;
const hit = (name) => name.toLowerCase().includes(k);
return rows.sort((a, b) => Number(hit(b.name)) - Number(hit(a.name)));
}
/** 该组内被选中的个数(行尾徽标「选 N」用) */
function tagPeekPickedCount(group, picked) {
const on = new Set(picked.map((x) => x.id));
return group.tag_ids.filter((e) => on.has(e.id)).length;
}
/**
* 组级搜索命中:组名 或 组内任一标签名 含 q(大小写不敏感;空 q => false)。
* 场景:组名是繁体「學生會也有洞」,但组内挂着简体 tag「学生会也有洞」——
* 组名打不中简体词,tag 变体能。等于借 tag 列表白嫖了繁简/别名覆盖,无需简繁转换库。
*/
function groupTagMatches(group, q) {
const k = q.trim().toLowerCase();
if (!k) return false;
return group.name.toLowerCase().includes(k) || group.tag_ids.some((e) => e.name.toLowerCase().includes(k));
}
var EMPTY_GROUPS = [];
var EMPTY_L1S = [];
/** 收藏快照变化时建一次索引;草稿、搜索和展开不再扫描整棵树查父子归属。 */
function buildTagRecScopeIndex(groups, l1s, themes) {
const themeIds = new Set(themes.map((t) => t.id));
const buckets = [...themes.map((t) => ({
id: t.id,
name: t.name
})), {
id: TAG_REC_VIEW_UNASSIGNED,
name: "未归属"
}];
const l1ById = new Map(l1s.map((l) => [l.id, l]));
const l1sByBucket = /* @__PURE__ */ new Map();
const groupsByL1 = /* @__PURE__ */ new Map();
const groupsByBucket = /* @__PURE__ */ new Map();
const selectableIds = /* @__PURE__ */ new Set();
const available = [];
const savedSelection = {};
for (const l of l1s) {
const bucket = tagRecViewBucketOf(l, themeIds);
const items = l1sByBucket.get(bucket) ?? [];
items.push(l);
l1sByBucket.set(bucket, items);
}
for (const g of groups) {
const parent = g.l1Id ? l1ById.get(g.l1Id) : void 0;
if (g.l1Id) {
const children = groupsByL1.get(g.l1Id) ?? [];
children.push(g);
groupsByL1.set(g.l1Id, children);
}
if (parent) {
const bucket = tagRecViewBucketOf(parent, themeIds);
const items = groupsByBucket.get(bucket) ?? [];
items.push(g);
groupsByBucket.set(bucket, items);
}
const hasConditions = g.tag_ids.length > 0 || hasTagRecCombination(g.combination);
savedSelection[g.id] = !!parent?.enabled && g.enabled && hasConditions;
if (hasConditions && g.weight > 0 && parent && parent.weight > 0) {
selectableIds.add(g.id);
available.push(g);
}
}
return {
themeIds,
buckets,
l1ById,
available,
savedSelection,
selectable: (g) => selectableIds.has(g.id),
l1sOf: (id) => l1sByBucket.get(id) ?? EMPTY_L1S,
childrenOf: (id) => groupsByL1.get(id) ?? EMPTY_GROUPS,
groupsOfBucket: (id) => groupsByBucket.get(id) ?? EMPTY_GROUPS
};
}
/** 展平数据而非 JSX,让动态高度的组内标签也能随所在行虚拟渲染。 */
function buildTagRecScopeRows(index, bucketId, expanded, query) {
const q = query.trim().toLowerCase();
const matches = (name) => name.toLowerCase().includes(q);
const rows = [];
const append = (l1, contextOnly, children, showChildren) => {
rows.push({
kind: "l1",
key: `l1:${l1.id}`,
l1,
contextOnly
});
if (!showChildren) return;
for (const group of children) rows.push({
kind: "group",
key: `group:${group.id}`,
l1,
group
});
if (!children.length) rows.push({
kind: "empty",
key: `empty:${l1.id}`
});
};
if (!q) {
for (const l of index.l1sOf(bucketId)) append(l, false, index.childrenOf(l.id), expanded.has(l.id));
return rows;
}
for (const bucket of index.buckets) {
const themeMatch = matches(bucket.name);
let addedHeader = false;
for (const l of index.l1sOf(bucket.id)) {
const ancestorMatch = themeMatch || matches(l.name);
const children = index.childrenOf(l.id);
const shown = ancestorMatch ? children : children.filter((g) => groupTagMatches(g, q));
if (!ancestorMatch && !shown.length) continue;
if (!addedHeader) {
rows.push({
kind: "theme",
key: `theme:${bucket.id}`,
bucket
});
addedHeader = true;
}
append(l, !ancestorMatch, shown, true);
}
}
return rows;
}
var scopeItemSize = (element, dimension) => element[dimension];
/** 草稿在离开后一次应用;只有保存按钮写入收藏表开关。 */
var TagRecThemeScopePicker = (0, import_react.memo)(function TagRecThemeScopePicker({ onChanged, getPopupContainer }) {
const [open, setOpen] = (0, import_react.useState)(false);
const [query, setQuery] = (0, import_react.useState)("");
const [focusBucket, setFocusBucket] = (0, import_react.useState)();
const [expanded, setExpanded] = (0, import_react.useState)(/* @__PURE__ */ new Set());
/** 二级组行内「标签 chips」展开位(只影响本面板渲染,不写任何 scope) */
const [tagPeek, setTagPeek] = (0, import_react.useState)(/* @__PURE__ */ new Set());
/** 选中的标签(可跨组):点「只看选中的 N 个标签」=> 首个当锚点、其余进扩选位,切 tab2 查询 */
const [pickedTags, setPickedTags] = (0, import_react.useState)([]);
const [draft, setDraft] = (0, import_react.useState)();
const [history, setHistory] = (0, import_react.useState)([]);
const [message, setMessage] = (0, import_react.useState)("");
const draftRef = (0, import_react.useRef)(void 0);
const baselineRef = (0, import_react.useRef)(void 0);
const openRef = (0, import_react.useRef)(false);
const inputRef = (0, import_react.useRef)(null);
const { scope, groupScope } = useSnapshot(viewScopeStore);
const { list: l1List } = useSnapshot(favoritesL1Store);
const { list } = useSnapshot(favoritesStore);
const { list: themeList } = useSnapshot(themesStore);
const { tempEnabledL1Ids, tempEnabledGroupIds } = useSnapshot(tagRecSharedSession);
const l1s = l1List;
const groups = list;
const index = (0, import_react.useMemo)(() => buildTagRecScopeIndex(groups, l1s, themeList), [
groups,
l1s,
themeList
]);
const { themeIds, buckets, l1ById, l1sOf, childrenOf, selectable, available, groupsOfBucket, savedSelection } = index;
const committed = (0, import_react.useMemo)(() => createTagRecGroupScopeDraft(groups, l1s, themeIds, scope, groupScope, tempEnabledL1Ids, tempEnabledGroupIds), [
groups,
l1s,
themeIds,
scope,
groupScope,
tempEnabledL1Ids,
tempEnabledGroupIds
]);
const selected = draft ?? committed;
const selectedOf = (g) => selected[g.id] ?? committed[g.id] ?? false;
const checkboxStates = /* @__PURE__ */ new Map();
const stateOf = (items) => {
const cached = checkboxStates.get(items);
if (cached) return cached;
const eligible = items.filter(selectable);
const count = eligible.filter(selectedOf).length;
const state = {
checked: eligible.length > 0 && count === eligible.length,
indeterminate: count > 0 && count < eligible.length,
count,
total: eligible.length
};
checkboxStates.set(items, state);
return state;
};
const focus = buckets.find((b) => b.id === focusBucket) ?? buckets[0];
const chosen = available.filter(selectedOf);
const chosenParentIds = new Set(chosen.map((g) => g.l1Id));
const chosenParents = l1s.filter((l) => chosenParentIds.has(l.id));
const chosenThemes = buckets.filter((b) => chosenParents.some((l) => tagRecViewBucketOf(l, themeIds) === b.id));
const summary = (0, import_react.useMemo)(() => {
if (groups.every((g) => committed[g.id] === savedSelection[g.id])) return "主题:当前启用";
const committedChosen = available.filter((g) => committed[g.id]);
const parentIds = new Set(committedChosen.map((g) => g.l1Id));
const parents = l1s.filter((l) => parentIds.has(l.id));
return committedChosen.length === 1 ? "只看:" + committedChosen[0].name : parents.length === 1 ? "只看:" + parents[0].name : parents.length ? "主题:自选 " + parents.length + " 个一级组" : "主题:未选";
}, [
groups,
l1s,
available,
committed,
savedSelection
]);
const q = query.trim().toLowerCase();
const matches = (name) => name.toLowerCase().includes(q);
const rows = (0, import_react.useMemo)(() => open ? buildTagRecScopeRows(index, focus.id, expanded, q) : [], [
open,
index,
focus.id,
expanded,
q
]);
const pending = !!draft && groups.some((g) => selectedOf(g) !== baselineRef.current?.[g.id]);
const persistCount = groups.filter((g) => selectable(g) && selectedOf(g) !== g.enabled).length + l1s.filter((l) => childrenOf(l.id).some(selectable) && childrenOf(l.id).some(selectedOf) !== l.enabled).length;
(0, import_react.useEffect)(() => () => {
tagRecSharedSession.scopeEditing = false;
}, []);
const setSelection = (next) => {
const previous = draftRef.current ?? committed;
if (groups.every((g) => previous[g.id] === next[g.id])) return;
setHistory((h) => [...h.slice(-49), previous]);
draftRef.current = next;
setDraft(next);
setMessage("");
};
const toggle = (items, on) => {
const next = { ...draftRef.current ?? committed };
for (const g of items.filter(selectable)) next[g.id] = on;
const parent = q && l1ById.get(items[0]?.l1Id ?? "");
if (parent) {
setFocusBucket(tagRecViewBucketOf(parent, themeIds));
setExpanded((prev) => /* @__PURE__ */ new Set([...prev, parent.id]));
}
setSelection(next);
};
const only = (items) => {
const ids = new Set(items.filter(selectable).map((g) => g.id));
const parent = q && l1ById.get(items[0]?.l1Id ?? "");
if (parent) {
setFocusBucket(tagRecViewBucketOf(parent, themeIds));
setExpanded(/* @__PURE__ */ new Set([parent.id]));
}
setSelection(Object.fromEntries(groups.map((g) => [g.id, ids.has(g.id)])));
};
/** 标签 chip 选中态切换(跨组累积) */
const togglePickedTag = (e) => setPickedTags((prev) => toggleTagSelection(prev, e));
/**
* 「只看选中的 N 个标签」:这不是缩小查看范围(view-scope 粒度是二级组),而是**换查询单元**——
* 复用 tab2 现成语义:首个标签当锚点(传名,走文本解析全管线,同 🏷 弹层 #id 口径),
* 其余进扩选暂存位。会话级、用完即弃,不写收藏表、不落长期画像。
*/
const onlyPickedTags = () => {
const plan = buildTagQueryPlan(pickedTags);
if (!plan) return;
beginTagRecCustomQuery(plan.anchor.name);
if (plan.expanded.length) pushExpandedIds(plan.expanded);
setPickedTags([]);
commit();
videoSourceTabState.value = ETab.TagRecCustom;
};
const commit = () => {
if (!openRef.current) return;
openRef.current = false;
const next = draftRef.current;
let changed = false;
if (next) {
const applied = materializeTagRecGroupScopeDraft(next, favoritesStore.list, favoritesL1Store.list, new Set(themesStore.list.map((t) => t.id)));
changed = favoritesStore.list.some((g) => next[g.id] !== baselineRef.current?.[g.id]);
viewScopeStore.scope = applied.scope;
viewScopeStore.groupScope = applied.groupScope;
tagRecSharedSession.tempEnabledL1Ids = applied.temporary;
tagRecSharedSession.tempEnabledGroupIds = applied.temporaryGroups;
}
tagRecSharedSession.scopeEditing = false;
draftRef.current = void 0;
setDraft(void 0);
setOpen(false);
if (changed) onChanged();
};
const escape = (0, import_react.useEffectEvent)((event) => {
if (event.key !== "Escape") return;
event.preventDefault();
event.stopImmediatePropagation();
if (query) setQuery("");
else commit();
});
(0, import_react.useEffect)(() => {
if (!open) return;
document.addEventListener("keydown", escape, { capture: true });
return () => document.removeEventListener("keydown", escape, true);
}, [open]);
const onOpenChange = (value) => {
if (!value) return commit();
const initial = createTagRecGroupScopeDraft(favoritesStore.list, favoritesL1Store.list, new Set(themesStore.list.map((t) => t.id)), viewScopeStore.scope, viewScopeStore.groupScope, tagRecSharedSession.tempEnabledL1Ids, tagRecSharedSession.tempEnabledGroupIds);
baselineRef.current = initial;
draftRef.current = initial;
setDraft(initial);
setHistory([]);
setMessage("");
setQuery("");
setExpanded(new Set(l1sOf(focus.id).slice(0, 1).map((l) => l.id)));
openRef.current = true;
tagRecSharedSession.scopeEditing = true;
setOpen(true);
};
const persist = () => {
const next = draftRef.current ?? committed;
for (const g of favoritesStore.list) if (selectable(g) && next[g.id] !== g.enabled) favorites.updateGroup(g.id, { enabled: !!next[g.id] });
for (const l of favoritesL1Store.list) {
const kids = childrenOf(l.id);
if (!kids.some(selectable)) continue;
const on = kids.some((g) => next[g.id]);
if (on !== l.enabled) favoritesL1.updateL1(l.id, { enabled: on });
}
viewScopeStore.scope = {};
viewScopeStore.groupScope = {};
tagRecSharedSession.tempEnabledL1Ids = [];
tagRecSharedSession.tempEnabledGroupIds = [];
setHistory([]);
setMessage("已保存到收藏表,下次沿用");
};
const undo = () => {
const previous = history.at(-1);
if (!previous) return;
draftRef.current = previous;
setDraft(previous);
setHistory((h) => h.slice(0, -1));
setMessage("");
};
const highlight = (name) => {
const index = q ? name.toLowerCase().indexOf(q) : -1;
return index < 0 ? name : /* @__PURE__ */ jsxs(Fragment, { children: [
name.slice(0, index),
/* @__PURE__ */ jsx("mark", {
className: "rounded-2px color-gate-primary",
style: { background: "color-mix(in oklab, var(--bilibili-gate--primary-color) 12%, transparent)" },
children: name.slice(index, index + q.length)
}),
name.slice(index + q.length)
] });
};
const checkbox = (label, items) => {
const st = stateOf(items);
return /* @__PURE__ */ jsx(Checkbox, {
"aria-label": label,
checked: st.checked,
indeterminate: st.indeterminate,
disabled: !st.total,
onChange: (e) => toggle(items, e.target.checked)
});
};
const themeRow = (b) => {
const items = groupsOfBucket(b.id);
const st = stateOf(items);
return /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px rounded-6px px-6px py-7px " + (focus.id === b.id && !q ? "bg-gate-bg-lv2" : "hover:bg-gate-bg-lv1"),
children: [
checkbox("观看主题:" + b.name, items),
/* @__PURE__ */ jsx("button", {
type: "button",
className: "min-w-0 flex-1 border-0 bg-transparent p-0 text-left text-12px text-gate-text",
onClick: () => {
setFocusBucket(b.id);
setQuery("");
setExpanded(new Set(l1sOf(b.id).slice(0, 1).map((l) => l.id)));
},
children: highlight(b.name)
}),
/* @__PURE__ */ jsx("span", {
className: "text-10px opacity-60",
children: l1sOf(b.id).length
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "px-3px color-gate-primary",
disabled: !st.total,
onClick: () => {
const active = items.filter((g) => committed[g.id]);
const saved = items.filter((g) => g.enabled);
only(active.length ? active : saved.length ? saved : items);
setFocusBucket(b.id);
},
children: "只看"
})
]
}, b.id);
};
const groupRow = (g, l) => {
const ready = selectable(g);
const temporary = selectedOf(g) && (!g.enabled || !l.enabled);
/** 搜索态下「组名没命中但组内标签命中」=> 自动展开标签行,让命中 chip 直接可见可点 */
const autoPeek = !!q && !matches(g.name) && g.tag_ids.some((e) => matches(e.name));
const peek = tagPeek.has(g.id) || autoPeek;
const pickedInGroup = tagPeekPickedCount(g, pickedTags);
return /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsxs("div", {
className: "ml-16px flex items-center gap-6px b-l-1px b-gate-border b-l-solid py-5px pl-14px pr-4px hover:bg-gate-bg-lv1",
children: [
/* @__PURE__ */ jsx(Checkbox, {
"aria-label": "观看二级组:" + l.name + " › " + g.name,
checked: selectedOf(g),
disabled: !ready,
onChange: (e) => toggle([g], e.target.checked)
}),
/* @__PURE__ */ jsx("span", {
className: "min-w-0 flex-1 break-words text-12px",
children: highlight(g.name)
}),
(!ready || !g.enabled || !l.enabled) && /* @__PURE__ */ jsx(Tag, {
className: "mr-0 text-10px",
color: temporary ? "orange" : void 0,
children: !g.tag_ids.length && !hasTagRecCombination(g.combination) ? "待添加标签" : g.weight === 0 || l.weight === 0 ? "挂起" : temporary ? "临时" : !l.enabled && g.enabled ? "一级组停用" : "停用"
}),
!!g.tag_ids.length && /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
className: `shrink-0 b-1px b-gate-primary rounded-3px b-solid px-6px text-11px ${peek || pickedInGroup ? "bg-gate-primary color-white" : "bg-gate-bg-lv1 color-gate-primary hover:bg-gate-primary hover:color-white"}`,
"aria-expanded": peek,
"aria-label": `${peek ? "收起" : "展开"}「${g.name}」的 ${g.tag_ids.length} 个标签`,
title: `看这组的 ${g.tag_ids.length} 个标签(可勾选做「只看」)`,
onClick: () => setTagPeek((prev) => {
const next = new Set(prev);
if (next.has(g.id)) next.delete(g.id);
else next.add(g.id);
return next;
}),
children: [
"🏷 ",
g.tag_ids.length,
pickedInGroup ? ` · 选${pickedInGroup}` : ""
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "shrink-0 px-4px color-gate-primary",
disabled: !ready,
"aria-label": "只看二级组:" + l.name + " › " + g.name,
onClick: () => only([g]),
children: g.enabled && l.enabled ? "只看" : "临时只看"
})
]
}), peek && /* @__PURE__ */ jsx("div", {
className: "ml-16px flex flex-wrap items-center gap-4px py-4px pl-44px pr-4px",
children: tagPeekRows(g, pickedTags, q).map((e) => /* @__PURE__ */ jsx("button", {
type: "button",
className: `b-1px rounded-3px b-solid px-6px py-1px text-11px ${e.picked ? "b-gate-primary bg-gate-primary color-white" : "b-gate-border bg-gate-bg-lv1 hover:b-gate-primary hover:color-gate-primary"}`,
"aria-pressed": e.picked,
title: `${e.name} · #${e.id} —— 勾选后点底部「只看选中的标签」`,
onClick: () => togglePickedTag({
id: e.id,
name: e.name
}),
children: highlight(e.name)
}, e.id))
})] }, g.id);
};
const l1Row = (l, contextOnly) => {
const kids = childrenOf(l.id);
const st = stateOf(kids);
const isExpanded = !!q || expanded.has(l.id);
return /* @__PURE__ */ jsx("div", {
className: "pb-3px",
children: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px rounded-6px px-5px py-6px hover:bg-gate-bg-lv1",
children: [
contextOnly ? /* @__PURE__ */ jsx("span", {
className: "w-16px shrink-0 text-center text-11px opacity-50",
children: "›"
}) : checkbox("观看一级组:" + l.name, kids),
/* @__PURE__ */ jsxs("button", {
type: "button",
disabled: !!q,
"aria-label": (isExpanded ? "收起" : "展开") + "一级组:" + l.name,
"aria-expanded": isExpanded,
className: "min-w-0 flex-1 border-0 bg-transparent p-0 text-left text-13px text-gate-text",
onClick: () => setExpanded((prev) => {
const next = new Set(prev);
if (next.has(l.id)) next.delete(l.id);
else next.add(l.id);
return next;
}),
children: [
/* @__PURE__ */ jsx("span", {
className: "mr-5px text-10px opacity-60",
children: q ? "›" : isExpanded ? "▾" : "▸"
}),
/* @__PURE__ */ jsx("strong", { children: highlight(l.name) }),
/* @__PURE__ */ jsxs("span", {
className: "ml-6px text-11px opacity-55",
children: [
q ? "一级组 · " : "",
kids.length,
" 个二级组"
]
})
]
}),
!l.enabled && /* @__PURE__ */ jsx(Tag, {
className: "mr-0 text-10px",
color: st.count ? "orange" : void 0,
children: st.count ? "临时" : "停用"
}),
!contextOnly && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "shrink-0 px-4px color-gate-primary",
disabled: !st.total,
onClick: () => {
const enabled = kids.filter((g) => g.enabled);
only(enabled.length ? enabled : kids);
setExpanded(/* @__PURE__ */ new Set([l.id]));
},
children: l.enabled ? "只看" : "临时只看"
})
]
})
}, l.id);
};
return /* @__PURE__ */ jsx(Popover, {
open,
onOpenChange,
trigger: "click",
placement: "bottomLeft",
getPopupContainer,
destroyOnHidden: true,
fresh: true,
styles: { container: { padding: 0 } },
content: open ? /* @__PURE__ */ jsxs("div", {
style: {
width: "min(660px, calc(100vw - 32px))",
padding: 12
},
onMouseLeave: () => {
if (inputRef.current?.input !== document.activeElement) commit();
},
onBlur: (e) => {
if (e.relatedTarget instanceof Node && !e.currentTarget.contains(e.relatedTarget)) commit();
},
children: [
/* @__PURE__ */ jsx(Input, {
ref: inputRef,
size: "small",
allowClear: true,
autoFocus: true,
placeholder: "搜主题、一级组、二级组或标签",
"aria-label": "搜索主题、一级组、二级组或标签",
value: query,
onChange: (e) => setQuery(e.target.value)
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-8px overflow-hidden b-t-1px b-gate-border b-t-solid",
style: {
display: "grid",
gridTemplateColumns: "minmax(140px, 0.32fr) minmax(0, 0.68fr)",
height: "min(350px, 50vh)"
},
children: [/* @__PURE__ */ jsxs("div", {
className: "overflow-y-auto b-r-1px b-gate-border b-r-solid pr-5px",
children: [/* @__PURE__ */ jsxs("div", {
className: "sticky top-0 z-1 flex items-center gap-6px bg-gate-bg-lv1 px-6px py-8px",
children: [
checkbox("全选主题", groups),
/* @__PURE__ */ jsx("strong", {
className: "text-12px",
children: "主题"
}),
/* @__PURE__ */ jsxs("span", {
className: "ml-auto text-10px opacity-55",
children: [
chosenThemes.length,
"/",
buckets.filter((b) => groupsOfBucket(b.id).some(selectable)).length
]
})
]
}), buckets.map(themeRow)]
}), /* @__PURE__ */ jsxs("div", {
className: "min-h-0 flex flex-col pl-6px",
children: [/* @__PURE__ */ jsxs("div", {
className: "z-1 flex shrink-0 items-center gap-6px bg-gate-bg-lv1 px-6px py-8px",
children: [
!q && checkbox("全选当前主题的组", groupsOfBucket(focus.id)),
/* @__PURE__ */ jsx("strong", {
className: "text-12px",
children: q ? "搜索结果" : focus.name
}),
/* @__PURE__ */ jsx("span", {
className: "ml-auto text-10px opacity-55",
children: q ? "主题 › 一级组 › 二级组 › 标签" : "展开查看二级组"
})
]
}), rows.length > 0 ? /* @__PURE__ */ jsx(es, {
data: rows,
style: {
flex: 1,
minHeight: 0
},
tabIndex: 0,
"aria-label": "主题组列表",
defaultItemHeight: 36,
itemSize: scopeItemSize,
increaseViewportBy: 80,
computeItemKey: (_, row) => row.key,
itemContent: (_, row) => {
switch (row.kind) {
case "theme": return /* @__PURE__ */ jsxs("div", {
className: "b-b-1px b-gate-border b-b-solid px-6px pb-7px pt-5px text-11px opacity-65",
children: [
highlight(row.bucket.name),
" ",
/* @__PURE__ */ jsx("span", {
className: "ml-4px opacity-60",
children: "主题"
})
]
});
case "l1": return l1Row(row.l1, row.contextOnly);
case "group": return groupRow(row.group, row.l1);
case "empty": return /* @__PURE__ */ jsx("span", {
className: "block py-5px pl-40px text-11px opacity-55",
children: "暂无二级组 · 可在收藏表新建"
});
}
}
}, q ? `search:${q}` : focus.id) : /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "block py-24px text-center text-12px",
children: q ? "无匹配结果" : "暂无一级组"
})]
})]
}),
pickedTags.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-8px flex items-center gap-8px b-1px b-gate-primary rounded-6px b-solid px-8px py-6px",
children: [
/* @__PURE__ */ jsxs("span", {
className: "min-w-0 flex-1 truncate text-11px",
children: [
"🏷 选中 ",
pickedTags.length,
" 个标签:",
pickedTags.slice(0, 3).map((t) => t.name).join("、"),
pickedTags.length > 3 ? " 等" : ""
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setPickedTags([]),
children: "清空"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
title: "到「标签发掘」页只看这些标签",
onClick: onlyPickedTags,
children: "只看选中的标签"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-8px flex items-center gap-8px b-t-1px b-gate-border b-t-solid pt-8px",
children: [
/* @__PURE__ */ jsxs("span", {
className: "min-w-0 flex-1 text-11px opacity-65",
children: [
"看 ",
chosenParents.length,
" 个一级组 / ",
chosen.length,
" 个二级组"
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setSelection({ ...savedSelection }),
children: "跟随收藏表"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !history.length,
onClick: undo,
children: "撤销"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
disabled: !persistCount,
title: "保存一级组和二级组开关,下次沿用",
onClick: persist,
children: "保存选择"
})
]
}),
/* @__PURE__ */ jsxs("div", {
role: "status",
"aria-live": "polite",
className: "mt-6px min-h-17px text-11px",
children: [message || (pending ? "离开面板更新推荐 · 临时选择不会改收藏表" : "勾选想看的,点「只看」只留这一项"), /* @__PURE__ */ jsx("span", {
className: "float-right opacity-55",
children: q ? "Esc 清除搜索,再按关闭" : "Esc 关闭并应用"
})]
})
]
}) : /* @__PURE__ */ jsx("span", {}),
children: /* @__PURE__ */ jsxs("button", {
type: "button",
className: "max-w-80vw inline-flex items-center gap-x-8px b-2px b-gate-primary rounded-8px b-solid bg-gate-bg-lv1 px-12px py-5px text-14px color-gate-primary font-600",
children: [/* @__PURE__ */ jsx("span", {
className: "truncate",
children: summary
}), /* @__PURE__ */ jsx("span", {
className: "text-11px",
children: "▾"
})]
})
});
});
/**
* [tag-rec] 「主题推荐」tabbarView (spec §5.2; R7 改名+主题下拉升级)
* 主题下拉 (R7): 双列复选树 (TagRecThemeScopePicker) —— 查看范围零写入 + 临时打开 (会话级);
* 默认勾选 = 启用集; 面板草稿在离开后统一提交并刷新; 持久化显式同步一级组 enabled
* 本查看范围临时过滤与标签发掘共用弹层;全局默认预设入口另列。
*/
function TagRecFavTabbarView({ service }) {
const onRefresh = useOnRefresh();
const onScopeChanged = (0, import_react.useCallback)(() => {
onRefresh("refresh");
}, [onRefresh]);
const [groupsOpen, setGroupsOpen] = (0, import_react.useState)(false);
const { recSharedEmitter } = useRecSelfContext();
const { ref, getPopupContainer } = usePopupContainer();
const { list } = useSnapshot(favoritesStore);
const { list: l1List } = useSnapshot(favoritesL1Store);
const { list: themeList } = useSnapshot(themesStore);
const { scope, groupScope } = useSnapshot(viewScopeStore);
const { tempEnabledL1Ids, tempEnabledGroupIds } = useSnapshot(tagRecSharedSession);
const { tagRec } = useSettingsSnapshot();
const { drawnGroupNames, softBottom, bypassSeen } = useSnapshot(tagRecFavSession);
const activePreset = tagRec.filter.activePreset;
const jumpToFilterPane = () => {
modalSettingsStore.tab = TabPaneKey.TagRecFilter;
showModalSettings();
};
const { drawable, parents, parentRows } = (0, import_react.useMemo)(() => {
const themeIds = new Set(themeList.map((t) => t.id));
const viewedIds = tagRecViewScopeViewedIds(scope, l1List, themeIds);
const tempSet = new Set(tempEnabledL1Ids);
const l1ById = new Map(l1List.map((l) => [l.id, l]));
const participating = withTagRecGroupScope(list, tagRecViewedGroupIds(list, groupScope), tempEnabledGroupIds).filter((g) => {
if (!isDeckDrawable(g)) return false;
const l1 = g.l1Id ? l1ById.get(g.l1Id) : void 0;
return !!l1 && isL1Drawable({
id: l1.id,
enabled: l1.enabled || tempSet.has(l1.id),
weight: l1.weight
}, viewedIds);
});
const parentIds = new Set(participating.map((g) => g.l1Id));
const parents = l1List.filter((l) => parentIds.has(l.id));
const themeNames = new Map(themeList.map((t) => [t.id, t.name]));
const namesByParent = /* @__PURE__ */ new Map();
for (const g of participating) {
const names = namesByParent.get(g.l1Id) ?? [];
names.push(g.name);
namesByParent.set(g.l1Id, names);
}
const parentRows = parents.map((l) => ({
id: l.id,
name: l.name,
theme: themeNames.get(l.themeId ?? "") ?? "未归属",
groups: namesByParent.get(l.id).join("、")
}));
return {
drawable: participating.length,
parents,
parentRows
};
}, [
list,
l1List,
themeList,
scope,
groupScope,
tempEnabledL1Ids,
tempEnabledGroupIds
]);
const tempCount = tempEnabledL1Ids.length;
return /* @__PURE__ */ jsxs("div", {
ref,
className: "flex flex-wrap items-center gap-x-10px gap-y-6px",
children: [
/* @__PURE__ */ jsx(TagRecThemeScopePicker, {
onChanged: onScopeChanged,
getPopupContainer
}),
/* @__PURE__ */ jsx(FilterPopover, {
scope: "fav",
getPopupContainer
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "全局默认过滤预设;点击前往「标签过滤」设置,长期生效",
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
onClick: jumpToFilterPane,
children: ["默认预设: ", activePreset || "自定义"]
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "浏览过的卡片不再滤除;主题推荐默认关闭,标签发掘默认开启",
children: /* @__PURE__ */ jsx(Checkbox, {
checked: bypassSeen,
onChange: (event) => {
tagRecFavSession.bypassSeen = event.target.checked;
onScopeChanged();
},
children: "显示已看"
})
}),
/* @__PURE__ */ jsx(Popover, {
open: groupsOpen,
onOpenChange: setGroupsOpen,
destroyOnHidden: true,
fresh: true,
trigger: "click",
getPopupContainer,
content: groupsOpen ? /* @__PURE__ */ jsxs("div", {
style: {
width: 320,
maxWidth: "calc(100vw - 48px)"
},
children: [
/* @__PURE__ */ jsx("strong", {
className: "text-13px",
children: "参与推荐的组"
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-3px text-11px opacity-60",
children: [
parents.length,
" 个一级组 / ",
drawable,
" 个二级组"
]
}),
/* @__PURE__ */ jsxs("div", {
className: "my-10px max-h-260px overflow-y-auto",
children: [parentRows.map((l) => /* @__PURE__ */ jsxs("div", {
className: "mb-10px",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px text-12px",
children: [/* @__PURE__ */ jsx("strong", { children: l.name }), /* @__PURE__ */ jsx("span", {
className: "text-11px opacity-55",
children: l.theme
})]
}), /* @__PURE__ */ jsx("div", {
className: "mt-3px break-words text-12px opacity-70",
children: l.groups
})]
}, l.id)), !parents.length && /* @__PURE__ */ jsx("span", {
className: "text-12px opacity-60",
children: "暂无可参与抽取的组"
})]
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "block text-11px",
children: "按一级组、二级组权重混着推荐,标签多不会独占推荐。单条视频的来源看归因徽章。"
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-8px b-t-1px b-gate-border b-t-solid pt-8px text-11px opacity-65",
children: [
"每次预取至多 ",
clampTagRecK(tagRec.k),
" 个一级组 / ",
drawable,
" 个二级组参与轮换",
tempCount > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
" · 含 ",
tempCount,
" 个临时一级组"
] }),
!!tempEnabledGroupIds.length && /* @__PURE__ */ jsxs(Fragment, { children: [
" · 含 ",
tempEnabledGroupIds.length,
" 个临时二级组"
] }),
drawnGroupNames.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-4px",
children: ["本批来源:", drawnGroupNames.join("、")]
})
]
})
]
}) : /* @__PURE__ */ jsx("span", {}),
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
className: "max-w-80vw text-12px",
"aria-label": "查看参与推荐的组",
children: [
/* @__PURE__ */ jsx("strong", { children: "推荐中" }),
/* @__PURE__ */ jsx("span", {
className: "mx-6px opacity-40",
children: "·"
}),
/* @__PURE__ */ jsxs("span", {
className: "truncate opacity-70",
children: [parents.length ? parents.slice(0, 2).map((l) => l.name).join("、") : "暂无参与组", parents.length > 2 ? ` +${parents.length - 2} 个一级组` : ""]
})
]
})
}),
softBottom && /* @__PURE__ */ jsx(Typography.Text, {
type: "warning",
className: "text-11px",
children: "池将见底"
}),
/* @__PURE__ */ jsx(TagRecRecheckButton, {
service,
onDone: () => void recSharedEmitter.emit("load-more")
}),
/* @__PURE__ */ jsx(TagRecBanner, {
session: tagRecFavSession,
onRedig: () => {
service.redigQuery().then(() => recSharedEmitter.emit("load-more"));
},
onPartitionRetry: () => {
service.retryPartitions();
recSharedEmitter.emit("load-more");
},
onDigMore: () => void service.digMore().then(() => recSharedEmitter.emit("load-more"))
}),
/* @__PURE__ */ jsx(TagRecShareImportModal, { origin: "deeplink" })
]
});
}
/**
* 票 37 深挖/产出态模块级共享 (与 tag-rec-custom 同款): 刷新重建 service 同资格指纹续挖 —
* 簇流页游标/兜底堆/选种/三停纪律/去重集/未上屏队列全保; 资格指纹变 => fetchMore 内原地重置
*/
var sharedLadderState = newTagRecLadderState();
var sharedSeenBvids = /* @__PURE__ */ new Set();
/** 交付队列写版本 (Proxy 拦写): bvid 懒索引的等长换血检出依据 —— splice 交付+push 回补净长不变也无处可藏 */
var sharedBufferQueueSeq = 0;
var sharedBufferQueue = tagRecVersionedArray(() => sharedBufferQueueSeq++);
var sharedCandidates = new TagRecDeferredCandidates();
/**
* 交付队列/兜底堆 bvid 懒索引 (mergeIncoming 每条到达物全扫 5000 深队列 => O(items×queue) 悬崖):
* 写版本变自动重建 —— 一切写路径 (push/splice/restore/测试直改) 全覆盖
*/
var sharedQueueIndex = createTagRecBvidIndex(() => sharedBufferQueue, () => sharedBufferQueueSeq, (it) => it.api === EApiType.TagRec);
var sharedHeapIndex = createTagRecBvidIndex(() => sharedLadderState.heap, tagRecHeapVersion);
/** 已见到达物是否仍挂未交付态 (归一/合并早退判): 队列 ∪ 兜底堆 ∪ 被挡候选表 */
var sharedHasPending = (bvid) => sharedQueueIndex.map().has(bvid) || sharedHeapIndex.map().has(bvid) || sharedCandidates.has(bvid);
var sharedFairState = new TagRecFairState();
var sharedSuppliedGroups = /* @__PURE__ */ new Set();
var sharedDeck = new TagRecUnitsDeck();
/** 一次预取最多 5 个二级组(k 更大时随 k,上限 10);库存少于半页才补。 */
var PREFETCH_GROUP_WINDOW = 5;
var STOCK_LOW_WATER = 10;
/**
* 票 41 逐源干态同享模块级 (与 sharedLadderState/sharedSeenBvids 同层): 冷收藏组被牌组反复抽中时
* 不再每轮重打空 tag/top —— 机制复用 tab2 TagRecTidDryTracker, 复位轴 = unitsFingerprint 变
* (资格集变=池集合变, 干态无效 => 与 37 共享态同生死)。会话级不落盘 (持久化票 41 裁: 另议)
*/
var sharedDryTids = new TagRecTidDryTracker();
var sharedUpSearch = new TagRecUpSearchPool();
var sharedFingerprint;
var sharedFilterSignature;
/** 测试用: 清模块级深挖共享态+干态+指纹 (等价整页重载) */
function resetTagRecFavSharedState() {
tagRecFavSession.partitionPaused = false;
tagRecFavSession.partitionPending = 0;
resetTagRecFavFilter();
tagRecFavSession.filterScopeSignature = void 0;
tagRecFavSession.filterScopeRevision = 0;
sharedUpSearch.reset();
resetTagRecLadderState(sharedLadderState);
sharedSeenBvids.clear();
sharedBufferQueue.length = 0;
sharedCandidates.clear();
sharedFairState.clear();
sharedSuppliedGroups.clear();
sharedDeck = new TagRecUnitsDeck();
sharedDryTids.clear();
sharedFingerprint = void 0;
sharedFilterSignature = void 0;
}
/** A4-6: 「清空已看」联动 —— 持久池清完, 跨轮内存去重同步清 (经 service-map 注册表调达, 保惰性边界) */
function clearTagRecFavSharedSeenBvids() {
sharedSeenBvids.clear();
}
/**
* 巨型收藏夹规模修复 (535 组实测单轮 ~1150 次全表重建 => 主线程冻结):
* 生效链求解器与三签名按 store 版号记忆化 —— 任一输入深变即换键重算;
* 非代理输入 (测试替身) 退化为逐次直算, 与缓存前语义一致。
* resolver 输入 = favorites/favoritesL1/blocks/settings.tagRec/favSession;
* 语境键加 viewScope.scope/themes/sharedSession + 派生 viewed 集
* (viewScopeStore.groupScope 无版号外泄, 用 viewedGroupIds 输出兜底覆盖)。
*/
function favResolverKey() {
const rk = tagRecVersionKey([
favorites.list,
favoritesL1.list,
tagRecCombinationBlocks,
settings.tagRec
]);
return rk === void 0 ? void 0 : `${rk}|ov:${JSON.stringify(tagRecFavSession.filterOverride ?? null)}`;
}
var favViewedIdsSlot = versionedSlot();
/** 派生 viewed 集同样版号化 —— groupScope 无外泄 getter, 其写点均与 favorites/l1 写配对, 由表版号覆盖 */
function favViewedIdsPart() {
return favViewedIdsSlot(tagRecVersionKey([
viewScope.scope,
themes.list,
favoritesL1.list,
favorites.list
]), () => `${[...viewScope.viewedL1Ids() ?? []].sort().join(",")}|${[...viewScope.viewedGroupIds?.() ?? []].sort().join(",")}`);
}
function favContextKey() {
const rk = favResolverKey();
const ctx = tagRecVersionKey([viewScope.scope, themes.list]);
const vp = favViewedIdsPart();
if (rk === void 0 || ctx === void 0 || vp === void 0) return void 0;
return `${rk}:${ctx}|${JSON.stringify([
tagRecFavSession.bypassSeen,
tagRecSharedSession.recoveryRevision,
tagRecSharedSession.tempEnabledL1Ids,
tagRecSharedSession.tempEnabledGroupIds
])}|${vp}`;
}
var favResolverSlot = versionedSlot();
var favSourceSigSlot = versionedSlot();
var favContextSigSlot = versionedSlot();
var favFilterSigWithSources = versionedSlot();
var favFilterSigNoSources = versionedSlot();
var favRecheckSlot = versionedSlot();
function favScopeResolver() {
return favResolverSlot(favResolverKey(), () => createTagRecScopeResolver({
globalExclusions: tagRecCombinationBlocks.list,
globalExclusionsInvalid: tagRecCombinationBlocks.invalid,
core: settings.tagRec.filter,
sessionCore: tagRecFavSession.filterOverride,
groupsByName: new Map(favorites.list.map((g) => [g.name, g])),
groupsById: new Map(favorites.list.map((g) => [g.id, g])),
l1ById: new Map(favoritesL1.list.map((l) => [l.id, l]))
}));
}
/** 票 44: 语境签名 (= 资格指纹: 查看范围 + 有效 L1 表 + L2 资格项) —— 构造期同步与 useRefresh 漂移判定复用 */
function computeTagRecFavSourceSignature() {
return favSourceSigSlot(favContextKey(), () => tagRecSourceSignature(participatingGroups().map((g) => ({
key: { id: g.id },
name: g.name
})), favScopeResolver()));
}
function computeTagRecFavContextSignature() {
return favContextSigSlot(favContextKey(), () => unitsFingerprint(effectiveGroups(), effectiveL1s(), viewScope.viewedL1Ids()) + `|seen:${!!tagRecFavSession.bypassSeen}:${tagRecSharedSession.recoveryRevision ?? 0}|sources:${computeTagRecFavSourceSignature()}`);
}
function effectiveGroups() {
return withTagRecGroupScope(favorites.list, viewScope.viewedGroupIds?.(), tagRecSharedSession.tempEnabledGroupIds);
}
/** R7: 有效 L1 表 = 持久表 + 临时打开合流 (会话级; 刷新还原, 不改 store) */
function effectiveL1s() {
return withTagRecTempEnables(favoritesL1.list, tagRecSharedSession.tempEnabledL1Ids);
}
function participatingGroups() {
const parents = new Set(effectiveL1s().filter((l) => isL1Drawable(l, viewScope.viewedL1Ids())).map((l) => l.id));
return effectiveGroups().filter((g) => isDeckDrawable(g) && g.l1Id && parents.has(g.l1Id));
}
/**
* 票 44: 签名比对前移 —— 「有旧指纹且不同」即原地重置共享态, 返回当前指纹;
* 构造期+fetchMore 双点调用。构造期必须: loadMore 先放 bufferQueue 后进 fetchMore,
* 不前置同步 = 旧境存货被当新境首页放行 (主题切换「没反应/加在视频后面」病根)
*/
function syncFavSharedState() {
syncTagRecFavFilterScope(JSON.stringify([
[...viewScope.viewedL1Ids() ?? []].sort(),
[...viewScope.viewedGroupIds?.() ?? []].sort(),
participatingGroups().map((g) => g.id).sort()
]));
const fingerprint = computeTagRecFavContextSignature();
const members = fingerprint.split("|sources:", 1)[0];
if (sharedFingerprint !== void 0 && members !== sharedFingerprint) {
sharedUpSearch.reset(true);
resetTagRecLadderState(sharedLadderState, true);
sharedSeenBvids.clear();
sharedBufferQueue.length = 0;
sharedCandidates.clear();
sharedFairState.clear();
sharedSuppliedGroups.clear();
sharedDeck = new TagRecUnitsDeck();
sharedDryTids.clear();
}
sharedFingerprint = members;
return fingerprint;
}
/**
* 实修票①「放宽过滤救不活已滤条」: 过滤生效签名 —— 覆盖生效链全部输入
* (全局核心 + 各组负向名单/严格标/overrides + 各 L1 overrides; 改名也入签 —— 链按名解析)。
* 变 => 仅清跨轮 seenBvids: 下轮 tag/top 重采样时已滤条重新进池参评 —— 零新增请求
* (浅池按 tid 采样本轮必拉, 签名只切条级去重记忆); 真看过的在 seen-set 不复活。
* 收紧侧由 refilterSharedQueue 覆盖存货 (构造期), 本签名管放宽侧的复活资格。
*/
/** A4-3: 过滤签名计算外提 —— service-map 漂移判定复用 (在途刷新的收紧型 emit 不再被静默丢) */
function computeTagRecFavFilterSignature(withoutSources = false) {
return (withoutSources ? favFilterSigNoSources : favFilterSigWithSources)(favResolverKey(), () => computeTagRecFilterSignature({
core: settings.tagRec.filter,
sessionCore: tagRecFavSession.filterOverride,
groups: favorites.list,
l1s: favoritesL1.list,
withoutSources
})) + `|globalExclusions:${JSON.stringify([tagRecCombinationBlocks.list, tagRecCombinationBlocks.invalid])}`;
}
function syncFavFilterState() {
const source = computeTagRecFavSourceSignature();
const sig = computeTagRecFavFilterSignature(true) + "|sources:" + source;
if (sharedFilterSignature !== void 0 && sig !== sharedFilterSignature) {
sharedUpSearch.invalidate();
sharedDryTids.revive(tagRecReopenedTopTids(sharedFilterSignature.split("|sources:", 2)[1], source, favorites.list));
sharedLadderState.epoch++;
sharedLadderState.earlyStopped = false;
sharedLadderState.lowYieldStreak = 0;
}
sharedFilterSignature = sig;
}
/**
* 票 18 (tag-share) 深链消费 (spec §6): `?gate-tab=tag-rec-fav&tag-rec-share=[&tag-rec-groups=]`
* —— 网站「导入到插件」回跳 tab1 的落点。选型与 tab2 consumeTagRecDeepLink 同款 (构造期同步段,
* 不依赖挂载/emit 时序): 解析后 history.replaceState 清两参 (防刷新重放; gate-tab 保留属既有机制),
* shareId 在 => 懒载 ./share-import 编排 (GM_xhr 详情拉取+预览 Modal+落表全在其内),
* 模块图常态不背分享管线; 失败全走 toast 不崩构造
*/
var tagRecShareDeepLinkConsumed = false;
function consumeTagRecShareDeepLink() {
if (tagRecShareDeepLinkConsumed) return;
tagRecShareDeepLinkConsumed = true;
if (typeof location === "undefined") return;
const { shareId, groupIds, paramsToClear } = parseTagRecShareDeepLink(location.search);
const { code: pkgCode, hasPkg } = parseTagRecPkgHash(location.hash);
if (!paramsToClear.length && !hasPkg) return;
for (const p of paramsToClear) {
const u = new URL(location.href);
u.searchParams.delete(p);
history.replaceState({}, "", u.href);
}
if (hasPkg) {
const u = new URL(location.href);
u.hash = "";
history.replaceState({}, "", u.href);
}
if (pkgCode) {
if (shareId) toast("链接同时携带分享与试改包参数, 已按试改包导入", 4e3);
module.import('./share-import-BmCYK7Oh-BpB7LIls.js').then((m) => m.requestShareImportPkg(pkgCode, "deeplink")).catch(() => toast("分享导入模块加载失败"));
} else if (shareId) module.import('./share-import-BmCYK7Oh-BpB7LIls.js').then((m) => m.requestShareImport(shareId, groupIds, "deeplink")).catch(() => toast("分享导入模块加载失败"));
}
/**
* [tag-rec] 「主题推荐」tab —— 三层公平抽样混合流 (spec §4.3 tab1 / §4.4; 票 17 L1/L2 层级)
* 供给与展示分离:一次预取至多 K 个 L1、窗口内多个 L2,各组 ≤10 个标签。
* 未展示候选同会话留存;每批 20 条按 L1 权重 → L2 权重交错出队。
* 组权重只在交付份额中计一次,标签数与库存深度不增加展示份额。
* 见底 (spec §4.4):
* 软 = 连续 2 轮产出 <5 => tagRecFavSession.softBottom (横幅归票 09, 不阻断)
* 硬 = 单轮产出 0 (含无可抽组) => 返回 [见底 separator] + hasMoreExceptQueue=false,
* 队列排空后 hasMore=false 截停 fetchMinCount 连发 (一轮一空转即截停)
* 手动「重抽一轮」 = useOnRefresh()('refresh') 重建 service (机制在此; 按钮 UI 归票 09)
*/
var TagRecFavService = exports("TagRecFavService",class TagRecFavService extends BaseTabService {
static PAGE_SIZE = 20;
/** 票 44: 语境签名快照 —— useRefresh 判定在途刷新是否被语境变更 (主题切换/资格集变) 作废顶替;
* A4-1: 非 readonly —— 被动漂移后 fetchMore 内随共享态重置重钉 (isStale 不再永真楔死深挖链) */
contextSignature;
/**
* A4-3: 过滤签名构造期钉点 —— drift 判定的「收紧型过滤变更」轴;
* 刻意不随 fetchMore 重钉 (放宽侧复活走 seenBvids 通道自愈, 钉点漂移保留给 drift 判定消费)
*/
filterSignature;
constructor() {
super(TagRecFavService.PAGE_SIZE);
consumeTagRecShareDeepLink();
this.pinnedEmpty = !favorites.list.length && !favoritesL1.list.length;
this.contextSignature = syncFavSharedState();
syncFavFilterState();
sharedUpSearch.sync(this.recheckPlan().up);
this.filterSignature = computeTagRecFavFilterSignature();
this.qs.bufferQueue = sharedBufferQueue;
this.refilterSharedQueue();
this.retryPartitions();
}
partitionBudget;
partitionHeld = /* @__PURE__ */ new Set();
partitionPausePolicy;
get autoLoadPaused() {
if (tagRecFavSession.partitionPaused && this.partitionPausePolicy !== computeTagRecFavFilterSignature()) {
this.partitionHeld.clear();
this.retryPartitions();
}
return tagRecFavSession.partitionPaused;
}
retryPartitions() {
tagRecFavSession.partitionPaused = false;
tagRecFavSession.partitionPending = 0;
}
beginLoadOperation(signal) {
this.endLoadOperation();
this.partitionBudget = new TagRecPartitionBudget(signal);
}
endLoadOperation() {
this.partitionBudget?.dispose();
this.partitionBudget = void 0;
}
tabbarView = /* @__PURE__ */ jsx(TagRecFavTabbarView, { service: this });
sidebarView = void 0;
/**
* 全参与组召回规划 + UP 源按组索引 —— 版号记忆化。
* hasUpSupply 原逐组各跑一次 planTagRecRecall (535 组=535 次小规划/轮);
* 合并 plan 的 up 源带 groupIds 并集, 按组过滤等效且只建一次; hasMore 仍读活池态。
*/
recheckBundle() {
return favRecheckSlot(favContextKey(), () => {
const groups = participatingGroups();
const plan = planTagRecRecall({
roundSources: groups.flatMap((g) => g.tag_ids.map((e) => ({
tid: e.id,
keyword: e.name,
groupIds: [g.id],
groupNames: [g.name]
}))),
digSources: deriveGroupCliques(groups.map((g) => ({
...g,
extra: tagRecSearchExtraOf(favScopeResolver()({ id: g.id }, "allV2"))
})), Infinity),
scopeConsumers: groups.map((group) => ({
key: { id: group.id },
name: group.name
}))
}, favScopeResolver());
const upsByGroup = /* @__PURE__ */ new Map();
for (const s of plan.up) for (const gid of s.groupIds ?? []) {
const arr = upsByGroup.get(gid) ?? [];
if (!arr.length) upsByGroup.set(gid, arr);
arr.push(s);
}
return {
plan,
upsByGroup
};
});
}
recheckPlan() {
return this.recheckBundle().plan;
}
getRecheckOptions() {
return tagRecRecheckOptions(this.recheckPlan());
}
recheck(selection, signal) {
if (tagRecBackoff.isEngaged || this.isStale()) return Promise.resolve({
requests: 0,
added: 0,
empty: false,
failed: false,
stale: true
});
return tagRecRecallScheduler.withForegroundSupply(() => this.ladder.recheck(selection, this.recheckPlan(), signal ?? this.lastFetchSignal)).then((result) => {
if (!result.stale && result.added) {
this.hasMoreExceptQueue = true;
tagRecFavSession.hardBottom = false;
tagRecFavSession.digPending = false;
}
return result;
});
}
restore() {
this.retryPartitions();
super.restore();
sharedCandidates.flushMerged();
const filtered = runTagRecFilter(this.qs.bufferQueue.filter((it) => it.api === EApiType.TagRec), {
resolve: this.scopeResolver(),
pools: tagRecNegativePools(),
deferPartitions: true
}).items;
const keep = new Set(filtered);
this.qs.bufferQueue.splice(0, this.qs.bufferQueue.length, ...this.qs.bufferQueue.filter((it) => it.api !== EApiType.TagRec || keep.has(it)));
}
hasMoreExceptQueue = true;
/** [tag-rec] R09: 未决轮 (零产出+失败源) 连续计数 —— 有界重试, 超限抛可重试错误 (定轮归零) */
failedSourceRounds = 0;
/** 与库存同生死;刷新保留进度,资格语境变化时重建。 */
get deck() {
return sharedDeck;
}
prefetchRunning;
/**
* 内层 tag 轮转游标 (票 17): L2 组 id => 游标; 抽中组 >10 id 每轮取 10 切片逐轮覆盖
* AUDIT-20: 升共享盒 —— 实例级游标随 refresh 归零 → 大组永远重拉首片 (ids 11..N 会话内饿死);
* 资格指纹变经 resetTagRecLadderState 随盒同清
*/
tagCursors = sharedLadderState.tagCursors;
/**
* AUDIT-15: 手动深挖生命周期闸 —— fetchMore 入口登记当前 abortSignal;
* service 被弃 (刷新顶替/卸载) 时 useRefresh abort 旧 controller => 在途/后续手动深挖批随截停
*/
lastFetchSignal;
/** 跨轮 bvid 去重 (语义同 recItemUniqer; 渲染层 concatRecItems 再兜底); 票 37: 模块级共享, 刷新续去重 */
seenBvids = sharedSeenBvids;
/**
* 票 41 逐源干态记忆 (tab2 票 32 同款移植): 空响应即干 / 零新增连击标干 —— 模块级共享,
* service 重建/点刷新存活; 资格指纹变或整页重载才清; 干键按 tid 不按轮次, 抽中才记账
*/
dryTids = sharedDryTids;
/**
* 票 13 兜底梯队: 抽中组成员规范名 => 同义词簇深挖流 (饿谁喂谁, D7/D8);
* 无候选清单/变体 hooks (tab1 无 Phase 2 UI) —— 编排与 tab2 同款 fallback-ladder.ts
*/
ladder = new TagRecFallbackLadder({
seenBvids: this.seenBvids,
resolve: () => this.scopeResolver(),
relatedSeedingAllowed: () => this.relatedSeedingAllowed(),
bypassSeen: () => tagRecFavSession.bypassSeen,
policySignature: () => computeTagRecFavSourceSignature() + computeTagRecFavFilterSignature(true),
observe: (items) => {
sharedCandidates.resolve = this.scopeResolver();
sharedCandidates.record(items);
},
recallPlan: () => this.recheckPlan(),
applyStages: (items, ctx) => this.applyStages(items, ctx),
rankItems: (items) => this.runScoreStage(items),
seedMinScoreOf: (item) => resolveTagRecSeedMinScore(item, this.scopeResolver()),
mergeExisting: (item) => sharedCandidates.mergeIncoming(item, sharedQueueIndex.map(), (items) => this.runScoreStage(items), sharedLadderState.heap, sharedHeapIndex.map()),
pushQueue: (items) => this.enqueueStock(items),
queueSize: () => this.qs.bufferQueue.filter((item) => item.api === EApiType.TagRec && (item.eligibleGroupKeys?.length ?? 1) > 0).length,
hasDemandStock: () => {
const groups = participatingGroups().filter((g) => this.hasAllowedSupply(g));
const counts = this.supplyView().groups;
return groups.length > 0 && groups.every((g) => (counts.get(tagRecGroupKey({ id: g.id }))?.size ?? 0) > 0);
},
poolItems: () => sharedCandidates.values(),
candidateScope: () => {
const groups = participatingGroups();
const counts = this.supplyView();
const keys = (g) => [tagRecGroupKey({ id: g.id }), tagRecGroupKey(g.name)];
return {
needed: new Set(groups.filter((g) => this.hasAllowedSupply(g) && counts.needs(tagRecGroupKey({ id: g.id }))).flatMap(keys)),
all: new Set(groups.flatMap(keys))
};
},
scoreParams: (it) => {
const core = settings.tagRec.filter;
const c = this.scoreParamsCache;
if (c.core !== core || c.sig !== sharedFilterSignature || c.patch !== tagRecFavSession.filterOverride) {
c.resolve = this.scopeResolver();
c.core = core;
c.sig = sharedFilterSignature;
c.patch = tagRecFavSession.filterOverride;
}
return resolveTagRecItemScoreParams(it, c.resolve);
}
}, {
...FALLBACK_LADDER_STRATEGY_AGGRESSIVE,
ladder: () => settings.tagRec.ladder
}, sharedLadderState);
digRunning;
produceTracker = createTagRecProduceTracker();
/** A5: scoreParams 记忆盒 —— {core, sig} 双闸失配即清 (见 ladder opts 注释) */
scoreParamsCache = {
core: void 0,
sig: void 0,
patch: void 0,
resolve: void 0
};
/** [tag-rec] 轮次阶段 (票 06/08 实装): 过滤(含黑名单维) => 算分排序 => 去已看 —— 只换实现不动骨架 */
stages = {
filterStage: (items, ctx) => this.runFilterStage(items, ctx),
scoreStage: (items, ctx) => this.runScoreStage(items, ctx),
seenStage: createTagRecSeenStage(tagRecFavSession, () => tagRecFavSession.bypassSeen)
};
/** 生效链求解器 (票01): 全表组名=>组 + L1=>id; 每次调用新建 (廉价, 内部按组×池缓存本趟解) */
scopeResolver() {
return favScopeResolver();
}
/**
* 实修 (组 override「刷新」不生效根因): sharedBufferQueue 跨刷新存活 (票37), 存货是旧配置滤出的,
* sliceFromQueue 直放不再过阶段 => 构造期原位重过一遍 filterStage
* (seenStage 不重跑 —— 队列货本就去过重, 重跑会被 seen 误杀; 分隔符不进过滤原样保留)
*/
refilterSharedQueue() {
const resolve = this.scopeResolver();
sharedCandidates.resolve = resolve;
const stats = refilterTagRecStock(sharedBufferQueue, this.effectiveFilter(), resolve, {
pools: tagRecNegativePools(),
candidates: sharedCandidates,
recover: (items) => tagRecSeenStage(items, tagRecFavSession.bypassSeen),
relatedFallback: this.relatedFallbackOn()
});
if (stats) tagRecFavSession.filterStats = stats;
}
/**
* 小池降级 (池浅已放宽) 时放行 related 路由条目的组合 mismatch —— 工作台之前的语义:
* 相关条带「相关」徽标交付、不冒充命中; 排除/屏蔽/来源闸不受影响 (见 filter-engine 判据)。
*/
relatedFallbackOn() {
return tagRecFavSession.filterStats.degraded;
}
/** 交集组合在的组: related 只是兜底料 —— 兜底门不开时停选种 (产物纯死库存, 火力留给 all/v2).
* 走 resolver 的派生交集标记 —— 生效链 tagAllow 计入同口径 (判定函数只见组合内 filters) */
relatedSeedingAllowed() {
const resolve = favScopeResolver();
return !participatingGroups().some((g) => g.combination && resolve({ id: g.id }, "allV2").intersection) || this.relatedFallbackOn();
}
effectiveFilter() {
const core = settings.tagRec.filter;
return tagRecFavSession.filterOverride ? materializeTagRecSessionFilter(core, tagRecFavSession.filterOverride) : core;
}
hasUpSupply(group, resolve) {
if (!upSearchMids(resolve, { id: group.id })) return;
return sharedUpSearch.hasMore(this.recheckBundle().upsByGroup.get(group.id) ?? []);
}
hasAllowedSupply(group) {
const resolve = this.scopeResolver();
const pools = resolve({ id: group.id }, "allV2").poolsEnabled;
return upSearchMids(resolve, { id: group.id }) ? pools.allV2 : TAG_REC_ALL_POOL_KEYS.some((p) => pools[p]);
}
supplyView() {
const stock = sharedBufferQueue.filter((item) => item.api === EApiType.TagRec);
const groups = Math.max(1, participatingGroups().length);
const lowWater = Math.min(STOCK_LOW_WATER, Math.max(1, Math.floor(TAG_REC_STOCK_TOTAL / groups)));
return tagRecSupplyView(sharedCandidates.values(), stock, this.scopeResolver(), lowWater);
}
needsSupply() {
const groups = participatingGroups();
const counts = this.supplyView();
if (counts.stockTotal >= 5e3) return false;
const resolve = this.scopeResolver();
return groups.some((g) => this.hasAllowedSupply(g) && counts.needs(tagRecGroupKey({ id: g.id })) && (this.hasUpSupply(g, resolve) ?? true));
}
/** 任一可供给组归属库存为零且本轮也没供到货 —— 与 preDig 内层 hasDemandStock 门同义, 作外层触发 */
hasStarvedGroup(roundItems) {
const counts = this.supplyView().groups;
const fed = new Set(roundItems.flatMap((it) => tagRecSourceClaims(it).flatMap((c) => (c.groupIds ?? []).filter((gid) => !it.eligibleGroupKeys || it.eligibleGroupKeys.includes(tagRecGroupKey({ id: gid }))))));
return participatingGroups().some((g) => this.hasAllowedSupply(g) && !fed.has(g.id) && !counts.get(tagRecGroupKey({ id: g.id }))?.size);
}
enqueueStock(items) {
const qmap = sharedQueueIndex.map();
const stagedBvids = /* @__PURE__ */ new Set();
const staged = [];
for (const item of items) {
if (item.api !== EApiType.TagRec) {
staged.push(item);
continue;
}
if (stagedBvids.has(item.bvid)) continue;
const queued = qmap.get(item.bvid);
if (queued) sharedCandidates.mergeQueued(queued, item, (xs) => this.runScoreStage(xs));
else {
staged.push(item);
stagedBvids.add(item.bvid);
}
}
sharedCandidates.flushMerged();
if (staged.length) sharedBufferQueue.push(...staged);
for (const item of trimTagRecStock(sharedBufferQueue, participatingGroups())) this.seenBvids.delete(item.bvid);
}
prefetch(abortSignal) {
if (participatingGroups().every((g) => !!upSearchMids(this.scopeResolver(), { id: g.id }))) return;
if (this.prefetchRunning || !this.hasMoreExceptQueue || abortSignal.aborted || this.isStale()) return;
if (tagRecBackoff.isEngaged || !this.needsSupply()) return;
const epoch = sharedLadderState.epoch;
this.prefetchRunning = this.fetchMore(abortSignal).then(async (items) => {
if (items?.length && !abortSignal.aborted && !this.isStale() && sharedLadderState.epoch === epoch) this.enqueueStock(items);
if (this.needsSupply() && !abortSignal.aborted && !this.isStale() && sharedLadderState.epoch === epoch) {
await this.ladder.maybeSeedRelated(abortSignal, sharedLadderState.stockEpoch);
if (!abortSignal.aborted && !this.isStale() && sharedLadderState.epoch === epoch) this.enqueueStock(this.ladder.releasePage(epoch));
}
}).catch((e) => appWarn("[tag-rec] fav prefetch failed: %o", e)).finally(() => {
this.prefetchRunning = void 0;
});
}
/** [tag-rec] 过滤阶段: 生效链逐认领组评估 (任一放行); 分维度计数/降级写 session */
runFilterStage(items, ctx) {
const resolve = ctx ? ctx.resolve ??= this.scopeResolver() : this.scopeResolver();
sharedCandidates.resolve = resolve;
sharedCandidates.record(items);
const { items: out, stats } = runTagRecFilter(items, {
resolve,
pools: tagRecNegativePools(),
poolSize: ctx?.poolSize ?? ctx?.fetchedCount ?? items.length,
poolItems: ctx?.poolItems ?? items,
deferPartitions: true,
relatedFallback: this.relatedFallbackOn()
});
const prev = tagRecFavSession.filterStats;
tagRecFavSession.filterStats = ctx?.mergeStats ? {
dropped: prev.dropped + stats.dropped,
byDim: Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(prev.byDim), ...Object.keys(stats.byDim)])].map((k) => [k, (prev.byDim[k] ?? 0) + (stats.byDim[k] ?? 0)])),
byScope: Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(prev.byScope ?? {}), ...Object.keys(stats.byScope ?? {})])].map((k) => [k, (prev.byScope?.[k] ?? 0) + (stats.byScope?.[k] ?? 0)])),
degraded: prev.degraded || stats.degraded,
unchecked: (prev.unchecked ?? 0) + (stats.unchecked ?? 0) || void 0,
seenDropped: prev.seenDropped
} : {
...stats,
degraded: prev.degraded || stats.degraded
};
return out;
}
/** [tag-rec] 算分排序阶段: 逐认领组链取 max (票01) + diversityGuard 两轴 (票09) */
runScoreStage(items, ctx) {
const { items: out } = runTagRecScoreStage(items, this.effectiveFilter(), {
resolve: ctx?.resolve ?? this.scopeResolver(),
diversityGuard: settings.tagRec.diversityGuard,
sliceSize: TagRecFavService.PAGE_SIZE
});
return out;
}
/** 旁路产出 (深挖 exact / 兜底梯队放行) 走同一套阶段 (spec §4.3「经同一管线」; ctx.poolSize=票06堆深注入, mergeStats=旁路账并入) */
applyStages(items, ctx) {
return applyTagRecStages(items, {
fetchedCount: items.length,
...ctx
}, this.stages);
}
/**
* [tag-rec] 上屏切片严格校验 (negative-slice.ts): 仅对「开了 negative_strict 的源组」条目
* 逐条 view/detail/tag 核查负向命中 —— 逐条请求是组级自愿付费, 默认关;
* 退避态直通 + 5s 时间盒 fail-open; 无 strict 源 => 直通
*/
async loadMore(abortSignal) {
if (abortSignal.aborted || this.autoLoadPaused) return;
const local = !this.partitionBudget;
const budget = this.partitionBudget ?? new TagRecPartitionBudget(abortSignal);
try {
return await this.loadPartitionChecked(abortSignal, budget);
} finally {
if (local) budget.dispose();
}
}
async loadPartitionChecked(abortSignal, budget) {
if (abortSignal.aborted) return;
this.lastFetchSignal = abortSignal;
this.contextSignature = syncFavSharedState();
const oldFilter = sharedFilterSignature;
syncFavFilterState();
if (oldFilter !== sharedFilterSignature) this.partitionHeld.clear();
if (oldFilter !== sharedFilterSignature || sharedCandidates.needsRefilter) this.refilterSharedQueue();
if (!sharedUpSearch.active && this.effectiveFilter().enabled && (this.effectiveFilter().dims.upAllow.enabled || favorites.list.some((g) => g.overrides?.negative?.upAllow?.enabled) || favoritesL1.list.some((g) => g.overrides?.negative?.upAllow?.enabled))) {
const resolve = this.scopeResolver();
if (participatingGroups().some((g) => upSearchMids(resolve, { id: g.id }))) {
resetTagRecLadderState(sharedLadderState);
sharedUpSearch.active = true;
}
}
const epoch = sharedLadderState.epoch;
this.enqueueStock(this.ladder.releasePage(epoch));
if (!this.hasMore) return;
if (this.prefetchRunning) await this.prefetchRunning;
if (this.hasMoreExceptQueue && (!sharedBufferQueue.length || this.needsSupply())) try {
const more = await this.fetchMore(abortSignal);
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch) return;
if (more?.length) this.enqueueStock(more);
} catch (e) {
if (!(e instanceof TagRecSourceFailureError) || !sharedBufferQueue.length) throw e;
}
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch) return;
const delivered = [];
const workingQueue = tagRecPartitionWorkQueue(sharedBufferQueue, this.partitionHeld);
const draftState = sharedFairState.clone();
const consumed = /* @__PURE__ */ new Set();
const turns = [];
const strictStats = [];
const pending = /* @__PURE__ */ new Map();
const policy = computeTagRecFavFilterSignature();
for (let pass = 0; pass < 2 && delivered.length < this.qs.ps; pass++) {
const preview = previewTagRecFairSlice(workingQueue, participatingGroups(), effectiveL1s(), draftState, this.qs.ps - delivered.length, settings.tagRec.diversityGuard, delivered);
if (!preview.items.length) break;
let items = preview.items;
if (this.effectiveFilter().enabled || hasTagRecPartitionRules(items, this.scopeResolver())) {
if (hasTagRecPartitionRules(items, this.scopeResolver())) {
const partitions = await checkTagRecPartitionSlice(items, this.scopeResolver(), budget, {
negative: { pools: tagRecNegativePools() },
relatedFallback: this.relatedFallbackOn()
});
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch || policy !== computeTagRecFavFilterSignature()) return;
items = partitions.items;
for (const [original, updated] of partitions.pending) pending.set(original, updated);
sharedCandidates.record(partitions.updated);
strictStats.push(partitions.stats);
}
const checked = await dropTagRecStrictTagListHits(items, this.effectiveFilter(), this.scopeResolver(), abortSignal, void 0, true, budget);
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch || policy !== computeTagRecFavFilterSignature()) return;
items = checked.items;
strictStats.push(checked.stats);
}
for (const item of preview.items) if (!pending.has(item)) consumed.add(item);
for (let i = workingQueue.length - 1; i >= 0; i--) if (consumed.has(workingQueue[i]) || pending.has(workingQueue[i])) workingQueue.splice(i, 1);
this.runScoreStage(items.filter((item) => item.api === EApiType.TagRec));
const checked = rankTagRecFairSlice(preview.turns, items);
draftState.commit(checked.turns, new Set(checked.items));
turns.push(...checked.turns);
delivered.push(...checked.items);
}
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch || policy !== computeTagRecFavFilterSignature()) return;
for (const [original, updated] of pending) Object.assign(original, updated);
const held = sharedBufferQueue.filter((item) => pending.has(item));
for (let i = sharedBufferQueue.length - 1; i >= 0; i--) if (consumed.has(sharedBufferQueue[i]) || pending.has(sharedBufferQueue[i])) sharedBufferQueue.splice(i, 1);
sharedBufferQueue.push(...held);
const remaining = new Set(sharedBufferQueue.flatMap((item) => item.api === EApiType.TagRec ? [item.bvid] : []));
this.partitionHeld = new Set([...this.partitionHeld, ...held.flatMap((item) => item.api === EApiType.TagRec ? [item.bvid] : [])].filter((bvid) => remaining.has(bvid)));
tagRecFavSession.partitionPending = pending.size;
tagRecFavSession.partitionPaused = pending.size > 0 || budget.exhausted && this.hasMore;
if (tagRecFavSession.partitionPaused) this.partitionPausePolicy = policy;
sharedFairState.commit(turns, new Set(delivered));
const tagStats = tagRecFavSession.filterStats;
for (const stats of strictStats) {
tagStats.dropped += stats.dropped;
for (const [key, count] of Object.entries(stats.byDim)) {
const dim = key;
tagStats.byDim[dim] = (tagStats.byDim[dim] ?? 0) + count;
}
for (const [key, count] of Object.entries(stats.byScope ?? {})) {
tagStats.byScope ??= {};
tagStats.byScope[key] = (tagStats.byScope[key] ?? 0) + count;
}
tagStats.unchecked = (tagStats.unchecked ?? 0) + (stats.unchecked ?? 0) || void 0;
}
this.qs.doReturnItems(delivered);
const out = this.noteDelivered(delivered);
if (!this.autoLoadPaused && delivered.some((item) => item.api === EApiType.TagRec)) this.prefetch(abortSignal);
return out;
}
/**
* [tag-rec] 票 19 (tag-share): 「实际渲染」条数观测点 —— 回赞横幅「tab1 实际渲染 ≥20 条」
* 计数源 = 本方法实际交付给 grid 的切片 (qs 队列切片经严格校验滤后; 分隔符不计)。
* 懒载 ./share-like —— 回赞编排不进 service 静态图 (同 consumeTagRecShareDeepLink 手法)
*/
noteDelivered(items) {
sharedCandidates.delivered(items ?? []);
const enrich = createTagRecAttributionEnricher(favorites.list, favoritesL1.list, themes.list);
const deliveredGroups = /* @__PURE__ */ new Set();
for (const item of items ?? []) {
if (item.api !== EApiType.TagRec) continue;
const sources = enrich(item);
invalidateTagRecCardData(item);
if (!sources.length) continue;
for (const g of sources) deliveredGroups.add(g.name);
}
if (deliveredGroups.size) tagRecFavSession.drawnGroupNames = [...deliveredGroups];
module.import('./share-like-wW2Ll3Z6-DQR4OCtA.js').then((m) => m.noteShareLikeDelivered(items)).catch(() => void 0);
return items;
}
/** 软见底标记 (票 09 横幅消费入口) */
get softBottom() {
return this.produceTracker.softBottom;
}
async fetchMore(abortSignal) {
this.lastFetchSignal = abortSignal;
tagRecFavSession.hardBottom = false;
tagRecFavSession.digPending = sharedUpSearch.pending && sharedUpSearch.hasAnyMore;
tagRecFavSession.noDrawableHint = void 0;
const k = clampTagRecK(settings.tagRec.k);
this.contextSignature = syncFavSharedState();
syncFavFilterState();
this.refilterSharedQueue();
const drawableGroups = participatingGroups().filter((g) => this.hasAllowedSupply(g));
const recallResolve = this.scopeResolver();
const onlyUp = drawableGroups.length > 0 && drawableGroups.every((g) => !!upSearchMids(recallResolve, { id: g.id }));
const stockCounts = this.supplyView();
const needyGroups = drawableGroups.filter((g) => {
if (!stockCounts.needs(tagRecGroupKey({ id: g.id }))) return false;
const up = this.hasUpSupply(g, recallResolve);
return up !== false && (up === void 0 || onlyUp || !sharedUpSearch.pending);
});
if (drawableGroups.length && !needyGroups.length) {
if (onlyUp && !sharedBufferQueue.length) return this.exhaustRound("UP 投稿关键词搜索已到末尾");
return [];
}
const sliceLive = (g) => !!upSearchMids(recallResolve, { id: g.id }) || recallResolve({ id: g.id }, "allV2").poolsEnabled.tagTop;
const digOnlyGroups = needyGroups.filter((g) => !sliceLive(g));
const groups = this.deck.drawUnits(needyGroups.filter(sliceLive), effectiveL1s(), k, viewScope.viewedL1Ids(), void 0, Math.max(PREFETCH_GROUP_WINDOW, k));
if (abortSignal.aborted) return;
const supplyGroups = [...groups, ...digOnlyGroups];
if (!supplyGroups.length) {
const viewedIds = viewScope.viewedL1Ids();
const inRange = effectiveL1s().filter((l) => !viewedIds || viewedIds.has(l.id));
const enableableL1Ids = inRange.filter((l) => !l.enabled).map((l) => l.id);
const reason = !(!participatingGroups().length || participatingGroups().some((g) => this.hasAllowedSupply(g))) ? "池筛选已关闭全部池 · 在「过滤」里开回至少一个池 (右键/设置里开的「仅看」也有同样风险)" : enableableL1Ids.length ? "当前查看范围内暂无可抽标签组 · 一级组未启用 (可启用或临时打开)" : inRange.length ? "当前查看范围内暂无可抽标签组 · 二级组未选中、停用、挂起或尚未添加标签" : "当前查看范围内无标签组 · 换主题或点「全选」恢复";
tagRecFavSession.noDrawableHint = {
reason,
enableableL1Ids
};
this.hasMoreExceptQueue = false;
return [makeTagRecExhaustSeparator(reason)];
}
const aliveGids = new Set(favorites.list.map((g) => g.id));
for (const k of this.tagCursors.keys()) if (!aliveGids.has(k)) this.tagCursors.delete(k);
const pendingCursors = [];
const l1ById = new Map(favoritesL1.list.map((l) => [l.id, l]));
const themeById = new Map(themes.list.map((t) => [t.id, t]));
const entriesByTid = groups.flatMap((g) => {
const sig = g.tag_ids.map((e) => e.id).join(",");
const ent = this.tagCursors.get(g.id);
const { entries, nextCursor } = pickTagIdsForRound(g.tag_ids, ent && ent.sig === sig ? ent.cursor : 0);
pendingCursors.push([
g.id,
nextCursor,
entries.map((e) => e.id),
sig
]);
const label = tagRecAttributionLabelOfGroup(g, l1ById, themeById);
return entries.map((e) => ({
tid: e.id,
keyword: e.name,
groupNames: [g.name],
groupIds: [g.id],
pn: this.dryTids.pageOf(e.id),
...label ? { attributionLabels: [label] } : {}
}));
});
const byTid = /* @__PURE__ */ new Map();
for (const source of entriesByTid) {
const old = byTid.get(source.tid);
if (!old) byTid.set(source.tid, source);
else {
old.groupIds = [.../* @__PURE__ */ new Set([...old.groupIds ?? [], ...source.groupIds ?? []])];
old.groupNames = [.../* @__PURE__ */ new Set([...old.groupNames, ...source.groupNames])];
old.attributionLabels = [.../* @__PURE__ */ new Set([...old.attributionLabels ?? [], ...source.attributionLabels ?? []])];
}
}
const sources = entriesByTid;
const recallPlan = planTagRecRecall({
roundSources: sources,
digSources: deriveGroupCliques(supplyGroups.map((g) => ({
...g,
extra: tagRecSearchExtraOf(recallResolve({ id: g.id }, "allV2"))
})), Infinity),
scopeConsumers: supplyGroups.map((group) => ({
key: { id: group.id },
name: group.name
}))
}, recallResolve);
const fullUpSources = this.recheckPlan().up;
sharedUpSearch.sync(fullUpSources);
const selectedIds = new Set(supplyGroups.map((g) => g.id));
recallPlan.up = fullUpSources.filter((source) => source.groupIds?.some((id) => selectedIds.has(id)));
const normalIds = new Set(recallPlan.dig.flatMap((s) => s.groupIds ?? []));
const normalGroups = supplyGroups.filter((g) => normalIds.has(g.id));
const epoch = sharedLadderState.epoch;
const policy = computeTagRecFavSourceSignature() + computeTagRecFavFilterSignature(true);
const isCurrent = () => sharedLadderState.epoch === epoch && policy === computeTagRecFavSourceSignature() + computeTagRecFavFilterSignature(true) && !this.isStale();
const negTids = supplyGroups.flatMap((g) => {
const eff = recallResolve({ id: g.id }, "tagTop");
return eff.cfg.enabled ? eff.neg.tagIds.map((e) => e.v.id) : [];
});
if (negTids.length) await ensureTagRecNegativePools(negTids, abortSignal);
if (abortSignal.aborted) return;
const round = await runTagRecRecallRound({
sources,
resolve: recallResolve,
plan: recallPlan,
isCurrent,
upPool: sharedUpSearch,
dryTids: this.dryTids,
seenBvids: this.seenBvids,
mergeExisting: (item) => sharedCandidates.mergeIncoming(item, sharedQueueIndex.map(), (items) => this.runScoreStage(items), sharedLadderState.heap, sharedHeapIndex.map()),
hasPending: sharedHasPending,
signal: abortSignal,
stages: this.stages
}).catch((e) => {
if (isRiskControlError(e)) return void 0;
throw e;
});
if (abortSignal.aborted || this.isStale() || sharedLadderState.epoch !== epoch) return;
if (!round) return;
for (const g of groups) sharedSuppliedGroups.add(g.id);
this.dryTids.note(round.sourceStats);
const failedTids = new Set(round.sourceStats.filter((s) => !s.ok).map((s) => s.tid));
for (const [id, c, tids, sig] of pendingCursors) if (tids.some((t) => round.sourceStats.some((s) => s.tid === t && s.ok) && recallPlan.tagTop.some((source) => source.tid === t && source.groupIds?.includes(id))) && !tids.some((t) => failedTids.has(t))) this.tagCursors.set(id, {
cursor: c,
sig
});
if (!round.items.length && failedTids.size) {
if (this.ladder.poolItems.length < 12) this.ladder.poolItems.push(...round.poolItems);
if (this.failedSourceRounds < 2) {
this.failedSourceRounds += 1;
return;
}
this.failedSourceRounds = 0;
throw new TagRecSourceFailureError();
}
this.failedSourceRounds = 0;
if (!round.items.length && this.hasTagTopPagesLeft(recallPlan.tagTop)) return [];
if (recallPlan.up.length && sharedUpSearch.pending && sharedUpSearch.hasAnyMore) tagRecFavSession.digPending = true;
if (onlyUp) {
const more = drawableGroups.some((g) => this.hasUpSupply(g, recallResolve));
if (!round.items.length) {
if (more && !sharedUpSearch.pending) return [];
if (more) {
tagRecFavSession.digPending = true;
return this.exhaustRound();
}
return this.exhaustRound("UP 投稿关键词搜索已到末尾");
}
if (!more || sharedUpSearch.pending) {
this.hasMoreExceptQueue = false;
tagRecFavSession.digPending = more;
}
return round.items;
}
if (!round.hasTagTop && !round.items.length && !recallPlan.dig.length && !recallPlan.archiveConsumers.length) return [];
if (!round.items.length && (!this.needsSupply() && sharedBufferQueue.length || needyGroups.some((g) => {
const up = this.hasUpSupply(g, recallResolve);
return up === void 0 ? recallResolve({ id: g.id }, "tagTop").poolsEnabled.tagTop && (!sharedSuppliedGroups.has(g.id) || g.tag_ids.some((t) => !this.dryTids.isDry(t.id))) : up && !sharedUpSearch.pending;
}))) return [];
let items = round.items;
let produce = items.length;
if (items.length < this.ladder.effTriggerYield() || this.hasStarvedGroup(items)) {
if (items.length) {
this.ladder.preDig({
signal: abortSignal,
prepareStreams: () => this.ladder.ensureCliqueStreams(normalGroups)
}).catch(() => 0);
if (abortSignal.aborted) return;
} else {
if (this.ladder.poolItems.length < 12) this.ladder.poolItems.push(...round.poolItems);
const r = await this.ladder.runLadder({
signal: abortSignal,
prepareStreams: () => this.ladder.ensureCliqueStreams(normalGroups)
});
if (abortSignal.aborted || r.kind === "aborted") return;
switch (r.kind) {
case "items":
items = r.items;
produce = r.items.length || this.qs.bufferQueue.filter((x) => x.api === EApiType.TagRec).length;
break;
case "manual-pending":
tagRecFavSession.digPending = true;
return this.exhaustRound();
case "source-unavailable":
case "retry-later":
tagRecFavSession.noDrawableHint = {
reason: r.reason,
enableableL1Ids: []
};
this.hasMoreExceptQueue = false;
return [makeTagRecExhaustSeparator(r.reason)];
default:
if (sharedUpSearch.hasAnyMore) {
if (sharedUpSearch.pending) {
tagRecFavSession.digPending = true;
return this.exhaustRound();
}
return [];
}
return this.exhaustRound("主题推荐已到池底 · 暂无更多新内容");
}
}
}
trackTagRecRoundProduce(this.produceTracker, produce);
this.ladder.noteProduce(produce);
tagRecFavSession.softBottom = this.produceTracker.softBottom;
return items;
}
/** UI「继续深挖」(横幅待决态动作): 全部未 exhausted 深挖流各 +3 页, 落货重挂自动加载; resolve = 本次/在途挖完 */
digMore() {
if (this.isStale()) return Promise.resolve();
if (sharedUpSearch.pending && sharedUpSearch.hasAnyMore) {
sharedUpSearch.continue();
this.hasMoreExceptQueue = true;
tagRecFavSession.digPending = false;
tagRecFavSession.hardBottom = false;
return Promise.resolve();
}
if (tagRecBackoff.isEngaged) {
toast("退避中,恢复后再挖");
return Promise.resolve();
}
if (this.digRunning) return tagRecRecallScheduler.withForegroundSupply(() => this.digRunning);
if (this.ladder.digRunning) return tagRecRecallScheduler.withForegroundSupply(() => this.ladder.digRunning);
tagRecFavSession.digRunning = true;
this.digRunning = tagRecRecallScheduler.withForegroundSupply(() => this.ladder.digPass("manual", this.lastFetchSignal)).catch((e) => (appWarn("[tag-rec] fav dig failed: %o", e), 0)).finally(() => {
this.digRunning = void 0;
if (!this.ladder.digRunning) tagRecFavSession.digRunning = false;
if (this.isStale()) return;
if (this.ladder.lastDigYield > 0) {
this.hasMoreExceptQueue = true;
tagRecFavSession.digPending = false;
} else if (this.ladder.hasDueStreams()) tagRecFavSession.digPending = true;
else {
tagRecFavSession.digPending = false;
tagRecFavSession.hardBottom = true;
}
});
return this.digRunning;
}
/**
* UI「重新发掘」(硬见底出口): 复位本查询全部深挖流游标 (含持久沉淀的 exhausted/冷却) 并立即重挖一轮。
* 请求有界 = 一次 digPass 的既有窗口 (≤4 流 ×3 页) + 退避/限流纪律; 退避期不点火。
*/
redigQuery() {
if (this.isStale()) return Promise.resolve();
this.ladder.resetDigCursors();
this.hasMoreExceptQueue = true;
tagRecFavSession.hardBottom = false;
tagRecFavSession.digPending = false;
return this.digMore();
}
/** 构造期资格表为空 = 未初始化钉点 (收藏表水合晚于建实例合法); 此后漂移不作 stale */
pinnedEmpty;
/**
* 过期判定 (AUDIT-16): 本实例资格指纹签名 vs 当前签名漂移即 stale ——
* 主题切换/资格集变后的弃实例收尾不写共享 session (digRunning/digPending/hardBottom 镜像)。
* 空表钉点的实例跨过水合期不算 stale —— 指纹里无 '' 哨兵 (theme=:::: 也是有效串),
* 靠构造期 pinnedEmpty 快照判「未初始化」
*/
isStale() {
if (this.pinnedEmpty) return false;
return this.contextSignature !== computeTagRecFavContextSignature();
}
/**
* 深翻页未完: 未干源里仍有页可翻 (pageOf < 上限) —— 零产出轮不判见底, 下一轮自动进页。
* 请求成本不变 (原本每轮也在重复打 pn=1); 到顶/空页由 dryTids 标干后自然回到原见底语义。
*/
hasTagTopPagesLeft(sources) {
return sources.some((source) => !this.dryTids.isDry(source.tid) && this.dryTids.pageOf(source.tid) < 3);
}
/**
* 硬见底/停轮 (spec §4.4): content 给了 => [separator] + hardBottom 镜像;
* 无 content = 深挖待决停轮 (票 13 manual-pending: hasMoreExceptQueue=false 截停自动加载, 不算见底)
*/
exhaustRound(content) {
this.hasMoreExceptQueue = false;
if (!content) return [];
tagRecFavSession.hardBottom = true;
return [makeTagRecExhaustSeparator(content)];
}
});})}}));
System.register("./tag-rec-custom-xQM0aUVF-CWj41HxL.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./discovery-policy-BBjOB0CZ-BWQIVxg7.js','./versioned-cache-6Gx4pmyX-Dad7R-K0.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports,module){'use strict';var tagRecBackoff,isRiskControlError,appWarn,__toESM,require_react,require_react_dom,BaseTabService,jsx,EApiType,getCachedTagRecVideoEvidence,tagRecPoolSizes,TAG_REC_ALL_POOL_KEYS,tagRecSourceClaims,poolOfVia,tagRecDeliverable,parseTagInput,deriveGroupCliques,tagRecSearchExtraOf,normalizeTagName,tagRecRecallScheduler,runTagRecFilter,settings,resolveTagRecItemScoreParams,resolveTagRecSeedMinScore,TagRecTidDryTracker,createTagRecProduceTracker,createTagRecScopeResolver,tagRecGroupKey,pickTagGroupsByIds,runTagRecScoreStage,applyTagRecStages,tagRecConditionAtoms,evalTagRecConditionAtom,tagRecConditionKey,validateTagRecCombination,createTagRecAttributionEnricher,evalTagRecItem,toast,hasTagRecCombination,makeTagRecExhaustSeparator,TagRecSourceFailureError,trackTagRecRoundProduce,perfMark,dedupeTagIdEntries,summarizeGroupNames,tagRecCombinationSignature,computeTagRecFilterSignature,cloneDeep,addTagRecCondition,evalTagRecCombination,projectTagRecActiveClaims,tagRecChainKeysOf,tagRecPoolsForChain,evalTagRecChain,tagRecPoolScopeKey,invalidateTagRecCardData,useOnRefresh,useRecSelfContext,usePopupContainer,theme,useSnapshot,fetchTagInfoById,fetchTagInfoByName,tagRecConditionAtom,subscribe,PARTITION_NAME_TO_PARENT,fetchAllV2Search,fetchSuggest,normalizeAllV2Item,tagRecCombinationOf,tagRecCombinationTagIds,tagRecConditionLabel,describeTagRecCondition,jsxs,button_default,Input,showModalSettings,Select,Fragment,Popconfirm,Checkbox,isTagRecCombinationIntersection,TAG_REC_CONDITION_FIELDS,Popover,Tooltip,Modal,nameTagRecCombination,Dropdown,findTagRecCondition,tagRecCustomSession,mergeTagRecVideoEvidence,materializeTagRecSessionFilter,favorites,favoritesL1,tagRecCombinationBlocks,pushExpandedIds,sediment,tagRecSharedSession,tagRecWorkbench,parseTagRecDeepLink,switchTagRecGroupContext,beginTagRecCustomQuery,favoritesStore,favoritesL1Store,themesStore,tagRecDiscoveryPreferences,tagRecDiscoveryUi,tagRecHoldReasonText,useTagRecDiscoveryShelf,useTagRecDiscoveryToolbar,useTagRecDiscoveryHeader,useTagRecDiscoveryMeta,activateTagRecWorkbench,currentTagRecTrial,openTagRecFavorite,newTagRecTrial,requestTagRecMaterialSearch,addTagRecWorkingCondition,tagRecMaterialWords,tagRecFieldTones,aggregateTagRecCooccur,tagRecStoresHydrated,Typography,switchTagRecTrial,closeTagRecTrial,openTagRecRecent,editTagRecWorkingGroup,saveTagRecWorkingGroup,unblockTagRecConditionGlobally,undoTagRecWorkingGroup,TagRecDiscoveryPools,Segmented,commitTagRecMaterialDraft,drawer_default,TagRecVideoMaterials,TAG_REC_POOL_LABELS,removeTagRecWorkingCondition,exploreTagRecTagOnly,excludeTagRecWorkingCondition,blockTagRecConditionGlobally,moveTagRecWorkingCondition,importTagRecFavoriteCombinations,tagRecMaterialKey,selectTagRecMaterial,exploreTagRecMaterialOnly,stageTagRecMaterial,TAG_REC_DROP_DIM_LABELS,mergeCandidateRows,pickTagIdsForRound,resolveAnchor,resolveCandidateRows,analyzeAllV2Videos,mergeTagRecCooccurAliases,TagRecUpSearchPool,TagRecPartitionBudget,TagRecDeferredCandidates,tagRecNegativePools,planTagRecRecall,tagRecRecheckOptions,TagRecFallbackLadder,FALLBACK_LADDER_STRATEGY_AGGRESSIVE,createTagRecSeenStage,tagRecConsumers,tagRecSupplyView,upSearchMids,refilterTagRecStock,hasTagRecPartitionRules,tagRecPartitionWorkQueue,spliceTagRecAtomFairSlice,checkTagRecPartitionSlice,dropTagRecStrictTagListHits,tagRecPartitionHeldNext,ensureTagRecNegativePools,runTagRecRecallRound,tagRecSourceSignature,resetTagRecLadderState,WeightedTurns,tagRecSeenStage,tagRecReopenedTopTids,tagRecVersionedArray,newTagRecLadderState,createTagRecBvidIndex,tagRecVersionKey,versionedSlot,FilterPopover,TagRecBanner,tagRecHeapVersion;return{setters:[function(module){tagRecBackoff=module.t;isRiskControlError=module.i;appWarn=module.a;__toESM=module._;require_react=module.r;require_react_dom=module.b;},function(module){BaseTabService=module.cf;jsx=module.a;EApiType=module.ck;getCachedTagRecVideoEvidence=module.cG;tagRecPoolSizes=module.cH;TAG_REC_ALL_POOL_KEYS=module.cq;tagRecSourceClaims=module.cr;poolOfVia=module.cI;tagRecDeliverable=module.cJ;parseTagInput=module.cK;deriveGroupCliques=module.cg;tagRecSearchExtraOf=module.ch;normalizeTagName=module.bB;tagRecRecallScheduler=module.ci;runTagRecFilter=module.cj;settings=module.t;resolveTagRecItemScoreParams=module.cm;resolveTagRecSeedMinScore=module.co;TagRecTidDryTracker=module.cl;createTagRecProduceTracker=module.cp;createTagRecScopeResolver=module.cD;tagRecGroupKey=module.cn;pickTagGroupsByIds=module.cL;runTagRecScoreStage=module.cs;applyTagRecStages=module.ct;tagRecConditionAtoms=module.b_;evalTagRecConditionAtom=module.cM;tagRecConditionKey=module.cN;validateTagRecCombination=module.cO;createTagRecAttributionEnricher=module.cv;evalTagRecItem=module.cP;toast=module.ak;hasTagRecCombination=module.aE;makeTagRecExhaustSeparator=module.cy;TagRecSourceFailureError=module.cu;trackTagRecRoundProduce=module.cA;perfMark=module.cQ;dedupeTagIdEntries=module.cR;summarizeGroupNames=module.cS;tagRecCombinationSignature=module.cT;computeTagRecFilterSignature=module.X;cloneDeep=module.aQ;addTagRecCondition=module.c1;evalTagRecCombination=module.cU;projectTagRecActiveClaims=module.cV;tagRecChainKeysOf=module.cW;tagRecPoolsForChain=module.cX;evalTagRecChain=module.cY;tagRecPoolScopeKey=module.cZ;invalidateTagRecCardData=module.cw;useOnRefresh=module.cB;useRecSelfContext=module.R;usePopupContainer=module.cC;theme=module.bo;useSnapshot=module.u;fetchTagInfoById=module.bF;fetchTagInfoByName=module.bG;tagRecConditionAtom=module.bZ;subscribe=module.c_;PARTITION_NAME_TO_PARENT=module.b$;fetchAllV2Search=module.c$;fetchSuggest=module.d0;normalizeAllV2Item=module.d1;tagRecCombinationOf=module.d2;tagRecCombinationTagIds=module.d3;tagRecConditionLabel=module.d4;describeTagRecCondition=module.d5;jsxs=module.j;button_default=module.n;Input=module.Y;showModalSettings=module.cE;Select=module.S;Fragment=module.F;Popconfirm=module.a2;Checkbox=module._;isTagRecCombinationIntersection=module.d6;TAG_REC_CONDITION_FIELDS=module.bY;Popover=module.az;Tooltip=module.d7;Modal=module.$;nameTagRecCombination=module.bi;Dropdown=module.bh;findTagRecCondition=module.bK;},function(module){tagRecCustomSession=module.ad;mergeTagRecVideoEvidence=module.ae;materializeTagRecSessionFilter=module.$;favorites=module.d;favoritesL1=module.f;tagRecCombinationBlocks=module.E;pushExpandedIds=module.ac;sediment=module.y;tagRecSharedSession=module.t;tagRecWorkbench=module.af;parseTagRecDeepLink=module.ag;switchTagRecGroupContext=module.ah;beginTagRecCustomQuery=module.ab;favoritesStore=module.n;favoritesL1Store=module.o;themesStore=module.p;tagRecDiscoveryPreferences=module.ai;tagRecDiscoveryUi=module.aj;tagRecHoldReasonText=module.ak;useTagRecDiscoveryShelf=module.al;useTagRecDiscoveryToolbar=module.am;useTagRecDiscoveryHeader=module.an;useTagRecDiscoveryMeta=module.ao;activateTagRecWorkbench=module.ap;currentTagRecTrial=module.aq;openTagRecFavorite=module.ar;newTagRecTrial=module.as;requestTagRecMaterialSearch=module.at;addTagRecWorkingCondition=module.au;tagRecMaterialWords=module.av;tagRecFieldTones=module.aw;aggregateTagRecCooccur=module.ax;tagRecStoresHydrated=module.ay;Typography=module.e;switchTagRecTrial=module.az;closeTagRecTrial=module.aA;openTagRecRecent=module.aB;editTagRecWorkingGroup=module.aC;saveTagRecWorkingGroup=module.aD;unblockTagRecConditionGlobally=module.aE;undoTagRecWorkingGroup=module.aF;TagRecDiscoveryPools=module.aG;Segmented=module.N;commitTagRecMaterialDraft=module.aH;drawer_default=module.aI;TagRecVideoMaterials=module.aJ;TAG_REC_POOL_LABELS=module.M;removeTagRecWorkingCondition=module.aK;exploreTagRecTagOnly=module.aL;excludeTagRecWorkingCondition=module.aM;blockTagRecConditionGlobally=module.aN;moveTagRecWorkingCondition=module.aO;importTagRecFavoriteCombinations=module.aP;tagRecMaterialKey=module.aQ;selectTagRecMaterial=module.aR;exploreTagRecMaterialOnly=module.aS;stageTagRecMaterial=module.aT;TAG_REC_DROP_DIM_LABELS=module.Q;},function(module){mergeCandidateRows=module.o;pickTagIdsForRound=module.k;resolveAnchor=module.q;resolveCandidateRows=module.r;analyzeAllV2Videos=module.v;mergeTagRecCooccurAliases=module.x;},function(module){TagRecUpSearchPool=module.T;TagRecPartitionBudget=module.a;TagRecDeferredCandidates=module.b;tagRecNegativePools=module.c;planTagRecRecall=module.p;tagRecRecheckOptions=module.t;TagRecFallbackLadder=module.d;FALLBACK_LADDER_STRATEGY_AGGRESSIVE=module.F;createTagRecSeenStage=module.e;tagRecConsumers=module.I;tagRecSupplyView=module.f;upSearchMids=module.u;refilterTagRecStock=module.r;hasTagRecPartitionRules=module.l;tagRecPartitionWorkQueue=module.i;spliceTagRecAtomFairSlice=module.J;checkTagRecPartitionSlice=module.m;dropTagRecStrictTagListHits=module.n;tagRecPartitionHeldNext=module.K;ensureTagRecNegativePools=module.q;runTagRecRecallRound=module.s;tagRecSourceSignature=module.v;resetTagRecLadderState=module.h;WeightedTurns=module.W;tagRecSeenStage=module.E;tagRecReopenedTopTids=module.w;tagRecVersionedArray=module.x;newTagRecLadderState=module.C;createTagRecBvidIndex=module.D;tagRecVersionKey=module.B;versionedSlot=module.A;FilterPopover=module.y;TagRecBanner=module.z;tagRecHeapVersion=module.G;},null,null,null,null,null],execute:(function(){exports({clearTagRecCustomSharedSeenBvids:clearTagRecCustomSharedSeenBvids,computeTagRecCustomContextSignature:computeTagRecCustomContextSignature,computeTagRecCustomFilterSignature:computeTagRecCustomFilterSignature,computeTagRecCustomSourceSignature:computeTagRecCustomSourceSignature,resetTagRecCustomSharedState:resetTagRecCustomSharedState,tagRecCustomQueryGroups:tagRecCustomQueryGroups,tagRecCustomSharedBufferQueue:tagRecCustomSharedBufferQueue,tagRecCustomSharedDeliveryReplay:tagRecCustomSharedDeliveryReplay,tagRecCustomSharedRemovedAtoms:tagRecCustomSharedRemovedAtoms,tagRecCustomSharedSeenBvids:tagRecCustomSharedSeenBvids,tagRecDeliveryReplayKey:tagRecDeliveryReplayKey,tagRecLocalConditionImpact:tagRecLocalConditionImpact});/** 仅看是原查询内的交集;相关来源须有标签或标题证据,归因名不能证明命中查询。 */
function createTagRecQueryMatcher(entries) {
const ids = new Set(entries.map((entry) => entry.id));
const names = new Set(entries.map((entry) => normalizeTagName(entry.name)).filter(Boolean));
const titleTerms = [...names].filter((name) => name.length >= 2);
return (item) => {
if (item.source.via === "tag-top" || item.source.via === "all-v2-exact") return true;
if (item.source.via === "up-search") return names.has(normalizeTagName(item.source.queryKeyword ?? ""));
if (item.ownTagIds?.some((id) => ids.has(id))) return true;
if (item.ownTagNames?.some((name) => names.has(normalizeTagName(name)))) return true;
const title = normalizeTagName(item.title);
return titleTerms.some((term) => title.includes(term));
};
}
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom(), 1);
/** 秒 -> 人类时长串: 3660→「1时1分」, 600→「10分」, 95→「1分35秒」, 45→「45秒」 */
function formatDurationSec(s) {
if (s >= 3600) return s % 3600 ? `${Math.floor(s / 3600)}时${Math.floor(s % 3600 / 60)}分` : `${s / 3600}时`;
if (s >= 60) return s % 60 ? `${Math.floor(s / 60)}分${s % 60}秒` : `${s / 60}分`;
return `${s}秒`;
}
function describeDim(key, rule) {
const label = TAG_REC_DROP_DIM_LABELS[key];
if (!rule.enabled) return `${label} · 关闭`;
if ("value" in rule) return `${label} · ${key.endsWith("Rate") ? `${+(rule.value * 100).toFixed(3)}%` : rule.value}`;
if ("list" in rule) return `${label} · ${rule.list.map((value) => typeof value === "string" ? value : value.name).join("、") || "空名单"}`;
if ("min" in rule) return `时长 · ${rule.min ? `${formatDurationSec(rule.min)}起` : ""}${rule.max ? `至${formatDurationSec(rule.max)}` : ""}`;
const windows = {
today: "今天",
d7: "7天内",
d30: "30天内",
d180: "180天内",
days: `${rule.days}天内`,
range: "指定日期"
};
return `发布时间 · ${rule.win ? windows[rule.win] : ""}${rule.from ? ` ${(/* @__PURE__ */ new Date(rule.from * 1e3)).toLocaleDateString()}起` : ""}${rule.to ? ` 至${(/* @__PURE__ */ new Date(rule.to * 1e3)).toLocaleDateString()}` : ""}${rule.tod ? " · 指定时段" : ""}`;
}
var scoreLabels = {
view: "播放",
viewLog10: "播放对数",
likeRate: "点赞率",
favRate: "收藏率",
coinRate: "投币率",
interactRate: "互动率"
};
/** Each explicit choice can be borrowed or returned to inheritance separately. */
function tagRecCombinationFilterChoices(patch) {
if (!patch) return [];
const out = [];
for (const key of [
"enabled",
"strictMode",
"smallPoolThreshold"
]) if (patch[key] !== void 0) out.push({
key,
label: key === "smallPoolThreshold" ? `小池线 · ${patch[key]}` : `${key === "enabled" ? "辅助过滤" : "缺字段严格过滤"} · ${patch[key] ? "开" : "关"}`,
patch: {
dims: {},
[key]: patch[key]
}
});
for (const [key, dim] of Object.entries(patch.dims)) out.push({
key: `dims.${key}`,
label: describeDim(key, dim),
patch: { dims: { [key]: dim } }
});
for (const half of ["weights", "anchors"]) for (const [key, value] of Object.entries(patch.score?.[half] ?? {})) out.push({
key: `score.${half}.${key}`,
label: `${scoreLabels[key]}${half === "weights" ? "权重" : "参照值"} · ${value}`,
patch: {
dims: {},
score: { [half]: { [key]: value } }
}
});
for (const [key, value] of Object.entries(patch.poolsEnabled ?? {})) out.push({
key: `poolsEnabled.${key}`,
label: `${TAG_REC_POOL_LABELS[key]} · ${value ? "开启" : "关闭"}`,
patch: {
dims: {},
poolsEnabled: { [key]: value }
}
});
for (const [pool, cell] of Object.entries(patch.pools ?? {})) {
const nested = tagRecCombinationFilterChoices({
...cell,
dims: cell.dims ?? {}
});
for (const choice of nested) out.push({
key: `pools.${pool}.${choice.key}`,
label: `${TAG_REC_POOL_LABELS[pool]} · ${choice.label}`,
patch: {
dims: {},
pools: { [pool]: choice.patch }
}
});
}
return out;
}
function removeTagRecCombinationFilterChoice(patch, key) {
const path = key.split(".");
const remove = (value, at) => {
const field = path[at];
if (at === path.length - 1) delete value[field];
else if (value[field] && typeof value[field] === "object") {
const child = value[field];
remove(child, at + 1);
if (!Object.keys(child).length) delete value[field];
}
};
remove(patch, 0);
patch.dims ??= {};
}
/** 按出现顺序摊平条件; 每个条件的 join 由它所在组的 mode 决定 (首个条件继承组的入边)。 */
function collectSlots(node, join, out) {
node.children.forEach((child, index) => {
const childJoin = index === 0 ? join : node.mode === "all" ? "and" : "or";
if (child.kind === "atom") out.push({
atom: child,
join: childJoin
});
else collectSlots(child, childJoin, out);
});
}
function tagRecCombinationPills(combination, { limit = 3, excludeLimit = 1 } = {}) {
const slots = [];
collectSlots(combination.include, void 0, slots);
const items = [];
for (const slot of slots.slice(0, limit)) {
if (items.length && slot.join) items.push({
kind: "join",
key: `${slot.atom.id}:join`,
label: slot.join === "and" ? "且" : "或"
});
items.push({
kind: "pill",
key: slot.atom.id,
field: slot.atom.field,
label: tagRecConditionLabel(slot.atom)
});
}
if (slots.length > limit) items.push({
kind: "more",
key: "include:more",
label: `+${slots.length - limit}`
});
const excluded = combination.exclude.slice(0, excludeLimit);
if (excluded.length) {
items.push({
kind: "join",
key: "exclude:join",
label: "不看"
});
for (const atom of excluded) items.push({
kind: "exclude",
key: atom.id,
label: tagRecConditionLabel(atom)
});
if (combination.exclude.length > excluded.length) items.push({
kind: "more",
key: "exclude:more",
label: `+${combination.exclude.length - excluded.length}`
});
}
return items;
}
/** Q9: 「继续挖共现」翻页游标 (按归一化输入词) —— 每点一次拉下一页, 共现词累加并入; 面板关闭不丢。 */
var cooccurDigCursor = /* @__PURE__ */ new Map();
/** 已计入共现统计的 bvid (按归一化输入词) —— 重复拉同页/翻页重叠不再重复累计共现频次 */
var cooccurSeenBvids = /* @__PURE__ */ new Map();
var COOCCUR_KEY_CAP = 32;
/** 共现账本 keyed map 写入: 同 key 覆盖移到最新位 + 上限 LRU, 防查询词无界增长 */
function cooccurLedgerSet(map, key, value) {
map.delete(key);
map.set(key, value);
while (map.size > COOCCUR_KEY_CAP) map.delete(map.keys().next().value);
}
function groupsOf(node) {
return [node, ...node.children.flatMap((child) => child.kind === "group" ? groupsOf(child) : [])];
}
/** 组显示名: 自动字段组 -> 字段中文名; 用户建的组 -> 自定义名; 根 -> 整体条件 */
function groupLabel(group) {
return group.name ? TAG_REC_CONDITION_FIELDS[group.name] || group.name : "整体条件";
}
function TagRecWorkbenchView({ service }) {
const onRefresh = useOnRefresh();
const { recSharedEmitter } = useRecSelfContext();
const [continuing, setContinuing] = (0, import_react.useState)(false);
const continueDiscovery = async () => {
setContinuing(true);
try {
await recSharedEmitter.emit("tag-rec:continue");
} finally {
setContinuing(false);
}
};
const { ref, getPopupContainer } = usePopupContainer();
const { token } = theme.useToken();
const session = useSnapshot(tagRecCustomSession);
const memory = useSnapshot(tagRecWorkbench);
const globalBlocks = useSnapshot(tagRecCombinationBlocks);
const library = useSnapshot(favoritesStore);
const parents = useSnapshot(favoritesL1Store);
const themesSnap = useSnapshot(themesStore);
const { bypassSeen } = session;
const compatible = memory.version === 1 && Array.isArray(memory.trials) && Array.isArray(memory.recent);
const trials = compatible ? memory.trials.filter((entry) => typeof entry?.id === "string" && typeof entry.name === "string" && validateTagRecCombination(entry.group?.combination)) : [];
const trial = trials.find((entry) => entry.id === memory.activeId);
const combination = session.workingGroup?.combination;
const [query, setQuery] = (0, import_react.useState)("");
const [field, setField] = (0, import_react.useState)("tag");
const [source, setSource] = (0, import_react.useState)("全部");
const [materialLimit, setMaterialLimit] = (0, import_react.useState)(50);
const [materials, setMaterials] = (0, import_react.useState)([]);
const [busy, setBusy] = (0, import_react.useState)(false);
const [advanced, setAdvanced] = (0, import_react.useState)(false);
const [target, setTarget] = (0, import_react.useState)();
const targetGroup = target && combination ? groupsOf(combination.include).find((group) => group.id === target) : void 0;
const [saveName, setSaveName] = (0, import_react.useState)("");
const [borrow, setBorrow] = (0, import_react.useState)();
const [shelfQuery, setShelfQuery] = (0, import_react.useState)("");
const [shelfSections, setShelfSections] = (0, import_react.useState)({
trials: true,
favorites: true,
recent: false
});
const shelfId = (0, import_react.useId)();
const shelfSearching = !!shelfQuery.trim();
const [materialOpen, setMaterialOpen] = (0, import_react.useState)(false);
const [saving, setSaving] = (0, import_react.useState)(false);
const [committing, setCommitting] = (0, import_react.useState)(false);
const [batch, setBatch] = (0, import_react.useState)(false);
const [recentLimit, setRecentLimit] = (0, import_react.useState)(8);
const display = useSnapshot(tagRecDiscoveryPreferences);
const discoveryUi = useSnapshot(tagRecDiscoveryUi);
const sedimentMap = useSnapshot(sediment.map);
const { riskControl } = useSnapshot(tagRecSharedSession);
const holdReason = tagRecHoldReasonText(session, riskControl);
const picks = trial?.pending ?? [];
const shelfTarget = useTagRecDiscoveryShelf();
const searchRef = (0, import_react.useRef)(void 0);
const searchRevision = (0, import_react.useRef)(0);
const onRefreshRef = (0, import_react.useRef)(onRefresh);
onRefreshRef.current = onRefresh;
const toolbarTarget = useTagRecDiscoveryToolbar();
const headerTarget = useTagRecDiscoveryHeader();
const metaTarget = useTagRecDiscoveryMeta();
(0, import_react.useEffect)(() => {
if (shelfSearching) setShelfSections({
trials: true,
favorites: true,
recent: true
});
}, [shelfSearching]);
(0, import_react.useEffect)(() => {
let active = true;
const input = tagRecCustomSession.inputText;
const selected = tagRecCustomSession.selectedGroupIds[0];
activateTagRecWorkbench().then(() => {
if (!active) return;
if (selected) {
const favorite = favoritesStore.list.find((g) => g.id === selected);
if (favorite && (!tagRecCustomSession.workingGroup || currentTagRecTrial()?.originId !== selected)) openTagRecFavorite(favorite);
}
if (input) {
if (hasTagRecCombination(tagRecCustomSession.workingGroup?.combination)) newTagRecTrial();
requestTagRecMaterialSearch(input);
const parsed = parseTagInput(input);
const id = currentTagRecTrial()?.id;
const revision = tagRecCustomSession.queryRevision;
(parsed?.kind === "id" ? fetchTagInfoById(parsed.id) : fetchTagInfoByName(input)).then((info) => {
if (active && info && currentTagRecTrial()?.id === id && tagRecCustomSession.queryRevision === revision) addTagRecWorkingCondition(tagRecConditionAtom("tag", {
id: info.id,
name: info.name,
tag_type: info.tag_type
}));
}).catch(() => void 0);
}
onRefreshRef.current("refresh");
}).catch(() => {
toast("读取本地试验失败,原数据未覆盖,请刷新重试");
});
return () => {
active = false;
searchRef.current?.abort();
};
}, []);
(0, import_react.useEffect)(() => {
let previous = JSON.stringify([
tagRecCustomSession.workingGroup,
tagRecCustomSession.allowUnverified,
tagRecCombinationBlocks.list,
tagRecCombinationBlocks.invalid
]);
let timer;
const changed = () => {
const next = JSON.stringify([
tagRecCustomSession.workingGroup,
tagRecCustomSession.allowUnverified,
tagRecCombinationBlocks.list,
tagRecCombinationBlocks.invalid
]);
if (next === previous) return;
previous = next;
clearTimeout(timer);
timer = setTimeout(() => onRefreshRef.current("refresh"), 250);
};
const stops = [subscribe(tagRecCustomSession, changed), subscribe(tagRecCombinationBlocks, changed)];
return () => {
clearTimeout(timer);
stops.forEach((stop) => stop());
};
}, []);
(0, import_react.useEffect)(() => {
setTarget(void 0);
setSaveName("");
setBorrow(void 0);
searchRef.current?.abort();
setBusy(false);
setMaterialOpen(false);
setSaving(false);
setQuery("");
setMaterials([]);
searchRevision.current++;
}, [memory.activeId]);
(0, import_react.useEffect)(() => {
const pending = tagRecDiscoveryUi.materialQuery;
if (!pending) return;
tagRecDiscoveryUi.materialQuery = void 0;
setField(pending.field);
setQuery(pending.text);
findMaterials(pending.text, pending.field);
}, [discoveryUi.materialQuery]);
const select = async (material, asExclude = material.exclude ?? false) => {
const trialId = tagRecWorkbench.activeId;
const queryRevision = tagRecCustomSession.queryRevision;
let atom = material.atom && cloneDeep(material.atom);
if (!atom) {
const info = material.tagId ? await fetchTagInfoById(material.tagId).catch(() => void 0) : await fetchTagInfoByName(material.name).catch(() => void 0);
if (!info) {
toast("暂时未解析到这个标签,可稍后重试或作为标题/简介词选入");
return;
}
atom = tagRecConditionAtom("tag", {
id: info.id,
name: info.name,
tag_type: info.tag_type
});
}
if (trialId !== tagRecWorkbench.activeId || queryRevision !== tagRecCustomSession.queryRevision) return;
if (target && !findTagRecCondition(tagRecCustomSession.workingGroup.combination.include, target)) {
toast("目标条件组已移出,请重新选择");
return;
}
if (asExclude === "global") {
blockTagRecConditionGlobally(atom);
toast("已加入全局排除,可在此工作台直接移除");
} else if (asExclude) excludeTagRecWorkingCondition(atom);
else addTagRecWorkingCondition(atom, target);
};
const findMaterials = async (raw = query, fieldSel = field) => {
const input = raw.trim();
if (!input) return;
setMaterialOpen(true);
searchRef.current?.abort();
const controller = new AbortController();
searchRef.current = controller;
const revision = ++searchRevision.current;
setBusy(true);
const literal = [];
if ((fieldSel === "title" || fieldSel === "desc" || fieldSel === "all") && normalizeTagName(input).length >= 2) for (const kind of fieldSel === "all" ? ["title", "desc"] : [fieldSel]) literal.push({
atom: tagRecConditionAtom(kind, input),
name: input,
source: "输入"
});
if (fieldSel === "up") {
const mid = /^\d+$/.test(input) ? Number(input) : void 0;
literal.push({
atom: tagRecConditionAtom("up", {
name: mid ? `UID ${mid}` : input,
...mid && { mid }
}),
name: input,
source: "输入"
});
}
if (fieldSel === "partition") {
const part = PARTITION_NAME_TO_PARENT[input];
if (part) literal.push({
atom: tagRecConditionAtom("partition", {
name: input,
...part,
level: "parent"
}),
name: input,
source: "输入"
});
else toast("请选择已知分区;视频字段中也可直接选子分区");
}
if (fieldSel !== "tag" && fieldSel !== "all") {
setMaterials(literal);
setSource("全部");
setBusy(false);
return;
}
const parsed = parseTagInput(input);
const results = await Promise.allSettled([
parsed?.kind === "id" ? fetchTagInfoById(parsed.id, controller.signal) : fetchTagInfoByName(input, controller.signal),
fetchAllV2Search(input, {
page: 1,
signal: controller.signal
}),
fetchSuggest(input, controller.signal)
]);
if (controller.signal.aborted || revision !== searchRevision.current) return;
const found = [...literal];
const info = results[0].status === "fulfilled" ? results[0].value : void 0;
if (info) found.unshift({
atom: tagRecConditionAtom("tag", {
id: info.id,
name: info.name,
tag_type: info.tag_type
}),
name: info.name,
source: "解析"
});
if (results[1].status === "fulfilled") {
const videos = results[1].value;
cooccurLedgerSet(cooccurDigCursor, normalizeTagName(input), 1);
cooccurLedgerSet(cooccurSeenBvids, normalizeTagName(input), new Set(videos.map((v) => v.bvid).filter((b) => !!b)));
const analysis = analyzeAllV2Videos(videos, normalizeTagName(input));
for (const alias of analysis.cooccurAliases.slice(0, 24)) found.push({
name: alias.name,
source: "共现",
count: alias.count
});
for (const raw of videos.slice(0, 8)) {
const item = normalizeAllV2Item(raw, {
via: "all-v2-exact",
groupNames: [],
srcRank: 0
});
if (item.owner?.name) found.push({
atom: tagRecConditionAtom("up", {
name: item.owner.name,
mid: item.owner.mid
}),
name: item.owner.name,
source: "视频"
});
for (const word of tagRecMaterialWords(item.title).slice(0, 16)) if (normalizeTagName(word).length >= 2) found.push({
atom: tagRecConditionAtom("title", word),
name: word,
source: "视频"
});
for (const word of tagRecMaterialWords(item.desc ?? "").slice(0, 16)) if (normalizeTagName(word).length >= 2) found.push({
atom: tagRecConditionAtom("desc", word),
name: word,
source: "视频"
});
if (item.tid) found.push({
atom: tagRecConditionAtom("partition", {
name: raw.typename || `分区 ${item.tid}`,
v1: item.tid,
level: "leaf"
}),
name: raw.typename || `分区 ${item.tid}`,
source: "视频"
});
}
}
if (results[2].status === "fulfilled") for (const name of results[2].value.slice(0, 10)) found.push({
name,
source: "解析"
});
if (fieldSel === "tag" || fieldSel === "all") found.push({
name: input,
source: "输入"
});
const parsedTags = new Map(found.flatMap((material) => material.atom?.field === "tag" ? [[normalizeTagName(material.name), material.atom]] : []));
const uniqueMap = /* @__PURE__ */ new Map();
for (const material of found) {
const atom = material.atom ?? parsedTags.get(normalizeTagName(material.name));
const key = atom ? tagRecConditionKey(atom) : `tag:name:${normalizeTagName(material.name)}`;
const previous = uniqueMap.get(key);
uniqueMap.set(key, {
...material,
...previous,
atom,
count: previous?.count ?? material.count
});
}
const unique = [...uniqueMap.values()];
setMaterials(unique);
setSource("全部");
setBusy(false);
if (!unique.length) toast("暂未找到素材,可直接选标题词、简介词或 UP UID");
};
/** Q9: 「继续挖共现」—— 锚点 = 当前组合的 tag 条件全集 (以用户已选为准); 每点一次给
* 「挖得最少」的锚点拉下一页搜索结果, 共现词累加并入快照行 (新词追加/同词计数相加);
* 语料行不受影响。无 tag 条件时退回输入词 (旧行为)。
* 生命周期与首查同轨 (searchRef/searchRevision): 换词/换试验/重查后在途结果落定即弃;
* 失败页不推进游标 (下点重试同页); 全部锚点挖到 ALLV2_PAGE_CAP 即终态不再发请求;
* 共现统计按锚点内 bvid 去重 —— 同页重拉或翻页重叠不再反复累计。 */
const digCooccurMore = async () => {
const alive = cooccurAnchors.filter((a) => (cooccurDigCursor.get(a.norm) ?? 0) < 50);
if (!alive.length) {
toast(`已挖到搜索末页(每词共 50 页),可换词重查`);
return;
}
const target = alive.reduce((a, b) => (cooccurDigCursor.get(a.norm) ?? 0) <= (cooccurDigCursor.get(b.norm) ?? 0) ? a : b);
const key = target.norm;
const cursor = cooccurDigCursor.get(key) ?? 0;
searchRef.current?.abort();
const controller = new AbortController();
searchRef.current = controller;
const revision = ++searchRevision.current;
setBusy(true);
try {
const page = cursor + 1;
const res = await fetchAllV2Search(target.name, {
page,
signal: controller.signal
}).then((v) => ({ v }), () => void 0);
if (controller.signal.aborted || revision !== searchRevision.current) return;
if (!res) {
toast(`「${target.name}」第 ${page} 页拉取失败,请重试`);
return;
}
cooccurLedgerSet(cooccurDigCursor, key, page);
const videos = res.v;
const seen = new Set(cooccurSeenBvids.get(key));
const fresh = videos.filter((v) => !v.bvid || !seen.has(v.bvid));
for (const v of fresh) if (v.bvid) seen.add(v.bvid);
cooccurLedgerSet(cooccurSeenBvids, key, seen);
const analysis = analyzeAllV2Videos(fresh, key);
setMaterials((prev) => [...prev.filter((material) => material.source !== "共现"), ...mergeTagRecCooccurAliases(prev.filter((material) => material.source === "共现"), analysis.cooccurAliases.slice(0, 24)).sort((a, b) => b.count - a.count).map((alias) => ({
name: alias.name,
source: "共现",
count: alias.count
}))]);
toast(videos.length ? `已拉「${target.name}」第 ${page} 页 · 共现词已累加并入` : `「${target.name}」第 ${page} 页无结果,可稍后再试`);
} finally {
setBusy(false);
}
};
const { fieldTones, excludeTone, neutralTone, materialTone } = tagRecFieldTones(token);
const chip = (atom, danger = false) => {
const tone = danger ? excludeTone : fieldTones[atom.field] ?? neutralTone;
const parentId = !danger && combination ? groupsOf(combination.include).find((group) => group.children.some((child) => child.id === atom.id))?.id : void 0;
return /* @__PURE__ */ jsxs("span", {
style: {
display: "inline-flex",
alignItems: "center",
border: `1px solid ${tone.bd}`,
background: tone.bg,
borderRadius: 6,
maxWidth: 300,
color: tone.fg
},
children: [/* @__PURE__ */ jsx(Dropdown, {
trigger: ["click", "contextMenu"],
menu: { items: [
...atom.field === "tag" ? [{
key: "only",
label: "仅看这个标签",
onClick: () => exploreTagRecTagOnly(cloneDeep(atom.value))
}] : [],
{
key: "exclude",
label: "这个组合不看",
onClick: () => excludeTagRecWorkingCondition(cloneDeep(atom))
},
{
key: "global",
label: "以后都不看",
onClick: () => blockTagRecConditionGlobally(cloneDeep(atom))
},
...combination && !danger ? [{
key: "move",
label: "移到…",
children: [
...groupsOf(combination.include).map((group) => ({
key: `to:${group.id}`,
label: group.id === parentId ? `${groupLabel(group)} (当前)` : groupLabel(group),
disabled: group.id === parentId,
onClick: () => moveTagRecWorkingCondition(atom.id, group.id)
})),
{ type: "divider" },
{
key: "to:new",
label: "+新建子组",
onClick: () => moveTagRecWorkingCondition(atom.id, "new")
}
]
}] : [],
{
key: "remove",
label: "移出条件",
onClick: () => removeTagRecWorkingCondition(atom.id)
}
] },
getPopupContainer,
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
style: {
height: 24,
padding: "0 2px 0 8px",
maxWidth: 240,
overflow: "hidden",
textOverflow: "ellipsis",
color: tone.fg,
fontSize: 12
},
title: `${TAG_REC_CONDITION_FIELDS[atom.field]} · ${tagRecConditionLabel(atom)} · 点开管理`,
children: [
TAG_REC_CONDITION_FIELDS[atom.field],
" · ",
tagRecConditionLabel(atom)
]
})
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
"aria-label": `移出${TAG_REC_CONDITION_FIELDS[atom.field]}${tagRecConditionLabel(atom)}`,
style: {
height: 24,
minWidth: 22,
padding: "0 5px",
color: tone.fg,
opacity: .65
},
onClick: () => removeTagRecWorkingCondition(atom.id),
children: "×"
})]
}, atom.id);
};
const renderGroup = (node, root = false) => {
if (!root && !advanced && !node.children.length) return null;
const groupTone = node.name ? fieldTones[node.name] : void 0;
const setMode = (mode) => editTagRecWorkingGroup((group) => {
const target = findTagRecCondition(group.combination.include, node.id);
if (target?.kind === "group") target.mode = mode;
});
const addHere = !root && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "dashed",
"aria-label": `往${groupLabel(node)}加条件`,
title: `往「${groupLabel(node)}」加条件`,
style: {
height: 24,
padding: "0 8px",
fontSize: 12,
color: token.colorTextTertiary
},
onClick: () => {
setTarget(node.id);
setMaterialOpen(true);
},
children: node.children.length ? "+" : "+ 加条件"
});
return /* @__PURE__ */ jsxs("div", {
style: {
marginTop: root ? 0 : 8,
paddingLeft: root ? 0 : 10,
borderLeft: root ? void 0 : `2px solid ${groupTone?.bd ?? token.colorBorderSecondary}`
},
children: [/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
children: [
advanced ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: {
fontSize: 12,
...!root && groupTone ? { color: groupTone.fg } : void 0
},
children: root ? "组合方式" : TAG_REC_CONDITION_FIELDS[node.name] || node.name || "条件组"
}), /* @__PURE__ */ jsx(Select, {
size: "small",
"aria-label": "条件匹配方式",
value: node.mode,
style: { width: 104 },
getPopupContainer,
options: [{
value: "any",
label: "任一命中"
}, {
value: "all",
label: "全部命中"
}],
onChange: setMode
})] }) : node.mode === "any" && /* @__PURE__ */ jsx(Dropdown, {
menu: {
items: [{
key: "all",
label: "改为全部命中"
}],
onClick: () => setMode("all")
},
trigger: ["click"],
getPopupContainer,
children: /* @__PURE__ */ jsx("span", {
title: `${root ? "整个组合" : "本组"}按任一命中取视频,点击可改回全部命中`,
style: {
display: "inline-flex",
alignItems: "center",
height: 18,
padding: "0 6px",
borderRadius: 4,
fontSize: 11,
color: token.colorPrimary,
border: `1px solid ${token.colorPrimaryBorder}`,
background: token.colorPrimaryBg,
cursor: "pointer",
whiteSpace: "nowrap"
},
children: "任一命中"
})
}),
node.children.filter((child) => child.kind === "atom").map((a) => chip(a)),
addHere,
advanced && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => {
const id = crypto.randomUUID();
if (!editTagRecWorkingGroup((group) => {
const target = findTagRecCondition(group.combination.include, node.id);
if (target?.kind === "group") target.children.push({
kind: "group",
id,
mode: "any",
name: "条件组",
children: []
});
})) return;
setTarget(id);
setMaterialOpen(true);
toast("已建条件组 · 点选的素材会落进它");
},
children: "+子组"
}), !root && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => removeTagRecWorkingCondition(node.id),
children: "移出整组"
})] })
]
}), node.children.filter((child) => child.kind === "group").map((child) => renderGroup(child))]
}, node.id);
};
const saved = borrow?.combination ? tagRecCombinationOf(borrow) : borrow ? tagRecCombinationOf(borrow) : void 0;
const cooccurAnchors = (0, import_react.useMemo)(() => {
const list = [];
const seen = /* @__PURE__ */ new Set();
const push = (name) => {
const norm = normalizeTagName(name);
if (norm && !seen.has(norm)) {
seen.add(norm);
list.push({
norm,
name
});
}
};
for (const atom of combination ? tagRecConditionAtoms(combination.include) : []) {
if (atom.field !== "tag") continue;
push(typeof atom.value === "string" ? atom.value : atom.value.name);
}
if (query.trim()) push(query.trim());
return list;
}, [combination, query]);
const cooccurPage = cooccurAnchors.reduce((sum, a) => sum + (cooccurDigCursor.get(a.norm) ?? 0), 0);
const cooccurExhausted = !cooccurAnchors.length || cooccurAnchors.every((a) => (cooccurDigCursor.get(a.norm) ?? 0) >= 50);
const corpusRows = (0, import_react.useMemo)(() => {
const norms = /* @__PURE__ */ new Set();
const queryNorm = normalizeTagName(query);
if (queryNorm) norms.add(queryNorm);
if (combination) for (const tag of tagRecCombinationTagIds(combination)) norms.add(normalizeTagName(tag.name));
if (!norms.size) return [];
const exclude = new Set(norms);
for (const atom of combination ? [...tagRecConditionAtoms(combination.include), ...combination.exclude] : []) if (atom.field === "tag" && typeof atom.value !== "string") exclude.add(normalizeTagName(atom.value.name));
for (const atom of tagRecCombinationBlocks.list) if (atom.field === "tag" && typeof atom.value !== "string") exclude.add(normalizeTagName(atom.value.name));
return aggregateTagRecCooccur(sedimentMap, [...norms], exclude, 30);
}, [
sedimentMap,
query,
combination
]);
const corpusMaterials = (0, import_react.useMemo)(() => corpusRows.filter((row) => !materials.some((material) => material.source === "共现" && normalizeTagName(material.name) === row.norm)).map((row) => ({
name: row.name,
source: "共现",
count: row.count,
corpus: true
})), [corpusRows, materials]);
const savedMaterials = (0, import_react.useMemo)(() => {
if (!borrow) return [];
const comb = tagRecCombinationOf(borrow);
const seen = /* @__PURE__ */ new Set();
const out = [];
for (const [atoms, ex] of [[tagRecConditionAtoms(comb.include), false], [comb.exclude, true]]) for (const atom of atoms) {
const key = `${tagRecConditionKey(atom)}:${ex}`;
if (seen.has(key)) continue;
seen.add(key);
out.push({
atom,
name: tagRecConditionLabel(atom),
source: "收藏",
...ex ? { exclude: true } : {}
});
}
return out;
}, [borrow]);
const [savedQuery, setSavedQuery] = (0, import_react.useState)("");
const [storesReady, setStoresReady] = (0, import_react.useState)(false);
(0, import_react.useEffect)(() => {
let alive = true;
tagRecStoresHydrated.then(() => alive && setStoresReady(true));
return () => {
alive = false;
};
}, []);
/** 单组摊平出的可选素材 (层级视图 L2 行内细选 / 过滤词命中用) */
const savedMaterialsOfComb = (comb) => {
return [...tagRecConditionAtoms(comb.include).map((atom) => ({
atom,
name: tagRecConditionLabel(atom),
source: "收藏"
})), ...comb.exclude.map((atom) => ({
atom,
name: tagRecConditionLabel(atom),
source: "收藏",
exclude: true
}))];
};
const savedTree = (0, import_react.useMemo)(() => {
const q = normalizeTagName(savedQuery);
const l1ById = new Map(parents.list.map((l1) => [l1.id, l1]));
const themeById = new Map(themesSnap.list.map((theme) => [theme.id, theme]));
const nodes = [];
for (const group of library.list) {
if (!group.enabled) continue;
const comb = tagRecCombinationOf(group);
const materials = savedMaterialsOfComb(comb);
if (q) {
const l1 = group.l1Id ? l1ById.get(group.l1Id) : void 0;
const theme = l1?.themeId ? themeById.get(l1.themeId) : void 0;
if (!(normalizeTagName(group.name).includes(q) || l1 && normalizeTagName(l1.name).includes(q) || theme && normalizeTagName(theme.name).includes(q) || materials.some((material) => normalizeTagName(material.name).includes(q)))) continue;
}
nodes.push({
group,
materials,
importable: materials.length > 0 || !!comb.filters
});
}
const l1Ids = new Set(parents.list.map((l1) => l1.id));
const themeIds = new Set(themesSnap.list.map((theme) => theme.id));
const byL1 = /* @__PURE__ */ new Map();
for (const node of nodes) {
const parent = node.group.l1Id && l1Ids.has(node.group.l1Id) ? node.group.l1Id : void 0;
const list = byL1.get(parent) ?? [];
list.push(node);
byL1.set(parent, list);
}
const byTheme = /* @__PURE__ */ new Map();
for (const l1 of parents.list) {
const l1Nodes = byL1.get(l1.id);
if (!l1Nodes?.length) continue;
const themeId = l1.themeId && themeIds.has(l1.themeId) ? l1.themeId : void 0;
const list = byTheme.get(themeId) ?? [];
list.push({
l1,
groups: l1Nodes
});
byTheme.set(themeId, list);
}
return {
themes: themesSnap.list.filter((theme) => byTheme.has(theme.id)).map((theme) => ({
theme,
l1s: byTheme.get(theme.id)
})),
looseL1s: byTheme.get(void 0) ?? [],
orphanGroups: byL1.get(void 0) ?? []
};
}, [
library,
parents,
themesSnap,
savedQuery
]);
/** 整组/整节导入: 每个 L2 的 include 整棵嫁接为子组 (保留原结构), exclude+filters 并入 */
const importSavedGroups = (groups, label) => {
setCommitting(true);
try {
const count = importTagRecFavoriteCombinations(groups.map((group) => {
const comb = tagRecCombinationOf(group);
comb.include.name ||= group.name.slice(0, 200);
return comb;
}), target);
const skipped = groups.length - count;
toast(count ? `已并入「${label}」的 ${count} 个组合条件${targetGroup ? `(落到「${groupLabel(targetGroup)}」)` : ""}${skipped ? `,${skipped} 个组未并入(超限或无可并入内容)` : ""}` : "未加入:超出条件上限、目标不可用或没有可导入条件");
} finally {
setCommitting(false);
}
};
/** 收藏层级视图: 单个 L2 行 —— 组名 + 整组导入 + 原子细选 chips; 空组不出导入入口 */
const savedL2Row = (node) => /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-4px",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: node.group.name
}), node.importable && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
style: {
padding: 0,
height: "auto"
},
disabled: committing,
onClick: () => void importSavedGroups([node.group], node.group.name),
children: "导入整组"
})]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-6px",
children: node.materials.map(materialButton)
})]
}, node.group.id);
/** 收藏层级视图: L1 块 —— L1 名 + 整组导入(可并入 L2) + L2 行 */
const savedL1Block = (node) => {
const importable = node.groups.filter((g) => g.importable).map((g) => g.group);
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-6px",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
style: { fontSize: 12 },
children: node.l1.name
}), importable.length > 0 && /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "link",
style: {
padding: 0,
height: "auto"
},
disabled: committing,
onClick: () => void importSavedGroups(importable, node.l1.name),
children: [
"整组导入 ",
importable.length,
" 组"
]
})]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-col gap-8px",
style: { paddingLeft: 12 },
children: node.groups.map(savedL2Row)
})]
}, node.l1.id);
};
const inputVerbatim = (() => {
const raw = query.trim();
if (!raw || field !== "tag" && field !== "all") return void 0;
return materials.find((material) => normalizeTagName(material.name) === normalizeTagName(raw)) ?? {
name: raw,
source: "输入"
};
})();
const displayed = source === "收藏" ? savedMaterials : source === "输入" ? [...inputVerbatim ? [inputVerbatim] : [], ...[...materials, ...corpusMaterials].filter((material) => material.source === "输入" && normalizeTagName(material.name) !== normalizeTagName(query))] : [...materials, ...corpusMaterials].filter((material) => source === "全部" || source === material.source);
const filterChoices = tagRecCombinationFilterChoices(combination?.filters);
const borrowedFilters = tagRecCombinationFilterChoices(saved?.filters);
const semantic = combination ? describeTagRecCondition(combination.include) : "";
const stage = (material, exclude = material.exclude ?? false) => stageTagRecMaterial(memory.activeId, {
...cloneDeep(material),
exclude
});
const materialButton = (material, index) => {
const key = material.atom && tagRecConditionKey(material.atom);
const selected = combination && (material.exclude ? combination.exclude : tagRecConditionAtoms(combination.include)).find((atom) => key ? tagRecConditionKey(atom) === key : !material.filter && atom.field === "tag" && normalizeTagName(atom.value.name) === normalizeTagName(material.name));
const pending = picks.some((entry) => tagRecMaterialKey(entry) === tagRecMaterialKey(material));
const tone = materialTone(material);
return /* @__PURE__ */ jsx(Dropdown, {
trigger: ["contextMenu"],
getPopupContainer,
menu: { items: [
...(material.atom ? material.atom.field === "tag" : !material.filter) ? [{
key: "only",
label: "仅看这个标签",
onClick: () => void exploreTagRecMaterialOnly(material)
}] : [],
{
key: "include",
label: "选入当前组合",
onClick: () => void selectTagRecMaterial({
...material,
exclude: false
}, target)
},
{
key: "stage",
label: "暂选,稍后一起加入",
onClick: () => stage(material, false)
},
{
key: "exclude",
label: "暂选为「这个组合不看」",
onClick: () => stage(material, true)
},
{
key: "global",
label: "以后都不看",
onClick: () => {
select(material, "global");
}
}
] },
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
title: `${material.atom ? TAG_REC_CONDITION_FIELDS[material.atom.field] : material.filter ? "过滤" : "标签"} · ${material.name}${material.exclude ? "(不看)" : ""}${material.count ? ` · ${material.corpus ? "语料" : "共现"} ${material.count}` : ""}`,
style: {
maxWidth: "100%",
height: "auto",
whiteSpace: "normal",
textAlign: "left",
background: selected && !batch ? tone.fg : tone.bg,
borderColor: selected || pending ? tone.fg : tone.bd,
borderStyle: pending && !selected ? "dashed" : "solid",
color: selected && !batch ? "#fff" : tone.fg,
fontWeight: selected || pending ? 600 : void 0
},
onClick: () => {
if (batch) stage(material);
else if (selected) removeTagRecWorkingCondition(selected.id);
else selectTagRecMaterial(material, target);
},
children: [
material.atom && material.atom.field !== "tag" ? `${TAG_REC_CONDITION_FIELDS[material.atom.field]} · ` : "",
material.filter ? "过滤 · " : "",
material.name,
material.exclude ? "(不看)" : "",
material.count ? ` · ${material.corpus ? "语料" : "共现"} ${material.count}` : "",
selected ? " ✓" : pending ? " · 已暂选" : ""
]
})
}, `${tagRecMaterialKey(material)}:${index}`);
};
const shelfRowCss = {
"display": "grid",
"gridTemplateColumns": "minmax(0, 1fr) 28px",
"gap": 2,
"alignItems": "start",
"borderRadius": 8,
"borderLeft": "3px solid transparent",
"&:hover": { background: token.colorFillTertiary },
"& .shelf-entry": {
gridColumn: "1 / -1",
gridRow: 1
},
"& .shelf-action": {
gridColumn: 2,
gridRow: 1,
marginTop: 4,
paddingInline: 4,
opacity: .65,
zIndex: 1
},
"&:hover .shelf-action, &:focus-within .shelf-action": { opacity: 1 }
};
const shelfRowActiveCss = {
"background": token.colorPrimaryBg,
"borderLeftColor": token.colorPrimary,
"&:hover": { background: token.colorPrimaryBg }
};
const shelfLine = {
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
overflow: "hidden",
whiteSpace: "normal",
overflowWrap: "anywhere",
lineHeight: 1.5,
fontSize: 13,
fontWeight: 500
};
const shelfButtonStyle = {
display: "block",
width: "100%",
minWidth: 0,
height: "auto",
textAlign: "left",
whiteSpace: "normal",
padding: "6px"
};
const shelfTitle = (combination) => combination ? nameTagRecCombination(combination) : "";
const shelfPendingText = (count) => count ? ` · 暂选 ${count}` : "";
const shelfMatch = (name, combination) => !shelfSearching || `${name} ${shelfTitle(combination)}`.toLowerCase().includes(shelfQuery.trim().toLowerCase());
const shelfName = (name, combination) => {
if (!name || name === "新试验" || name === shelfTitle(combination)) return void 0;
const atoms = combination ? tagRecConditionAtoms(combination.include) : [];
const formulaName = name.replace(/^(+/, "");
if (atoms.some((atom) => name === tagRecConditionLabel(atom)) || Object.values(TAG_REC_CONDITION_FIELDS).some((field) => formulaName.startsWith(`${field}「`))) return void 0;
return name;
};
const shelfTrials = trials.filter((entry) => shelfMatch(entry.name, entry.group.combination));
const shelfFavorites = library.list.filter((group) => shelfMatch(group.name, tagRecCombinationOf(group)));
const shelfRecent = (compatible ? memory.recent : []).filter((entry) => (hasTagRecCombination(entry?.group?.combination) || !!entry.pending?.length) && shelfMatch(entry.name, entry.group.combination));
const shelfSection = (key, label, count) => /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
"aria-label": `${shelfSections[key] ? "收起" : "展开"}${label}`,
"aria-expanded": shelfSections[key],
"aria-controls": `${shelfId}-${key}`,
onClick: () => setShelfSections((previous) => ({
...previous,
[key]: !previous[key]
})),
style: {
display: "flex",
width: "100%",
paddingInline: 4,
gap: 6,
color: token.colorTextSecondary
},
children: [
/* @__PURE__ */ jsx("span", {
"aria-hidden": true,
style: {
width: 12,
transform: shelfSections[key] ? "rotate(90deg)" : void 0
},
children: "›"
}),
/* @__PURE__ */ jsx("span", {
style: {
flex: 1,
textAlign: "left",
fontSize: 12,
fontWeight: 600
},
children: label
}),
/* @__PURE__ */ jsx("span", {
style: {
fontSize: 11,
color: token.colorTextTertiary
},
children: count
})
]
});
const pillToneOf = (item) => item.kind === "exclude" ? excludeTone : item.kind === "more" ? neutralTone : fieldTones[item.field] ?? neutralTone;
const shelfPillStyle = (tone) => ({
display: "inline-block",
minHeight: 20,
padding: "0 5px",
borderRadius: 4,
border: `1px solid ${tone.bd}`,
background: tone.bg,
color: tone.fg,
fontSize: 11,
lineHeight: "18px",
minWidth: 0,
maxWidth: "100%",
whiteSpace: "normal",
overflowWrap: "anywhere"
});
const renderPills = (combination, part = "all") => {
const allItems = combination ? tagRecCombinationPills(combination, {
limit: Infinity,
excludeLimit: Infinity
}) : [];
let split = allItems.length;
let conditions = 0;
for (const [index, item] of allItems.entries()) {
if (item.kind !== "pill" && item.kind !== "exclude") continue;
if (conditions === 2) {
split = allItems[index - 1]?.kind === "join" ? index - 1 : index;
break;
}
conditions++;
}
const items = part === "lead" ? allItems.slice(0, split) : part === "rest" ? allItems.slice(split) : allItems;
if (!items.length && part === "rest") return null;
if (!items.length) return /* @__PURE__ */ jsx("span", {
className: "shelf-join",
style: {
fontSize: 12,
color: token.colorTextSecondary
},
children: "未选条件"
});
const groups = [];
let pendingJoin;
for (const item of items) {
if (item.kind === "join") {
pendingJoin = item.label;
continue;
}
const last = groups.at(-1);
if (pendingJoin) groups.push({
key: item.key,
join: pendingJoin,
pills: [item]
});
else if (last) last.pills.push(item);
else groups.push({
key: item.key,
pills: [item]
});
pendingJoin = void 0;
}
return /* @__PURE__ */ jsx("span", {
className: "flex flex-wrap items-center gap-3px",
children: groups.map((group) => /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-3px",
style: {
maxWidth: "100%",
minWidth: 0
},
children: [!!group.join && group.join !== "或" && /* @__PURE__ */ jsx("span", {
className: "shelf-join",
style: {
fontSize: 10,
color: token.colorTextTertiary
},
children: group.join === "且" ? "·" : group.join
}), group.pills.map((pill) => /* @__PURE__ */ jsx("span", {
className: "shelf-pill",
style: shelfPillStyle(pillToneOf(pill)),
children: pill.label
}, pill.key))]
}, group.key))
});
};
const renderShelfEntry = (name, combination, { actionWidth = 0, active = false, pending } = {}) => {
const label = shelfName(name, combination);
const current = active ? /* @__PURE__ */ jsx("span", {
style: {
flexShrink: 0,
fontSize: 10,
lineHeight: "20px",
color: token.colorPrimary
},
children: "当前"
}) : null;
return /* @__PURE__ */ jsxs("span", {
style: {
display: "flex",
flexDirection: "column",
gap: 4,
minWidth: 0,
textAlign: "left"
},
children: [
label ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
style: {
display: "flex",
alignItems: "start",
gap: 4,
minWidth: 0,
paddingRight: actionWidth
},
children: [/* @__PURE__ */ jsx("span", {
style: {
...shelfLine,
flex: 1,
minWidth: 0,
color: active ? token.colorPrimary : void 0
},
children: label
}), current]
}), renderPills(combination)] }) : /* @__PURE__ */ jsxs("span", {
style: {
display: "flex",
alignItems: "center",
gap: 4,
minWidth: 0,
paddingRight: actionWidth
},
children: [/* @__PURE__ */ jsx("span", {
style: {
flex: 1,
minWidth: 0
},
children: renderPills(combination, "lead")
}), current]
}),
!label && renderPills(combination, "rest"),
!!pending && /* @__PURE__ */ jsxs("span", {
style: {
fontSize: 11,
color: token.colorTextSecondary
},
children: [
"暂选 ",
pending,
" 项"
]
})
]
});
};
const shelf = /* @__PURE__ */ jsxs("div", {
"data-discovery-shelf-content": true,
css: {
"display": "flex",
"flexDirection": "column",
"gap": 8,
"width": "100%",
"minHeight": 0,
"maxHeight": "inherit",
"boxSizing": "border-box",
"padding": display.collapsed ? 7 : 10,
"@media (max-width: 1100px)": display.collapsed ? {
flexDirection: "row",
alignItems: "center"
} : {}
},
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-center justify-between gap-4px",
children: [!display.collapsed && /* @__PURE__ */ jsx(Typography.Text, {
strong: true,
style: { fontSize: 14 },
children: "我的组合"
}), /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
"aria-label": display.collapsed ? "展开组合书架" : "收起组合书架",
"aria-expanded": !display.collapsed,
"aria-controls": shelfId,
title: display.collapsed ? "展开我的组合" : "收起我的组合",
css: {
"display": "flex",
"flexDirection": display.collapsed ? "column" : "row",
"height": "auto",
"padding": display.collapsed ? "8px 2px" : "2px 6px",
"gap": 6,
"& .shelf-rail-label": {
writingMode: "vertical-rl",
letterSpacing: 2,
fontSize: 12
},
"@media (max-width: 1100px)": {
"flexDirection": "row",
"padding": "2px 6px",
"& .shelf-rail-label": {
writingMode: "horizontal-tb",
letterSpacing: 0
}
}
},
onClick: () => {
tagRecDiscoveryPreferences.collapsed = !display.collapsed;
},
children: [/* @__PURE__ */ jsx("span", {
"aria-hidden": true,
children: display.collapsed ? "›" : "‹"
}), display.collapsed ? /* @__PURE__ */ jsx("span", {
className: "shelf-rail-label",
children: "我的组合"
}) : "收起"]
})]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
block: !display.collapsed,
"aria-label": "创建新组合",
title: "创建新组合",
style: {
flexShrink: 0,
paddingInline: display.collapsed ? 5 : void 0
},
onClick: () => newTagRecTrial(),
children: display.collapsed ? "+" : "+ 新组合"
}),
!display.collapsed && /* @__PURE__ */ jsxs("div", {
id: shelfId,
style: {
display: "flex",
flexDirection: "column",
gap: 8,
minHeight: 0
},
children: [
/* @__PURE__ */ jsx(Input.Search, {
size: "small",
value: shelfQuery,
onChange: (event) => setShelfQuery(event.target.value),
allowClear: true,
"aria-label": "查找我的组合",
placeholder: "搜索名称或条件"
}),
/* @__PURE__ */ jsxs("div", {
"data-discovery-shelf-scroll": true,
style: {
minHeight: 0,
overflowY: "auto",
overscrollBehavior: "contain",
paddingRight: 3
},
children: [
shelfSection("trials", "正在试", shelfTrials.length),
/* @__PURE__ */ jsx("div", {
id: `${shelfId}-trials`,
hidden: !shelfSections.trials,
children: shelfTrials.map((entry) => {
const active = entry.id === memory.activeId;
return /* @__PURE__ */ jsxs("div", {
css: {
...shelfRowCss,
...active ? shelfRowActiveCss : {}
},
children: [/* @__PURE__ */ jsx(button_default, {
className: "shelf-entry",
size: "small",
type: "text",
style: shelfButtonStyle,
"aria-current": active ? "true" : void 0,
title: `${entry.name} · ${shelfTitle(entry.group.combination)}${shelfPendingText(entry.pending?.length)}`,
onClick: () => switchTagRecTrial(entry.id),
children: renderShelfEntry(entry.name, entry.group.combination, {
actionWidth: 28,
active,
pending: entry.pending?.length
})
}), /* @__PURE__ */ jsx(button_default, {
className: "shelf-action",
size: "small",
type: "text",
"aria-label": `关闭试验${entry.name}`,
title: "关闭这个试验",
onClick: () => closeTagRecTrial(entry.id),
children: "×"
})]
}, entry.id);
})
}),
shelfSection("favorites", "二级组收藏", shelfFavorites.length),
/* @__PURE__ */ jsxs("div", {
id: `${shelfId}-favorites`,
hidden: !shelfSections.favorites,
children: [shelfFavorites.map((group) => {
const savedCombination = tagRecCombinationOf(group);
return /* @__PURE__ */ jsxs("div", {
css: {
...shelfRowCss,
gridTemplateColumns: "minmax(0, 1fr) 48px"
},
children: [/* @__PURE__ */ jsx(button_default, {
className: "shelf-entry",
size: "small",
type: "text",
style: shelfButtonStyle,
title: `${group.name} · ${shelfTitle(savedCombination)}`,
onClick: () => openTagRecFavorite(group),
children: renderShelfEntry(group.name, savedCombination, { actionWidth: 48 })
}), /* @__PURE__ */ jsx(button_default, {
className: "shelf-action",
size: "small",
type: "text",
style: { fontSize: 12 },
"aria-label": `从${group.name}取几项`,
title: "从这个收藏组合取几项条件",
onClick: () => {
setBorrow(group);
setSource("收藏");
setMaterialOpen(true);
},
children: "取几项"
})]
}, group.id);
}), !library.list.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "试好的组合存到这里。"
})]
}),
shelfSection("recent", "最近尝试", shelfRecent.length),
/* @__PURE__ */ jsxs("div", {
id: `${shelfId}-recent`,
hidden: !shelfSections.recent,
children: [shelfRecent.slice(0, recentLimit).map((entry) => /* @__PURE__ */ jsx("div", {
css: {
...shelfRowCss,
gridTemplateColumns: "minmax(0, 1fr)"
},
children: /* @__PURE__ */ jsx(button_default, {
className: "shelf-entry",
size: "small",
type: "text",
style: shelfButtonStyle,
title: `${shelfTitle(entry.group.combination)}${shelfPendingText(entry.pending?.length)}`,
onClick: () => openTagRecRecent(entry),
children: renderShelfEntry(entry.name, entry.group.combination, { pending: entry.pending?.length })
})
}, entry.id)), shelfRecent.length > recentLimit && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setRecentLimit((limit) => limit + 20),
children: "查看更早的尝试"
})]
}),
shelfSearching && !shelfTrials.length && !shelfFavorites.length && !shelfRecent.length && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: {
display: "block",
padding: 8,
fontSize: 12
},
children: "没有匹配的组合,试试名称或条件词。"
})
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
style: {
flexShrink: 0,
borderTop: `1px solid ${token.colorBorderSecondary}`,
borderRadius: 0
},
onClick: showModalSettings,
children: "收藏管理与备份"
})
]
})
]
});
const saveFields = /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-12px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "保存标签、排除、匹配方式和明确改过的过滤与来源;其余继续继承。"
}),
/* @__PURE__ */ jsx(Input, {
"aria-label": "二级组名称",
placeholder: "留空按条件命名",
value: saveName,
maxLength: 200,
onChange: (event) => setSaveName(event.target.value)
}),
/* @__PURE__ */ jsx(Select, {
"aria-label": "归属一级组",
value: session.workingGroup?.l1Id || "auto",
getPopupContainer,
options: [{
value: "auto",
label: "自动建立同名一级组"
}, ...parents.list.map((parent) => ({
value: parent.id,
label: parent.name
}))],
onChange: (id) => editTagRecWorkingGroup((group) => {
group.l1Id = id === "auto" ? void 0 : id;
})
}),
trial?.originId && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
children: [
"来自收藏「",
library.list.find((group) => group.id === trial.originId)?.name ?? trial.name,
"」。另存后原组仍保留。"
]
})
]
});
const more = /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-10px",
style: { maxWidth: 400 },
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setAdvanced(!advanced),
children: advanced ? "收起完整条件" : "完整条件与组合关系"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
const current = currentTagRecTrial();
if (current) newTagRecTrial(current.group, current.originId, `${current.name} · 分支`);
},
children: "分支试验"
}),
trial?.originId && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
const original = library.list.find((group) => group.id === trial.originId);
if (original) openTagRecFavorite(original);
},
children: "回到原组合"
}), /* @__PURE__ */ jsx(Popconfirm, {
title: "用当前完整条件更新原二级组?",
description: "其他试验保留,原收藏会被这份条件更新。",
onConfirm: () => saveTagRecWorkingGroup(saveName, true),
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
children: "更新原二级组"
})
})] }),
/* @__PURE__ */ jsx(Checkbox, {
checked: bypassSeen,
onChange: (event) => {
tagRecCustomSession.bypassSeen = event.target.checked;
onRefresh("refresh");
},
children: "显示已看(临时)"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: showModalSettings,
children: "过滤预设、发现策略与收藏管理"
}),
globalBlocks.list.length > 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "danger",
children: "以后都不看"
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-6px",
children: globalBlocks.list.map((atom) => /* @__PURE__ */ jsxs(button_default, {
size: "small",
danger: true,
onClick: () => unblockTagRecConditionGlobally(atom.id),
children: [tagRecConditionLabel(atom), " ×"]
}, atom.id))
})] })
]
});
const toolbar = /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-6px bg-gate-bg",
style: {
paddingBlock: 4,
borderBottom: `1px solid ${token.colorBorderSecondary}`,
transition: "border-color .15s ease"
},
children: [
/* @__PURE__ */ jsx("div", {
className: "min-w-0 flex items-center gap-6px",
children: trial && trial.name !== "新试验" ? /* @__PURE__ */ jsx(Typography.Text, {
strong: true,
ellipsis: true,
style: {
fontSize: 14,
maxWidth: 220
},
title: trial.originId ? `试改自「${library.list.find((g) => g.id === trial.originId)?.name ?? ""}」` : void 0,
children: trial.name
}) : /* @__PURE__ */ jsx("span", {
style: {
flex: "none",
fontSize: 11,
color: token.colorTextTertiary,
border: `1px solid ${token.colorBorder}`,
borderRadius: 4,
padding: "0 5px",
whiteSpace: "nowrap"
},
title: trial?.originId ? "试改自收藏的二级组,尚未另存" : "尚未保存为二级组",
children: "未保存"
})
}),
combination && isTagRecCombinationIntersection(combination) && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: {
fontSize: 11,
flex: "none",
whiteSpace: "nowrap"
},
title: "交集收窄:tag/top 与相关池默认收窄,火力集中在精准搜索;可在「过滤 → 内容来源」开回",
children: "交集 · 收窄"
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px",
style: {
flex: "0 1 auto",
minWidth: 0
},
children: [
/* @__PURE__ */ jsx(Select, {
size: "small",
"aria-label": "素材字段",
value: field,
style: {
width: 92,
flex: "none"
},
getPopupContainer,
options: [{
value: "all",
label: "全部字段"
}, ...Object.entries(TAG_REC_CONDITION_FIELDS).map(([value, label]) => ({
value,
label
}))],
onChange: setField
}),
/* @__PURE__ */ jsx(Input.Search, {
size: "small",
style: {
flex: "0 1 240px",
minWidth: 140
},
"aria-label": "查找可选条件",
placeholder: field === "up" ? "UP 名称或 UID" : field === "partition" ? "分区名,如 科学科普" : field === "tag" ? "标签名或标签 ID,如 三体" : "输入要查找的条件",
value: query,
onChange: (event) => setQuery(event.target.value),
onSearch: () => void findMaterials(),
enterButton: "查找",
loading: busy
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
style: { flex: "none" },
"aria-label": display.topCollapsed ? "展开组合条件卡" : "收起组合条件卡",
onClick: () => {
tagRecDiscoveryPreferences.topCollapsed = !display.topCollapsed;
},
children: display.topCollapsed ? "展开" : "收起"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px",
style: { marginLeft: "auto" },
children: [
/* @__PURE__ */ jsxs(button_default, {
size: "small",
style: { flex: "none" },
onClick: () => setMaterialOpen(true),
children: ["可选条件", picks.length ? ` · 暂选 ${picks.length}` : ""]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
loading: continuing,
onClick: () => void continueDiscovery(),
children: "加载更多"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => {
if (!undoTagRecWorkingGroup()) toast("没有可撤销的条件改动");
},
children: "撤销"
}),
/* @__PURE__ */ jsx(FilterPopover, { getPopupContainer }),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: () => setSaving(true),
children: "存为二级组"
}),
/* @__PURE__ */ jsx(Popover, {
trigger: "click",
placement: "bottomRight",
content: more,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
"aria-label": "更多操作",
children: "⋯"
})
})
]
})
]
});
const combinationCard = /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px gap-y-5px",
style: {
padding: "6px 12px",
borderRadius: 10,
background: token.colorBgContainer,
backgroundImage: `linear-gradient(${token.colorFillQuaternary}, ${token.colorFillQuaternary})`,
border: `1px solid ${token.colorBorderSecondary}`
},
children: [
combination && renderGroup(combination.include, true),
!!combination?.exclude.length && /* @__PURE__ */ jsxs("span", {
className: "flex flex-wrap items-center gap-4px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "danger",
style: { fontSize: 12 },
children: "不看"
}), combination.exclude.map((atom) => chip(atom, true))]
}),
filterChoices.filter((choice) => !choice.key.startsWith("poolsEnabled.")).map((choice) => /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
"aria-label": `恢复继承${choice.label}`,
style: {
color: token.colorTextTertiary,
paddingInline: 4,
fontSize: 12
},
onClick: () => editTagRecWorkingGroup((group) => {
removeTagRecCombinationFilterChoice(group.combination.filters, choice.key);
}),
children: [choice.label, " ×"]
}, choice.key)),
!semantic && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "选条件开始试看:标签 / UP / 标题 / 简介 / 分区"
}),
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
advanced && /* @__PURE__ */ jsx(Select, {
size: "small",
"aria-label": "新条件落点组",
value: target || "auto",
style: {
minWidth: 150,
flex: "none"
},
getPopupContainer,
options: [{
value: "auto",
label: "自动按字段组织"
}, ...combination ? groupsOf(combination.include).map((group) => ({
value: group.id,
label: groupLabel(group)
})) : []],
onChange: (id) => setTarget(id === "auto" ? void 0 : id)
}),
/* @__PURE__ */ jsx(Tooltip, {
title: semantic || void 0,
placement: "bottomRight",
overlayStyle: { maxWidth: 480 },
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setAdvanced(!advanced),
children: advanced ? "收起关系" : "关系"
})
})
]
});
const metaRow = /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-2px",
style: { fontSize: 11 },
children: [
/* @__PURE__ */ jsx(TagRecDiscoveryPools, {
service,
onRefresh: () => {
onRefresh("refresh");
}
}),
session.replayDelivered > 0 && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
title: "该条件此前交付过的内容,本地零请求复用",
children: [
"回放 ",
session.replayDelivered,
" 条"
]
}),
(session.partitionPending > 0 || session.allowUnverified || holdReason) && /* @__PURE__ */ jsxs("span", {
className: "flex flex-wrap items-center gap-6px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: session.partitionPending > 0 ? `待核实 ${session.partitionPending} 条${holdReason ? ` · ${holdReason}` : ""}` : holdReason ?? "已放出未核实内容(未证明满足当前组合)"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => {
tagRecCustomSession.allowUnverified = !tagRecCustomSession.allowUnverified;
service.retryPartitions();
},
children: session.allowUnverified ? "恢复暂存" : "暂时查看未核实内容"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => {
continueDiscovery();
},
children: "重试核实"
})
]
}),
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
/* @__PURE__ */ jsxs("span", {
className: "flex items-center gap-4px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: "视频大小"
}), /* @__PURE__ */ jsx(Segmented, {
size: "small",
value: display.size,
options: [
{
value: "small",
label: "小"
},
{
value: "medium",
label: "中"
},
{
value: "large",
label: "大"
}
],
onChange: (size) => {
tagRecDiscoveryPreferences.size = size;
}
})]
})
]
});
if (!compatible) return /* @__PURE__ */ jsx(Typography.Text, {
type: "danger",
children: "本地试验格式不兼容,原数据已保留,请升级插件后继续。"
});
return /* @__PURE__ */ jsxs("div", {
ref,
className: "w-full flex flex-col gap-8px",
style: { marginBottom: 8 },
"data-tag-rec-workbench": true,
children: [
shelfTarget ? (0, import_react_dom.createPortal)(shelf, shelfTarget) : /* @__PURE__ */ jsx("aside", {
style: { maxWidth: 230 },
children: shelf
}),
toolbarTarget ? (0, import_react_dom.createPortal)(toolbar, toolbarTarget) : toolbar,
!display.topCollapsed && (headerTarget ? (0, import_react_dom.createPortal)(combinationCard, headerTarget) : combinationCard),
!!picks.length && /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
style: {
padding: "6px 10px",
borderRadius: 8,
background: token.colorFillQuaternary,
borderLeft: `3px solid ${token.colorWarning}`
},
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
style: { fontSize: 12 },
children: [
"暂选 ",
picks.length,
" 项"
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => setMaterialOpen(true),
children: "查看"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
onClick: () => {
const current = currentTagRecTrial();
if (current) current.pending = [];
},
children: "清空"
}),
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
loading: committing,
onClick: async () => {
setCommitting(true);
try {
await commitTagRecMaterialDraft(target);
} finally {
setCommitting(false);
}
},
children: "加入当前组合"
})
]
}),
metaTarget && !display.topCollapsed ? (0, import_react_dom.createPortal)(metaRow, metaTarget) : metaRow,
globalBlocks.invalid && /* @__PURE__ */ jsx(Typography.Text, {
type: "danger",
children: "全局排除条件不兼容,原数据已保留,请升级插件后继续。"
}),
/* @__PURE__ */ jsx(TagRecBanner, {
hideIdleDropText: true,
hidePartitionPending: true,
onDigMore: () => {
continueDiscovery();
},
onRedig: () => {
service.redigQuery().then(() => recSharedEmitter.emit("load-more"));
},
session: tagRecCustomSession,
onPartitionRetry: () => {
continueDiscovery();
}
}),
/* @__PURE__ */ jsx(Modal, {
title: "存为新二级组",
open: saving,
onCancel: () => setSaving(false),
okText: "保存新组",
onOk: () => {
if (saveTagRecWorkingGroup(saveName)) setSaving(false);
},
children: saveFields
}),
/* @__PURE__ */ jsx(drawer_default, {
title: source === "收藏" ? borrow ? `从「${borrow.name}」取几项` : "收藏素材" : query ? `「${query}」的可选条件` : "可选条件",
open: materialOpen,
onClose: () => setMaterialOpen(false),
size: 520,
mask: { closable: true },
footer: picks.length ? /* @__PURE__ */ jsxs(button_default, {
block: true,
type: "primary",
loading: committing,
onClick: async () => {
setCommitting(true);
try {
await commitTagRecMaterialDraft(target);
} finally {
setCommitting(false);
}
},
children: [
"加入暂选的 ",
picks.length,
" 项"
]
}) : void 0,
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-14px",
children: [
/* @__PURE__ */ jsx(Input.Search, {
"aria-label": "在可选条件中继续查找",
value: query,
onChange: (event) => setQuery(event.target.value),
onSearch: () => void findMaterials(),
loading: busy,
enterButton: "查找",
placeholder: "换个词继续找,不改变已选条件"
}),
targetGroup && /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: [
"选中的条件落到「",
groupLabel(targetGroup),
"」"
]
}), /* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
style: {
padding: 0,
height: "auto"
},
onClick: () => setTarget(void 0),
children: "改回自动"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center justify-between gap-x-12px gap-y-4px",
children: [/* @__PURE__ */ jsx(Checkbox, {
checked: batch,
onChange: (event) => setBatch(event.target.checked),
children: "批量暂选,最后一起加入"
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
style: { fontSize: 12 },
children: batch ? "暂选随当前试验保存,换视频或刷新后可继续" : "点击加入组合,右键可仅看、不看或暂选"
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-6px",
children: [/* @__PURE__ */ jsx(Segmented, {
size: "small",
value: source,
options: [
{
value: "全部",
label: "全部"
},
{
value: "解析",
label: "解析新词"
},
{
value: "共现",
label: "共现新词"
},
{
value: "视频",
label: "视频字段"
},
{
value: "收藏",
label: "收藏素材"
},
{
value: "输入",
label: "输入"
}
],
onChange: (value) => setSource(String(value))
}), source === "共现" && /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "link",
style: { padding: "0 4px" },
loading: busy,
disabled: cooccurExhausted,
title: "按当前组合的标签轮流拉下一页搜索结果并累加共现词(无标签条件时按输入词);语料行随深挖自动更新",
onClick: () => void digCooccurMore(),
children: ["继续挖共现", cooccurPage > 1 ? ` · 已挖 ${cooccurPage} 页` : ""]
})]
}),
source === "收藏" && !borrow ? /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-10px",
children: [/* @__PURE__ */ jsx(Input, {
size: "small",
allowClear: true,
placeholder: "过滤主题 / 一级组 / 组名 / 条件词",
value: savedQuery,
onChange: (event) => setSavedQuery(event.target.value)
}), !storesReady ? /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "正在读取收藏素材…"
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
savedTree.themes.length + savedTree.looseL1s.length + savedTree.orphanGroups.length === 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: savedQuery ? "没有匹配的收藏组或条件" : "还没有启用的收藏组"
}),
savedTree.themes.map((node) => {
const importable = node.l1s.flatMap((l1) => l1.groups).filter((g) => g.importable).map((g) => g.group);
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-6px",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-8px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
strong: true,
style: { fontSize: 13 },
children: ["主题 · ", node.theme.name]
}), importable.length > 0 && /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "link",
style: {
padding: 0,
height: "auto"
},
disabled: committing,
onClick: () => void importSavedGroups(importable, node.theme.name),
children: [
"整组导入 ",
importable.length,
" 组"
]
})]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-col gap-8px",
style: { paddingLeft: 12 },
children: node.l1s.map(savedL1Block)
})]
}, node.theme.id);
}),
savedTree.looseL1s.map(savedL1Block),
savedTree.orphanGroups.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-8px",
children: [(savedTree.themes.length > 0 || savedTree.looseL1s.length > 0) && /* @__PURE__ */ jsx(Typography.Text, {
strong: true,
style: { fontSize: 13 },
children: "未分组"
}), savedTree.orphanGroups.map(savedL2Row)]
})
] })]
}) : /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-8px",
children: [source === "收藏" && borrow && /* @__PURE__ */ jsxs(button_default, {
size: "small",
style: { alignSelf: "flex-start" },
disabled: committing,
onClick: () => void importSavedGroups([borrow], borrow.name),
children: [
"导入整组「",
borrow.name,
"」(保留结构)"
]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-8px",
children: displayed.slice(0, materialLimit).map(materialButton)
})]
}),
!busy && !displayed.length && source !== "收藏" && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "暂无可选条件。输入标签名查找,或从视频的「选条件」中挑选。"
}),
busy && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
children: "正在查找…"
}),
!(source === "收藏" && !borrow) && displayed.length > materialLimit && /* @__PURE__ */ jsxs(button_default, {
size: "small",
onClick: () => setMaterialLimit((value) => value + 50),
children: [
"更多素材(还有 ",
displayed.length - materialLimit,
" 项)"
]
}),
source === "收藏" && borrowedFilters.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
children: "这份组合的明确过滤条件"
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-6px",
children: borrowedFilters.map((choice, i) => materialButton({
name: choice.label,
source: "收藏",
filter: choice.patch
}, i))
})] }),
!!picks.length && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Typography.Text, {
strong: true,
children: [
"已暂选 ",
picks.length,
" 项"
]
}), /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-6px",
children: picks.map((material) => {
const ptone = materialTone(material);
return /* @__PURE__ */ jsxs(button_default, {
size: "small",
title: `点按移出暂选`,
style: {
background: ptone.bg,
borderColor: ptone.bd,
color: ptone.fg
},
onClick: () => stage(material),
children: [material.name, " ×"]
}, material.pickId);
})
})] })
]
})
}),
/* @__PURE__ */ jsx(TagRecVideoMaterials, {})
]
});
}
/** Every discovery entry edits a working copy; material searches never discard that copy. */
function TagRecCustomTabbarView({ service }) {
return /* @__PURE__ */ jsx(TagRecWorkbenchView, { service });
}
var DEFAULT_LIMITS = {
maxConditions: 12,
maxItemsPerCondition: 120
};
function createTagRecDeliveryReplay(limits = {}) {
const { maxConditions, maxItemsPerCondition } = {
...DEFAULT_LIMITS,
...limits
};
const slots = /* @__PURE__ */ new Map();
const touch = (key, slot) => {
slots.delete(key);
slots.set(key, slot);
while (slots.size > maxConditions) slots.delete(slots.keys().next().value);
};
return {
remember(conditionKey, items) {
if (!conditionKey || !items.length) return;
const slot = slots.get(conditionKey) ?? {
items: [],
seen: /* @__PURE__ */ new Set()
};
for (const item of items) {
const bvid = item.bvid;
if (bvid && slot.seen.has(bvid)) continue;
if (slot.items.length >= maxItemsPerCondition) break;
slot.items.push(cloneDeep(item));
if (bvid) slot.seen.add(bvid);
}
touch(conditionKey, slot);
},
replay(conditionKey) {
const slot = slots.get(conditionKey);
if (!slot) return [];
touch(conditionKey, slot);
return [...slot.items];
},
forget(conditionKey) {
slots.delete(conditionKey);
},
clear() {
slots.clear();
},
size() {
return slots.size;
}
};
}
/**
* [tag-rec] 「标签发掘」tab —— Phase 1 + Phase 2 (spec §4.1/§4.3)
* Phase 1: 输入 => tag/info 锚点 => _pipeline 一轮 (tag/top 池 => bvid 跨轮去重 => 统一字段视图 => 产出)
* Phase 2 后台四重产出: ①字形变体 id 并入查询单元(下一轮起)+语义别名候选清单
* ②精确召回 => qs.bufferQueue 尾部
* ③兜底留存 => 池浅时标「相关」补召回
* ④共现边 sediment.merge
* 展示兜底梯队: tag/top 主池 => all/v2 精确 => 其余命中「相关」=> 全空提示
* 查询单元 = 选中组并集 (tagRecCustomSession.selectedGroupIds, 正常+挂起) ∪ 临时输入 id (票 07)
* 轮次主干共享 _pipeline.ts (票 05); 过滤算分/去已看经 stages 注入点, 归票 06/08
* 票 32 冷池主动性: 逐 tid 干态记忆 (空即干/零新增连击干, 成员签名变全清)
* + 首轮/变员后台预热深挖流 (选定组簇流 + 每扩选 id 一流, 与 tag/top 并行)
*/
/**
* 票 24 深链消费 (spec §4.7): `?gate-tab=tag-rec-custom` 直达后由 service 构造时消费二级参数 —
* `tag-rec-group=` => 选定组语境 (失效 id 静默不选 + toast「组不存在」);
* `tag-rec-input=` => 直输查询 (数字 id 逃生口, 票 01); 同现 group 优先;
* `tag-rec-new=&tag-rec-id=` => 新试验组合 (弹层 #id, id 缺/坏回落 input);
* 消费后 history.replaceState 清参 (防刷新重放/分享干净, gate-tab 保留属既有机制)
* 消费点选型 (实机修复): 原挂 tabbar useMount + emit 'refresh' —— 撞上 RecGrid mount 'reuse'
* refresh 在途时「同 tab 刷新中」守卫把 emit 丢弃 (useRefresh.ts checkIsSameTabButConditionsChanged
* 对本 tab 恒 false), 输入框填了值却永不解析。挪进构造函数 = 在建服务的那次 refresh 内部先写
* 会话种子, 随后的 doFetch 直接读到 —— 不依赖 emit 时序, race 由构造消除
*/
var tagRecDeepLinkConsumed = false;
function consumeTagRecDeepLink() {
if (tagRecDeepLinkConsumed) return;
tagRecDeepLinkConsumed = true;
if (typeof location === "undefined") return;
const { target, paramsToClear } = parseTagRecDeepLink(location.search);
if (!paramsToClear.length) return;
for (const p of paramsToClear) {
const u = new URL(location.href);
u.searchParams.delete(p);
history.replaceState({}, "", u.href);
}
if (!target) return;
perfMark("tagrec-deeplink-consume", { [target.kind]: 1 });
if (target.kind === "new") {
module.import('./workbench-CBkmOf8A-DgjoaFMd.js').then((w) => w.activateTagRecWorkbench().then(() => {
w.seedTagRecNewTrialWithTag({
id: target.id,
name: target.name
});
}));
return;
}
if (target.kind === "group") {
if (favorites.list.some((g) => g.id === target.groupId)) switchTagRecGroupContext(target.groupId);
else toast("组不存在");
return;
}
beginTagRecCustomQuery(target.input);
}
/**
* [tag-rec] perf 票 06: 深链锚点预热 —— 模块求值期 (mounted 前 ~1s) 即按 `tag-rec-input` 发 tag/info,
* 与指纹铸型 (fingerprint.ts 模块级预热) / store 水合 / 页面挂载全并行;
* 建服务后 resolveAnchor 撞 SessionFetchCache 同一归一 key 的在途/已结 promise,
* 锚点段 (GM 读 + spi 等 + x/tag/info 往返) 整段退出深链首屏关键路径。
* group 档零解析不预热; 失败静默 (正式链走原样重试语义); 数字输入预热带 id 反查。
* deeplink.ts 是故意不碰 location/GM 的纯逻辑叶 (见该文件头注), 预热只能挂在本模块体;
* node 测试 location/GM 双守卫早退。
*/
function prefetchTagRecDeepLinkAnchor() {
if (typeof location === "undefined" || typeof GM === "undefined" || typeof GM.xmlHttpRequest !== "function") return;
const { target } = parseTagRecDeepLink(location.search);
if (target?.kind !== "input") return;
const parsed = parseTagInput(target.input);
if (parsed?.kind === "name") fetchTagInfoByName(parsed.name).catch(() => void 0);
else if (parsed?.kind === "id") fetchTagInfoById(parsed.id).catch(() => void 0);
}
prefetchTagRecDeepLinkAnchor();
/**
* 票 32 干态/成员签名模块级共享: service 实例随 refresh/重建轮换, 但「这个查询语境哪些 tid 已干」
* 是会话事实不该随实例丢 —— 刷干池再刷新不再把死源的 tag/top 重打一轮抢占深挖闸位 (冷池首屏提速);
* 成员签名变 => fetchMore 内全清重获一轮机会; 整页重载 (F5) 自然清零
* 票 39: persistEmpty 开 —— 空响应标干落 GM `tag-rec:dry-tids` (TTL 60min), F5 跨会话不再重打死源;
* 零新增连击维持会话级不落盘 (相对本会话 seenBvids 判定, F5 后语义不成立)
*/
var sharedDryTids = new TagRecTidDryTracker({ persistEmpty: true });
var sharedMemberSignature;
/**
* 票 37 深挖/产出态同享模块级: 刷新重建 service 续挖不重启 —— 流页游标/兜底堆/选种标记/三停纪律、
* 跨轮去重集、未上屏队列全保; 已付过代价的请求与已攒下的货都不再重付。签名变 => fetchMore 内原地重置
*/
var sharedLadderState = newTagRecLadderState();
var sharedSeenBvids = /* @__PURE__ */ new Set();
/** 交付队列写版本 (Proxy 拦写): bvid 懒索引的等长换血检出依据 —— splice 交付+push 回补净长不变也无处可藏 */
var sharedBufferQueueSeq = 0;
var sharedBufferQueue = tagRecVersionedArray(() => sharedBufferQueueSeq++);
var sharedCandidates = new TagRecDeferredCandidates();
/**
* 交付队列/兜底堆 bvid 懒索引 (mergeIncoming 每条到达物全扫 => O(items×queue) 悬崖):
* 写版本变自动重建 —— 一切写路径 (push/splice/restore/测试直改) 全覆盖
*/
var sharedQueueIndex = createTagRecBvidIndex(() => sharedBufferQueue, () => sharedBufferQueueSeq, (it) => it.api === EApiType.TagRec);
var sharedHeapIndex = createTagRecBvidIndex(() => sharedLadderState.heap, tagRecHeapVersion);
/** 已见到达物是否仍挂未交付态 (归一/合并早退判): 队列 ∪ 兜底堆 ∪ 被挡候选表 */
var sharedHasPending = (bvid) => sharedQueueIndex.map().has(bvid) || sharedHeapIndex.map().has(bvid) || sharedCandidates.has(bvid);
var sharedUpSearch = new TagRecUpSearchPool();
/** 组合组内原子级公平混排的欠额记账 (跨页平滑; 死原子由 advance 的 live 清理自动失效)。 */
var sharedAtomTurns = new WeightedTurns();
/**
* 已交付内容的有界回放池 (票 002): 「显示已看」打开时零请求回放"第一次看到的内容"。
* 不再依赖"清 seen 重新拉取"—— 交付时条目已出队、游标已越过, 重拉拉不回来。
*/
var sharedDeliveryReplay = createTagRecDeliveryReplay();
/**
* 回放池键 (票 002 + 审计修复): 直接取语境签名的**成员段** —— 工作台组合签名 /
* 非工作台的 组id+tag_ids+扩选+输入词 全在里面 (原实现只读 workingGroup,
* 非工作台模式全语境共用 `[null,null,null]` 串池, 选组 A 的内容会回放进选组 B)。
* `|seen:` 段只剥**开关位** (seen:: → seen:*:): 开关只切显示不换键;
* **代际保留** —— 「清空已看」(rev++) 后池子不再复活已交付内容。
* `|sources:` 段不入键: 取数态/干态一变就换键, 回放会永远命中不了。
* 试验身份仍前置 (workbench: 段用的是恒等 __tagrec_working__, 不含 activeId ——
* 同组合的两个试验不能串池); 输入词尾置双保险 (wg 段签名不含它)。
*/
/** 成员段的 seen 段只剥**开关位** (seen:: → seen:*:): 显示切换不换键; 代际保留。 */
function tagRecMembersCore(members) {
return members.replace(/\|seen:[^:]*:/, "|seen:*:");
}
function tagRecDeliveryReplayKey() {
const members = computeTagRecCustomContextSignature().split("|sources:", 1)[0];
if (!members) return "";
return `${tagRecWorkbench.activeId ?? ""}|${tagRecMembersCore(members)}|in:${normalizeTagName(tagRecCustomSession.inputText ?? "")}`;
}
var sharedFilterSignature;
/** 上次同步的工作台尝试身份 (切尝试=重开收集的判定, 见 syncCustomSharedState)。 */
var sharedWorkbenchTrialId;
/**
* 票 004: 组合原子集变迁账 —— 同试验配方编辑时, 被删的原子用于标"旧语境存货"。
* 切试验不标 (换语境不是删条件, 存货已被清场/暂存)。
* atomsSig 只在**原子集**真正变化时重算 —— 输入词/选组变动不算配方编辑。
*/
var sharedPrevConditionAtoms;
var sharedPrevAtomsSig;
var sharedRemovedConditionAtoms = [];
var TRIAL_STASH_CAP = 6;
/** 票 005: 每轮轮转重试的停牌条数上限 —— 补位不吃满整轮核查额度 */
var TAG_REC_PARTITION_HELD_RETRY_QUOTA = 4;
/** 票 005: 同一条连扑 3 轮放弃轮转 (留队不自愈就停补查; 「重试核实」清零) */
var TAG_REC_PARTITION_HELD_MAX_ATTEMPTS = 3;
var trialStash = /* @__PURE__ */ new Map();
/** 清场前调用: 把当前共享盒整体拷进该试验的暂存档 (值拷贝, 与活容器不共享) */
function trialStashCapture(trialId) {
const s = sharedLadderState;
trialStash.delete(trialId);
trialStash.set(trialId, {
seenBvids: new Set(sharedSeenBvids),
dryTids: sharedDryTids.snapshot(),
bufferQueue: [...sharedBufferQueue],
candidates: sharedCandidates.snapshot(),
upSearch: sharedUpSearch.snapshot(),
atomCredits: new Map(sharedAtomTurns.credits),
removedAtoms: [...sharedRemovedConditionAtoms],
prevAtoms: sharedPrevConditionAtoms ? [...sharedPrevConditionAtoms] : void 0,
prevAtomsSig: sharedPrevAtomsSig,
ladder: {
streams: new Map(s.streams),
heap: [...s.heap],
poolItems: [...s.poolItems],
exactPoolItems: [...s.exactPoolItems],
autoDigs: s.autoDigs,
earlyStopped: s.earlyStopped,
earlyStopReason: s.earlyStopReason,
lowYieldStreak: s.lowYieldStreak,
relatedSeeded: s.relatedSeeded,
pagesFetched: s.pagesFetched,
lastDigYield: s.lastDigYield,
lastDigError: s.lastDigError,
seededBvids: new Set(s.seededBvids),
seedRequests: new Map(s.seedRequests),
seedScanCursor: s.seedScanCursor,
producedTotal: s.producedTotal,
tagCursors: new Map(s.tagCursors),
queryCursor: s.queryCursor,
wordCursor: s.wordCursor,
wordWindow: new Set(s.wordWindow)
}
});
while (trialStash.size > TRIAL_STASH_CAP) trialStash.delete(trialStash.keys().next().value);
}
/**
* 切回时原地恢复暂存档: 全部容器「清+回填」保对象身份; 无档 => false (调用侧走原清场路径)。
* 票 003: 不再因「显示已看」打开而弃档 —— 那正是"开着显示已看切回 = 空场"的成因。
*/
function trialStashRestore(trialId) {
const stash = trialStash.get(trialId);
trialStash.delete(trialId);
if (!stash) return false;
sharedSeenBvids.clear();
for (const b of stash.seenBvids) sharedSeenBvids.add(b);
sharedBufferQueue.length = 0;
sharedBufferQueue.push(...stash.bufferQueue);
sharedCandidates.restore(stash.candidates);
sharedUpSearch.restore(stash.upSearch);
sharedDryTids.restore(stash.dryTids);
sharedAtomTurns.credits.clear();
for (const [k, v] of stash.atomCredits) sharedAtomTurns.credits.set(k, v);
sharedRemovedConditionAtoms = stash.removedAtoms;
sharedPrevConditionAtoms = stash.prevAtoms;
sharedPrevAtomsSig = stash.prevAtomsSig;
const s = sharedLadderState;
s.streams.clear();
stash.ladder.streams.forEach((v, k) => s.streams.set(k, v));
s.heap.length = 0;
s.heap.push(...stash.ladder.heap);
s.poolItems.length = 0;
s.poolItems.push(...stash.ladder.poolItems);
s.exactPoolItems.length = 0;
s.exactPoolItems.push(...stash.ladder.exactPoolItems);
s.seededBvids.clear();
for (const b of stash.ladder.seededBvids) s.seededBvids.add(b);
s.seedRequests.clear();
stash.ladder.seedRequests.forEach((v, k) => s.seedRequests.set(k, v));
s.tagCursors.clear();
stash.ladder.tagCursors.forEach((v, k) => s.tagCursors.set(k, v));
s.wordWindow.clear();
for (const w of stash.ladder.wordWindow) s.wordWindow.add(w);
s.autoDigs = stash.ladder.autoDigs;
s.earlyStopped = stash.ladder.earlyStopped;
s.earlyStopReason = stash.ladder.earlyStopReason;
s.lowYieldStreak = stash.ladder.lowYieldStreak;
s.relatedSeeded = stash.ladder.relatedSeeded;
s.pagesFetched = stash.ladder.pagesFetched;
s.lastDigYield = stash.ladder.lastDigYield;
s.lastDigError = stash.ladder.lastDigError;
s.seedScanCursor = stash.ladder.seedScanCursor;
s.producedTotal = stash.ladder.producedTotal;
s.queryCursor = stash.ladder.queryCursor;
s.wordCursor = stash.ladder.wordCursor;
s.digRunning = void 0;
s.epoch++;
s.stockEpoch++;
return true;
}
/** 测试用: 清模块级干态+签名+深挖共享态 (等价整页重载; 同文件多 service 测试需防跨用例串态) */
function resetTagRecCustomSharedState() {
tagRecCustomSession.partitionPaused = false;
tagRecCustomSession.partitionPending = 0;
tagRecCustomSession.partitionAbandoned = 0;
tagRecCustomSession.partitionBudgetExhausted = false;
tagRecCustomSession.replayDelivered = 0;
tagRecCustomSession.localFirst = false;
sharedUpSearch.reset();
sharedDryTids.clear();
sharedMemberSignature = void 0;
resetTagRecLadderState(sharedLadderState);
sharedDeliveryReplay.clear();
sharedSeenBvids.clear();
sharedPrevConditionAtoms = void 0;
sharedPrevAtomsSig = void 0;
sharedRemovedConditionAtoms = [];
sharedBufferQueue.length = 0;
sharedCandidates.clear();
sharedAtomTurns.credits.clear();
sharedWorkbenchTrialId = void 0;
sharedFilterSignature = void 0;
trialStash.clear();
}
/** 测试/诊断用: 拉取级去重集 (共享存货的一部分; 切尝试/打开显示已看时重开收集)。 */
function tagRecCustomSharedSeenBvids() {
return sharedSeenBvids;
}
/** 测试/诊断用: 未上屏共享队列 (本地先行闸/成本预览的存量来源)。 */
function tagRecCustomSharedBufferQueue() {
return sharedBufferQueue;
}
/** 测试/诊断用: 已交付内容的有界回放池 (票 002; 「显示已看」打开时零请求供给)。 */
function tagRecCustomSharedDeliveryReplay() {
return sharedDeliveryReplay;
}
/** 测试/诊断用: 同试验配方编辑时被删的条件原子 (票 004; 旧语境存货判定的原料)。 */
function tagRecCustomSharedRemovedAtoms() {
return sharedRemovedConditionAtoms;
}
/**
* 选条件成本预览 (Q8a, 零请求): 假设该原子加入当前组合后, 本地存量 (队列 ∪ 候选池) 有多少条
* 会被证成 match, 以及它会并入同字段「或」组还是跨字段「且」(与 addTagRecCondition 落点一致)。
*/
function tagRecLocalConditionImpact(atom) {
const combination = tagRecCustomSession.workingGroup?.combination;
const join = combination?.include.children.some((child) => child.kind === "group" && child.name === atom.field) ? "any" : "all";
if (!combination) return {
hits: 0,
join
};
const next = cloneDeep(combination);
if (!addTagRecCondition(next, atom) || !validateTagRecCombination(next)) return {
hits: 0,
join
};
const pool = /* @__PURE__ */ new Map();
for (const item of [...sharedBufferQueue, ...sharedCandidates.values()]) if (item.api === EApiType.TagRec && item.bvid) pool.set(item.bvid, item);
let hits = 0;
for (const item of pool.values()) if (evalTagRecCombination(item, next).state === "match") hits += 1;
return {
hits,
join
};
}
/** A4-6: 「清空已看」联动 —— 持久池清完, 跨轮内存去重同步清 (经 service-map 注册表调达, 保惰性边界) */
function clearTagRecCustomSharedSeenBvids() {
sharedSeenBvids.clear();
}
/**
* 票 32/44: 成员语境签名 = 选定组 tag_ids ∪ 扩选暂存位 归一去重 id 集 (序不敏感) ——
* 提升模块级: 构造期同步与 useRefresh 漂移判定复用 (体同原 computeMemberSignature)
* AUDIT-09: 并入输入原文归一化 —— 锚点随 inputText 派生, 输入变即语境变;
* 修「expandedIds 空+组未动时 beginTagRecCustomQuery(new) 的 refresh 被签名恒等判丢」
*/
function tagRecCustomQueryGroups() {
return tagRecCustomSession.workingGroup ? [tagRecCustomSession.workingGroup] : pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds);
}
/**
* tab1 同款版号记忆化 (巨型收藏夹 535 组规模修复): resolver/签名输入集 =
* favorites/favoritesL1/blocks/settings.tagRec/customSession (+sharedSession 入语境键);
* 非代理输入退化为逐次直算, 与缓存前语义一致。
*/
function customResolverKey() {
const rk = tagRecVersionKey([
favorites.list,
favoritesL1.list,
tagRecCombinationBlocks,
settings.tagRec
]);
if (rk === void 0) return void 0;
return `${rk}|s:${JSON.stringify([
tagRecCustomSession.workingGroup ?? null,
tagRecCustomSession.draftGroup ?? null,
tagRecCustomSession.inputText,
tagRecCustomSession.expandedIds,
tagRecCustomSession.selectedGroupIds,
tagRecCustomSession.filterOverride ?? null,
tagRecCustomSession.allowUnverified
])}`;
}
function customContextKey() {
const rk = customResolverKey();
if (rk === void 0) return void 0;
return `${rk}|sh:${JSON.stringify([tagRecCustomSession.bypassSeen, tagRecSharedSession.recoveryRevision])}`;
}
var customResolverSlot = versionedSlot();
var customSourceSigSlot = versionedSlot();
var customContextSigSlot = versionedSlot();
var customFilterSigWithSources = versionedSlot();
var customFilterSigNoSources = versionedSlot();
function computeTagRecCustomSourceSignature() {
return customSourceSigSlot(customResolverKey(), () => {
const consumers = tagRecCustomQueryGroups().map((g) => ({
key: { id: g.id },
name: g.name
}));
if (tagRecCustomSession.inputText || tagRecCustomSession.expandedIds.length) consumers.push({
key: void 0,
name: "",
input: true
});
const d = tagRecCustomSession.draftGroup;
const resolve = createTagRecScopeResolver({
globalExclusions: tagRecCombinationBlocks.list,
globalExclusionsInvalid: tagRecCombinationBlocks.invalid,
core: settings.tagRec.filter,
sessionCore: tagRecCustomSession.filterOverride,
groupsById: new Map(favorites.list.map((g) => [g.id, g])),
groupsByName: new Map(favorites.list.map((g) => [g.name, g])),
l1ById: new Map(favoritesL1.list.map((l) => [l.id, l])),
fallbackGroup: tagRecCustomSession.workingGroup ?? (d ? {
id: "__draft__",
name: d.name,
tag_ids: [],
enabled: true,
weight: 1,
addedAt: 0,
overrides: { negative: d.negative }
} : void 0)
});
return tagRecSourceSignature(consumers, resolve);
});
}
function computeTagRecCustomContextSignature() {
return customContextSigSlot(customContextKey(), () => {
const working = tagRecCustomSession.workingGroup;
if (working) return `workbench:${JSON.stringify([
working.id,
working.l1Id,
working.combination ? tagRecCombinationSignature(working.combination) : working.tag_ids
])}|seen:${!!tagRecCustomSession.bypassSeen}:${tagRecSharedSession.recoveryRevision}|sources:${computeTagRecCustomSourceSignature()}`;
const ids = /* @__PURE__ */ new Set();
for (const g of tagRecCustomQueryGroups()) for (const e of g.tag_ids) ids.add(e.id);
for (const e of tagRecCustomSession.expandedIds) ids.add(e.id);
const inputNorm = normalizeTagName(tagRecCustomSession.inputText);
if (!ids.size && !inputNorm && !tagRecCustomSession.selectedGroupIds.length) return "";
const gids = [...tagRecCustomSession.selectedGroupIds].sort().join("+");
return [...ids].sort((a, b) => a - b).join(",") + `|in:${inputNorm}|g:${gids}|seen:${!!tagRecCustomSession.bypassSeen}:${tagRecSharedSession.recoveryRevision ?? 0}|sources:${computeTagRecCustomSourceSignature()}`;
});
}
/**
* 票 44: 签名比对前移 —— 「有旧签名且不同」即原地重置共享态; 构造期+fetchMore 双点调用。
* 构造期必须: loadMore 先放 bufferQueue 后进 fetchMore, 不前置同步 = 旧境存货被当新境首页放行
*/
function syncCustomSharedState() {
const sig = computeTagRecCustomContextSignature();
if (!sig) return {
sig,
changed: false
};
const members = sig.split("|sources:", 1)[0];
const changed = sharedMemberSignature !== void 0 && members !== sharedMemberSignature;
const seenOnlyDelta = changed && tagRecMembersCore(members) === tagRecMembersCore(sharedMemberSignature);
const trialId = tagRecWorkbench.activeId;
if (tagRecCustomSession.workingGroup) {
const trialChanged = sharedWorkbenchTrialId !== void 0 && trialId !== sharedWorkbenchTrialId;
const currentAtoms = tagRecCustomSession.workingGroup.combination ? tagRecConditionAtoms(tagRecCustomSession.workingGroup.combination.include) : [];
const atomsSig = currentAtoms.map(tagRecConditionKey).join("|");
if (trialChanged) {
if (sharedWorkbenchTrialId) trialStashCapture(sharedWorkbenchTrialId);
sharedPrevConditionAtoms = void 0;
sharedPrevAtomsSig = void 0;
sharedRemovedConditionAtoms = [];
} else if (atomsSig !== sharedPrevAtomsSig) {
const curKeys = new Set(currentAtoms.map(tagRecConditionKey));
const removed = /* @__PURE__ */ new Map();
for (const atom of [...sharedRemovedConditionAtoms, ...sharedPrevConditionAtoms ?? []]) {
const key = tagRecConditionKey(atom);
if (!curKeys.has(key)) removed.set(key, atom);
}
sharedRemovedConditionAtoms = [...removed.values()];
sharedPrevConditionAtoms = currentAtoms;
sharedPrevAtomsSig = atomsSig;
}
if (trialChanged) {
sharedSeenBvids.clear();
sharedDryTids.clear();
if (!(trialId && trialStashRestore(trialId))) {
resetTagRecLadderState(sharedLadderState, true);
sharedUpSearch.reset(true);
sharedBufferQueue.length = 0;
sharedCandidates.clear();
sharedAtomTurns.credits.clear();
}
tagRecCustomSession.replayDelivered = 0;
tagRecCustomSession.partitionAbandoned = 0;
tagRecCustomSession.partitionBudgetExhausted = false;
} else if (seenOnlyDelta) ; else if (!changed) {
sharedUpSearch.invalidate();
sharedLadderState.epoch++;
sharedLadderState.earlyStopped = false;
sharedLadderState.lowYieldStreak = 0;
}
}
sharedWorkbenchTrialId = trialId;
if (changed && !(tagRecCustomSession.workingGroup && seenOnlyDelta)) {
tagRecCustomSession.replayDelivered = 0;
tagRecCustomSession.partitionAbandoned = 0;
tagRecCustomSession.partitionBudgetExhausted = false;
if (tagRecCustomSession.workingGroup) {
sharedUpSearch.invalidate();
sharedLadderState.epoch++;
sharedLadderState.earlyStopped = false;
sharedLadderState.lowYieldStreak = 0;
} else {
sharedUpSearch.reset(true);
sharedDryTids.clear();
resetTagRecLadderState(sharedLadderState, true);
sharedSeenBvids.clear();
sharedBufferQueue.length = 0;
sharedCandidates.clear();
}
}
sharedMemberSignature = members;
return {
sig,
changed: changed && !(tagRecCustomSession.workingGroup && seenOnlyDelta)
};
}
/**
* 实修票①「放宽过滤救不活已滤条」: 过滤生效签名 —— 覆盖生效链全部输入
* (全局核心 + 会话覆盖副本 + 各组负向/严格/overrides + 各 L1 + 草稿组负向件; 改名也入签)。
* 变 => 仅清跨轮 seenBvids: 下轮 tag/top 重采样时已滤条重新进池参评 —— 零新增请求
* (浅池按 tid 采样本轮必拉, 签名只切条级去重记忆); 真看过的在 seen-set 不复活。
* 收紧侧由 refilterSharedQueue 覆盖存货 (构造期), 本签名管放宽侧的复活资格;
* 深挖已消费页中的未交付原料由候补缓存重评,页游标不倒退。
*/
/** A4-3: 过滤签名计算外提 —— service-map 漂移判定复用 (在途刷新的收紧型 emit 不再被静默丢) */
function computeTagRecCustomFilterSignature(withoutSources = false) {
return (withoutSources ? customFilterSigNoSources : customFilterSigWithSources)(customResolverKey(), () => {
const d = tagRecCustomSession.draftGroup;
return computeTagRecFilterSignature({
core: settings.tagRec.filter,
sessionCore: tagRecCustomSession.filterOverride,
groups: favorites.list,
l1s: favoritesL1.list,
withoutSources,
fallbackGroup: tagRecCustomSession.workingGroup ?? (d ? {
name: d.name,
negative_tag_ids: d.negative_tag_ids,
negative_strict: d.strict,
overrides: d.negative ? { negative: d.negative } : void 0
} : void 0)
});
}) + `|pace:${tagRecCustomSession.supplyPaused}|unverified:${tagRecCustomSession.allowUnverified}|globalExclusions:${JSON.stringify([tagRecCombinationBlocks.list, tagRecCombinationBlocks.invalid])}`;
}
function syncCustomFilterState() {
const source = computeTagRecCustomSourceSignature();
const sig = computeTagRecCustomFilterSignature(true) + "|sources:" + source;
if (sharedFilterSignature !== void 0 && sig !== sharedFilterSignature) {
sharedUpSearch.invalidate();
sharedDryTids.revive(tagRecReopenedTopTids(sharedFilterSignature.split("|sources:", 2)[1], source, favorites.list));
sharedLadderState.epoch++;
sharedLadderState.earlyStopped = false;
sharedLadderState.lowYieldStreak = 0;
}
sharedFilterSignature = sig;
}
/** [tag-rec] 票 13-fix: 空语境(待解析输入)退避期有界重试上限 —— 超限终止本轮, 防零让出空转/无限挂起刷新 */
var TAG_REC_EMPTY_CONTEXT_MAX_RETRIES = 2;
var TagRecCustomService = exports("TagRecCustomService",class TagRecCustomService extends BaseTabService {
static PAGE_SIZE = 20;
/**
* 票 44: 语境签名快照 —— useRefresh 判定在途刷新是否被语境变更 (选组/扩选/锚点变) 作废顶替;
* AUDIT-09/16: 输入解析成功触发「组⇄草稿互斥」弃组 => 签名换型 (id段∪in段 => in段),
* doResolveAnchor 内同步共享态后原地重钉本字段 —— 故非 readonly
*/
contextSignature;
/**
* A4-3: 过滤签名构造期钉点 —— drift 判定的「收紧型过滤变更」轴;
* 刻意不随 fetchMore 重钉 (放宽侧复活走 seenBvids 通道自愈, 钉点漂移保留给 drift 判定消费)
*/
filterSignature;
constructor() {
super(TagRecCustomService.PAGE_SIZE);
consumeTagRecDeepLink();
const context = syncCustomSharedState();
tagRecCustomSession.replayDelivered = 0;
this.contextSignature = context.sig;
syncCustomFilterState();
sharedUpSearch.sync(this.recheckPlan().up);
this.filterSignature = computeTagRecCustomFilterSignature();
this.qs.bufferQueue = sharedBufferQueue;
this.refilterSharedQueue();
this.retryPartitions();
}
partitionBudget;
/** 票 005: 停牌 bvid → 已被暂停轮数 (重试有上限, 不再无限期白吃额度) */
partitionHeld = /* @__PURE__ */ new Map();
partitionPausePolicy;
get autoLoadPaused() {
if (tagRecCustomSession.partitionPaused && this.partitionPausePolicy !== computeTagRecCustomFilterSignature()) {
this.partitionHeld.clear();
this.retryPartitions();
}
return tagRecCustomSession.partitionPaused || tagRecCustomSession.supplyPaused && !this.qs.bufferQueue.length;
}
retryPartitions() {
for (const bvid of this.partitionHeld.keys()) this.partitionHeld.set(bvid, 0);
tagRecCustomSession.partitionPaused = false;
tagRecCustomSession.partitionPending = 0;
tagRecCustomSession.partitionAbandoned = 0;
tagRecCustomSession.partitionBudgetExhausted = false;
}
beginLoadOperation(signal) {
this.endLoadOperation();
this.partitionBudget = new TagRecPartitionBudget(signal, tagRecCustomSession.supplyPaused ? {
requests: 0,
candidates: 40,
ms: 5e3
} : void 0);
}
endLoadOperation() {
this.partitionBudget?.dispose();
this.partitionBudget = void 0;
}
tabbarView = /* @__PURE__ */ jsx(TagRecCustomTabbarView, { service: this });
sidebarView = void 0;
/**
* Actual local candidates; counts are not result totals.
* deliverable/held 与交付路径共用同一套本地闸: 生效链逐池 evalTagRecChain
* (来源池/负向/名单/组合三态/小池降级兜底, deferPartitions 同 runFilterStage),
* 再叠组合三态 —— 真过滤会杀掉的条目不再谎报可交付; 分区暂存条计 held 不计库存。
*/
getDiscoveryStatus() {
const candidates = /* @__PURE__ */ new Map();
for (const item of [...sharedCandidates.values(), ...sharedBufferQueue]) if (item.api === EApiType.TagRec) candidates.set(item.bvid, mergeTagRecVideoEvidence(item, getCachedTagRecVideoEvidence(item.bvid)));
const combination = tagRecCustomSession.workingGroup?.combination;
const all = [...candidates.values()];
const resolve = this.scopeResolver();
const poolSizes = tagRecPoolSizes(all);
const negPools = tagRecNegativePools();
const passForPool = (item, pool) => {
projectTagRecActiveClaims(item, resolve);
const ctx = {
negative: { pools: negPools },
queryMatches: this.queryMatcher(),
deferPartitions: true,
relatedFallback: this.relatedFallbackOn()
};
for (const key of tagRecChainKeysOf(item)) for (const p of tagRecPoolsForChain(item, key)) {
if (p !== pool) continue;
const eff = resolve(key, p);
if (evalTagRecChain(item, eff, {
...ctx,
sourcePool: p,
poolSize: poolSizes[tagRecPoolScopeKey(key, p)]
}) == null) return true;
}
return false;
};
return TAG_REC_ALL_POOL_KEYS.map((pool) => {
const items = all.filter((item) => tagRecSourceClaims(item).some((claim) => poolOfVia(claim.via) === pool));
const verdicts = items.map((item) => [
tagRecDeliverable(item, combination),
passForPool(item, pool),
this.partitionHeld.has(item.bvid)
]);
return {
pool,
cached: items.length,
deliverable: verdicts.filter(([state, pass, held]) => state === "match" && pass && !held).length,
held: verdicts.filter(([state, pass, held]) => pass && (state === "pending" || held)).length,
tagsPending: items.filter((item) => item.ownTagIds === void 0).length
};
});
}
/** Explicit, bounded batch for browsing tag/top materials; never raises request limits. */
async completeDiscoveryTags(pool, signal) {
const policy = computeTagRecCustomFilterSignature();
if (tagRecBackoff.isEngaged || signal.aborted || this.isSessionStale()) return {
updated: 0,
attempted: 0,
stale: true
};
const candidates = /* @__PURE__ */ new Map();
for (const item of [...sharedBufferQueue, ...sharedCandidates.values()]) if (item.api === EApiType.TagRec && tagRecSourceClaims(item).some((claim) => poolOfVia(claim.via) === pool) && item.ownTagIds === void 0) candidates.set(item.bvid, item);
const pending = [...candidates.values()].slice(0, 20);
const budget = new TagRecPartitionBudget(signal, {
requests: 20,
candidates: 40,
ms: 5e3
});
let cursor = 0;
let updated = 0;
let attempted = 0;
const stale = () => signal.aborted || this.isSessionStale() || policy !== computeTagRecCustomFilterSignature();
try {
await Promise.all(Array.from({ length: 2 }, async () => {
while (cursor < pending.length && !stale() && !tagRecBackoff.isEngaged) {
const item = pending[cursor++];
attempted++;
const evidence = await budget.lookupEvidence(item.bvid);
if (stale()) return;
if (evidence?.ownTagIds !== void 0) {
Object.assign(item, mergeTagRecVideoEvidence(item, evidence));
updated++;
}
}
}));
if (!stale() && updated) sharedCandidates.needsRefilter = true;
return {
updated,
attempted,
stale: stale()
};
} finally {
budget.dispose();
}
}
recheckPlan() {
const groups = tagRecCustomQueryGroups();
const inputs = this.queryEntries().filter((e) => !groups.some((g) => g.tag_ids.some((t) => t.id === e.id)));
const rounds = [...groups.flatMap((g) => g.tag_ids.map((e) => ({
tid: e.id,
keyword: e.name,
groupIds: [g.id],
groupNames: [g.name]
}))), ...inputs.map((e) => ({
tid: e.id,
keyword: e.name,
groupNames: [e.name],
input: true
}))];
const numeric = parseTagInput(tagRecCustomSession.inputText)?.kind === "id";
return planTagRecRecall({
roundSources: rounds,
digSources: [...deriveGroupCliques(groups.map((g) => ({
...g,
extra: tagRecSearchExtraOf(this.scopeResolver()({ id: g.id }, "allV2"))
})), Infinity), ...!numeric ? inputs.map((e) => ({
key: normalizeTagName(e.name),
keyword: e.name,
attribution: [e.name],
input: true
})) : []],
scopeConsumers: groups.map((group) => ({
key: { id: group.id },
name: group.name
}))
}, this.scopeResolver());
}
getRecheckOptions() {
return tagRecRecheckOptions(this.recheckPlan());
}
recheck(selection, signal) {
if (tagRecBackoff.isEngaged || this.isSessionStale()) return Promise.resolve({
requests: 0,
added: 0,
empty: false,
failed: false,
stale: true
});
return tagRecRecallScheduler.withForegroundSupply(() => this.ladder.recheck(selection, this.recheckPlan(), signal ?? this.lastFetchSignal)).then((result) => {
if (!result.stale && result.added) {
this.hasMoreExceptQueue = true;
tagRecCustomSession.hardBottom = false;
tagRecCustomSession.digPending = false;
}
return result;
});
}
restore() {
this.retryPartitions();
super.restore();
for (const it of this.qs.bufferQueue) if (it.api === EApiType.TagRec) delete it.tagRecReplayed;
sharedCandidates.flushMerged();
const filtered = runTagRecFilter(this.qs.bufferQueue.filter((it) => it.api === EApiType.TagRec), {
resolve: this.scopeResolver(),
pools: tagRecNegativePools(),
queryMatches: this.queryMatcher(),
deferPartitions: true
}).items;
const keep = new Set(filtered);
this.qs.bufferQueue.splice(0, this.qs.bufferQueue.length, ...this.qs.bufferQueue.filter((it) => it.api !== EApiType.TagRec || keep.has(it)));
}
hasMoreExceptQueue = true;
/**
* 审计回填 (A1): 基类 hasMore 只看 `queue.length>0`, 全 stale 队列 (删原子后队列
* 只剩归因被删原子的残留条) 配 hasMoreExceptQueue=false 时恒真 => footer 视口内
* 无界空转 (每轮空 loadMore + 全网格 uniqBy/重渲)。活货口径: 任一非 stale 条目
* 即算有货 —— 原子加回会签名漂移重开语境, 不会假死。
*/
get hasMore() {
if (!this.qs.bufferQueue.length) return this.hasMoreExceptQueue;
return this.queueHasLiveStock() || this.hasMoreExceptQueue;
}
liveStockCache;
queueHasLiveStock() {
const stale = this.stalePredicate();
if (!stale) return true;
const key = `${sharedBufferQueueSeq}|${computeTagRecCustomContextSignature()}`;
if (this.liveStockCache?.key === key) return this.liveStockCache.hasLive;
const hasLive = this.qs.bufferQueue.some((item) => item.api !== EApiType.TagRec || !stale(item));
this.liveStockCache = {
key,
hasLive
};
return hasLive;
}
/** [tag-rec] 票 13-fix: 空语境(待解析输入)退避期重试计数 (成功轮/新查询归零) */
emptyContextRetries = 0;
/** [tag-rec] R09: 未决轮 (零产出+失败源) 连续计数 —— 有界重试, 超限抛可重试错误 (定轮归零) */
failedSourceRounds = 0;
/** 跨轮 bvid 去重 (语义同 recItemUniqer; 渲染层 concatRecItems 再兜底); 票 37: 模块级共享, 刷新续去重 */
seenBvids = sharedSeenBvids;
/** 锚点解析结果缓存 (promise 级, 并发轮共享; 失败不缓存允许重试) */
anchorPromise;
anchorEntry;
anchorInfo;
anchorInput = "";
inputNorm = "";
isIdPath = false;
/** 本 service 查询单元: 锚点 + 字形变体 id (Phase 2 自动并入) + 用户扩选 id */
queryIds = /* @__PURE__ */ new Map();
discoveredVariants = [];
/** A5: scoreParams 记忆盒 —— {core, sig} 双闸失配即清 (见 ladder opts 注释) */
scoreParamsCache = {
core: void 0,
sig: void 0,
patch: void 0,
resolve: void 0
};
/**
* 内层 tag 轮转游标 (票 17): 选定 L2 组 id => 游标; 该组 >10 id 每轮取 10 切片逐轮覆盖
* AUDIT-20: 升共享盒 —— 实例级游标随 refresh 归零 → 永远重拉首片 (全已见 contributed=0 →
* 干态连击标干 → ids 11..N 会话内饿死); 签名变经 resetTagRecLadderState 随盒同清
*/
tagCursors = sharedLadderState.tagCursors;
/** 输入单元 (锚点+变体+扩选) 轮转游标 —— 非 L2 归属, 独立标量 (同 pickTagIdsForRound 语义) */
get queryCursor() {
return sharedLadderState.queryCursor;
}
set queryCursor(v) {
sharedLadderState.queryCursor = v;
}
/**
* 票 13 兜底梯队: 兜底堆(all/v2 related + archive/related) / all/v2 深挖流(输入锚点流+组同义词簇流)
* / 共现选种 / 分批放行 / 自动深挖 / 手动待决 / 硬见底 —— D1~D8 编排见 fallback-ladder.ts
* 票 17 策略分家: tab2 激进档 —— 浅水位 (<10) 同轮预挖 + 自适应自动深挖 (三停转手动横幅)
*/
ladder = new TagRecFallbackLadder({
seenBvids: this.seenBvids,
resolve: () => this.scopeResolver(),
relatedSeedingAllowed: () => this.relatedSeedingAllowed(),
bypassSeen: () => tagRecCustomSession.bypassSeen,
policySignature: () => computeTagRecCustomSourceSignature() + computeTagRecCustomFilterSignature(true),
observe: (items) => {
sharedCandidates.resolve = this.scopeResolver();
sharedCandidates.record(items);
},
recallPlan: () => this.recheckPlan(),
applyStages: (items, ctx) => this.applyStages(items, ctx),
rankItems: (items) => this.runScoreStage(items),
seedMinScoreOf: (item) => resolveTagRecSeedMinScore(item, this.scopeResolver()),
mergeExisting: (item) => sharedCandidates.mergeIncoming(item, sharedQueueIndex.map(), (items) => this.runScoreStage(items), sharedLadderState.heap, sharedHeapIndex.map()),
pushQueue: (items) => this.enqueueStock(items),
queueSize: () => this.qs.bufferQueue.filter((item) => item.api === EApiType.TagRec && (item.eligibleGroupKeys?.length ?? 1) > 0).length,
poolItems: () => sharedCandidates.values(),
candidateScope: () => this.supplyScope(),
hasDemandStock: () => this.supplyScope().ready,
scoreParams: (it) => {
const core = settings.tagRec.filter;
const c = this.scoreParamsCache;
if (c.core !== core || c.sig !== sharedFilterSignature || c.patch !== tagRecCustomSession.filterOverride) {
c.resolve = this.scopeResolver();
c.core = core;
c.sig = sharedFilterSignature;
c.patch = tagRecCustomSession.filterOverride;
}
return resolveTagRecItemScoreParams(it, c.resolve);
},
mergeCandidates: (rows) => {
tagRecCustomSession.candidates = mergeCandidateRows(tagRecCustomSession.candidates, rows);
},
mergeVariants: (entries) => {
for (const v of entries) if (!this.queryIds.has(v.id) && !this.discoveredVariants.some((d) => d.id === v.id)) this.discoveredVariants.push({
id: v.id,
name: v.name
});
},
isStale: () => this.isSessionStale()
}, {
...FALLBACK_LADDER_STRATEGY_AGGRESSIVE,
ladder: () => settings.tagRec.ladder
}, sharedLadderState);
/**
* 票 32 逐源干态记忆: tag/top 空响应即干 / 零新增连击标干 —— 挂模块级共享 (见文件尾部 shared*):
* service 重建/点刷新存活 (刷干的池刷新不再空放一轮 tag/top 抢并发闸, 冷池首屏提速);
* 成员签名变才清内存干态 —— 票 39 起 empty 标干落 GM 持久图 (TTL 复活), F5 也不再重打死源
*/
dryTids = sharedDryTids;
phase2Running;
phase2Started = false;
/**
* AUDIT-15: 手动深挖生命周期闸 —— fetchMore 入口登记当前 abortSignal;
* service 被弃 (刷新顶替/卸载) 时 useRefresh abort 旧 controller => 在途/后续手动深挖批随截停,
* 不再裸跑推共享游标/往 detach 队列落死货白打 seenBvids
*/
lastFetchSignal;
produceTracker = createTagRecProduceTracker();
firstDataMarked = false;
/**
* 轮次阶段注入点 (spec §4.3): 过滤/算分实装 (票 06), 去已看实装票 08 —— 只换实现不动骨架
* [tag-rec] filterStage: 全局 settings.tagRec.filter + 会话覆盖副本 + 组内负向 (票 14), 计数/降级写 session
*/
stages = {
filterStage: (items, ctx) => this.runFilterStage(items, ctx),
scoreStage: (items, ctx) => this.runScoreStage(items, ctx),
seenStage: createTagRecSeenStage(tagRecCustomSession, () => tagRecCustomSession.bypassSeen)
};
/** 需要完整 core 的边界合入本次差分;取数与算分仍使用各组继承链。 */
effectiveFilter() {
const core = settings.tagRec.filter;
return tagRecCustomSession.filterOverride ? materializeTagRecSessionFilter(core, tagRecCustomSession.filterOverride) : core;
}
/** 草稿组 => TagGroup 伪形 (resolver fallbackGroup 入参): 持本会话一切未归属来源的负向位 (票02/14) */
draftAsGroup() {
if (tagRecCustomSession.workingGroup) return tagRecCustomSession.workingGroup;
const d = tagRecCustomSession.draftGroup;
if (!d) return void 0;
return {
id: "__draft__",
name: d.name,
tag_ids: this.queryEntries(),
negative_tag_ids: d.negative_tag_ids,
negative_strict: d.strict,
weight: 1,
enabled: true,
addedAt: 0,
...d.negative ? { overrides: { negative: d.negative } } : {}
};
}
/** 生效链求解器 (票01): 会话副本插链顶 + 草稿组兜底未归属名 (负向); 按 store 版号记忆化, 内部按组×池缓存 */
scopeResolver() {
const base = customResolverKey();
return customResolverSlot(base === void 0 ? void 0 : `${base}|q:${JSON.stringify([
tagRecCustomSession.anchor ?? null,
[...this.queryIds],
this.discoveredVariants
])}`, () => {
const groups = tagRecCustomSession.workingGroup ? [tagRecCustomSession.workingGroup] : favorites.list;
return createTagRecScopeResolver({
globalExclusions: tagRecCombinationBlocks.list,
globalExclusionsInvalid: tagRecCombinationBlocks.invalid,
core: settings.tagRec.filter,
sessionCore: tagRecCustomSession.filterOverride,
groupsByName: new Map(groups.map((g) => [g.name, g])),
groupsById: new Map(groups.map((g) => [g.id, g])),
l1ById: new Map(favoritesL1.list.map((l) => [l.id, l])),
fallbackGroup: this.draftAsGroup(),
allowUnverified: !!tagRecCustomSession.workingGroup && tagRecCustomSession.allowUnverified,
queryEntries: this.queryEntries()
});
});
}
/** 任一可供给消费者归属库存为零且本轮也没供到货 —— 与 preDig 内层 ready 门同义, 作外层触发 */
hasStarvedConsumer(roundItems) {
const empty = this.supplyScope().empty;
if (!empty.size) return false;
const combination = tagRecCustomSession.workingGroup?.combination;
const fed = new Set(roundItems.filter((it) => tagRecDeliverable(it, combination) === "match").flatMap((it) => tagRecSourceClaims(it).flatMap((claim) => tagRecConsumers(claim).map((c) => tagRecGroupKey(c.key)).filter((k) => !it.eligibleGroupKeys || it.eligibleGroupKeys.includes(k)))));
return [...empty].some((k) => !fed.has(k));
}
supplyScope() {
const groups = tagRecCustomQueryGroups();
const consumers = groups.map((g) => ({
key: { id: g.id },
name: g.name
}));
if (tagRecCustomSession.inputText || this.queryEntries().some((e) => !groups.some((g) => g.tag_ids.some((t) => t.id === e.id)))) consumers.push({
key: void 0,
name: "",
input: true
});
const resolve = this.scopeResolver();
const combination = tagRecCustomSession.workingGroup?.combination;
const stock = this.qs.bufferQueue.filter((item) => item.api === EApiType.TagRec && tagRecDeliverable(item, combination) === "match");
const counts = tagRecSupplyView(sharedCandidates.values(), stock, resolve, 10);
const active = consumers.filter((c) => {
const pools = resolve(c.key, "allV2").poolsEnabled;
return upSearchMids(resolve, c.key) ? pools.allV2 : TAG_REC_ALL_POOL_KEYS.some((pool) => pools[pool]);
});
const keys = (c) => [tagRecGroupKey(c.key), ...c.name && !c.input ? [tagRecGroupKey(c.name)] : []];
return {
all: new Set(consumers.flatMap(keys)),
needed: new Set(active.filter((c) => counts.needs(tagRecGroupKey(c.key))).flatMap(keys)),
/** 归属库存为零的可供给消费者主键 —— 外层欠产触发用 (与 ready 同口径的逐项分解) */
empty: new Set(active.filter((c) => !counts.groups.get(tagRecGroupKey(c.key))?.size).map((c) => tagRecGroupKey(c.key))),
ready: consumers.length ? active.length > 0 && active.every((c) => (counts.groups.get(tagRecGroupKey(c.key))?.size ?? 0) > 0) : stock.length > 0
};
}
queryMatcher() {
if (tagRecCustomSession.workingGroup?.combination) return () => true;
return createTagRecQueryMatcher(this.queryEntries());
}
queryEntries() {
if (tagRecCustomSession.workingGroup) return tagRecCustomSession.workingGroup.tag_ids;
return [
...pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds).flatMap((group) => group.tag_ids),
...tagRecCustomSession.anchor ? [tagRecCustomSession.anchor] : [],
...[...this.queryIds].map(([id, name]) => ({
id,
name
})),
...this.discoveredVariants,
...tagRecCustomSession.expandedIds
];
}
/**
* 实修 (会话覆盖/组 override「刷新」不生效根因): sharedBufferQueue 跨刷新存活 (票37),
* 存货是旧配置滤出来的, sliceFromQueue 直放不再过阶段 => 配置变了也原样上屏。
* 构造期把存货原位重过一遍 filterStage (seenStage 不重跑 —— 队列货本就是去重过的, 重跑会被 seen 误杀);
* 分隔符原样保留 (非 TagRecItem 不进过滤)
*/
refilterSharedQueue() {
const resolve = this.scopeResolver();
sharedCandidates.resolve = resolve;
const stats = refilterTagRecStock(sharedBufferQueue, this.effectiveFilter(), resolve, {
queryMatches: this.queryMatcher(),
pools: tagRecNegativePools(),
candidates: sharedCandidates,
recover: (items) => tagRecSeenStage(items, tagRecCustomSession.bypassSeen),
diversityGuard: settings.tagRec.diversityGuard,
sliceSize: TagRecCustomService.PAGE_SIZE,
relatedFallback: this.relatedFallbackOn()
});
if (stats) tagRecCustomSession.filterStats = stats;
}
/**
* 小池降级 (池浅已放宽) 时放行 related 路由条目的组合 mismatch —— 工作台之前的语义:
* 相关条带「相关」徽标交付、不冒充命中; 排除/屏蔽/来源闸不受影响 (见 filter-engine 判据)。
*/
relatedFallbackOn() {
return tagRecCustomSession.filterStats.degraded;
}
/**
* 交集下 related 只是兜底料 —— 兜底门 (degraded) 不开时选种产物纯死库存 (只堆不吐),
* 且选种要烧名单补查 + archive/related 页请求; 这些火力交集下应留给 all/v2。
*/
relatedSeedingAllowed() {
const wg = tagRecCustomSession.workingGroup;
return !(wg?.combination && this.scopeResolver()({ id: wg.id }, "allV2").intersection) || this.relatedFallbackOn();
}
/** [tag-rec] 过滤阶段: 生效链逐认领组评估 (任一放行); 分维度滤除计数+降级标记写 session */
runFilterStage(items, ctx) {
const resolve = ctx ? ctx.resolve ??= this.scopeResolver() : this.scopeResolver();
sharedCandidates.resolve = resolve;
sharedCandidates.record(items);
const { items: out, stats } = runTagRecFilter(items, {
resolve,
queryMatches: this.queryMatcher(),
pools: tagRecNegativePools(),
poolSize: ctx?.poolSize ?? ctx?.fetchedCount ?? items.length,
poolItems: ctx?.poolItems ?? items,
deferPartitions: true,
relatedFallback: this.relatedFallbackOn()
});
const prev = tagRecCustomSession.filterStats;
tagRecCustomSession.filterStats = ctx?.mergeStats ? {
dropped: prev.dropped + stats.dropped,
byDim: Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(prev.byDim), ...Object.keys(stats.byDim)])].map((k) => [k, (prev.byDim[k] ?? 0) + (stats.byDim[k] ?? 0)])),
byScope: Object.fromEntries([.../* @__PURE__ */ new Set([...Object.keys(prev.byScope ?? {}), ...Object.keys(stats.byScope ?? {})])].map((k) => [k, (prev.byScope?.[k] ?? 0) + (stats.byScope?.[k] ?? 0)])),
degraded: prev.degraded || stats.degraded,
unchecked: (prev.unchecked ?? 0) + (stats.unchecked ?? 0) || void 0,
seenDropped: prev.seenDropped
} : {
...stats,
degraded: prev.degraded || stats.degraded
};
return out;
}
/**
* 入队门 bvid 唯一性: 并发生产者窗口 (seenBvids 占位先于 push 落定) 下重复到达并入存量而非复推;
* 批内同 bvid 只留首份 (stagedBvids)。fav 侧同款守卫见 enqueueStock
*/
enqueueStock(items) {
const qmap = sharedQueueIndex.map();
const stagedBvids = /* @__PURE__ */ new Set();
const staged = [];
for (const item of items) {
if (stagedBvids.has(item.bvid)) continue;
const queued = qmap.get(item.bvid);
if (queued) sharedCandidates.mergeQueued(queued, item, (xs) => this.runScoreStage(xs));
else {
staged.push(item);
stagedBvids.add(item.bvid);
}
}
sharedCandidates.flushMerged();
if (staged.length) this.qs.bufferQueue.push(...staged);
}
/** [tag-rec] 算分排序阶段: 逐认领组链取 max (票01) + diversityGuard 两轴 (票09) */
runScoreStage(items, ctx) {
const { items: out } = runTagRecScoreStage(items, this.effectiveFilter(), {
resolve: ctx?.resolve ?? this.scopeResolver(),
diversityGuard: settings.tagRec.diversityGuard,
sliceSize: TagRecCustomService.PAGE_SIZE
});
return out;
}
/** 旁路产出 (Phase 2 精确召回 / 兜底梯队) 走同一套阶段 (spec §4.3「经同一管线」; ctx.poolSize=票06堆深注入, mergeStats=旁路账并入) */
applyStages(items, ctx) {
return applyTagRecStages(items, {
fetchedCount: items.length,
...ctx
}, this.stages);
}
/**
* [tag-rec] 上屏切片严格校验 (negative-slice.ts): 仅对「开了 negative_strict 的源组」条目
* 逐条 view/detail/tag 核查负向命中 —— 逐条请求是组级自愿付费, 默认关;
* 退避态直通 + 5s 时间盒 fail-open (negative-slice 内部); 无 strict 源 => 直通
*/
async loadMore(abortSignal) {
if (abortSignal.aborted || this.autoLoadPaused) return;
const local = !this.partitionBudget;
const budget = this.partitionBudget ?? new TagRecPartitionBudget(abortSignal, tagRecCustomSession.supplyPaused ? {
requests: 0,
candidates: 40,
ms: 5e3
} : void 0);
try {
return await this.loadPartitionChecked(abortSignal, budget);
} finally {
if (local) budget.dispose();
}
}
/** 组合原子分桶键: 条目在当前组合下命中的 include 原子 (公平混排只读判定结果, 不改语义)。 */
atomKeysOf(combination) {
const atoms = tagRecConditionAtoms(combination.include);
return (item) => atoms.filter((atom) => evalTagRecConditionAtom(item, atom).state === "match").map((atom) => tagRecConditionKey(atom));
}
/**
* 票 004: 旧语境判定 —— 当前原子全不命中 ∧ 归因里引用着被删原子的值。
* 相关池条目的 claims 不计入 tag 命中 (via 闸), 所以在新配方下只判 unknown 留队,
* 正好成了残留 —— 用**归因口径** (source.tagIds ∪ claims[].tagIds ∪ 召回词名) 而不是命中口径来标。
* 审计回填: 搜索类渠道 (all-v2/up-search) 的 claims 从不带 tagIds 只有 queryKeyword ——
* 补按名归一匹配, 否则删 up/标题/简介原子的残留结构性漏网, tag 原子的 all-v2 召回
* 残留 (queryKeyword=被删标签名) 同样漏网。双命中条目仍安全: stale 前提是全不命中。
*/
stalePredicate() {
const combination = tagRecCustomSession.workingGroup?.combination;
if (!sharedRemovedConditionAtoms.length || !combination || !validateTagRecCombination(combination)) return void 0;
const atomKeysOf = this.atomKeysOf(combination);
const keywordHit = (item, word) => item.source.queryKeyword !== void 0 && normalizeTagName(item.source.queryKeyword) === word || !!item.source.claims?.some((claim) => claim.queryKeyword !== void 0 && normalizeTagName(claim.queryKeyword) === word);
const tests = sharedRemovedConditionAtoms.map((atom) => {
if (atom.field === "tag") {
const id = atom.value.id;
const name = atom.value.name ? normalizeTagName(atom.value.name) : "";
return (item) => !!item.source.tagIds?.includes(id) || !!item.source.claims?.some((claim) => claim.tagIds?.includes(id)) || !!name && keywordHit(item, name);
}
const word = atom.field === "up" ? normalizeTagName(atom.value.name) : atom.field === "title" || atom.field === "desc" ? normalizeTagName(atom.value) : "";
return word ? (item) => keywordHit(item, word) : () => false;
});
return (item) => !atomKeysOf(item).length && tests.some((test) => test(item));
}
/** 本地可交付存量 (localFirst 闸用): 队列内已证满足当前组合且未暂存的条数。 */
localDeliverableCount() {
const combination = tagRecCustomSession.workingGroup?.combination;
return this.qs.bufferQueue.filter((item) => item.api === EApiType.TagRec && !this.partitionHeld.has(item.bvid) && tagRecDeliverable(item, combination) === "match").length;
}
async loadPartitionChecked(abortSignal, budget) {
const oldFilter = sharedFilterSignature;
syncCustomFilterState();
if (oldFilter !== sharedFilterSignature) {
this.partitionHeld.clear();
this.retryPartitions();
}
if (oldFilter !== sharedFilterSignature || sharedCandidates.needsRefilter) this.refilterSharedQueue();
if (!this.hasMore) return;
const onlyHeld = this.qs.bufferQueue.length > 0 && this.qs.bufferQueue.every((item) => item.api !== EApiType.TagRec || this.partitionHeld.has(item.bvid));
if (tagRecCustomSession.localFirst && !this.localDeliverableCount()) tagRecCustomSession.localFirst = false;
if (!this.qs.bufferQueue.length || this.hasMoreExceptQueue && !tagRecCustomSession.localFirst && (onlyHeld || this.supplyScope().needed.size > 0)) {
const more = await this.fetchMore(abortSignal);
if (abortSignal.aborted || this.isSessionStale()) return;
if (more?.length) this.qs.bufferQueue.push(...more);
}
if (abortSignal.aborted || this.isSessionStale()) return;
const enrich = createTagRecAttributionEnricher(tagRecCustomSession.workingGroup ? [...favorites.list, tagRecCustomSession.workingGroup] : favorites.list, favoritesL1.list);
const combination = tagRecCustomSession.workingGroup?.combination;
const enriched = (items) => {
sharedCandidates.delivered(items);
for (const item of items) if (item.api === EApiType.TagRec) {
enrich(item);
invalidateTagRecCardData(item);
}
const tagItems = items.filter((item) => item.api === EApiType.TagRec);
const ranked = this.runScoreStage(tagItems);
const pickOrder = new Map(tagItems.map((item, index) => [item, index]));
const ordered = combination ? [...ranked].sort((a, b) => (pickOrder.get(a) ?? 0) - (pickOrder.get(b) ?? 0)) : ranked;
let at = 0;
return items.map((item) => item.api === EApiType.TagRec ? ordered[at++] : item);
};
if (!this.effectiveFilter().enabled && !sharedRemovedConditionAtoms.length && !hasTagRecPartitionRules(sharedBufferQueue, this.scopeResolver())) {
const out = enriched(this.qs.sliceFromQueue());
sharedDeliveryReplay.remember(tagRecDeliveryReplayKey(), out.filter((it) => it.api === EApiType.TagRec));
return this.fillFromDeliveryReplay(out);
}
const policy = computeTagRecCustomFilterSignature();
const delivered = [];
const consumed = /* @__PURE__ */ new Set();
const pending = /* @__PURE__ */ new Map();
const attemptedBvids = /* @__PURE__ */ new Set();
const rounds = [];
const atomKeysOf = combination && validateTagRecCombination(combination) ? this.atomKeysOf(combination) : void 0;
const isStaleContext = this.stalePredicate();
const working = tagRecPartitionWorkQueue(this.qs.bufferQueue, this.partitionHeld, isStaleContext, TAG_REC_PARTITION_HELD_RETRY_QUOTA, (bvid) => (this.partitionHeld.get(bvid) ?? 0) < TAG_REC_PARTITION_HELD_MAX_ATTEMPTS);
for (let pass = 0; pass < 2 && delivered.length < this.qs.ps; pass++) {
const want = this.qs.ps - delivered.length;
const slice = atomKeysOf ? spliceTagRecAtomFairSlice(working, want, sharedAtomTurns, atomKeysOf, settings.tagRec.diversityGuard) : working.splice(0, want);
if (!slice.length) break;
const partitions = hasTagRecPartitionRules(slice, this.scopeResolver()) ? await checkTagRecPartitionSlice(slice, this.scopeResolver(), budget, {
queryMatches: this.queryMatcher(),
negative: { pools: tagRecNegativePools() },
relatedFallback: this.relatedFallbackOn()
}) : {
items: slice,
pending: /* @__PURE__ */ new Map(),
updated: [],
attempted: /* @__PURE__ */ new Set(),
stats: {
dropped: 0,
byDim: {},
degraded: false
}
};
if (abortSignal.aborted || this.isSessionStale() || policy !== computeTagRecCustomFilterSignature()) return;
const tagChecked = await dropTagRecStrictTagListHits(partitions.items, this.effectiveFilter(), this.scopeResolver(), abortSignal, this.queryMatcher(), !tagRecCustomSession.supplyPaused, budget);
if (abortSignal.aborted || this.isSessionStale() || policy !== computeTagRecCustomFilterSignature()) return;
for (const [original, updated] of partitions.pending) pending.set(original, updated);
for (const bvid of partitions.attempted) attemptedBvids.add(bvid);
for (const item of slice) if (!pending.has(item)) consumed.add(item);
sharedCandidates.record(partitions.updated);
rounds.push(partitions.stats, tagChecked.stats);
delivered.push(...tagChecked.items);
}
if (abortSignal.aborted || this.isSessionStale() || policy !== computeTagRecCustomFilterSignature()) return;
for (const [original, updated] of pending) Object.assign(original, updated);
const held = this.qs.bufferQueue.filter((item) => pending.has(item));
for (let i = this.qs.bufferQueue.length - 1; i >= 0; i--) if (consumed.has(this.qs.bufferQueue[i]) || pending.has(this.qs.bufferQueue[i])) this.qs.bufferQueue.splice(i, 1);
this.qs.bufferQueue.push(...held);
const remaining = new Set(this.qs.bufferQueue.flatMap((item) => item.api === EApiType.TagRec && !isStaleContext?.(item) ? [item.bvid] : []));
this.partitionHeld = tagRecPartitionHeldNext(this.partitionHeld, held.filter((it) => it.api === EApiType.TagRec), remaining, TAG_REC_PARTITION_HELD_MAX_ATTEMPTS, attemptedBvids);
tagRecCustomSession.partitionAbandoned = [...this.partitionHeld.values()].filter((attempts) => attempts >= TAG_REC_PARTITION_HELD_MAX_ATTEMPTS).length;
tagRecCustomSession.partitionPending = this.partitionHeld.size;
const freshRemaining = this.qs.bufferQueue.some((item) => item.api === EApiType.TagRec && !this.partitionHeld.has(item.bvid) && !isStaleContext?.(item));
tagRecCustomSession.partitionPaused = pending.size > 0 && !freshRemaining || !tagRecCustomSession.supplyPaused && budget.exhausted && this.hasMore;
tagRecCustomSession.partitionBudgetExhausted = !tagRecCustomSession.supplyPaused && budget.exhausted && this.hasMore;
if (tagRecCustomSession.partitionPaused) this.partitionPausePolicy = policy;
const tagStats = tagRecCustomSession.filterStats;
for (const stats of rounds) {
tagStats.dropped += stats.dropped;
for (const [key, count] of Object.entries(stats.byDim)) {
const dim = key;
tagStats.byDim[dim] = (tagStats.byDim[dim] ?? 0) + count;
}
for (const [key, count] of Object.entries(stats.byScope ?? {})) {
tagStats.byScope ??= {};
tagStats.byScope[key] = (tagStats.byScope[key] ?? 0) + count;
}
tagStats.unchecked = (tagStats.unchecked ?? 0) + (stats.unchecked ?? 0) || void 0;
}
const out = enriched(delivered);
sharedDeliveryReplay.remember(tagRecDeliveryReplayKey(), out.filter((it) => it.api === EApiType.TagRec));
const withReplay = this.fillFromDeliveryReplay(out);
this.qs.doReturnItems(withReplay);
return withReplay;
}
/**
* 本次访问已展示过的 bvid (票 002): 同一次访问内不重复回放; 切走再切回会新建 service,
* 集合清空 => 完整回放"第一次看到的那批" (用户期望: 第一次什么样, 切回来就什么样)。
*/
replayShown = /* @__PURE__ */ new Set();
/**
* 票 002/003: 「显示已看」打开且**本轮队列没填满**时, 用回放池补位 (零请求)。
* 只在缺口里补 —— 队列供得上时不动, 避免正常滚动时把回放内容重复上屏。
*/
fillFromDeliveryReplay(items) {
for (const it of items) if (it.api === EApiType.TagRec && it.bvid) this.replayShown.add(it.bvid);
if (!tagRecCustomSession.bypassSeen) return items;
let want = Math.max(0, this.qs.ps - items.length);
if (!want) return items;
const filled = [];
for (const item of sharedDeliveryReplay.replay(tagRecDeliveryReplayKey())) {
if (want <= 0) break;
if (!item.bvid || this.replayShown.has(item.bvid)) continue;
const replayItem = {
...item,
tagRecReplayed: true
};
if (!evalTagRecItem(replayItem, this.scopeResolver(), {
queryMatches: this.queryMatcher(),
negative: { pools: tagRecNegativePools() },
relatedFallback: this.relatedFallbackOn(),
deferPartitions: true,
poolSize: replayItem.filterPoolSize,
poolSizes: replayItem.filterPoolSizes
}).pass) continue;
this.replayShown.add(item.bvid);
filled.push(replayItem);
want--;
}
if (filled.length) tagRecCustomSession.replayDelivered += filled.length;
return filled.length ? [...filled, ...items] : items;
}
async fetchMore(abortSignal) {
if (tagRecCustomSession.supplyPaused) return [];
this.lastFetchSignal = abortSignal;
tagRecCustomSession.hardBottom = false;
tagRecCustomSession.digPending = sharedUpSearch.pending && sharedUpSearch.hasAnyMore;
const anchor = await this.resolveAnchorOnce().catch((e) => {
if (isRiskControlError(e)) {
toast("命中风控 · 已退避降速,恢复后自动继续");
return;
}
throw e;
});
if (abortSignal.aborted) return;
const selectedGroups = tagRecCustomQueryGroups();
const unit = this.collectQueryIds();
const byTid = /* @__PURE__ */ new Map();
const atomicSources = [];
const keywords = /* @__PURE__ */ new Map();
const byTidGids = /* @__PURE__ */ new Map();
const push = (tid, name, gid, keyword = name) => {
atomicSources.push({
tid,
keyword,
groupNames: [name],
groupIds: gid ? [gid] : void 0,
input: !gid,
pn: this.dryTids.pageOf(tid)
});
keywords.set(tid, keyword);
const names = byTid.get(tid) ?? /* @__PURE__ */ new Set();
names.add(name);
byTid.set(tid, names);
if (gid) {
const ids = byTidGids.get(tid) ?? /* @__PURE__ */ new Set();
ids.add(gid);
byTidGids.set(tid, ids);
}
};
const aliveGids = /* @__PURE__ */ new Set([...favorites.list.map((g) => g.id), ...selectedGroups.map((g) => g.id)]);
for (const k of this.tagCursors.keys()) if (!aliveGids.has(k)) this.tagCursors.delete(k);
const pendingCursors = [];
for (const g of selectedGroups) {
const sig = g.tag_ids.map((e) => e.id).join(",");
const ent = this.tagCursors.get(g.id);
const { entries, nextCursor } = pickTagIdsForRound(g.tag_ids, ent && ent.sig === sig ? ent.cursor : 0);
pendingCursors.push([
g.id,
nextCursor,
entries.map((e) => e.id),
sig
]);
for (const e of entries) push(e.id, g.name, g.id, e.name);
}
const unitPick = pickTagIdsForRound(unit.map(([id, name]) => ({
id,
name
})), this.queryCursor);
pendingCursors.push([
"",
unitPick.nextCursor,
unitPick.entries.map((e) => e.id),
""
]);
for (const e of unitPick.entries) push(e.id, e.name);
if (!byTid.size && !selectedGroups.some((group) => hasTagRecCombination(group.combination))) {
if (!!tagRecCustomSession.inputText && tagRecBackoff.isEngaged && this.emptyContextRetries < TAG_REC_EMPTY_CONTEXT_MAX_RETRIES) {
this.emptyContextRetries += 1;
return;
}
return this.exhaustRound();
}
this.emptyContextRetries = 0;
const negResolve = this.scopeResolver();
const effNegTids = (key) => {
const eff = negResolve(key, "tagTop");
return eff.cfg.enabled ? eff.neg.tagIds.map((e) => e.v.id) : [];
};
const negTids = [...selectedGroups.flatMap((g) => effNegTids({ id: g.id })), ...effNegTids(void 0)];
if (negTids.length) await ensureTagRecNegativePools(negTids, abortSignal);
if (abortSignal.aborted) return;
const { sig, changed: signatureChanged } = syncCustomSharedState();
if (tagRecCustomSession.workingGroup ? tagRecMembersCore(sig.split("|sources:", 1)[0]) !== tagRecMembersCore(this.contextSignature.split("|sources:", 1)[0]) : sig.split("|sources:", 1)[0] !== this.contextSignature.split("|sources:", 1)[0]) {
this.anchorPromise = void 0;
this.anchorInfo = void 0;
this.anchorEntry = void 0;
this.anchorInput = "";
this.phase2Started = false;
this.inputNorm = "";
this.isIdPath = false;
this.queryIds.clear();
this.discoveredVariants = [];
}
this.contextSignature = sig;
syncCustomFilterState();
const supply = this.supplyScope();
if (!supply.needed.size && supply.ready) return [];
const sources = atomicSources.filter((source) => tagRecConsumers(source).some((consumer) => supply.needed.has(tagRecGroupKey(consumer.key))));
const recallResolve = this.scopeResolver();
const digSources = [...deriveGroupCliques(selectedGroups.map((g) => ({
...g,
extra: tagRecSearchExtraOf(recallResolve({ id: g.id }, "allV2"))
})), Infinity), ...!this.isIdPath ? unit.map(([, name]) => ({
key: normalizeTagName(name),
keyword: name,
attribution: [name],
input: true
})) : []];
const recallPlan = planTagRecRecall({
roundSources: sources,
digSources,
scopeConsumers: [...selectedGroups.map((g) => ({
key: { id: g.id },
name: g.name
})), ...unit.length ? [{
key: void 0,
name: "",
input: true
}] : []]
}, recallResolve);
const fullUpSources = this.recheckPlan().up;
sharedUpSearch.sync(fullUpSources);
recallPlan.up = fullUpSources.filter((source) => tagRecConsumers(source).some((consumer) => supply.needed.has(tagRecGroupKey(consumer.key))));
const onlyUp = recallPlan.up.length > 0 && !recallPlan.tagTop.length && !recallPlan.dig.length && !recallPlan.archiveConsumers.length;
const normalIds = new Set(recallPlan.dig.flatMap((s) => s.groupIds ?? []));
const normalGroups = selectedGroups.filter((g) => normalIds.has(g.id));
const normalInputs = new Set(recallPlan.dig.filter((s) => s.input).map((s) => s.key));
for (const stream of this.ladder.streams.values()) if (!stream.groupIds?.length && normalInputs.has(stream.key)) stream.input = true;
if (!recallPlan.tagTop.length && !recallPlan.up.length && !recallPlan.dig.length && !recallPlan.archiveConsumers.length) {
this.hasMoreExceptQueue = false;
return [makeTagRecExhaustSeparator("池筛选已关闭当前路线 · 请开启适用的推荐来源")];
}
const epoch = sharedLadderState.epoch;
const policy = computeTagRecCustomSourceSignature() + computeTagRecCustomFilterSignature(true);
const isCurrent = () => sharedLadderState.epoch === epoch && policy === computeTagRecCustomSourceSignature() + computeTagRecCustomFilterSignature(true) && !this.isSessionStale();
const roundPromise = runTagRecRecallRound({
sources,
resolve: recallResolve,
plan: recallPlan,
isCurrent,
upPool: sharedUpSearch,
dryTids: this.dryTids,
seenBvids: this.seenBvids,
mergeExisting: (item) => sharedCandidates.mergeIncoming(item, sharedQueueIndex.map(), (items) => this.runScoreStage(items), sharedLadderState.heap, sharedHeapIndex.map()),
hasPending: sharedHasPending,
signal: abortSignal,
stages: this.stages
});
if (normalInputs.has(normalizeTagName(this.anchorEntry?.name ?? this.anchorInput)) && this.anchorInput && !this.phase2Started && !tagRecBackoff.isEngaged) {
this.phase2Started = true;
this.startPhase2("auto");
}
if (!onlyUp && (signatureChanged || !this.ladder.streams.size)) this.warmupDigStreams(normalGroups, abortSignal, normalInputs);
const round = await roundPromise.catch((e) => {
if (isRiskControlError(e)) return void 0;
throw e;
});
if (abortSignal.aborted || !isCurrent()) return;
if (!round) return;
this.dryTids.note(round.sourceStats);
const failedTids = new Set(round.sourceStats.filter((s) => !s.ok).map((s) => s.tid));
for (const [k, c, tids, sig] of pendingCursors) {
if (!tids.some((t) => round.sourceStats.some((s) => s.tid === t && s.ok) && recallPlan.tagTop.some((source) => source.tid === t && source.consumers.some((consumer) => k ? typeof consumer.key === "object" && consumer.key?.id === k : consumer.input)))) continue;
if (tids.some((t) => failedTids.has(t))) continue;
if (k === "") this.queryCursor = c;
else this.tagCursors.set(k, {
cursor: c,
sig
});
}
if (!round.items.length && failedTids.size) {
if (this.ladder.poolItems.length < 12) this.ladder.poolItems.push(...round.poolItems);
if (this.failedSourceRounds < 2) {
this.failedSourceRounds += 1;
await tagRecBackoff.waitTurn?.(abortSignal)?.catch(() => void 0);
return;
}
this.failedSourceRounds = 0;
throw new TagRecSourceFailureError();
}
this.failedSourceRounds = 0;
if (!round.items.length && this.hasTagTopPagesLeft(recallPlan.tagTop)) return [];
if (recallPlan.up.length && sharedUpSearch.pending && sharedUpSearch.hasAnyMore) tagRecCustomSession.digPending = true;
if (onlyUp) {
const more = sharedUpSearch.hasAnyMore;
if (!round.items.length) {
if (more && !sharedUpSearch.pending) return [];
if (more) {
tagRecCustomSession.digPending = true;
return this.exhaustRound();
}
return this.exhaustRound("UP 投稿关键词搜索已到末尾");
}
if (!more || sharedUpSearch.pending) {
this.hasMoreExceptQueue = false;
tagRecCustomSession.digPending = more;
}
return round.items;
}
let items = round.items;
let produce = items.length;
if (items.length < this.ladder.effTriggerYield() || this.hasStarvedConsumer(items)) {
if (items.length) {
this.ladder.preDig({
signal: abortSignal,
prepareStreams: () => this.ladder.ensureCliqueStreams(normalGroups)
}).catch(() => 0).finally(() => {
if (!this.isSessionStale()) this.syncPhase2Session();
});
if (abortSignal.aborted) return;
} else {
if (this.ladder.poolItems.length < 12) this.ladder.poolItems.push(...round.poolItems);
const r = await this.ladder.runLadder({
signal: abortSignal,
prepareStreams: () => this.ladder.ensureCliqueStreams(normalGroups)
});
if (abortSignal.aborted || r.kind === "aborted") return;
if (!this.isSessionStale()) this.syncPhase2Session();
switch (r.kind) {
case "items":
items = r.items;
produce = r.items.length || this.qs.bufferQueue.filter((x) => x.api === EApiType.TagRec).length;
break;
case "manual-pending":
if (!this.isSessionStale()) tagRecCustomSession.digPending = true;
return this.exhaustRound();
case "source-unavailable":
case "retry-later":
this.hasMoreExceptQueue = false;
return [makeTagRecExhaustSeparator(r.reason)];
default:
if (sharedUpSearch.pending && sharedUpSearch.hasAnyMore) {
tagRecCustomSession.digPending = true;
return this.exhaustRound();
}
if (anchor) toast(`「${anchor.name}」标签池暂无新内容`);
return this.exhaustRound("已到池底 · 该标签池暂无更多新内容");
}
}
}
trackTagRecRoundProduce(this.produceTracker, produce);
this.ladder.noteProduce(produce);
tagRecCustomSession.softBottom = this.produceTracker.softBottom;
if (items.length && !this.firstDataMarked) {
this.firstDataMarked = true;
perfMark("tagrec-first-data", { n: items.length });
}
return items;
}
/** 查询单元 = 锚点 + 字形变体 (自动并入, 下一轮起) + 「扩进本次查询」扩选 */
collectQueryIds() {
if (tagRecCustomSession.workingGroup) return [];
for (const v of this.discoveredVariants) this.queryIds.set(v.id, v.name);
for (const e of tagRecCustomSession.expandedIds) this.queryIds.set(e.id, e.name);
return [...this.queryIds.entries()];
}
/**
* 票 32 首载/变员预热 (后台不阻塞, fetchMore 内 tag/top 入闸后调用):
* 选定组同义词簇流 (D7 同款) + 每个扩选 id 一条独立流 (key=归一名, 归因=tag 名同查询单元口径),
* 顺手一发 manual 深挖与 tag/top 并行 —— 深挖条目仍经 queue/heap 管线不直喂,
* manual 档不占 auto 三停计数 (预热是投机动作不该烧穿深挖纪律); 退避期不点火 (请求纪律不放宽)
*/
warmupDigStreams(groups, signal, inputKeywords) {
if (tagRecBackoff.isEngaged) return;
this.ladder.ensureCliqueStreams(groups);
for (const e of tagRecCustomSession.expandedIds) {
const key = normalizeTagName(e.name);
if (!key || !inputKeywords.has(key)) continue;
const exist = this.ladder.streams.get(key);
if (exist) {
if (!exist.attribution.includes(e.name)) exist.attribution.push(e.name);
continue;
}
this.ladder.addStream({
key,
keyword: e.name,
attribution: [e.name],
input: true
});
}
if (!this.ladder.canAutoDig()) return;
this.ladder.digPass("auto", signal).catch(() => 0).finally(() => {
if (!this.isSessionStale()) this.syncPhase2Session();
});
}
/**
* Phase 1 锚点解析, 有真锚点才缓存 promise (并发轮共享);
* AUDIT-09: 空输入/未命中不缓存 —— `??=` 把空输入解出的 undefined 钉死后该实例永不读新
* inputText (滚动也救不回); 失败不缓存允许重试
*/
resolveAnchorOnce() {
this.anchorPromise ??= this.doResolveAnchor().then((entry) => {
if (!entry) this.anchorPromise = void 0;
return entry;
}).catch((e) => {
this.anchorPromise = void 0;
throw e;
});
return this.anchorPromise;
}
/** 输入分流 (spec §4.1): 纯数字 => tag_id 直输反查名(逃生口); 文本 => tag_name 解析 + Phase 2 后台 */
async doResolveAnchor() {
if (tagRecCustomSession.workingGroup) return void 0;
const input = tagRecCustomSession.inputText.trim();
if (!input) return void 0;
perfMark("tagrec-anchor-resolve");
const { anchor, isIdPath } = await resolveAnchor(input);
perfMark("tagrec-anchor-resolved", {
found: anchor ? 1 : 0,
idPath: isIdPath ? 1 : 0
});
this.isIdPath = isIdPath;
this.anchorInfo = anchor;
this.anchorEntry = anchor ? {
id: anchor.id,
name: anchor.name
} : void 0;
if (this.isSessionStale()) return this.anchorEntry;
tagRecCustomSession.anchor = this.anchorEntry;
if (!anchor) {
toast(`未能解析标签「${input}」`);
return;
}
this.queryIds.set(anchor.id, anchor.name);
tagRecCustomSession.draftGroup ??= {
name: anchor.name,
negative_tag_ids: [],
strict: false
};
const droppedGroups = pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds);
if (droppedGroups.length) toast(`已切换到临时输入「${anchor.name}」,已弃选定组「${droppedGroups[0].name}」`);
tagRecCustomSession.selectedGroupIds = [];
this.contextSignature = syncCustomSharedState().sig;
if (!isIdPath) {
this.anchorInput = input;
this.inputNorm = normalizeTagName(anchor.name);
this.ladder.addStream({
key: this.inputNorm,
keyword: anchor.name,
attribution: [anchor.name],
input: true,
withVariants: true,
withSuggest: true,
anchor: this.anchorInfo
});
}
return this.anchorEntry;
}
/** 后台深挖一轮 (输入锚点流首轮启动 /「继续深挖」复用): 单飞, 完成即镜像 phase2; 返回在途 promise */
startPhase2(mode = "manual") {
if (tagRecBackoff.isEngaged) return Promise.resolve(0);
if (this.phase2Running) return this.phase2Running;
if (this.ladder.digRunning) return this.ladder.digRunning;
if (this.isSessionStale()) return Promise.resolve(0);
if (mode === "auto" && !this.ladder.canAutoDig()) return Promise.resolve(0);
tagRecCustomSession.phase2.status = "running";
tagRecCustomSession.digRunning = true;
this.phase2Running = this.ladder.digPass(mode, this.lastFetchSignal).catch((e) => (appWarn("[tag-rec] phase2 dig failed: %o", e), 0)).finally(() => {
this.phase2Running = void 0;
if (!this.ladder.digRunning) tagRecCustomSession.digRunning = false;
if (this.isSessionStale()) return;
this.syncPhase2Session();
if (this.ladder.lastDigYield > 0) {
this.hasMoreExceptQueue = true;
tagRecCustomSession.digPending = false;
} else if (this.ladder.hasDueStreams()) tagRecCustomSession.digPending = true;
else {
tagRecCustomSession.digPending = false;
tagRecCustomSession.hardBottom = true;
}
});
return this.phase2Running;
}
/**
* UI「继续深挖」: 全部未 exhausted 深挖流各 +3 页 (spec §4.1 扩展为票 13 多流); resolve = 本次/在途挖完
* 判据 = 梯队有无 due 流 (组簇流可在无锚点下独立存在 —— 纯组模式 anchorEntry 为空也必须能挖)
* 票 38-C②选项①: 退避期手动 dig 拒发 —— 深挖成功率本就低, 烧的是退避时隙 (请求纪律不放宽)
*/
digMore(automatic = false) {
if (sharedUpSearch.pending && sharedUpSearch.hasAnyMore) {
if (this.isSessionStale()) return Promise.resolve();
sharedUpSearch.continue();
this.hasMoreExceptQueue = true;
tagRecCustomSession.digPending = false;
tagRecCustomSession.hardBottom = false;
return Promise.resolve();
}
if (tagRecBackoff.isEngaged) {
toast("退避中,恢复后再挖");
return Promise.resolve();
}
if (!this.ladder.hasDueStreams()) return Promise.resolve();
if (automatic && !this.ladder.canAutoDig()) {
tagRecCustomSession.digPending = true;
return Promise.resolve();
}
return tagRecRecallScheduler.withForegroundSupply(() => this.startPhase2(automatic ? "auto" : "manual"));
}
/**
* UI「重新发掘」(硬见底出口): 复位本查询全部深挖流游标 (含持久沉淀的 exhausted/冷却) 并立即重挖一轮。
* 请求有界 = 一次 digPass 的既有窗口 (≤4 流 ×3 页) + 退避/限流纪律; 退避期不点火。
* 已挖穿/冷却中的词重获完整召回 —— 免去「去设置里清空沉淀」或干等 TTL。
*/
redigQuery() {
if (this.isSessionStale()) return Promise.resolve();
this.ladder.resetDigCursors();
this.hasMoreExceptQueue = true;
tagRecCustomSession.hardBottom = false;
tagRecCustomSession.digPending = false;
return this.digMore();
}
/** 梯队聚合态 => session.phase2 镜像 (横幅待决态/tabbar 深挖按钮/计数文案订阅) */
syncPhase2Session() {
const p2 = tagRecCustomSession.phase2;
const ladder2 = this.ladder.phase2;
p2.status = this.phase2Running || this.ladder.digRunning ? "running" : ladder2.status;
p2.pagesFetched = ladder2.pagesFetched;
p2.exhausted = ladder2.exhausted;
p2.autoDigs = ladder2.autoDigs;
p2.lastDigYield = ladder2.lastDigYield;
}
/**
* 勾选即后台预解析 (票 18 增强): 未解析行打勾瞬间后台 tag/info, use/解析态随后回填。
* 动作点 (expandQuery/saveAsFavoriteGroup) 仍走 resolveCandidateRows 兜底——
* 会话缓存按 key 合并在途请求, 两处共享同一 promise, 动作点零新增等待。
*/
resolveCandidateInBackground(key) {
const live = tagRecCustomSession.candidates.find((c) => c.key === key);
if (!live || live.tag_id !== void 0 || live.resolving) return;
live.resolving = true;
const snapshot = { ...live };
const isCurrent = this.captureActionContext();
resolveCandidateRows([snapshot]).catch(() => {
live.resolveFailed = true;
}).then((resolved) => {
live.resolving = false;
if (resolved && isCurrent()) this.applyResolvedRows(resolved);
});
}
/** 惰性解析回填 (票 18): 解析结果写回 session 候选行 (key 不换, 仅更新 tag_id/use/resolveFailed) */
applyResolvedRows(resolved) {
for (const r of resolved) {
const idx = tagRecCustomSession.candidates.findIndex((c) => c.key === r.key);
if (idx === -1) continue;
const live = tagRecCustomSession.candidates[idx];
live.tag_id = r.tag_id;
live.tag_name = r.tag_name;
live.use = r.use;
live.resolveFailed = r.resolveFailed;
const dup = tagRecCustomSession.candidates.find((c) => c !== live && c.tag_id !== void 0 && c.tag_id === live.tag_id);
if (dup) {
dup.checked ||= live.checked;
dup.expanded ||= live.expanded;
tagRecCustomSession.candidates.splice(idx, 1);
}
}
}
/** 用户动作绑定发起时的语境;同组新增暂存不使动作失效,切走再切回也不复活旧动作。 */
captureActionContext() {
const ids = tagRecCustomSession.selectedGroupIds.join(",");
const groups = tagRecCustomSession.selectedGroupIds.map((id) => favorites.list.find((g) => g.id === id));
const input = tagRecCustomSession.inputText;
const draft = tagRecCustomSession.draftGroup;
const candidates = tagRecCustomSession.candidates;
return () => ids === tagRecCustomSession.selectedGroupIds.join(",") && input === tagRecCustomSession.inputText && draft === tagRecCustomSession.draftGroup && candidates === tagRecCustomSession.candidates && groups.every((group, i) => group === favorites.list.find((g) => g.id === tagRecCustomSession.selectedGroupIds[i]));
}
/** 独立解析 B 的确认入口:只写已有扩选暂存,不接管主查询或收藏组。 */
expandResolvedTags(entries, queryRevision) {
if (queryRevision !== tagRecCustomSession.queryRevision) return [];
const selectedGroup = pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds)[0];
if (tagRecCustomSession.inputText ? !tagRecCustomSession.anchor : !selectedGroup) return [];
const savedGroup = tagRecCustomSession.draftGroup?.savedGroupId ? favorites.list.find((g) => g.id === tagRecCustomSession.draftGroup.savedGroupId) : void 0;
const added = pushExpandedIds(entries, (selectedGroup ?? savedGroup)?.tag_ids);
for (const entry of added) sediment.confirm(normalizeTagName(entry.name));
if (added.length) tagRecCustomSession.expandedCollapsed = false;
toast(added.length ? `已扩选 ${added.length} 个标签, 下一轮起生效` : "所选标签已在本次查询中");
return added;
}
/** UI「扩进本次查询」: 勾选行并入查询单元 (下一轮起) + confirmed 边; 未解析行现场 tag/info 换 id (票 18) */
async expandQuery(rows) {
const isCurrent = this.captureActionContext();
const pendings = rows.filter((r) => r.checked && !r.expanded);
if (!pendings.length) {
toast("先勾选要并入的标签");
return;
}
const resolved = await resolveCandidateRows(pendings);
if (!isCurrent()) return;
this.applyResolvedRows(resolved);
const ok = resolved.filter((r) => r.tag_id !== void 0);
const failed = resolved.length - ok.length;
const currentGroup = pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds)[0];
const staged = new Set(pushExpandedIds(ok.map((r) => ({
id: r.tag_id,
name: r.tag_name
})), currentGroup?.tag_ids).map((e) => e.id));
for (const r of ok) {
sediment.confirm(normalizeTagName(r.tag_name));
const live = tagRecCustomSession.candidates.find((c) => c.key === r.key);
if (live && r.tag_id !== void 0 && staged.has(r.tag_id)) {
live.expanded = true;
live.checked = true;
}
}
if (!ok.length) {
toast(`${failed} 个标签解析失败`);
return;
}
const skippedExisting = ok.length - staged.size;
const lead = staged.size ? `已扩进 ${staged.size} 个标签, 下一轮起生效` : "所选标签已在组或扩选暂存中";
toast(`${lead}${skippedExisting && staged.size ? `; ${skippedExisting} 个已在组/暂存` : ""}${failed ? `; ${failed} 个解析失败跳过` : ""}`);
if (!failed) tagRecCustomSession.candidatesCollapsed = true;
}
/**
* UI「顺手存成项 / 并入当前组」(§4.2) —— 票 22 语境分叉 (暂存层整体固化):
* 选定组语境 => 勾选行 ∪ expandedIds 暂存层 `addTagIdsToGroup` 并入**该组** (复用门面去重/负向撞车计数),
* 并入后清暂存位, 不建组; 零勾选但暂存非空同样可并入 (纯扩选固化)
* 草稿语境 => 维持升级建新组路径: 会话负向随组入表 (票 14/票 12 联动)
* 未解析行现场解析 (票 18)
*/
async saveAsFavoriteGroup(rows) {
const isCurrent = this.captureActionContext();
const checked = rows.filter((r) => r.checked);
const selectedGroup = pickTagGroupsByIds(favorites.list, tagRecCustomSession.selectedGroupIds)[0];
const draft = tagRecCustomSession.draftGroup;
const mergeTarget = selectedGroup ?? (draft?.savedGroupId ? favorites.list.find((g) => g.id === draft.savedGroupId) : void 0);
const expandedSnapshot = [...tagRecCustomSession.expandedIds];
const anchor = tagRecCustomSession.anchor;
if (!checked.length && !expandedSnapshot.length && (mergeTarget || !anchor)) {
toast("先勾选要存的标签");
return;
}
const resolved = await resolveCandidateRows(checked);
if (!isCurrent()) return;
this.applyResolvedRows(resolved);
const ok = [...new Map(resolved.filter((r) => r.tag_id !== void 0).map((r) => [r.tag_id, r])).values()];
const failed = resolved.length - ok.length;
if (mergeTarget && !favorites.list.some((g) => g.id === mergeTarget.id)) {
toast("并入目标组已被删除, 暂存位已保留", 4e3);
return;
}
if (mergeTarget) {
if (!ok.length && !expandedSnapshot.length) {
toast(`${failed} 个标签解析失败, 未并入组`);
return;
}
const tagIds = dedupeTagIdEntries([...ok.map((r) => ({
id: r.tag_id,
name: r.tag_name
})), ...expandedSnapshot]);
const expandedMerged = tagIds.length - ok.length;
const res = favorites.addTagIdsToGroup(mergeTarget.id, tagIds, { silent: true });
const savedIds = new Set(expandedSnapshot.map((e) => e.id));
tagRecCustomSession.expandedIds = tagRecCustomSession.expandedIds.filter((e) => !savedIds.has(e.id));
for (const r of ok) sediment.confirm(normalizeTagName(r.tag_name));
const live = favorites.list.find((x) => x.id === mergeTarget.id);
const parts = [res.added || res.moved ? `已并入「${mergeTarget.name}」, 共 ${live?.tag_ids.length ?? mergeTarget.tag_ids.length} 个标签` : `「${mergeTarget.name}」已含所选标签`];
if (res.moved) parts.push(`${res.moved} 个由负向转正向`);
if (expandedMerged) parts.push(`含 ${expandedMerged} 个扩选并入`);
if (res.alsoIn.length) parts.push(`也存在于 ${summarizeGroupNames(res.alsoIn)}`);
if (failed) parts.push(`${failed} 个解析失败跳过`);
toast(parts.join("; "), 3500);
if (!resolved.some((r) => r.tag_id === void 0)) tagRecCustomSession.candidatesCollapsed = true;
return;
}
const tagIds = dedupeTagIdEntries([
...anchor ? [{
id: anchor.id,
name: anchor.name
}] : [],
...ok.map((r) => ({
id: r.tag_id,
name: r.tag_name
})),
...expandedSnapshot
]);
if (!tagIds.length) {
toast(`${failed} 个标签解析失败, 未存组`);
return;
}
const expandedMerged = tagIds.length - ok.length - (anchor ? 1 : 0);
const posIds = new Set(tagIds.map((e) => e.id));
const negDropped = (draft?.negative_tag_ids ?? []).filter((e) => posIds.has(e.id)).length;
const group = favorites.addGroup({
name: ok[0]?.tag_name ?? anchor.name,
tag_ids: tagIds,
negative_tag_ids: draft?.negative_tag_ids,
negative_strict: draft?.strict,
overrides: draft?.negative ? { negative: draft.negative } : void 0
})?.group;
if (!group) return;
if (draft) draft.savedGroupId = group.id;
const savedIds = new Set(expandedSnapshot.map((e) => e.id));
tagRecCustomSession.expandedIds = tagRecCustomSession.expandedIds.filter((e) => !savedIds.has(e.id));
for (const r of ok) sediment.confirm(normalizeTagName(r.tag_name));
const negNote = group.negative_tag_ids?.length ? `, 含 ${group.negative_tag_ids.length} 个负向` : "";
const negDropNote = negDropped ? `, ${negDropped} 个负向与正向撞车剔除` : "";
const expNote = expandedMerged ? `, 含 ${expandedMerged} 个扩选并入` : "";
const failNote = failed ? `, ${failed} 个解析失败跳过` : "";
toast(`已存为标签组「${group.name}」(${group.tag_ids.length} 个 tag_id${negNote}${negDropNote}${expNote}${failNote})`);
if (!resolved.some((r) => r.tag_id === void 0)) tagRecCustomSession.candidatesCollapsed = true;
return group;
}
/**
* 过期判定 (AUDIT-16): 本实例语境签名 vs 当前签名漂移即 stale ——
* 组语境 A→B 切换/输入提交/扩选并入全判到 (原只比 inputText, 组语境 anchorInput='' 恒不 stale);
* 已弃 service 的异步收尾一律不写共享 session (digPending/digRunning/phase2 镜像/候选清单)。
* '' 钉点不算语境 (与 syncCustomSharedState 不钉空签名同款): 构造期先于 session 就绪是合法态
* (deeplink/预热建实例时 session 可空), 空签钉住的实例跨过语境建立不作 stale,
* 否则首轮水合即把本实例冤判弃 —— 只有真签名钉住后漂移才算被顶替
*/
isSessionStale() {
return !!this.contextSignature && this.contextSignature !== computeTagRecCustomContextSignature();
}
/**
* 深翻页未完: 未干源里仍有页可翻 (pageOf < 上限) —— 零产出轮不判见底, 下一轮自动进页。
* 请求成本不变 (原本每轮也在重复打 pn=1); 到顶/空页由 dryTids 标干后自然回到原见底语义。
*/
hasTagTopPagesLeft(sources) {
return sources.some((source) => !this.dryTids.isDry(source.tid) && this.dryTids.pageOf(source.tid) < 3);
}
/** 硬见底 (spec §4.4): content 给了 => 返回 [见底 separator]; 队列排空后 hasMore=false 停自动加载 */
exhaustRound(content) {
this.hasMoreExceptQueue = false;
if (content) tagRecCustomSession.hardBottom = true;
return content ? [makeTagRecExhaustSeparator(content)] : [];
}
});})}}));
System.register("./settings-C2jYGGFh-DPveTHbD.js", ['./routes-B0HXSp2g-8gD5jzg6.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var settings;return{setters:[function(module){settings=module.t;exports("settings",module.t);},null,null,null,null,null,null],execute:(function(){})}}));
System.register("./fav-theme-folder-CTfuYzmX-Dcl__BwQ.js", ['./fav-theme-folder-5j6GivBE-BDXSEryb.js','./routes-B0HXSp2g-8gD5jzg6.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match','./___monkey.entry.js'],(function(exports){'use strict';return{setters:[function(module){exports({TAG_REC_FAV_THEME_FOLDER_DAILY_HINT:module.T,alignThemeFolderName:module.a,createThemeFolderAndFav:module.c,favToThemeFolder:module.f,legacyThemeNameOf:module.l,matchThemeFolders:module.m,resolveCardThemeName:module.r});},null,null,null,null,null,null,null,null],execute:(function(){})}}));
System.register("./risk-log-BNk-ehK5-B3n67n9D.js", ['./risk-log-ZLVUftoN-DnHKear8.js','axios'],(function(exports){'use strict';return{setters:[function(module){exports({RISK_LOG_BODY_SNIPPET_MAX:module.R,RISK_LOG_EVENT_RING_MAX:module.c,RISK_LOG_KEEP_PAGE_KEYS:module.d,RISK_LOG_RECENT_SEQ_MAX:module.e,RISK_LOG_REQUEST_LEDGER_MAX:module.f,RISK_LOG_WINDOW_MS:module.g,clear:module.h,dump:module.j,recordAnonRequest:module.k,recordUpstreamResponse:module.l,summary:module.s});},null],execute:(function(){})}}));
System.register("./share-import-BmCYK7Oh-BpB7LIls.js", ['./share-import-DgpdTju8-Dey7mX2T.js','./routes-B0HXSp2g-8gD5jzg6.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match','./___monkey.entry.js','./share-tokens-DwS1MNAq-C8u87gZL.js'],(function(exports){'use strict';return{setters:[function(module){exports({dismissShareImportGuide:module.d,requestShareImport:module.a,requestShareImportPkg:module.r});},null,null,null,null,null,null,null,null,null],execute:(function(){})}}));
System.register("./request-DatC6abj-uCHRJZHB.js", ['./routes-B0HXSp2g-8gD5jzg6.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var isWebApiSuccess,request;return{setters:[function(module){isWebApiSuccess=module.d8;request=module.d9;exports({isWebApiSuccess:module.d8,request:module.d9});},null,null,null,null,null,null],execute:(function(){})}}));
System.register("./workbench-CBkmOf8A-DgjoaFMd.js", ['./___monkey.entry.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','./routes-B0HXSp2g-8gD5jzg6.js','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';return{setters:[function(module){exports({activateTagRecWorkbench:module.ap,seedTagRecNewTrialWithTag:module.aU});},null,null,null,null,null,null,null],execute:(function(){})}}));
System.register("./video-play-page-JZeYpXRO-CUekkLRQ.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./shared-DUfk4XGY-a2sr8va8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var appWarn,createReactRoot,baseDebug,__toESM,BiliDomain,getCurrentPageBvid,matchesKeyboardEvent,shouldDisableShortcut,poll,settings,antMessage,bv2av,UserFavApi,startModifyFavItemToTargetFolders,handleModifyFavItemToFolders,hasDocumentPictureInPicture,isMac,EQueryKey,EPlayerScreenMode,delay$1,getBiliPlayerConfigAutoPlay,EForceAutoPlay,jsx,AppRoot,openInPipOrPopup,require_ms,useSnapshot,button_default,IconForCollection,jsxs,Fragment,AntdTooltip,createEmotion,proxy,FavQueryKey,throttle,snapshot,setupForNoneHomepage,globalEmitter;return{setters:[function(module){appWarn=module.a;createReactRoot=module.m;baseDebug=module.n;__toESM=module._;BiliDomain=module.o;},function(module){getCurrentPageBvid=module.da;matchesKeyboardEvent=module.db;shouldDisableShortcut=module.dc;poll=module.dd;settings=module.t;antMessage=module.ad;bv2av=module.de;UserFavApi=module.df;startModifyFavItemToTargetFolders=module.dg;handleModifyFavItemToFolders=module.dh;hasDocumentPictureInPicture=module.di;isMac=module.dj;EQueryKey=module.dk;EPlayerScreenMode=module.dl;delay$1=module.c5;getBiliPlayerConfigAutoPlay=module.dm;EForceAutoPlay=module.dn;jsx=module.a;AppRoot=module.dp;openInPipOrPopup=module.dq;require_ms=module.bQ;useSnapshot=module.u;button_default=module.n;IconForCollection=module.dr;jsxs=module.j;Fragment=module.F;AntdTooltip=module.A;createEmotion=module.ds;proxy=module.aP;FavQueryKey=module.dt;throttle=module.du;snapshot=module.dv;},function(module){setupForNoneHomepage=module.s;globalEmitter=module.g;},null,null,null,null,null],execute:(function(){exports("initVideoPlayPage",initVideoPlayPage);async function setupCustomFavPicker() {
GM.registerMenuCommand?.("⭐️ 加入收藏", () => addToFav());
if (!getCurrentPageBvid()) return;
const willUseCustomFavPicker = () => settings.fav.useCustomFavPicker.onPlayPage;
document.addEventListener("keydown", (e) => {
if (matchesKeyboardEvent(e, "Shift+E") || willUseCustomFavPicker() && matchesKeyboardEvent(e, "E")) {
if (!getCurrentPageBvid()) return;
if (shouldDisableShortcut()) return;
if (e.target.closest("bili-comments")) return;
e.stopImmediatePropagation();
e.preventDefault();
addToFav();
}
}, { capture: true });
if (willUseCustomFavPicker()) (await poll(() => document.querySelector(".video-fav.video-toolbar-left-item"), {
interval: 100,
timeout: 5e3
}))?.addEventListener("click", (e) => {
if (!willUseCustomFavPicker() || !getCurrentPageBvid()) return;
e.stopImmediatePropagation();
e.preventDefault();
addToFav();
}, { capture: true });
}
async function addToFav(sourceFavFolderIds) {
const bvid = getCurrentPageBvid();
if (!bvid) return antMessage.error("无法解析视频 BVID !");
const avid = bv2av(bvid);
if (sourceFavFolderIds === void 0) {
const result = await UserFavApi.getVideoFavState(avid);
if (result) sourceFavFolderIds = result.favFolderIds;
}
await startModifyFavItemToTargetFolders({
srcFolderIds: sourceFavFolderIds,
modifyOkAction: async (targetFolder) => {
if (!await handleModifyFavItemToFolders(avid, sourceFavFolderIds, targetFolder)) return;
const nextFavedState = !!targetFolder.length;
document.querySelector(".video-fav.video-toolbar-left-item")?.classList.toggle("on", nextFavedState);
return true;
}
});
}
var _createEmotion = createEmotion({ key: "css" });
var css = _createEmotion.css;
function setupQuickLinks() {
if (!settings.videoPlayPage.addQuickLinks) return;
setupCollectionQuickLink();
}
async function setupCollectionQuickLink() {
await delay$1(2e3);
const referenceSelector = ".rcmd-tab .video-pod .header-bottom .subscribe-btn";
const btnSubscribe = await poll(() => document.querySelector(referenceSelector), {
interval: 250,
timeout: 1e4
});
if (!btnSubscribe) return appWarn("failed to find .subscribe-btn");
const div = document.createElement("div");
btnSubscribe.insertAdjacentElement("afterend", div);
btnSubscribe.parentElement?.classList.add(/* @__PURE__ */ css("display:flex;flex-direction:row-reverse;align-items:center;.subscribe-btn{flex:none;}"));
createReactRoot(div).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(CollectionQuickLink, {}) }));
}
var store = proxy({
href: location.href,
collectionUrl: void 0,
queryCollectionUrl: throttle(function() {
store.collectionUrl = document.querySelector(".video-pod .header-top a.title")?.href;
}, 100),
get gateCollectionUrl() {
const { collectionUrl } = this;
if (!collectionUrl) return;
const sid = new URL(collectionUrl, this.href).searchParams.get("sid");
if (!sid) return;
return `https://${BiliDomain.Main}/?${FavQueryKey.CollectionId}=${sid}`;
}
});
globalEmitter.on("navigate-success", async () => {
const prevSnap = snapshot(store);
store.href = location.href;
store.queryCollectionUrl();
if (store.href !== prevSnap.href) for (const d of [
100,
200,
400,
1e3,
2e3,
4e3
]) {
await delay$1(d);
store.queryCollectionUrl();
}
});
function CollectionQuickLink() {
const { gateCollectionUrl } = useSnapshot(store);
return !!gateCollectionUrl && /* @__PURE__ */ jsx(AntdTooltip, {
title: /* @__PURE__ */ jsxs(Fragment, { children: [
"在「",
"Bilibili-Gate",
"」中查看合集 "
] }),
children: /* @__PURE__ */ jsx(button_default, {
className: "mr-5px icon-only-round-button size-24px",
href: gateCollectionUrl,
target: "_blank",
children: /* @__PURE__ */ jsx(IconForCollection, { className: "size-16px" })
})
});
}
var import_ms = /* @__PURE__ */ __toESM(require_ms(), 1);
function getBiliPlayer() {
if (typeof unsafeWindow === "undefined") return;
return unsafeWindow.player;
}
/**
* 创意来源: https://github.com/hakadao/BewlyBewly/issues/101#issuecomment-1874308120
* 试用了下, 感觉不错, 在本脚本里实现了
*/
var debug = baseDebug.extend("main:video-play-page");
async function setupVideoPlayerState() {
registerOpenInPipCommand();
registerOpenInIinaCommand();
await Promise.all([syncFullscreenState(), syncForceAutoPlayState()]);
}
function registerOpenInPipCommand() {
if (!hasDocumentPictureInPicture) return;
GM.registerMenuCommand?.("🎦 小窗打开", () => {
pausePlayingVideo();
openInPipWindow();
});
}
function registerOpenInIinaCommand() {
if (!isMac) return;
GM.registerMenuCommand?.("▶️ IINA 打开", () => {
pausePlayingVideo();
openInIina();
});
}
async function syncFullscreenState() {
const targetMode = new URL(location.href).searchParams.get(EQueryKey.PlayerScreenMode);
debug("targetMode=%s", targetMode);
if (!(targetMode === EPlayerScreenMode.WebFullscreen || targetMode === EPlayerScreenMode.Fullscreen)) return;
let action;
if (targetMode === EPlayerScreenMode.WebFullscreen) action = () => document.querySelector("[role=\"button\"][aria-label=\"网页全屏\"]")?.click();
if (targetMode === EPlayerScreenMode.Fullscreen) action = () => document.querySelector("[role=\"button\"][aria-label=\"全屏\"]")?.click();
const getCurrentMode = () => document.querySelector("#bilibili-player .bpx-player-container")?.dataset.screen || EPlayerScreenMode.Normal;
const timeoutAt = Date.now() + (0, import_ms.default)("30s");
while (getCurrentMode() !== targetMode && Date.now() <= timeoutAt) {
debug("current mode: %s", getCurrentMode());
action?.();
await delay$1(100);
}
debug("handleFullscreen to %s complete", targetMode);
}
async function syncForceAutoPlayState() {
if (getBiliPlayerConfigAutoPlay()) return;
if (!(new URL(location.href).searchParams.get(EQueryKey.ForceAutoPlay) === EForceAutoPlay.ON)) return;
const playing = () => {
const player = getBiliPlayer();
return !!player && !player.isPaused();
};
const timeoutAt = Date.now() + (0, import_ms.default)("30s");
while (Date.now() <= timeoutAt && !playing()) {
getBiliPlayer()?.play();
await delay$1(1e3);
}
debug("syncForceAutoPlayState complete, playing = %s", playing());
}
function pausePlayingVideo() {
const player = getBiliPlayer();
if (player && !player.isPaused()) player.pause();
}
function openInPipWindow() {
const u = new URL(location.href);
u.searchParams.set(EQueryKey.PlayerScreenMode, EPlayerScreenMode.WebFullscreen);
const newHref = u.href;
openInPipOrPopup(newHref, "");
}
function openInIina() {
const iinaUrl = `iina://open?url=${encodeURIComponent(location.href)}`;
window.open(iinaUrl, "_self");
}
function initVideoPlayPage() {
setupForNoneHomepage();
return Promise.all([
setupCustomFavPicker(),
setupVideoPlayerState(),
setupQuickLinks()
]);
}})}}));
System.register("./space-page-dGOxOoCW-DvcBeGod.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./shared-DUfk4XGY-a2sr8va8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var APP_NAMESPACE,createReactRoot,BiliDomain,limitFunction,baseDebug,APP_NAME,tryAction,jsx,AppRoot,proxy,reusePendingPromise,useSnapshot,jsxs,delay$1,debounce,poll,SpaceUploadQueryKey,DynamicFeedQueryKey,button_default,IconForSpaceUpload,AntdTooltip,IconForDynamicFeed,FavQueryKey,IconForCollection,r,clsx,setupForNoneHomepage,globalEmitter;return{setters:[function(module){APP_NAMESPACE=module.q;createReactRoot=module.m;BiliDomain=module.o;limitFunction=module.u;baseDebug=module.n;APP_NAME=module.A;},function(module){tryAction=module.dw;jsx=module.a;AppRoot=module.dp;proxy=module.aP;reusePendingPromise=module.dx;useSnapshot=module.u;jsxs=module.j;delay$1=module.c5;debounce=module.dy;poll=module.dd;SpaceUploadQueryKey=module.dz;DynamicFeedQueryKey=module.dA;button_default=module.n;IconForSpaceUpload=module.dB;AntdTooltip=module.A;IconForDynamicFeed=module.dC;FavQueryKey=module.dt;IconForCollection=module.dr;r=module.a8;clsx=module.m;},function(module){setupForNoneHomepage=module.s;globalEmitter=module.g;},null,null,null,null,null],execute:(function(){exports("initSpacePage",initSpacePage);var rootElId = `${APP_NAMESPACE}-${crypto.randomUUID()}`;
async function addActionButtons() {
if (!state.mid) return;
const oldSelector = ".h-action";
const newSelector = ".upinfo .operations";
await tryAction([oldSelector, newSelector].join(","), (container) => {
state.href = location.href;
state.usingNewSpacePage = container.matches(newSelector);
getFollowedStatus();
const rootEl = document.createElement("span");
rootEl.id = rootElId;
rootEl.classList.add("mr-24px");
container.prepend(rootEl);
createReactRoot(rootEl).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(ActionButtons, {}) }));
}, {
pollTimeout: 1e4,
pollInterval: 1e3
});
}
var state = proxy({
href: location.href,
usingNewSpacePage: false,
followed: false,
get mid() {
return parseMid(this.href);
},
get collectionId() {
const u = new URL(this.href);
{
const match = this.href.match(/https:\/\/space.bilibili.com\/(?\d+)\/lists\/(?\d+)(?:\?type=season)?/);
if (match?.groups?.collectionId && (u.searchParams.get("type") === "season" || !u.searchParams.get("type"))) return Number(match?.groups?.collectionId);
}
if (/https:\/\/space.bilibili.com\/\d+\/channel\/collectiondetail\?/.test(this.href)) {
const collectionId = new URL(this.href).searchParams.get("sid")?.trim();
if (collectionId) return Number(collectionId);
}
},
get isCollectionPage() {
return typeof this.collectionId === "number";
},
get searchKeyword() {
if (!/https:\/\/space.bilibili.com\/\d+\/search/.test(this.href)) return;
return new URLSearchParams(location.search).get("keyword") ?? void 0;
},
get isSearching() {
return !!this.searchKeyword?.trim();
}
});
var getFollowedStatus = reusePendingPromise(async () => {
state.followed = !!await poll(() => {
if (Array.from(document.querySelectorAll(".space-follow-btn")).filter((el) => el.textContent?.trim() === "已关注").length > 0) return true;
}, {
interval: 100,
timeout: 5e3
});
});
globalEmitter.on("navigate-success", () => {
state.href = location.href;
getFollowedStatus();
});
function ActionButtons() {
const { mid, collectionId, followed, isSearching, searchKeyword } = useSnapshot(state);
if (!mid) return;
const clsBtn = "w-34px b-white/33% rounded-full hover:b-gate-primary hover:bg-gate-primary";
const clsIcon = "size-17px";
let btnSpaceUpload;
{
let href = `https://www.bilibili.com/?${SpaceUploadQueryKey.Mid}=${mid}`;
if (isSearching && searchKeyword) href += `&${SpaceUploadQueryKey.SearchText}=${searchKeyword}`;
btnSpaceUpload = /* @__PURE__ */ jsx(ActionButton, {
className: clsBtn,
href,
tooltip: `在「${APP_NAME}」中查看 UP 的投稿`,
children: /* @__PURE__ */ jsx(IconForSpaceUpload, { className: clsIcon })
}, "btnSpaceUpload");
}
let btnDynamicFeed;
if (followed) {
const href = `https://www.bilibili.com/?${DynamicFeedQueryKey.Mid}=${mid}`;
btnDynamicFeed = /* @__PURE__ */ jsx(ActionButton, {
className: clsBtn,
href,
tooltip: `在「${APP_NAME}」中查看 UP 的动态`,
children: /* @__PURE__ */ jsx(IconForDynamicFeed, { className: clsIcon })
}, "btnDynamicFeed");
}
let btnViewCollection;
if (typeof collectionId === "number") btnViewCollection = /* @__PURE__ */ jsx(ActionButton, {
className: clsBtn,
href: `https://www.bilibili.com/?${FavQueryKey.CollectionIdFull}=${collectionId}`,
target: "_blank",
tooltip: `在「${APP_NAME}」中查看合集`,
children: /* @__PURE__ */ jsx(IconForCollection, { className: clsIcon })
}, "btnViewCollection");
return /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-8px",
children: [
btnViewCollection,
btnSpaceUpload,
btnDynamicFeed
]
});
}
function ActionButton({ href, children, className, style, tooltip, ...restProps }) {
const { usingNewSpacePage } = useSnapshot(state);
const _className = r("h-34px w-150px flex cursor-pointer items-center justify-center b-1px b-white/20% rounded-6px b-solid bg-white/14% text-14px color-white font-700 transition-duration-300 transition-property-all hover:bg-white/40%", className);
const btn = usingNewSpacePage ? /* @__PURE__ */ jsx("a", {
...restProps,
href,
className: _className,
style,
children
}) : /* @__PURE__ */ jsx("a", {
href,
className: clsx("h-f-btn", className),
style: {
width: "auto",
paddingInline: "15px",
...style
},
...restProps,
children
});
if (tooltip) return /* @__PURE__ */ jsx(AntdTooltip, {
title: tooltip,
children: btn
});
else return btn;
}
function parseMid(href = location.href) {
const mid = new URL(href).pathname.split("/").map((x) => x.trim()).find(Boolean);
if (!mid || !/^\d+$/.test(mid)) return;
return mid;
}
/**
* 现在 bilibili.com 页面无法完成从视频 -> 合集的跳转
* @NOTE: 未来需检查 space.bilibili.com 是否修复此问题
*
* https://space.bilibili.com/34842921/lists?sid=4976283
* ->
* https://space.bilibili.com/34842921/lists/4976283?type=season
*/
function fixCollectionUrl() {
globalEmitter.on("navigate-success", redirectCollectionUrl);
}
function redirectCollectionUrl() {
const u = new URL(location.href);
if (u.hostname !== "space.bilibili.com") return;
const match = /^\/(?\d+)\/lists\?sid=(?\d+)$/.exec(u.pathname + u.search);
if (!match) return;
const newUrl = new URL(`/${match.groups?.mid}/lists/${match.groups?.sid}`, location.href).href;
location.href = newUrl;
}
function addUpCardQuickLinks() {
queryAllThenProcess();
globalEmitter.on("navigate-success", () => delay$1(300).then(queryAllThenProcess));
const followMain = document.querySelector(".follow-main");
if (followMain) {
const debounced = debounce(queryAllThenProcess, 100);
new MutationObserver(() => debounced()).observe(followMain, {
childList: true,
subtree: true
});
}
}
var quickLinksAddedAttr = "gateQuickLinksAdded";
function queryAllThenProcess() {
const cards = document.querySelectorAll(".follow-main .item .relation-card");
for (const c of cards) {
if (c.dataset[quickLinksAddedAttr]) continue;
c.dataset[quickLinksAddedAttr] = "true";
processSingleUpCard(c);
}
}
function processSingleUpCard(card) {
const rootEl = document.createElement("div");
card.querySelector(".relation-card-info-option .follow-btn")?.insertAdjacentElement("afterend", rootEl);
const info = parseUpCard(card);
createReactRoot(rootEl).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(UpCardQuickLinks, { mid: info.mid }) }));
}
function UpCardQuickLinks({ mid }) {
const spaceUploadHref = `https://${BiliDomain.Main}/?${SpaceUploadQueryKey.Mid}=${mid}`;
const dynamicFeedHref = `https://${BiliDomain.Main}/?${DynamicFeedQueryKey.Mid}=${mid}`;
return /* @__PURE__ */ jsxs("span", {
className: "ml-2 inline-flex items-center gap-x-2",
children: [/* @__PURE__ */ jsx(AntdTooltip, {
title: `在「${APP_NAME}」中查看 UP 的投稿`,
children: /* @__PURE__ */ jsx(button_default, {
variant: "link",
className: "icon-only-round-button size-24px",
href: spaceUploadHref,
target: "_blank",
children: /* @__PURE__ */ jsx(IconForSpaceUpload, { className: "size-16px" })
})
}), /* @__PURE__ */ jsx(AntdTooltip, {
title: `在「${APP_NAME}」中查看 UP 的动态`,
children: /* @__PURE__ */ jsx(button_default, {
variant: "link",
className: "icon-only-round-button size-24px",
href: dynamicFeedHref,
target: "_blank",
children: /* @__PURE__ */ jsx(IconForDynamicFeed, { className: "size-16px" })
})
})]
});
}
function parseUpCard(c) {
return { mid: c.querySelector(".relation-card-avatar")?.dataset.userProfileId || "0" };
}
var debug = baseDebug.extend("main:space-page:following:fix-follow-group-url");
async function fixFollowGroupUrl() {
await tryAction(".space-follow .relation-collapse-more", (x) => x.click(), { pollTimeout: 1e3 });
const fixFollowGroupUrl = limitFunction(async () => {
if (!isViewingFollowGroup(location.href)) return;
await selectGroupFromQuerystring(new URLSearchParams(location.search).get("tagid"));
}, 1);
fixFollowGroupUrl();
globalEmitter.on("navigate-success", () => fixFollowGroupUrl());
}
async function selectGroupFromQuerystring(tagid) {
if (!tagid) return;
debug("selectGroupFromQuerystring: %s", tagid);
const el = await poll(() => document.querySelector(`.follow-sidebar-item a[href*="tagid=${CSS.escape(tagid)}"] .vui_sidebar-item`), {
interval: 200,
timeout: 2e3
});
if (!el) return;
if (el.classList.contains(".vui_sidebar-item--active")) return;
debug("click group %s", tagid);
el.scrollIntoViewIfNeeded?.();
el.click();
}
function isViewingFollowGroup(url) {
const u = new URL(url);
return u.host === BiliDomain.Space && /^\/\d+\/relation\/follow$/.test(u.pathname) && u.searchParams.has("tagid");
}
function initSpaceFollingPage() {
if (!isViewingFollowGroup(location.href)) return;
fixFollowGroupUrl();
addUpCardQuickLinks();
}
function initSpacePage() {
setupForNoneHomepage();
fixCollectionUrl();
addActionButtons();
initSpaceFollingPage();
}})}}));
System.register("./search-page-Cw6IXwjk-DBfiWMoB.js", ['./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./shared-DUfk4XGY-a2sr8va8.js','./risk-log-ZLVUftoN-DnHKear8.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var settings,setupLargePreview,isInIframe,setupForNoneHomepage;return{setters:[function(module){settings=module.t;},function(module){setupLargePreview=module.aV;},function(module){isInIframe=module.i;setupForNoneHomepage=module.s;},null,null,null,null,null,null],execute:(function(){exports("initSearchPage",initSearchPage);function initSearchPage() {
if (isInIframe()) return;
setupForNoneHomepage();
if (settings.videoCard.videoPreview.addTo.searchPage) addLargePreviewForSearchResults();
}
function addLargePreviewForSearchResults() {
setupLargePreview({ itemsSelector: ".video-list-item:has(> .bili-video-card),div:has(> .bili-video-card)" });
}})}}));
System.register("./shared-DUfk4XGY-a2sr8va8.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js'],(function(exports){'use strict';var createReactRoot,registerSettingsGmCommand,Emittery,ae,once,jsx,SetupForPage,AppRoot;return{setters:[function(module){createReactRoot=module.m;},function(module){registerSettingsGmCommand=module.dD;Emittery=module.dE;ae=module.bJ;once=module.dF;jsx=module.a;SetupForPage=module.dG;AppRoot=module.dp;}],execute:(function(){exports({i:isInIframe,s:setupForNoneHomepage});function setupForNoneHomepage() {
setupAppRootForNoneHomepage();
registerSettingsGmCommand();
}
var _setupOnce = once(() => {
const container = document.createElement("div");
document.body.appendChild(container);
createReactRoot(container).render(/* @__PURE__ */ jsx(AppRoot, { children: /* @__PURE__ */ jsx(SetupForPage, {
antd: true,
baseGlobalStyle: true
}) }));
});
function setupAppRootForNoneHomepage() {
_setupOnce();
}
function isInIframe() {
return ae.try(() => globalThis.self !== window.top).unwrapOr(true);
}
var globalEmitter = exports("g",new Emittery());
var setupForNavigationListeners = once(() => {
window.navigation?.addEventListener?.("navigatesuccess", () => {
globalEmitter.emit("navigate-success");
});
});
queueMicrotask(setupForNavigationListeners);})}}));
System.register("./fav-theme-folder-modal-DEvndVyg-BlQgbjJW.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./fav-theme-folder-5j6GivBE-BDXSEryb.js','axios','ua-parser-js','spark-md5','localforage','pinyin-match'],(function(exports){'use strict';var __toESM,require_react,antMessage,Emittery,wrapComponent,favStore,jsx,jsxs,button_default,Fragment,Tag,Input,Modal,Typography,createThemeFolderAndFav,matchThemeFolders,tagRecFavThemeFolderCreatedTodayCount,legacyThemeNameOf,favToThemeFolder,alignThemeFolderName;return{setters:[function(module){__toESM=module._;require_react=module.r;},function(module){antMessage=module.ad;Emittery=module.dE;wrapComponent=module.dH;favStore=module.dI;jsx=module.a;jsxs=module.j;button_default=module.n;Fragment=module.F;Tag=module.T;Input=module.Y;Modal=module.$;},function(module){Typography=module.e;},function(module){createThemeFolderAndFav=module.c;matchThemeFolders=module.m;tagRecFavThemeFolderCreatedTodayCount=module.t;legacyThemeNameOf=module.l;favToThemeFolder=module.f;alignThemeFolderName=module.a;},null,null,null,null,null],execute:(function(){exports({createFavThemeFolderQuick:createFavThemeFolderQuick,startFavThemeFolderModal:startFavThemeFolderModal});var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
var NAME_LIMIT = 20;
var nameOf = (themeName, prefixed) => (prefixed ? `tag-rec·${themeName}` : themeName).slice(0, NAME_LIMIT);
function FavThemeFolderModal({ show, avid, themeName, onClose }) {
const [matches, setMatches] = (0, import_react.useState)([]);
const [legacyOf, setLegacyOf] = (0, import_react.useState)({});
const [prefixed, setPrefixed] = (0, import_react.useState)(false);
const [name, setName] = (0, import_react.useState)("");
const [privacy, setPrivacy] = (0, import_react.useState)(0);
const [todayCount, setTodayCount] = (0, import_react.useState)(0);
const [busy, setBusy] = (0, import_react.useState)(false);
const capReached = todayCount >= 5;
matches.find((m) => m.kind === "exact");
const dupName = matches.some((m) => m.folder.title === name.trim());
(0, import_react.useEffect)(() => {
if (!show) return;
setPrefixed(false);
setName(nameOf(themeName, false));
setPrivacy(0);
setMatches([]);
setLegacyOf({});
let alive = true;
(async () => {
const [ms, count] = await Promise.all([matchThemeFolders(themeName), tagRecFavThemeFolderCreatedTodayCount()]);
if (!alive) return;
setMatches(ms);
setTodayCount(count);
const legacy = {};
for (const m of ms) {
const old = await legacyThemeNameOf(m.folder.id, themeName);
if (old) legacy[m.folder.id] = old;
}
if (alive) setLegacyOf(legacy);
})();
return () => {
alive = false;
};
}, [show, themeName]);
const doFav = async (folderId, folderTitle) => {
setBusy(true);
const r = await favToThemeFolder(avid, folderId, folderTitle);
setBusy(false);
if (r.ok) {
antMessage.success(r.message);
onClose();
} else antMessage.error(r.message);
};
const doCreate = async () => {
const title = name.trim().slice(0, NAME_LIMIT);
if (!title) return antMessage.warning("请填写收藏夹名称");
setBusy(true);
const r = await createThemeFolderAndFav(avid, themeName, {
title,
privacy
});
setBusy(false);
if (r.ok) {
antMessage.success(r.message);
onClose();
} else antMessage.error(r.message);
};
const doAlign = async (m) => {
const r = await alignThemeFolderName(m.folder.id, themeName);
if (!r.ok) return antMessage.error(r.message);
antMessage.success(r.message);
setLegacyOf((x) => {
const next = { ...x };
delete next[m.folder.id];
return next;
});
setMatches((ms) => ms.map((x) => x.folder.id === m.folder.id ? {
...x,
folder: {
...x.folder,
title: themeName
}
} : x));
};
const others = favStore.folders.filter((f) => !matches.some((m) => m.folder.id === f.id));
return /* @__PURE__ */ jsx(Modal, {
title: "收藏到主题夹",
open: show,
onCancel: onClose,
footer: null,
width: 540,
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "text-13px",
children: ["本视频主题: ", /* @__PURE__ */ jsx("b", { children: themeName })]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "匹配到的收藏夹"
}), matches.length ? matches.map((m) => /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-4px",
children: [/* @__PURE__ */ jsxs(button_default, {
size: "small",
disabled: busy,
onClick: () => doFav(m.folder.id, m.folder.title),
children: [
"📁 ",
m.folder.title,
" (",
m.folder.media_count,
")"
]
}), legacyOf[m.folder.id] && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Tag, {
className: "mr-0",
color: "orange",
children: [
"旧名「",
legacyOf[m.folder.id],
"」"
]
}), /* @__PURE__ */ jsxs(button_default, {
size: "small",
disabled: busy,
onClick: () => doAlign(m),
children: [
"改名为「",
themeName,
"」"
]
})] })]
}, m.folder.id)) : /* @__PURE__ */ jsxs(Typography.Text, {
type: "warning",
className: "text-12px",
children: [
"没有匹配到「",
themeName,
"」相关的收藏夹 → 建议新建"
]
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "新建收藏夹"
}),
/* @__PURE__ */ jsx(Input, {
size: "small",
style: { width: 200 },
value: name,
maxLength: NAME_LIMIT,
onChange: (e) => setName(e.target.value)
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: name.length >= NAME_LIMIT ? "warning" : "secondary",
className: "text-11px",
children: [
name.length,
"/",
NAME_LIMIT
]
}),
dupName && /* @__PURE__ */ jsx(Typography.Text, {
type: "warning",
className: "text-11px",
children: "已存在同名夹 → 点匹配项直接收藏"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "命名"
}), [["bare", "主题名(推荐 · 干净)"], ["prefixed", "tag-rec·主题名(可选 · 便于区分)"]].map(([k, label]) => /* @__PURE__ */ jsx(button_default, {
size: "small",
type: !prefixed && k === "bare" || prefixed && k === "prefixed" ? "primary" : "default",
onClick: () => {
const next = k === "prefixed";
setPrefixed(next);
setName(nameOf(themeName, next));
},
children: label
}, k))]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "隐私"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: privacy === 0 ? "primary" : "default",
onClick: () => setPrivacy(0),
children: "公开(B站默认)"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: privacy === 1 ? "primary" : "default",
onClick: () => setPrivacy(1),
children: "私密"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "今日配额"
}), capReached ? /* @__PURE__ */ jsxs(Typography.Text, {
type: "warning",
className: "text-11px",
children: [
"⚠ 今日建夹已达上限(B站约 ",
5,
" 个/日)→ 建议选匹配夹,或明天再来"
]
}) : /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-11px",
children: [
"本脚本今日已建 ",
todayCount,
"/",
5,
"(B站每日上限约",
" ",
5,
" 个,含手动建夹也计数)"
]
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsx(button_default, {
type: "primary",
loading: busy,
onClick: doCreate,
children: "✨ 创建并收藏"
}), /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-11px",
children: [
"来源标记进简介(intro 写「tag-rec · 主题「",
themeName,
"」」,不占标题)"
]
})]
}),
!!others.length && /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "w-96px text-12px font-bold",
children: "全部收藏夹"
}), others.slice(0, 8).map((f) => /* @__PURE__ */ jsxs(button_default, {
size: "small",
disabled: busy,
onClick: () => doFav(f.id, f.title),
children: [
f.title,
" (",
f.media_count,
")"
]
}, f.id))]
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "夹 ↔ 主题不做持续同步:命名即映射(默认主题名,前缀可选);只在动作时一次性对齐;删主题不删夹"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "建夹/收藏走登录态通道(账号维度):只在点击时发,不自动/不预取/不批量;夹列表复用缓存"
})
]
})
});
}
var { updateProps } = wrapComponent({
Component: FavThemeFolderModal,
defaultProps: {
show: false,
avid: "",
themeName: "",
onClose
},
containerClassName: "FavThemeFolderModal"
});
var emitter = new Emittery();
function onClose() {
updateProps({ show: false });
emitter.emit("modal-close");
}
/** 打开「收藏到主题夹」弹层 (等关闭) */
async function startFavThemeFolderModal({ avid, themeName }) {
updateProps({
show: true,
avid,
themeName
});
await emitter.once("modal-close");
}
/** 子菜单「新建「X」」直通: 用默认名 (裸主题名) 创建并收藏 */
async function createFavThemeFolderQuick({ avid, themeName }) {
const r = await createThemeFolderAndFav(avid, themeName, { title: nameOf(themeName, false) });
if (r.ok) antMessage.success(r.message);
else antMessage.error(r.message);
}})}}));
System.register("./fav-theme-folder-5j6GivBE-BDXSEryb.js", ['./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js'],(function(exports){'use strict';var renameFavFolder,addFav,createFavFolder,updateFavFolderList,favStore,stripRelatedSuffix,resolveTagRecThemeNameOfGroups,favoritesL1,themes,favorites;return{setters:[function(module){renameFavFolder=module.dJ;addFav=module.dK;createFavFolder=module.dL;updateFavFolderList=module.dM;favStore=module.dI;stripRelatedSuffix=module.dN;resolveTagRecThemeNameOfGroups=module.dO;},function(module){favoritesL1=module.f;themes=module.w;favorites=module.d;}],execute:(function(){exports({a:alignThemeFolderName,c:createThemeFolderAndFav,f:favToThemeFolder,l:legacyThemeNameOf,m:matchThemeFolders,r:resolveCardThemeName,t:tagRecFavThemeFolderCreatedTodayCount});var RANK = {
exact: 0,
prefix: 1,
contains: 2
};
/**
* R9: 主题名匹配收藏夹 (「收藏到主题夹」用)
* - 排序: 精确 > 前缀 > 包含; 同档保持列表原序 (sort 稳定)
* - 上限 3 (设计定案; 更多走「选择其他文件夹…」原弹层)
* - 大小写不敏感; 空主题/空夹名跳过
* - 不依赖 `tag-rec·` 前缀 (命名软化定案): 手建夹与旧前缀夹都能被匹配到
*/
function matchFavFoldersByTheme(theme, folders, limit = 3) {
const t = theme.trim().toLowerCase();
if (!t) return [];
const out = [];
for (const folder of folders) {
const title = folder.title.trim().toLowerCase();
if (!title) continue;
const kind = title === t ? "exact" : title.startsWith(t) ? "prefix" : title.includes(t) ? "contains" : void 0;
if (kind) out.push({
folder,
kind
});
}
return out.sort((a, b) => RANK[a.kind] - RANK[b.kind]).slice(0, limit);
}
var STORAGE_KEY = "tag-rec:fav-theme-folders";
/** 账本保留窗口 (旧记录仅服务改名对齐, 90 天足够) */
var KEEP_MS = 7776e6;
var FLUSH_DEBOUNCE_MS = 1e3;
var rows;
var rowsPromise;
var flushTimer;
function pool() {
rowsPromise ??= Promise.resolve().then(() => GM.getValue(STORAGE_KEY, [])).then((list) => {
const now = Date.now();
rows = (list ?? []).filter((x) => x && x.createdAt > now - KEEP_MS).slice(-500);
return rows;
}).catch(() => {
rowsPromise = void 0;
});
return rowsPromise;
}
pool();
function scheduleFlush() {
if (flushTimer) return;
flushTimer = setTimeout(() => {
flushTimer = void 0;
Promise.resolve().then(() => GM.setValue(STORAGE_KEY, rows ?? [])).catch(() => void 0);
}, FLUSH_DEBOUNCE_MS);
}
/** 记账: 本脚本新建了一个主题夹 */
function noteTagRecFavThemeFolderCreated(rec) {
pool().then((r) => {
if (!r) return;
r.push(rec);
scheduleFlush();
});
}
/** 改名对齐后同步记账名 (旧名夹转正) */
function updateTagRecFavThemeFolderName(folderId, themeName) {
pool().then((r) => {
const hit = r?.find((x) => x.folderId === folderId);
if (!hit) return;
hit.themeName = themeName;
scheduleFlush();
});
}
/** 今日已建数 (本地账口径; B站上限约 5/日 且含手动建夹) */
async function tagRecFavThemeFolderCreatedTodayCount() {
const r = await pool() ?? [];
const start = /* @__PURE__ */ new Date();
start.setHours(0, 0, 0, 0);
return r.filter((x) => x.createdAt >= start.getTime()).length;
}
/** 账本记录查询 (旧名夹识别用; undefined = 非本脚本创建/无账) */
async function tagRecFavThemeFolderRecordOf(folderId) {
return (await pool() ?? []).find((x) => x.folderId === folderId);
}
/**
* [tag-rec] R9 「收藏到主题夹」编排 (右键菜单/小弹层共用)
* - 设计定案 (map.md Round 1~3): 单位=主题; 默认裸主题名; 优先匹配已有夹 (主题名子串);
* 无匹配提示建夹; 撞名=精确复用; 夹 ↔ 主题不做持续同步 (命名即映射 + 动作时一次性对齐)
* - 风控: 收藏面 = 登录态通道 (账号维度) —— 只在显式点击时发, 零自动化; 夹列表复用 favStore 缓存 (5min)
* - 配额: B站每日建夹上限约 5 个 (社区口径, 含手动建夹); 达上限/建夹失败 => 降级到匹配夹
*/
/** B站每日建夹上限 (社区口径 ~5; 仅作前置提示, 真实限制以 API 拒绝为准) */
var TAG_REC_FAV_THEME_FOLDER_DAILY_HINT = exports("T",5);
/** 卡片归属主题名 (tab1 组源; 组→L1→themeId→主题表; 多认领取首个命中) */
function resolveCardThemeName(groupNames) {
const names = [...new Set((groupNames ?? []).map(stripRelatedSuffix))];
const l1ById = new Map(favoritesL1.list.map((l) => [l.id, l]));
const themeById = new Map(themes.list.map((t) => [t.id, t]));
return resolveTagRecThemeNameOfGroups(names, favorites.list, l1ById, themeById);
}
/** 主题夹匹配 (读 favStore 缓存, 5min 内零请求; 缓存缺失/过期才发一次 list-all) */
async function matchThemeFolders(themeName) {
try {
await updateFavFolderList();
} catch {}
return matchFavFoldersByTheme(themeName, favStore.folders);
}
/** 旧名夹识别 (改名对齐): 记账名 ≠ 当前主题名 => 返回旧名 */
async function legacyThemeNameOf(folderId, themeName) {
const rec = await tagRecFavThemeFolderRecordOf(folderId);
return rec && rec.themeName !== themeName ? rec.themeName : void 0;
}
function errorReasonOf(err) {
const code = err?.response?.data?.code;
if (code === -509) return "操作过快 (-509)";
if (code === -412) return "触发风控 (-412)";
if (code === -101) return "未登录 (-101)";
const msg = err?.response?.data?.message;
return msg ? `创建失败: ${msg}` : "创建失败";
}
/** 降级候选: 精确 > 前缀 > 包含 的首个匹配夹 */
async function firstFallback(themeName) {
const ms = await matchThemeFolders(themeName);
return ms.find((m) => m.kind === "exact") ?? ms[0];
}
/** 收藏到指定夹 (菜单「匹配夹」项/弹层「全部夹」项共用) */
async function favToThemeFolder(avid, folderId, folderTitle) {
return (await addFav(avid, [folderId])).isOk() ? {
ok: true,
message: `已收藏到「${folderTitle}」`,
folderTitle
} : {
ok: false,
message: "收藏失败, 请稍后再试"
};
}
/**
* 创建并收藏 (V3 弹层 [创建并收藏]):
* 配额前置检查 → 建夹 (intro 写来源标记) → 记账 → 收藏;
* 达上限/建夹失败 => 降级到匹配夹; 无匹配 => 明确报错
*/
async function createThemeFolderAndFav(avid, themeName, opts) {
if (await tagRecFavThemeFolderCreatedTodayCount() >= 5) {
const fb = await firstFallback(themeName);
if (fb) {
if ((await addFav(avid, [fb.folder.id])).isOk()) return {
ok: true,
message: `今日建夹已达上限 (约 5/日) → 已收藏到「${fb.folder.title}」`,
folderTitle: fb.folder.title
};
}
return {
ok: false,
message: "今日建夹已达上限 (约 5/日), 且无匹配收藏夹 —— 明天再来"
};
}
const res = await createFavFolder({
title: opts.title,
intro: `tag-rec · 主题「${themeName}」`,
privacy: opts.privacy
});
if (res.isErr()) {
const reason = errorReasonOf(res.error);
const fb = await firstFallback(themeName);
if (fb) {
if ((await addFav(avid, [fb.folder.id])).isOk()) return {
ok: true,
message: `${reason} → 已收藏到「${fb.folder.title}」`,
folderTitle: fb.folder.title
};
}
return {
ok: false,
message: reason
};
}
const { id } = res.value;
if (id) noteTagRecFavThemeFolderCreated({
folderId: id,
themeName,
createdAt: Date.now()
});
if ((await addFav(avid, id ? [id] : void 0)).isErr()) return {
ok: false,
message: "收藏夹已创建, 但收藏失败, 请重试"
};
return {
ok: true,
message: `已创建并收藏到「${opts.title}」`,
folderTitle: opts.title,
created: true
};
}
/** 旧名对齐 (一次性, 用户显式点): 把夹改名为当前主题名 + 同步记账 */
async function alignThemeFolderName(folderId, themeName) {
if ((await renameFavFolder(folderId, themeName)).isErr()) return {
ok: false,
message: "改名失败, 请稍后再试"
};
updateTagRecFavThemeFolderName(folderId, themeName);
return {
ok: true,
message: `已把夹改名为「${themeName}」`,
folderTitle: themeName
};
}})}}));
System.register("./share-import-modal-CeNu8LoU-N2aE6hH8.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./discovery-policy-BBjOB0CZ-BWQIVxg7.js','./share-tokens-DwS1MNAq-C8u87gZL.js','./share-import-DgpdTju8-Dey7mX2T.js'],(function(exports){'use strict';var __toESM,require_react,useSnapshot,jsxs,jsx,Spin,Modal,resolveTagRecImportApply,settings,cloneDeep,useRecSelfContext,theme,Tag,Fragment,Checkbox,button_default,Switch,nameTagRecCombination,Select,Radio,hasTagRecCombination,showModalSettings,ETab,hideModalSettings,toast,TAG_REC_CONDITION_FIELDS,tagRecConditionLabel,tagRecSharedSession,Typography,themes,favoritesL1,favorites,mergeTagGroups,reconcileTagRecMergedGroupContext,blockTagRecConditionGlobally,blacklistStore,favoritesL1Store,themesStore,TAG_REC_POOL_KEYS,TAG_REC_POOL_LABELS,videoSourceTabState,TAG_REC_DROP_DIM_LABELS,Alert,pickTagRecScopePatch,tagRecPoolSlotSummary,TabPaneKey,modalSettingsStore,resolveShareImportL1Enabled,closeShareImportDraft,noteShareImportDone;return{setters:[function(module){__toESM=module._;require_react=module.r;},function(module){useSnapshot=module.u;jsxs=module.j;jsx=module.a;Spin=module.dP;Modal=module.$;resolveTagRecImportApply=module.dQ;settings=module.t;cloneDeep=module.aQ;useRecSelfContext=module.R;theme=module.bo;Tag=module.T;Fragment=module.F;Checkbox=module._;button_default=module.n;Switch=module.bj;nameTagRecCombination=module.bi;Select=module.S;Radio=module.a9;hasTagRecCombination=module.aE;showModalSettings=module.cE;ETab=module.E;hideModalSettings=module.aG;toast=module.ak;TAG_REC_CONDITION_FIELDS=module.bY;tagRecConditionLabel=module.d4;},function(module){tagRecSharedSession=module.t;Typography=module.e;themes=module.w;favoritesL1=module.f;favorites=module.d;mergeTagGroups=module.aW;reconcileTagRecMergedGroupContext=module.aX;blockTagRecConditionGlobally=module.aN;blacklistStore=module.B;favoritesL1Store=module.o;themesStore=module.p;TAG_REC_POOL_KEYS=module.L;TAG_REC_POOL_LABELS=module.M;videoSourceTabState=module.s;TAG_REC_DROP_DIM_LABELS=module.Q;},function(module){Alert=module.A;pickTagRecScopePatch=module.p;tagRecPoolSlotSummary=module.y;TabPaneKey=module.T;modalSettingsStore=module.m;},function(module){resolveShareImportL1Enabled=module.B;},function(module){closeShareImportDraft=module.c;noteShareImportDone=module.n;}],execute:(function(){exports({T:TagRecShareImportModal,a:applyTagRecImport,b:TagRecShareGroupContents,c:TagRecConditionTreeView});var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
/**
* [tag-rec] 导入计划落表门面 (票 18 抽提 —— pane JSON 导入 confirm 与分享导入 Modal 共用):
* - 序: 主题 → L1 → L2 (组携 l1Id 的归属校验须先见新 L1, 否则被 auto-wrap 截胡; 票 17)
* - merge 并入在入表后 —— 宿主可能是本批新入组; 宿主缺席 (被 skip/未勾选) => 回落照常入表
* - onlyGroupIds (分享勾选子集): 只落这些组, l1/themes 裁到实际归属链 (resolveTagRecImportApply)
* - applyFilterConfig: 勾选制整体覆盖 (JSON 档传 data.filterConfig; 票26 起分享档传
* draft.filterConfig —— 出厂占位不携, Modal 勾选裁决; 克隆落表不共享引用)
* - activeThemeId: plan 裁决过的照走 (JSON 档可携视图态; 分享路径 planShareImport 已剥键 —— 分享是数据不是视图态)
*/
function applyTagRecImport(input) {
const applied = resolveTagRecImportApply(input.plan, {
choices: input.choices,
onlyGroupIds: input.onlyGroupIds
});
themes.addThemes(applied.themesToAdd);
favoritesL1.addL1s(applied.l1ToAdd);
favorites.addGroups(applied.groupsToAdd);
let merged = 0;
let fallbackAdded = 0;
const sourcesByTarget = /* @__PURE__ */ new Map();
for (const m of applied.merges) {
const sources = sourcesByTarget.get(m.targetId) ?? [];
sources.push(m.src);
sourcesByTarget.set(m.targetId, sources);
}
for (const [targetId, sources] of sourcesByTarget) {
const dst = favorites.list.find((g) => g.id === targetId);
if (!dst || dst.combination || sources.some((g) => g.combination)) {
favorites.addGroups(sources);
fallbackAdded += sources.length;
continue;
}
Object.assign(dst, mergeTagGroups(dst, sources));
reconcileTagRecMergedGroupContext([], targetId);
merged += sources.length;
}
let blacklistAdded = 0;
if (input.includeBlacklist ?? true) {
for (const atom of input.plan.combinationExclusionsToAdd ?? []) blockTagRecConditionGlobally(atom);
blacklistStore.list.push(...input.plan.blacklistToAdd);
blacklistAdded = input.plan.blacklistToAdd.length;
}
const filterToApply = input.applyFilterConfig ? input.filterConfig : void 0;
const filterApplied = !!filterToApply;
if (filterToApply) settings.tagRec.filter = cloneDeep(filterToApply);
if (input.plan.activeThemeId !== void 0) themes.setActive(input.plan.activeThemeId ?? void 0);
return {
themesAdded: applied.themesToAdd.length,
l1Added: applied.l1ToAdd.length,
added: applied.groupsToAdd.length + fallbackAdded,
merged,
skipped: applied.skippedCount,
blacklistAdded,
filterApplied
};
}
var FIELD_TONE = {
tag: {
fg: "#0958d9",
bg: "#e6f4ff"
},
up: {
fg: "#531dab",
bg: "#f9f0ff"
},
title: {
fg: "#c41d7f",
bg: "#fff0f6"
},
desc: {
fg: "#d46b08",
bg: "#fff7e6"
},
partition: {
fg: "#389e0d",
bg: "#f6ffed"
}
};
function AtomChip({ atom, danger, onRemove }) {
const tone = FIELD_TONE[atom.field];
return /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-2px rounded-4px px-6px py-0 text-12px",
style: {
background: danger ? "#fff1f0" : tone?.bg,
color: danger ? "#cf1322" : tone?.fg,
border: `1px solid ${danger ? "#ffa39e" : "transparent"}`
},
title: `${TAG_REC_CONDITION_FIELDS[atom.field]} · ${tagRecConditionLabel(atom)}`,
children: [
/* @__PURE__ */ jsx("span", {
className: "text-11px opacity-60",
children: TAG_REC_CONDITION_FIELDS[atom.field]
}),
tagRecConditionLabel(atom),
onRemove && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
"aria-label": `移除${tagRecConditionLabel(atom)}`,
className: "h-auto min-w-16px px-0 text-12px leading-none opacity-60",
onClick: onRemove,
children: "×"
})
]
});
}
function ModeBadge({ node, root, onSetMode }) {
const label = node.mode === "all" ? "全部命中" : "任一命中";
return /* @__PURE__ */ jsx("span", {
className: "inline-flex items-center rounded-4px px-6px text-11px font-600",
style: {
height: 20,
color: node.mode === "all" ? "#0958d9" : "#d48806",
background: node.mode === "all" ? "#e6f4ff" : "#fff7e6",
cursor: onSetMode ? "pointer" : void 0
},
title: onSetMode ? `点击切换为${node.mode === "all" ? "任一" : "全部"}命中` : label,
onClick: onSetMode ? () => onSetMode(node.id, node.mode === "all" ? "any" : "all") : void 0,
children: root ? `组合方式 · ${label}` : label
});
}
function GroupNode({ node, root, edit }) {
const atoms = node.children.filter((c) => c.kind === "atom");
const groups = node.children.filter((c) => c.kind === "group");
if (!root && !edit && !node.children.length) return null;
return /* @__PURE__ */ jsxs("div", {
className: "mt-6px",
style: {
paddingLeft: root ? 0 : 10,
borderLeft: root ? void 0 : "2px solid #f0f0f0"
},
children: [/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px gap-y-4px",
children: [
/* @__PURE__ */ jsx(ModeBadge, {
node,
root,
onSetMode: edit?.onSetMode
}),
!root && !!node.name && TAG_REC_CONDITION_FIELDS[node.name] === void 0 && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: node.name
}),
atoms.map((a) => /* @__PURE__ */ jsx(AtomChip, {
atom: a,
onRemove: edit?.onRemoveNode ? () => edit.onRemoveNode(a.id) : void 0
}, a.id)),
edit?.renderAdder?.({
kind: "group",
id: node.id
}),
edit?.onRemoveNode && !root && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "h-auto px-4px text-11px opacity-60",
onClick: () => edit.onRemoveNode(node.id),
children: "移出整组"
})
]
}), groups.map((child) => /* @__PURE__ */ jsx(GroupNode, {
node: child,
edit
}, child.id))]
});
}
/** combination.filters 声明摘要 (只读) —— 逐键列出, 与「本组声明」列同口径 */
function FiltersBlock({ filters }) {
const chips = Array.from(Object.keys(filters.dims ?? {}), (k) => `维·${TAG_REC_DROP_DIM_LABELS[k] ?? k}`);
if (filters.score) chips.push("算分");
if (filters.smallPoolThreshold !== void 0) chips.push(`小池阈值 ${filters.smallPoolThreshold}`);
for (const [p, v] of Object.entries(filters.poolsEnabled ?? {})) chips.push(`${TAG_REC_POOL_LABELS[p] ?? p}·${v ? "开" : "关"}`);
if (filters.pools) for (const p of Object.keys(filters.pools)) chips.push(`格·${TAG_REC_POOL_LABELS[p] ?? p}`);
if (filters.enabled !== void 0) chips.push(`过滤总开关·${filters.enabled ? "开" : "关"}`);
if (filters.strictMode !== void 0) chips.push(`严格模式·${filters.strictMode ? "开" : "关"}`);
if (!chips.length) return null;
return /* @__PURE__ */ jsxs("div", {
className: "mt-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "inline-flex items-center rounded-4px px-6px text-11px font-600",
style: {
height: 20,
color: "#531dab",
background: "#f9f0ff"
},
children: "组合自带过滤"
}), /* @__PURE__ */ jsx("div", {
className: "mt-4px flex flex-wrap gap-4px",
style: {
paddingLeft: 10,
borderLeft: "2px solid #f9f0ff"
},
children: chips.map((c) => /* @__PURE__ */ jsx("span", {
className: "rounded-4px bg-#f9f0ff px-6px text-12px",
style: { color: "#531dab" },
children: c
}, c))
})]
});
}
function TagRecConditionTreeView({ combination, tagIds, edit }) {
if (!combination) return /* @__PURE__ */ jsxs("div", { children: [tagIds?.length ? /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-4px",
children: [
tagIds.map((t) => /* @__PURE__ */ jsx("span", {
className: "rounded-4px px-6px text-12px",
style: {
background: "#e6f4ff",
color: "#0958d9"
},
children: t.name || `#${t.id}`
}, t.id)),
edit?.renderAdder?.({
kind: "group",
id: ""
}),
edit?.renderAdder?.({ kind: "exclude" })
]
}) : /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-6px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "(无条件)"
}),
edit?.renderAdder?.({
kind: "group",
id: ""
}),
edit?.renderAdder?.({ kind: "exclude" })
]
}), edit?.renderAdder && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-2px block text-11px",
children: "加入首个条件会把本组转为组合组(既有标签自动并入 tag 行)"
})] });
const include = combination.include;
if (!include.children.length && !combination.exclude.length && !combination.filters) return /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "(空组合)"
});
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsx(GroupNode, {
node: include,
root: true,
edit
}),
!!combination.exclude.length && /* @__PURE__ */ jsxs("div", {
className: "mt-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "inline-flex items-center rounded-4px px-6px text-11px font-600",
style: {
height: 20,
color: "#cf1322",
background: "#fff1f0"
},
children: "排除"
}), /* @__PURE__ */ jsxs("div", {
className: "mt-4px flex flex-wrap items-center gap-4px",
style: {
paddingLeft: 10,
borderLeft: "2px solid #fff1f0"
},
children: [combination.exclude.map((a) => /* @__PURE__ */ jsx(AtomChip, {
atom: a,
danger: true,
onRemove: edit?.onRemoveExclude ? () => edit.onRemoveExclude(a.id) : void 0
}, a.id)), edit?.renderAdder?.({ kind: "exclude" })]
})]
}),
edit?.renderAdder && !combination.exclude.length && /* @__PURE__ */ jsx("div", {
className: "mt-6px",
children: edit.renderAdder({ kind: "exclude" })
}),
combination.filters && /* @__PURE__ */ jsx(FiltersBlock, { filters: combination.filters })
] });
}
/**
* 集市预览「包内声明」展开块 (wayfinding 005): 口径 = 这组随包带了什么 (非导入后生效值)。
* 条件树复用同渲染件只读态 + 声明摘要行 (overrides/格槽/池筛选/负向); 展开与否由调用方控。
*/
function TagRecShareGroupContents({ group }) {
const o = group.overrides;
const summary = [];
const slot = pickTagRecScopePatch(o);
if (slot) summary.push(`覆盖: ${tagRecPoolSlotSummary(slot)}`);
for (const p of TAG_REC_POOL_KEYS) {
const cell = o?.pools?.[p];
if (cell) summary.push(`格·${TAG_REC_POOL_LABELS[p]}: ${tagRecPoolSlotSummary(cell)}`);
}
const poolsOff = TAG_REC_POOL_KEYS.filter((p) => o?.poolsEnabled?.[p] === false);
const poolsOn = TAG_REC_POOL_KEYS.filter((p) => o?.poolsEnabled?.[p] === true);
if (poolsOff.length) summary.push(`关池: ${poolsOff.map((p) => TAG_REC_POOL_LABELS[p]).join("·")}`);
if (poolsOn.length) summary.push(`开池: ${poolsOn.map((p) => TAG_REC_POOL_LABELS[p]).join("·")}`);
const negBits = [];
if (group.negative_tag_ids?.length) negBits.push(`${group.negative_tag_ids.length} 标签`);
const neg = o?.negative;
if (neg) {
if (neg.title?.length) negBits.push(`${neg.title.length} 标题词`);
if (neg.up?.length) negBits.push(`${neg.up.length} UP`);
if (neg.partitionBlock?.length) negBits.push(`${neg.partitionBlock.length} 分区`);
if (neg.strict) negBits.push("严格负向");
}
if (negBits.length) summary.push(`负向: ${negBits.join("·")}`);
return /* @__PURE__ */ jsxs("div", {
className: "ml-18px mt-4px border-l-2px border-#f0f0f0 border-solid pl-10px",
children: [/* @__PURE__ */ jsx(TagRecConditionTreeView, {
combination: hasTagRecCombination(group.combination) ? group.combination : void 0,
tagIds: group.tag_ids
}), !!summary.length && /* @__PURE__ */ jsx("div", {
className: "mt-6px flex flex-wrap gap-x-10px gap-y-2px text-11px opacity-75",
children: summary.map((s) => /* @__PURE__ */ jsx("span", { children: s }, s))
})]
});
}
/**
* [tag-rec] 分享导入预览 Modal (tag-share-platform 票 18 / spec §6) —— 深链与粘贴两条入口共用:
* - 双挂载点: tabbar-fav (origin='deeplink' —— 深链回跳, 面板未开也能弹) +
* pane 工具区 (origin='paste' —— 粘贴导入); session.shareImport.origin 过滤防双开
* - 卡头 = 分享元信息 (标题/分享者链空间/赞数/打开分享页); 清单默认全勾,
* 深链 tag-rec-groups 圈过子集时清单本就只含这些组 (planShareImport 前置裁剪),
* 指定 id 不在包内 => 「N 个组不在包内」警示
* - 一级组默认全启用 (planShareImport 归一 true), 行/节/批量开关反向取消 => disabledL1Ids;
* 勾选结果 = 最终启用集, 落表后逐条 updateL1 (resolveShareImportL1Enabled 裁决);
* 导入后不切主题 (后导入横幅另给「只看这个包的主题」)
* - 冲突裁量/落表与 JSON 导入同一套 (resolveTagRecImportApply → applyTagRecImport);
* 黑名单默认并入可关; 票26: 包内非占位 filterConfig 时裁决区加「带入过滤配置」勾选
* (勾上 = 分享者私配整套覆写当前过滤配置), 出厂占位包不渲染该项 (无可带入内容)
* - 确认后: 挂「待回赞」账 (票 19) + 武装一次性「勾选想看的主题/组」引导横幅
*/
function TagRecShareImportModal({ origin }) {
const si = useSnapshot(tagRecSharedSession).shareImport;
if (si.origin !== origin) return null;
if (si.loading) return /* @__PURE__ */ jsxs(Modal, {
open: true,
title: "导入分享",
footer: null,
maskClosable: false,
onCancel: closeShareImportDraft,
width: 420,
children: [
/* @__PURE__ */ jsx(Spin, { size: "small" }),
" ",
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "ml-8px text-13px",
children: "正在获取分享内容…"
})
]
});
const draft = si.draft;
if (!draft) return null;
return /* @__PURE__ */ jsx(ShareImportInner, { draft }, draft.key);
}
function ShareImportInner({ draft }) {
const { recSharedEmitter } = useRecSelfContext();
const { token } = theme.useToken();
const groups = draft.plan.favoritesToAdd;
const conflictById = (0, import_react.useMemo)(() => new Map(draft.plan.conflicts.map((c) => [c.group.id, c])), [draft]);
/** 勾选集 (默认全勾 —— 深链子集/整包同此) */
const [checkedIds, setCheckedIds] = (0, import_react.useState)(() => new Set(groups.map((g) => g.id)));
/** 「包内声明」展开集 (wayfinding 005: 默认折叠, 行尾 ▸/▾ 逐组切换, 头行一键全展) */
const [expandedIds, setExpandedIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
/** 冲突默认裁决 (与 JSON 导入同口径: 同源实体/被全覆盖 => 并入宿主, 部分重叠 => 保留两份) */
const defaultChoiceOf = (c) => {
const covered = c.group.tag_ids.every((e) => c.hits[0].shared.includes(e.id));
return c.hits[0].sameEntity || covered ? "merge" : "keep";
};
const [choices, setChoices] = (0, import_react.useState)(() => Object.fromEntries(draft.plan.conflicts.map((c) => [c.group.id, defaultChoiceOf(c)])));
const [includeBlacklist, setIncludeBlacklist] = (0, import_react.useState)(true);
/** 票26: 包内非占位 filterConfig 时可勾「带入」(覆盖当前整套);
* 默认不勾 —— 覆写过滤私配是高权动作, 须明示动作 (票26「默认安全档」) */
const [applyFilter, setApplyFilter] = (0, import_react.useState)(false);
/** 一级组反向取消集: 默认全启用 (planShareImport 归一 true), 取消勾选的行/节/批量开关把 id 记入此集;
* 空集 = 全启用; 落表裁决见 resolveShareImportL1Enabled (勾选结果 = 最终启用集) */
const [disabledL1Ids, setDisabledL1Ids] = (0, import_react.useState)(/* @__PURE__ */ new Set());
/** 票27 落点裁决: 'feed'=回主题推荐看推荐 (默认) / 'panel'=开收藏表管理继续编辑 (试改版带走再改的场景) */
const [landing, setLanding] = (0, import_react.useState)("feed");
/** 冲突逐条裁决直接钉在清单行上 (原型B): choices 行内 select + bulkbar 全并入/全保留 兜底;
* 初值 = 智能分配同口径 (同源撞车/被全覆盖→并入, 部分重叠→保留两份) */
/** 勾选组归属 L1 名解析 (表内既有 + 本批新增双源) */
const l1NameOf = (0, import_react.useMemo)(() => {
const m = /* @__PURE__ */ new Map();
for (const l of favoritesL1Store.list) m.set(l.id, l.name);
for (const l of draft.plan.l1ToAdd) m.set(l.id, l.name);
return m;
}, [draft]);
/** L1 → 主题 id 解析 (双源同名不同字段); 主题名解析给节头用 */
const l1ThemeOf = (0, import_react.useMemo)(() => {
const m = /* @__PURE__ */ new Map();
for (const l of favoritesL1Store.list) m.set(l.id, l.themeId);
for (const l of draft.plan.l1ToAdd) m.set(l.id, l.themeId);
return m;
}, [draft]);
const themeNameOf = (0, import_react.useMemo)(() => {
const m = /* @__PURE__ */ new Map();
for (const t of themesStore.list) m.set(t.id, t.name);
for (const t of draft.plan.themesToAdd) m.set(t.id, t.name);
return m;
}, [draft]);
/** 主题分节 (录入序): 组经 l1Id→themeId 归节; 无 l1/未归属主题 → undefined 桶(显示「未归属」) */
const sections = (0, import_react.useMemo)(() => {
const order = [];
const map = /* @__PURE__ */ new Map();
for (const g of groups) {
const th = g.l1Id ? l1ThemeOf.get(g.l1Id) : void 0;
if (!map.has(th)) {
map.set(th, []);
order.push(th);
}
map.get(th).push(g);
}
return order.map((themeId) => ({
themeId,
rows: map.get(themeId)
}));
}, [groups, l1ThemeOf]);
/** 预览决议 (清单摘要与落表同一口径 —— resolveTagRecImportApply 纯逻辑可重算) */
const applied = (0, import_react.useMemo)(() => resolveTagRecImportApply(draft.plan, {
choices,
onlyGroupIds: checkedIds
}), [
draft,
choices,
checkedIds
]);
const listedConflicts = draft.plan.conflicts.filter((c) => checkedIds.has(c.group.id));
/** 全部行涉及的一级组 id (保序去重) —— 批量/节开关的作用域与「全部启用」勾选态判据 */
const scopeL1Ids = (0, import_react.useMemo)(() => [...new Set(groups.map((g) => g.l1Id).filter((x) => !!x))], [groups]);
/** 落表后 L1 启用裁决 (新增 ∪ 本批落表组归属既有; 默认全启用, 反向集落停用) —— onOk 与警示条同源 */
const l1Decisions = (0, import_react.useMemo)(() => resolveShareImportL1Enabled({
groups: [...applied.groupsToAdd, ...applied.merges.map((m) => m.src)],
l1ToAdd: applied.l1ToAdd,
disabledL1Ids
}), [applied, disabledL1Ids]);
/** 实际将落停用的 L1 数 (作用域外的反向集条目不计 —— 警示条文案位) */
const disabledCount = l1Decisions.filter((d) => !d.enabled).length;
/** 反向集增删 (行/节/批量开关共用): disabled=true 记入停用, false 摘除启用 */
const setL1Disabled = (ids, disabled) => setDisabledL1Ids((prev) => {
const next = new Set(prev);
for (const id of ids) if (disabled) next.add(id);
else next.delete(id);
return next;
});
const toggle = (id, checked) => setCheckedIds((prev) => {
const next = new Set(prev);
if (checked) next.add(id);
else next.delete(id);
return next;
});
const onOk = () => {
const res = applyTagRecImport({
plan: draft.plan,
choices,
onlyGroupIds: checkedIds,
includeBlacklist,
applyFilterConfig: applyFilter,
filterConfig: draft.filterConfig
});
for (const { id, enabled } of l1Decisions) favoritesL1.updateL1(id, { enabled });
closeShareImportDraft();
if (landing === "panel") {
modalSettingsStore.tab = TabPaneKey.TagRecFavorites;
showModalSettings();
} else {
videoSourceTabState.value = ETab.TagRecFav;
hideModalSettings();
}
if (res.added + res.merged > 0) {
const pkgL1Ids = l1Decisions.map((d) => d.id);
const pkgThemeIds = [...new Set(pkgL1Ids.map((id) => favoritesL1.list.find((l) => l.id === id)?.themeId).filter((x) => !!x))];
noteShareImportDone(draft.shareId, {
l1Ids: pkgL1Ids,
themeIds: pkgThemeIds
});
recSharedEmitter.emit("refresh", "refresh");
}
const synthesized = draft.plan.synthesizedThemes ? `, 其中 ${draft.plan.synthesizedThemes} 个为悬挂引用合成` : "";
const invalid = draft.plan.invalidGroups.length ? `; ${draft.plan.invalidGroups.length} 组无效未入` : "";
toast(`已导入分享「${draft.title}」: ${res.themesAdded} 主题${synthesized} / ${res.l1Added} 一级组 / ${res.added} 组新入${res.merged ? ` / ${res.merged} 组并入既有` : ""}${res.skipped ? ` / ${res.skipped} 组跳过` : ""} / 黑名单 ${res.blacklistAdded} 项${res.filterApplied ? " / 过滤配置已覆盖" : ""}${invalid}`, 6e3);
};
return /* @__PURE__ */ jsx(Modal, {
title: "导入分享",
open: true,
onCancel: closeShareImportDraft,
onOk,
okText: "导入",
okButtonProps: { disabled: !checkedIds.size },
width: 660,
children: /* @__PURE__ */ jsxs("div", {
className: "max-h-60vh overflow-y-auto",
children: [
/* @__PURE__ */ jsxs("div", {
className: "b-1px b-gate-border rounded-6px b-solid px-10px py-8px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex items-baseline justify-between gap-x-8px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
ellipsis: { tooltip: draft.title },
className: "text-14px",
children: draft.title
}), /* @__PURE__ */ jsxs("span", {
className: "flex shrink-0 items-center gap-x-8px",
children: [draft.isPkg && /* @__PURE__ */ jsx(Tag, {
color: "blue",
children: "试改版"
}), draft.siteUrl && /* @__PURE__ */ jsx(Typography.Link, {
href: draft.siteUrl,
target: "_blank",
children: draft.isPkg ? "打开原分享页" : "打开分享页"
})]
})]
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "mt-2px block text-12px",
children: [
draft.mid !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
"分享者:",
" ",
/* @__PURE__ */ jsx(Typography.Link, {
href: `https://space.bilibili.com/${draft.mid}`,
target: "_blank",
children: draft.nickname
}),
` · ${draft.likesCount} 赞 · `
] }) : "本地试改版 · ",
`${groups.length} 组 / ${groups.reduce((s, g) => s + g.tag_ids.length, 0)} 个 tag_id`,
draft.hasSubset ? " · 链接已圈定组子集" : ""
]
}),
draft.description && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-2px block text-12px",
children: draft.description
})
]
}),
draft.missingGroupCount > 0 && /* @__PURE__ */ jsx(Alert, {
type: "warning",
showIcon: true,
className: "mt-8px",
title: `链接指定的 ${draft.missingGroupCount} 个组不在分享包内, 已略过`
}),
groups.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
className: "mt-8px flex items-center gap-x-14px text-12px",
children: [
/* @__PURE__ */ jsx(Checkbox, {
checked: checkedIds.size === groups.length,
indeterminate: checkedIds.size > 0 && checkedIds.size < groups.length,
onChange: (e) => setCheckedIds(e.target.checked ? new Set(groups.map((g) => g.id)) : /* @__PURE__ */ new Set()),
children: /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "全选"
})
}),
/* @__PURE__ */ jsx(Checkbox, {
checked: scopeL1Ids.every((id) => !disabledL1Ids.has(id)),
onChange: (e) => setDisabledL1Ids(e.target.checked ? /* @__PURE__ */ new Set() : new Set(scopeL1Ids)),
children: /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "全部启用"
})
}),
listedConflicts.length > 0 && /* @__PURE__ */ jsxs("span", {
style: { color: token.colorWarning },
children: [
listedConflicts.length,
" 组冲突",
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "ml-6px h-auto px-0 text-12px",
onClick: () => setChoices((prev) => ({
...prev,
...Object.fromEntries(listedConflicts.map((c) => [c.group.id, "merge"]))
})),
children: "全并入"
}),
" · ",
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "h-auto px-0 text-12px",
onClick: () => setChoices((prev) => ({
...prev,
...Object.fromEntries(listedConflicts.map((c) => [c.group.id, "keep"]))
})),
children: "全保留"
})
]
}),
/* @__PURE__ */ jsx(button_default, {
type: "link",
size: "small",
className: "ml-auto h-auto px-0 text-12px",
onClick: () => setExpandedIds((prev) => prev.size === groups.length ? /* @__PURE__ */ new Set() : new Set(groups.map((g) => g.id))),
children: expandedIds.size === groups.length ? "全部收起" : "全部展开"
})
]
}), /* @__PURE__ */ jsx("div", {
className: "mt-4px max-h-280px overflow-y-auto",
children: sections.map((sec) => {
const secL1s = [...new Set(sec.rows.map((g) => g.l1Id).filter((x) => !!x))];
const secAll = secL1s.every((id) => !disabledL1Ids.has(id));
return /* @__PURE__ */ jsxs("div", {
className: "mb-6px b-1px b-gate-border rounded-6px b-solid",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px b-0 b-b-1px b-gate-border b-solid bg-gate-bg-lv2 px-10px py-4px",
children: [
/* @__PURE__ */ jsx("span", {
className: "text-13px font-600",
children: sec.themeId ? themeNameOf.get(sec.themeId) ?? "#未知主题" : "未归属"
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [sec.rows.length, " 组"]
}),
/* @__PURE__ */ jsx(Switch, {
className: "ml-auto",
size: "small",
title: "本主题一级组启用",
checked: secAll,
onChange: (v) => setL1Disabled(secL1s, !v)
})
]
}), /* @__PURE__ */ jsx("div", {
className: "px-10px py-2px",
children: sec.rows.map((g) => {
const on = checkedIds.has(g.id);
const conf = conflictById.get(g.id);
return /* @__PURE__ */ jsxs("div", {
className: `py-3px ${on ? "" : "opacity-45"}`,
children: [/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "text",
className: "h-auto w-16px shrink-0 px-0 text-11px opacity-70",
title: "展开包内声明(条件树 / 覆盖 / 负向)",
onClick: () => setExpandedIds((prev) => {
const next = new Set(prev);
if (next.has(g.id)) next.delete(g.id);
else next.add(g.id);
return next;
}),
children: expandedIds.has(g.id) ? "▾" : "▸"
}),
/* @__PURE__ */ jsx(Checkbox, {
checked: on,
onChange: (e) => toggle(g.id, e.target.checked)
}),
/* @__PURE__ */ jsxs("span", {
className: "min-w-0 flex-1",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
ellipsis: { tooltip: g.name },
className: "text-13px",
children: g.name
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "ml-6px text-12px",
children: [
g.tag_ids.length,
" 个 id",
g.negative_tag_ids?.length ? ` · ${g.negative_tag_ids.length} 负向` : "",
g.l1Id && l1NameOf.get(g.l1Id) && l1NameOf.get(g.l1Id) !== g.name ? ` · ${l1NameOf.get(g.l1Id)}` : ""
]
}),
g.combination && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
ellipsis: { tooltip: nameTagRecCombination(g.combination) },
className: "block text-12px",
children: ["条件: ", nameTagRecCombination(g.combination)]
})
]
}),
conf && /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-108px shrink-0",
disabled: !on,
value: choices[g.id] ?? "keep",
onChange: (v) => setChoices((prev) => ({
...prev,
[g.id]: v
})),
options: [
{
value: "merge",
label: "并入宿主"
},
{
value: "keep",
label: "保留两份"
},
{
value: "skip",
label: "跳过不入"
}
]
}),
g.l1Id && /* @__PURE__ */ jsx(Switch, {
size: "small",
disabled: !on,
title: "启用",
checked: !disabledL1Ids.has(g.l1Id),
onChange: (v) => setL1Disabled([g.l1Id], !v)
})
]
}), expandedIds.has(g.id) && /* @__PURE__ */ jsx(TagRecShareGroupContents, { group: g })]
}, g.id);
})
})]
}, sec.themeId ?? "未归属");
})
})] }) : /* @__PURE__ */ jsx(Alert, {
type: "info",
showIcon: true,
className: "mt-8px",
title: "该分享没有可导入的标签组"
}),
draft.plan.blacklistToAdd.length > 0 && /* @__PURE__ */ jsxs(Checkbox, {
className: "mt-10px",
checked: includeBlacklist,
onChange: (e) => setIncludeBlacklist(e.target.checked),
children: [
"并入标签黑名单 (",
draft.plan.blacklistToAdd.length,
" 项新增)"
]
}),
draft.filterConfig && /* @__PURE__ */ jsx(Checkbox, {
className: "mt-10px",
checked: applyFilter,
onChange: (e) => setApplyFilter(e.target.checked),
children: "带入分享者的过滤配置 (覆盖当前整套: 维度阈值/屏蔽词/UP主名单/算分/预设)"
}),
/* @__PURE__ */ jsxs("div", {
className: "mt-10px flex items-center gap-x-10px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-13px",
children: "导入完成后"
}), /* @__PURE__ */ jsxs(Radio.Group, {
value: landing,
onChange: (e) => setLanding(e.target.value),
children: [/* @__PURE__ */ jsx(Radio, {
value: "feed",
children: "去「主题推荐」看推荐"
}), /* @__PURE__ */ jsx(Radio, {
value: "panel",
children: "去管理面板继续编辑"
})]
})]
}),
landing === "feed" && disabledCount > 0 && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "mt-2px block text-12px",
children: [disabledCount, " 个一级组未启用 —— 不会出现在推荐里; 之后可在主题下拉里临时打开或到「标签收藏」启用"]
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "mt-8px block text-12px",
children: [
"勾选 ",
checkedIds.size,
"/",
groups.length,
" 组 · 将新增 ",
applied.themesToAdd.length,
" 主题 /",
" ",
applied.l1ToAdd.length,
" 一级组 / ",
applied.groupsToAdd.length,
" 组",
applied.merges.length ? ` / ${applied.merges.length} 组并入既有` : "",
applied.skippedCount ? ` / ${applied.skippedCount} 组跳过` : "",
draft.plan.synthesizedThemes ? ` (其中 ${draft.plan.synthesizedThemes} 主题为悬挂引用合成)` : "",
draft.plan.invalidGroups.length ? `; ${draft.plan.invalidGroups.length} 组无效不入` : "",
" · ",
"组默认启用 · 一级组默认全启用 (行尾开关可逐个取消)",
draft.filterConfig ? applyFilter ? " · 过滤配置将覆盖当前" : " · 过滤配置不带入" : " · 过滤配置不覆盖"
]
})
]
})
});
}})}}));
System.register("./versioned-cache-6Gx4pmyX-Dad7R-K0.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js','./discovery-policy-BBjOB0CZ-BWQIVxg7.js','axios'],(function(exports,module){'use strict';var waitForAbortable,tagRecBackoff,appWarn,__toESM,require_react,retryAfterDelay,normalizeTagTopItem,getCachedTagRecPartition,fetchVideoPartition,getCachedTagRecVideoEvidence,getPersistedVideoTags,hasCompleteTagIds,fetchVideoTags,TAG_REC_ALL_POOL_KEYS,tagRecCombinationSeeds,tagRecCombinationRequiresUp,tagRecConditionAtoms,mergeTagRecCandidate,cloneDeep,projectTagRecActiveClaims,tagRecSourceClaims,tagRecGroupKey,EApiType,cooccurVocab,countCooccurDegree,computeScore,tagRecPoolScopeKey,compareFallbackItems,tagRecChainKeysOf,tagRecPoolsForChain,newDigStream,digResumeFromSediment,deriveGroupCliques,tagRecSearchExtraOf,tagRecDeliverable,fetchVideoTagNames,selectCooccurSeeds,fetchArchiveRelated,tagRecRecallScheduler,normalizeAllV2Item,runTagRecFilter,runTagRecScoreStage,compareTagRecGroupItems,compareTagRecItems,mergeTagRecPartitionFields,evalTagRecChain,evalTagRecItem,SessionFetchCache,fetchTagRecRoundSources,finishTagRecRound,applyTagRecStages,theme,useOnRefresh,useSnapshot,usePopoverBorderColor,settings,proxy,subscribe,jsxs,jsx,button_default,resolveTagRecEffective,Popconfirm,Tag,Popover,usePopupContainer,Space,Fragment,AntdTooltip,fetchTagTop,emptyTagRecRoundResult,parseAllV2Duration,Select,toast,showModalSettings,getVersion,fetchTagRecVideoEvidence,sediment,sedimentNoteProbe,sedimentClearDig,mergeTagRecVideoEvidence,tagRecFavSession,tagRecCustomSession,favorites,favoritesL1Store,createTagRecSessionFilterBase,materializeTagRecSessionFilter,diffTagRecSessionFilter,favoritesStore,clearTagRecTempWrites,keepTagRecTempEntryToGroup,keepTagRecTempEntryToGlobal,canKeepTempEntryToGlobal,removeTagRecTempWriteAt,Typography,tagRecSharedSession,resolveTagRecBannerState,formatTagRecDroppedText,viewScope,tagRecHoldReasonText,filterTagRecSeenItems,resetTagRecFavFilter,resetTagRecCustomFilter,TAG_REC_POOL_KEYS,TAG_REC_POOL_LABELS,favoritesL1,seen,tagRecDiscoveryPolicy,runPhase2Round,Alert,TagRecPresetRow,tagRecPoolSlotSummary,DimsSection,AdvancedSection,pickTagRecScopePatch,TagRecScopeConfigEditor,TabPaneKey,modalSettingsStore,clearTagRecSeenRecords,isAxiosError;return{setters:[function(module){waitForAbortable=module.w;tagRecBackoff=module.t;appWarn=module.a;__toESM=module._;require_react=module.r;retryAfterDelay=module.v;},function(module){normalizeTagTopItem=module.dR;getCachedTagRecPartition=module.dS;fetchVideoPartition=module.dT;getCachedTagRecVideoEvidence=module.cG;getPersistedVideoTags=module.dU;hasCompleteTagIds=module.dV;fetchVideoTags=module.dW;TAG_REC_ALL_POOL_KEYS=module.cq;tagRecCombinationSeeds=module.dX;tagRecCombinationRequiresUp=module.dY;tagRecConditionAtoms=module.b_;mergeTagRecCandidate=module.dZ;cloneDeep=module.aQ;projectTagRecActiveClaims=module.cV;tagRecSourceClaims=module.cr;tagRecGroupKey=module.cn;EApiType=module.ck;cooccurVocab=module.d_;countCooccurDegree=module.d$;computeScore=module.e0;tagRecPoolScopeKey=module.cZ;compareFallbackItems=module.e1;tagRecChainKeysOf=module.cW;tagRecPoolsForChain=module.cX;newDigStream=module.e2;digResumeFromSediment=module.e3;deriveGroupCliques=module.cg;tagRecSearchExtraOf=module.ch;tagRecDeliverable=module.cJ;fetchVideoTagNames=module.e4;selectCooccurSeeds=module.e5;fetchArchiveRelated=module.e6;tagRecRecallScheduler=module.ci;normalizeAllV2Item=module.d1;runTagRecFilter=module.cj;runTagRecScoreStage=module.cs;compareTagRecGroupItems=module.e7;compareTagRecItems=module.e8;mergeTagRecPartitionFields=module.e9;evalTagRecChain=module.cY;evalTagRecItem=module.cP;SessionFetchCache=module.ea;fetchTagRecRoundSources=module.eb;finishTagRecRound=module.ec;applyTagRecStages=module.ct;theme=module.bo;useOnRefresh=module.cB;useSnapshot=module.u;usePopoverBorderColor=module.ed;settings=module.t;proxy=module.aP;subscribe=module.c_;jsxs=module.j;jsx=module.a;button_default=module.n;resolveTagRecEffective=module.bq;Popconfirm=module.a2;Tag=module.T;Popover=module.az;usePopupContainer=module.cC;Space=module.w;Fragment=module.F;AntdTooltip=module.A;fetchTagTop=module.ee;emptyTagRecRoundResult=module.ef;parseAllV2Duration=module.eg;Select=module.S;toast=module.ak;showModalSettings=module.cE;getVersion=module.eh;},function(module){fetchTagRecVideoEvidence=module.aY;sediment=module.y;sedimentNoteProbe=module.aZ;sedimentClearDig=module.a_;mergeTagRecVideoEvidence=module.ae;tagRecFavSession=module.O;tagRecCustomSession=module.ad;favorites=module.d;favoritesL1Store=module.o;createTagRecSessionFilterBase=module.a$;materializeTagRecSessionFilter=module.$;diffTagRecSessionFilter=module.b0;favoritesStore=module.n;clearTagRecTempWrites=module.b1;keepTagRecTempEntryToGroup=module.b2;keepTagRecTempEntryToGlobal=module.b3;canKeepTempEntryToGlobal=module.b4;removeTagRecTempWriteAt=module.b5;Typography=module.e;tagRecSharedSession=module.t;resolveTagRecBannerState=module.b6;formatTagRecDroppedText=module.b7;viewScope=module.m;tagRecHoldReasonText=module.ak;filterTagRecSeenItems=module.b8;resetTagRecFavFilter=module.a0;resetTagRecCustomFilter=module.b9;TAG_REC_POOL_KEYS=module.L;TAG_REC_POOL_LABELS=module.M;favoritesL1=module.f;seen=module.x;},function(module){tagRecDiscoveryPolicy=module.e;runPhase2Round=module.z;Alert=module.A;TagRecPresetRow=module.B;tagRecPoolSlotSummary=module.y;DimsSection=module.D;AdvancedSection=module.h;pickTagRecScopePatch=module.p;TagRecScopeConfigEditor=module.g;TabPaneKey=module.T;modalSettingsStore=module.m;clearTagRecSeenRecords=module.c;},function(module){isAxiosError=module.isAxiosError;}],execute:(function(){exports({A:versionedSlot,B:tagRecVersionKey,C:newTagRecLadderState,D:createTagRecBvidIndex,G:tagRecHeapVersion,I:tagRecConsumers,J:spliceTagRecAtomFairSlice,K:tagRecPartitionHeldNext,c:tagRecNegativePools,e:createTagRecSeenStage,f:tagRecSupplyView,g:trimTagRecStock,h:resetTagRecLadderState,i:tagRecPartitionWorkQueue,k:previewTagRecFairSlice,l:hasTagRecPartitionRules,m:checkTagRecPartitionSlice,n:dropTagRecStrictTagListHits,o:rankTagRecFairSlice,p:planTagRecRecall,q:ensureTagRecNegativePools,r:refilterTagRecStock,s:runTagRecRecallRound,t:tagRecRecheckOptions,u:upSearchMids,v:tagRecSourceSignature,w:tagRecReopenedTopTids,x:tagRecVersionedArray,y:FilterPopover,z:TagRecBanner});/**
* [tag-rec] 横幅槽位 (spec §5.4, 票 09) —— 统一提示区, 两 tab 复用
* tab1 传 tagRecFavSession / tab2 传 tagRecCustomSession (结构见 TagRecBannerSource)
* - 三态互斥按严重度: 硬见底(error) > 深挖待决(info) > 软见底 > 池浅降级
* - 仅「有可操作位」的态上横幅 (硬见底三动作 / 深挖待决「继续深挖」);
* 软见底/池浅降级无动作 => 不整行展示, 回落常显滤除计数 + 尾注 (「· 池将见底」「· 池浅已放宽」)
* - 空态: 槽位常显灰小字滤除计数; 有横幅时计数并进横幅文案
* - basis-full => tab-bar 左区 flex-wrap 内换行自成一行, 随 sticky tab-bar 常显
* - 硬见底三动作: [重抽一轮]=useOnRefresh()('refresh') 重建 service 全量重拉 /
* [临时放开已看]=session.bypassSeen (各 tab 自有 flag: tab1 默认开 / tab2 默认关, §3.4) /
* [管理收藏表]=深链「标签收藏」pane
* - 硬见底判据 = session.hardBottom 镜像: service 单轮产出 0 => exhaustRound 置位。
* 不读 service.hasMoreExceptQueue —— tab2「未输入/解析失败」也翻 false (非池见底) 且非响应式
*/
function TagRecBanner({ session, onDigMore, onPartitionRetry, onRedig, hideIdleDropText, hidePartitionPending }) {
const snap = useSnapshot(session);
const bypassSeenOn = snap.bypassSeen;
const { riskControl, shareGuide, shareLike } = useSnapshot(tagRecSharedSession);
const { list: l1List } = useSnapshot(favoritesL1Store);
const { token } = theme.useToken();
const onRefresh = useOnRefresh();
const { ref, getPopupContainer } = usePopupContainer();
const state = resolveTagRecBannerState(snap);
const dropText = formatTagRecDroppedText(snap.filterStats);
const riskControlAlert = riskControl && /* @__PURE__ */ jsx(Alert, {
type: "warning",
showIcon: true,
className: "py-4px",
title: "命中风控 · 请求已退避降速并自动重试, 恢复后自动继续"
});
const redraw = () => onRefresh("refresh");
const bypassSeen = () => {
session.bypassSeen = true;
onRefresh("refresh");
};
const clearSeen = () => {
clearTagRecSeenRecords();
onRefresh("refresh");
};
const openFavoritesPane = () => {
modalSettingsStore.tab = TabPaneKey.TagRecFavorites;
showModalSettings();
};
const actionable = state === "no-drawable" || state === "hard-bottom" || state === "dig-pending" && !!onDigMore || state === "partition-pending";
const dismissGuide = () => void module.import('./share-import-BmCYK7Oh-BpB7LIls.js').then((m) => m.dismissShareImportGuide()).catch(() => void 0);
let shareGuideAlert = null;
if (shareGuide && shareGuide.l1Ids.length) {
const pkg = shareGuide;
const enabledCount = pkg.l1Ids.filter((id) => l1List.some((l) => l.id === id && l.enabled)).length;
const total = pkg.l1Ids.length;
const allEnabled = enabledCount >= total;
shareGuideAlert = /* @__PURE__ */ jsx(Alert, {
type: "info",
showIcon: true,
className: "py-4px",
title: allEnabled ? `已导入 ${total} 组 · 已全部启用` : `已导入 ${total} 组 · ${enabledCount} 个已启用,${total - enabledCount} 个未启用 (可在主题下拉里临时打开)`,
action: /* @__PURE__ */ jsxs(Space, {
size: 4,
children: [
allEnabled && pkg.themeIds.length > 0 && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: () => {
viewScope.onlyThemes(pkg.themeIds);
dismissGuide();
onRefresh("refresh");
},
children: "只看这个包的主题"
}),
!allEnabled && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: () => {
openFavoritesPane();
dismissGuide();
},
children: "去开启"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: dismissGuide,
children: "知道了"
})
]
})
});
}
let shareLikeAlert = null;
if (shareLike) {
const actLike = () => void module.import('./share-like-wW2Ll3Z6-DQR4OCtA.js').then((m) => m.actShareLike()).catch(() => void 0);
const dismissLike = () => void module.import('./share-like-wW2Ll3Z6-DQR4OCtA.js').then((m) => m.dismissShareLike()).catch(() => void 0);
shareLikeAlert = /* @__PURE__ */ jsx(Alert, {
type: "info",
showIcon: true,
className: "py-4px",
title: "这个分享帮到你了吗?",
action: /* @__PURE__ */ jsxs(Space, {
size: 4,
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
loading: shareLike.busy,
onClick: actLike,
children: "👍 赞一个"
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: shareLike.busy,
onClick: dismissLike,
children: "不了"
})]
})
});
}
const enableDisabledL1s = () => {
for (const id of snap.noDrawableHint?.enableableL1Ids ?? []) favoritesL1.updateL1(id, { enabled: true });
onRefresh("refresh");
};
let stateAlert = null;
if (state === "no-drawable") {
const hint = snap.noDrawableHint;
stateAlert = /* @__PURE__ */ jsx(Alert, {
type: "warning",
showIcon: true,
className: "py-4px",
title: /* @__PURE__ */ jsxs(Fragment, { children: [hint.reason, /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "ml-8px text-12px",
children: dropText
})] }),
action: /* @__PURE__ */ jsxs(Space, {
size: 4,
children: [!!hint.enableableL1Ids.length && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: enableDisabledL1s,
children: "启用一级组并重抽"
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: openFavoritesPane,
children: "管理收藏表"
})]
})
});
} else if (state === "partition-pending" && !hidePartitionPending) {
const holdReason = tagRecHoldReasonText(snap, riskControl);
stateAlert = /* @__PURE__ */ jsx(Alert, {
type: "info",
showIcon: true,
className: "py-4px",
title: snap.partitionPending ? `本批 ${snap.partitionPending} 条内容待确认分区,已保留 · ${holdReason ?? "可以继续核验或调整筛选"}` : `本批分区筛选已暂停${holdReason ? ` · ${holdReason}` : " · 可以继续筛选剩余候选"}`,
action: /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: onPartitionRetry,
children: "继续筛选"
})
});
} else if (actionable && state !== "partition-pending") {
const drawnSuffix = state === "hard-bottom" && snap.drawnGroupNames?.length ? ` · 抽中组: ${snap.drawnGroupNames.join("、")}` : "";
const title = state === "hard-bottom" ? `本轮没有新结果${drawnSuffix}` : "池底保底已展开 · 自动深挖已达上限";
stateAlert = /* @__PURE__ */ jsx(Alert, {
type: state === "hard-bottom" ? "warning" : "info",
showIcon: true,
className: "py-4px",
title: /* @__PURE__ */ jsxs(Fragment, { children: [
title,
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "ml-8px text-12px",
children: dropText
}),
state === "hard-bottom" && /* @__PURE__ */ jsx("div", {
className: "text-12px font-normal",
children: bypassSeenOn ? "当前已显示已看内容;仍无结果时,可点「重新发掘」复位本词的深挖游标,或放宽过滤条件。" : "浏览过的卡片会被已看去重跳过,可以显示已看、清空已看,或「重新发掘」从头挖。"
})
] }),
action: state === "hard-bottom" ? /* @__PURE__ */ jsxs(Space, {
size: 4,
children: [
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
onClick: redraw,
children: "重抽一轮"
}),
onRedig && /* @__PURE__ */ jsx(AntdTooltip, {
title: "复位本查询全部深挖流的页游标(含已挖穿/冷却中的词与持久沉淀记录)并重新挖一轮;请求有界,遵守退避与限流。",
getPopupContainer,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
loading: snap.digRunning,
onClick: onRedig,
children: "重新发掘"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "重新获取包含已看的视频,并从头发掘;本次浏览有效,重新加载页面后恢复去重,已看记录保留。",
getPopupContainer,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: bypassSeenOn,
onClick: bypassSeen,
children: "显示已看"
})
}),
/* @__PURE__ */ jsx(AntdTooltip, {
title: "删除已看记忆:下次刷新重新收集全部内容。与「显示已看」不同——那个只改视图不删记忆。",
getPopupContainer,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: clearSeen,
children: "清空已看"
})
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: openFavoritesPane,
children: "管理收藏表"
})
]
}) : /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "primary",
loading: snap.digRunning,
onClick: onDigMore,
children: "继续深挖"
})
});
}
const idleText = !actionable && !hideIdleDropText ? /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: [
dropText,
state === "degraded" && /* @__PURE__ */ jsx("span", {
style: { color: token.colorInfo },
children: " · 池浅已放宽"
}),
state === "soft-bottom" && /* @__PURE__ */ jsx(Typography.Text, {
type: "warning",
className: "text-12px",
children: " · 池将见底"
})
]
}) : null;
if (!riskControlAlert && !stateAlert && !idleText && !shareGuideAlert && !shareLikeAlert) return null;
return /* @__PURE__ */ jsxs("div", {
ref,
className: "flex basis-full flex-col gap-y-4px",
children: [
riskControlAlert,
stateAlert,
idleText,
shareGuideAlert,
shareLikeAlert
]
});
}
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
/**
* [tag-rec] R8 会话过滤编辑器 (tab2「过滤」弹层主体) —— 对齐标签收藏表组配置弹层
* - 作用范围: [整档 (全池共用) | 格·tag/top | 格·搜索池 | 格·搜索相关 | 格·推荐相关]
* - 整档三 tab: 名单 (屏蔽/仅看家族) / 阈值 (池筛选 + 数值维) / 算分 (权重锚点小池严格)
* - 格 = 会话覆盖里按池的参数 (dims/score/spt; 名单不进格) —— 摘要 + ⚙ 深编 (复用 draft-diff 单元格编辑器)
* - 直接编辑传入的 core proxy (会话覆盖或「跟随镜像」; 物化/脏跟踪由 FilterPopover 负责)
*/
function SessionFilterEditor({ core, getPopupContainer, resolveScope, declaredPools, basePoolsEnabled, onPoolInherit }) {
const snap = useSnapshot(core);
const [scope, setScope] = (0, import_react.useState)("core");
const [tab, setTab] = (0, import_react.useState)("list");
const [cellEditing, setCellEditing] = (0, import_react.useState)();
const isCell = scope !== "core";
const tabs = isCell ? [["dims", "阈值"], ["score", "算分"]] : [
["list", "名单"],
["dims", "阈值"],
["score", "算分"]
];
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
/* @__PURE__ */ jsx(TagRecPresetRow, {
core,
scope: "session",
getPopupContainer
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "text-12px font-bold",
children: "作用范围"
}), /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-190px",
value: scope,
getPopupContainer,
onChange: (v) => {
setScope(v);
setTab(v === "core" ? "list" : "dims");
},
options: [{
value: "core",
label: "整档 (全池共用)"
}, ...TAG_REC_POOL_KEYS.map((p) => ({
value: p,
label: `格 · ${TAG_REC_POOL_LABELS[p]}`
}))]
})]
}),
isCell ? /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-6px",
children: [
/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: ["本格覆盖: ", core.pools?.[scope] ? tagRecPoolSlotSummary(core.pools[scope]) : "全继承 (无槽)"]
}),
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setCellEditing(scope),
children: "⚙ 深编 (阈值/算分/小池阈值)"
}) }),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "格 = 会话覆盖里按池的参数 (对齐标签收藏表组配置的「格 · 池」); 名单不进格"
})
]
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", {
className: "flex gap-x-4px b-b-1px b-gate-border b-b-solid pb-4px",
children: tabs.map(([k, label]) => /* @__PURE__ */ jsx(button_default, {
size: "small",
type: tab === k ? "primary" : "default",
onClick: () => setTab(k),
children: label
}, k))
}),
tab === "list" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(DimsSection, {
core,
snap,
disabled: false,
only: "list",
getPopupContainer
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "标签解析候选独立于草稿组、选定组和扩选暂存。此处严格校验仅作用于辅助名单,复用视频标签缓存; 查询失败、超时或退避时放行并计入未校验。组合必需条件的内容证据另行按「未知暂存」处理,不受本开关影响。"
})] }),
tab === "dims" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-8px",
children: TAG_REC_POOL_KEYS.map((p) => /* @__PURE__ */ jsxs("label", { children: [
TAG_REC_POOL_LABELS[p],
" ",
/* @__PURE__ */ jsx(Select, {
size: "small",
value: declaredPools?.[p] === void 0 ? "inherit" : declaredPools[p] ? "on" : "off",
options: [
{
value: "inherit",
label: `继承(${basePoolsEnabled?.[p] === false ? "不使用" : "使用"})`
},
{
value: "on",
label: "使用"
},
{
value: "off",
label: "不使用"
}
],
onChange: (value) => {
core.poolsEnabled ??= {};
if (value === "inherit") {
onPoolInherit?.(p);
core.poolsEnabled[p] = basePoolsEnabled?.[p] ?? true;
} else core.poolsEnabled[p] = value === "on";
}
})
] }, p))
}), /* @__PURE__ */ jsx(DimsSection, {
core,
snap,
disabled: false,
only: "numeric",
getPopupContainer
})] }),
tab === "score" && /* @__PURE__ */ jsx(AdvancedSection, {
core,
snap,
disabled: false
})
] }),
cellEditing && /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `${TAG_REC_POOL_LABELS[cellEditing]} · 格`,
core,
slots: [{
key: `cell:${cellEditing}`,
label: TAG_REC_POOL_LABELS[cellEditing],
cell: true
}],
resolve: () => resolveScope?.(cellEditing) ?? resolveTagRecEffective(core, cellEditing).cfg,
resolveBase: () => resolveScope?.(cellEditing, true) ?? resolveTagRecEffective({
...core,
pools: {
...core.pools,
[cellEditing]: void 0
}
}, cellEditing).cfg,
storedOverrideOf: () => pickTagRecScopePatch(core.pools?.[cellEditing]),
onSave: (entries) => {
const e = entries.find((x) => x.key === `cell:${cellEditing}`);
if (!e) return;
if (e.patch) (core.pools ??= {})[cellEditing] = e.patch;
else if (core.pools) {
delete core.pools[cellEditing];
if (!Object.keys(core.pools).length) delete core.pools;
}
toast("已保存格覆盖");
},
onClose: () => setCellEditing(void 0)
})
]
});
}
function FilterPopover({ getPopupContainer, scope = "custom" }) {
const { token } = theme.useToken();
const onRefresh = useOnRefresh();
const session = scope === "fav" ? tagRecFavSession : tagRecCustomSession;
const sessionSnap = useSnapshot(session);
const { filterOverride, tempWriteLog } = sessionSnap;
const scopeRevision = "queryRevision" in sessionSnap ? sessionSnap.queryRevision : sessionSnap.filterScopeRevision;
const getRevision = () => scope === "fav" ? tagRecFavSession.filterScopeRevision : tagRecCustomSession.queryRevision;
const [open, setOpen] = (0, import_react.useState)(false);
const dirtyRef = (0, import_react.useRef)(false);
const [mirrorEpoch, setMirrorEpoch] = (0, import_react.useState)(0);
const borderColor = usePopoverBorderColor();
const contentRef = (0, import_react.useRef)(null);
const scrollRef = (0, import_react.useRef)(null);
const getInnerPopupContainer = (0, import_react.useCallback)(() => contentRef.current ?? document.body, []);
const writingRef = (0, import_react.useRef)(false);
const patchJsonRef = (0, import_react.useRef)(JSON.stringify(session.filterOverride));
const editor = (0, import_react.useMemo)(() => {
const group = scope === "custom" ? tagRecCustomSession.workingGroup ?? favorites.list?.find((g) => g.id === tagRecCustomSession.selectedGroupIds?.[0]) : void 0;
const l1 = favoritesL1Store.list?.find((l) => l.id === group?.l1Id);
const base = createTagRecSessionFilterBase(settings.tagRec.filter, group, l1);
return {
base,
core: proxy(materializeTagRecSessionFilter(base, session.filterOverride)),
group,
l1
};
}, [
scope,
mirrorEpoch,
scopeRevision
]);
const core = editor.core;
(0, import_react.useEffect)(() => subscribe(core, () => {
if (getRevision() !== scopeRevision) return;
writingRef.current = true;
try {
session.filterOverride = diffTagRecSessionFilter(core, editor.base, session.filterOverride);
patchJsonRef.current = JSON.stringify(session.filterOverride);
dirtyRef.current = true;
} finally {
writingRef.current = false;
}
}, true), [
core,
editor,
scopeRevision
]);
(0, import_react.useEffect)(() => subscribe(session, () => {
const json = JSON.stringify(session.filterOverride);
if (json === patchJsonRef.current) return;
patchJsonRef.current = json;
if (!writingRef.current) setMirrorEpoch((e) => e + 1);
}, true), [session]);
(0, import_react.useEffect)(() => {
const changed = () => setMirrorEpoch((e) => e + 1);
const stops = [
subscribe(settings.tagRec, changed),
subscribe(favoritesStore, changed),
subscribe(favoritesL1Store, changed)
];
return () => stops.forEach((stop) => stop());
}, []);
(0, import_react.useEffect)(() => {
dirtyRef.current = false;
setOpen(false);
}, [scopeRevision]);
const onOpenChange = (next) => {
setOpen(next);
if (next) scrollRef.current?.scrollTo({ top: 0 });
if (!next && dirtyRef.current) {
dirtyRef.current = false;
onRefresh("refresh");
}
};
const resetToGlobal = () => {
if (scope === "fav") resetTagRecFavFilter();
else resetTagRecCustomFilter();
setMirrorEpoch((e) => e + 1);
dirtyRef.current = false;
setOpen(false);
onRefresh("refresh");
};
const content = /* @__PURE__ */ jsxs("div", {
ref: contentRef,
className: "max-h-72vh max-w-80vw w-460px flex flex-col",
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-8px flex shrink-0 flex-wrap items-center justify-between gap-6px",
children: [/* @__PURE__ */ jsx("span", {
className: "font-bold",
children: scope === "custom" && tagRecCustomSession.workingGroup ? "组合过滤" : "临时过滤"
}), /* @__PURE__ */ jsxs("div", {
className: "flex gap-x-6px",
children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !filterOverride && !tempWriteLog.length,
onClick: resetToGlobal,
children: scope === "custom" && tagRecCustomSession.workingGroup ? "全部恢复继承" : "清除临时过滤"
}), /* @__PURE__ */ jsx(button_default, {
size: "small",
"aria-label": "关闭临时过滤面板",
onClick: () => onOpenChange(false),
children: "关闭"
})]
})]
}),
/* @__PURE__ */ jsx(Alert, {
type: filterOverride ? "warning" : "info",
className: "mb-8px shrink-0",
title: scope === "fav" ? filterOverride ? "本查看范围临时过滤 · 切换范围自动清除" : "跟随「标签过滤」及各组配置 · 改动仅对本查看范围生效" : tagRecCustomSession.workingGroup ? "明确改过的条件随组合保存;未改项继承默认和所属一级组配置" : filterOverride ? "本锚点临时过滤 · 换锚点池自动清除" : "跟随「标签过滤」及本组配置 · 改动仅对本锚点池生效",
description: scope === "custom" && tagRecCustomSession.workingGroup ? "标签、简介与分区等条件缺少依据时自动补查,未核实先暂存;切换或改条件后临时查看权限恢复。" : "只覆盖本次修改的字段,其余继续继承;关闭临时仅看可解除本次限制,组内屏蔽继续生效。"
}),
/* @__PURE__ */ jsxs("div", {
ref: scrollRef,
className: "min-h-0 overflow-y-auto",
children: [/* @__PURE__ */ jsx(SessionFilterEditor, {
core,
declaredPools: filterOverride?.poolsEnabled,
basePoolsEnabled: editor.base.poolsEnabled,
onPoolInherit: (pool) => {
if (session.filterOverride?.poolsEnabled) delete session.filterOverride.poolsEnabled[pool];
},
resolveScope: (pool, withoutCell) => {
const patch = session.filterOverride;
return resolveTagRecEffective(settings.tagRec.filter, pool, editor.group, editor.l1, withoutCell && patch ? {
...patch,
pools: {
...patch.pools,
[pool]: void 0
}
} : patch).cfg;
},
getPopupContainer: getInnerPopupContainer
}, `${scopeRevision}-${mirrorEpoch}`), tempWriteLog.length > 0 && /* @__PURE__ */ jsxs("div", {
className: "mt-10px b-t-1px b-gate-border b-t-solid pt-8px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex items-center justify-between",
children: [/* @__PURE__ */ jsxs("span", {
className: "text-12px font-bold",
style: { color: token.colorWarning },
children: [
"临时名单 (来自右键 · 本会话) ",
tempWriteLog.length,
" 条"
]
}), /* @__PURE__ */ jsx(Popconfirm, {
title: "清空临时名单? (只清右键临时档, 你手调的编辑不动)",
okText: "清空",
cancelText: "取消",
getPopupContainer: getInnerPopupContainer,
onConfirm: () => {
clearTagRecTempWrites(scope);
dirtyRef.current = true;
},
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
children: "清空临时"
})
})]
}),
/* @__PURE__ */ jsx("div", {
className: "flex flex-col gap-y-4px",
children: tempWriteLog.map((e, i) => /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px",
children: [
/* @__PURE__ */ jsx(Tag, {
color: "orange",
className: "mr-0",
style: { borderStyle: "dashed" },
children: e.label
}),
/* @__PURE__ */ jsxs(button_default, {
size: "small",
disabled: !(e.owningGroupIds?.length ?? e.owningGroupNames.length),
onClick: () => {
keepTagRecTempEntryToGroup(i, scope);
dirtyRef.current = true;
},
children: ["固化到本组", e.owningGroupIds?.length ?? e.owningGroupNames.length ? ` (${e.owningGroupIds?.length ?? e.owningGroupNames.length})` : " (无认领组)"]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled: !canKeepTempEntryToGlobal(e.kind),
onClick: () => {
keepTagRecTempEntryToGlobal(i, scope).then(() => {
if (getRevision() === scopeRevision) dirtyRef.current = true;
});
},
children: "固化到「标签过滤」"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => {
removeTagRecTempWriteAt(i, scope);
dirtyRef.current = true;
},
children: "×"
})
]
}, `${e.kind}-${i}`))
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-4px block text-11px",
children: "固化到本组 = 写入卡片认领组 (写入时记账; 永不走草稿组); 目标组已删/值已存在 => 跳过计数"
})
]
})]
})
]
});
return /* @__PURE__ */ jsx(Popover, {
open,
onOpenChange,
afterOpenChange: (next) => {
if (next) scrollRef.current?.scrollTo({ top: 0 });
},
trigger: "click",
arrow: false,
placement: "bottomLeft",
getPopupContainer,
styles: { container: { border: `1px solid ${borderColor}` } },
content,
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
children: ["过滤", filterOverride && /* @__PURE__ */ jsx(Tag, {
color: "orange",
className: "mr-0",
children: "覆盖中"
})]
})
});
}
/** 堆顶 = 稀缺度最深者 (牺牲最深归属保冷池); 稀缺度平手按入桶序 (同逐条 pickTagRecCapacityVictim 的 ordered 语义) */
var scarcityHigher = (a, b) => a[0] > b[0] || a[0] === b[0] && a[1] < b[1];
function scarcityHeapPush(heap, entry) {
heap.push(entry);
let i = heap.length - 1;
while (i > 0) {
const p = i - 1 >> 1;
if (!scarcityHigher(heap[i], heap[p])) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
}
function scarcityHeapPop(heap) {
const top = heap[0];
const last = heap.pop();
if (heap.length) {
heap[0] = last;
let i = 0;
for (;;) {
const l = i * 2 + 1;
const r = l + 1;
let m = i;
if (l < heap.length && scarcityHigher(heap[l], heap[m])) m = l;
if (r < heap.length && scarcityHigher(heap[r], heap[m])) m = r;
if (m === i) break;
[heap[i], heap[m]] = [heap[m], heap[i]];
i = m;
}
}
return top;
}
/**
* [tag-rec] 写版本化数组: Proxy 只拦 set/deleteProperty (push/splice/unshift/length=0 全走这两个 trap),
* 读路径 (get/迭代/原生方法) 不注册 trap 近似原生速度; 每次写 bump() —— 等长换血也无处可藏。
*/
function tagRecVersionedArray(bump, init = []) {
return new Proxy([...init], {
set: (target, prop, value) => (bump(), Reflect.set(target, prop, value)),
deleteProperty: (target, prop) => (bump(), Reflect.deleteProperty(target, prop))
});
}
/**
* [tag-rec] 数组→bvid 懒索引 (mergeIncoming 的 queued/heap 查找 O(items×len) → O(items)):
* - map() 按 (数组身份, length, 写版本) 缓存 —— 传 version() 时等长换血也检出; 否则长度/身份兜底;
* - 键域取 bvid 真值项 —— ItemsSeparator 这类无 bvid 元素自动跳过。
*/
function createTagRecBvidIndex(source, version, keep = () => true) {
let cache;
return {
map() {
const arr = source();
const seq = version?.() ?? -1;
if (!cache || cache.arr !== arr || cache.len !== arr.length || cache.seq !== seq) {
const map = /* @__PURE__ */ new Map();
for (const it of arr) {
const bvid = it.bvid;
if (typeof bvid === "string" && bvid && keep(it)) map.set(bvid, it);
}
cache = {
arr,
len: arr.length,
seq,
map
};
}
return cache.map;
},
invalidate: () => cache = void 0
};
}
/** 当前语境内未交付的原料;阈值变化可以重评,存量按组与来源有界保留。 */
var TagRecDeferredCandidates = exports("b",class {
perScope;
total;
items = /* @__PURE__ */ new Map();
/** 复用设计 3: mergeIncoming 改过的排队条目待收编集 —— 不逐条 record (每条全池重建桶),
* 下次 record 批一并入库, 容量整理只在批次末尾跑一次 */
retouched = /* @__PURE__ */ new Map();
/** 批化评分: mergeQueued 逐条 rank 的批化 —— 按 BV 去重收集本轮事实变过的排队条目,
* 批末 (record/enqueueStock/flushMerged) 一次算分; 逐条算分在 200 重叠池下是 ~3k 次/轮 */
dirty = /* @__PURE__ */ new Map();
dirtyRank;
needsRefilter = false;
resolve;
constructor(perScope = 200, total = 2e3) {
this.perScope = perScope;
this.total = total;
}
clear() {
this.items.clear();
this.retouched.clear();
this.dirty.clear();
this.dirtyRank = void 0;
this.needsRefilter = false;
}
/** 待收编排队条目入库 (merge-or-add 同 record 逐条语义: 表内克隆同步新归因; 同对象免自并) */
absorbRetouched() {
if (!this.retouched.size) return;
for (const item of this.retouched.values()) {
const existing = this.items.get(item.bvid);
if (existing === item) continue;
if (existing) mergeTagRecCandidate(existing, item);
else this.items.set(item.bvid, cloneDeep(item));
}
this.retouched.clear();
}
snapshot() {
this.flushMerged();
this.absorbRetouched();
return {
items: cloneDeep(this.items),
needsRefilter: this.needsRefilter
};
}
restore(snap) {
this.items.clear();
this.retouched.clear();
this.dirty.clear();
this.dirtyRank = void 0;
for (const [k, v] of snap.items) this.items.set(k, cloneDeep(v));
this.needsRefilter = snap.needsRefilter;
}
get size() {
return this.items.size;
}
values() {
return [...this.items.values()];
}
record(items) {
this.flushMerged();
this.absorbRetouched();
for (const item of items) {
const existing = this.items.get(item.bvid);
if (existing) mergeTagRecCandidate(existing, item);
else this.items.set(item.bvid, cloneDeep(item));
}
if (!this.items.size) return;
const scopes = /* @__PURE__ */ new Map();
const dormant = /* @__PURE__ */ new Map();
const members = /* @__PURE__ */ new Map();
const dormantPools = /* @__PURE__ */ new Set();
for (const item of this.items.values()) {
if (this.resolve) projectTagRecActiveClaims(item, this.resolve);
const claims = item.activeSourceClaims?.length === 0 ? tagRecSourceClaims(item) : item.activeSourceClaims ?? tagRecSourceClaims(item);
const target = item.activeSourceClaims?.length === 0 ? dormant : scopes;
const keys = new Set(claims.flatMap((claim) => {
return (claim.input ? [tagRecGroupKey(void 0)] : claim.groupIds?.length ? claim.groupIds.map((id) => tagRecGroupKey({ id })) : (claim.groupNames.length ? claim.groupNames : [""]).map(tagRecGroupKey)).map((owner) => JSON.stringify([owner, claim.via]));
}));
for (const key of keys) {
let bucket = target.get(key);
if (!bucket) target.set(key, bucket = /* @__PURE__ */ new Set());
bucket.add(item.bvid);
let pools = members.get(item.bvid);
if (!pools) members.set(item.bvid, pools = /* @__PURE__ */ new Set());
pools.add(bucket);
}
}
for (const bucket of dormant.values()) dormantPools.add(bucket);
const remove = (bvid) => {
this.items.delete(bvid);
for (const pool of members.get(bvid) ?? []) pool.delete(bvid);
members.delete(bvid);
};
for (const bucket of dormant.values()) while (bucket.size > this.perScope) remove(bucket.values().next().value);
if (this.items.size > this.total) for (const item of this.items.values()) {
if (this.items.size <= this.total) break;
if (item.activeSourceClaims?.length === 0) remove(item.bvid);
}
const scarcity = (bvid) => {
let min = Infinity;
for (const pool of members.get(bvid) ?? []) if (!dormantPools.has(pool) && pool.size < min) min = pool.size;
return min;
};
const pickers = /* @__PURE__ */ new Map();
const pickerOf = (bucket) => {
let pick = pickers.get(bucket);
if (pick) return pick;
const heap = [];
let seq = 0;
for (const bvid of bucket) scarcityHeapPush(heap, [
scarcity(bvid),
seq++,
bvid
]);
pick = () => {
while (heap.length) {
const top = scarcityHeapPop(heap);
if (!bucket.has(top[2])) continue;
const cur = scarcity(top[2]);
if (cur < top[0]) {
scarcityHeapPush(heap, [
cur,
top[1],
top[2]
]);
continue;
}
return top[2];
}
};
pickers.set(bucket, pick);
return pick;
};
for (const bucket of scopes.values()) while (bucket.size > this.perScope) {
const victim = pickerOf(bucket)();
if (victim === void 0) break;
remove(victim);
}
while (this.items.size > this.total) {
let largest;
for (const pool of scopes.values()) if (pool.size > (largest?.size ?? 0)) largest = pool;
if (!largest?.size) break;
const victim = pickerOf(largest)();
if (victim === void 0) break;
remove(victim);
}
}
delivered(items) {
for (const item of items) if (item.api === EApiType.TagRec) {
this.items.delete(item.bvid);
this.retouched.delete(item.bvid);
this.dirty.delete(item.bvid);
}
if (!this.dirty.size) this.dirtyRank = void 0;
}
/** 队列/兜底堆之外的存在性查询 (hasPending 早退用): 已挡候选表内是否仍有该 BV */
has(bvid) {
return this.items.has(bvid);
}
/** mergeIncoming queued 分支复用 (入队门同款合并): 并发生产者窗口下重复到达并入存量而非复推/丢弃 */
mergeQueued(queued, item, rank) {
mergeTagRecCandidate(queued, item);
this.retouched.set(queued.bvid, queued);
this.needsRefilter = true;
this.dirty.set(queued.bvid, queued);
this.dirtyRank = rank;
}
/**
* 批末一次评分 (mergeQueued 逐条 rank 的批化): 评分消费者 = 入队淘汰/切片排序/卡片字段,
* 批内无读取 ⇒ record 入库点与 enqueueStock 收尾处调用; 中性/组中位回退按 flush 集合取
* (原逐条调用退化为条目自身, 批口径与整队重滤的中位语义更接近)
*/
flushMerged() {
if (!this.dirty.size) return;
const items = [...this.dirty.values()];
const rank = this.dirtyRank;
this.dirty.clear();
this.dirtyRank = void 0;
rank?.(items);
}
/**
* 已排队只更新证据;被挡候选重新进阶段;已交付且不在存量中的 BV 不复活。
* queue 传 bvid 索引 Map (createTagRecBvidIndex) 可省逐条全扫, 传数组回退线性查;
* heapIndex 同理省兜底堆 findIndex。
*/
mergeIncoming(item, queue, rank, heap = [], heapIndex) {
const queued = Array.isArray(queue) ? queue.find((other) => other.api === EApiType.TagRec && other.bvid === item.bvid) : queue.get(item.bvid);
if (queued) {
this.mergeQueued(queued, item, rank);
return;
}
const heapHit = heapIndex?.get(item.bvid);
const at = heapIndex ? heapHit ? heap.indexOf(heapHit) : -1 : heap.findIndex((other) => other.bvid === item.bvid);
if (at !== -1) return mergeTagRecCandidate(heap.splice(at, 1)[0], item);
const held = this.items.get(item.bvid);
return held ? mergeTagRecCandidate(held, item) : void 0;
}
});
/** 加权轮转按实际交付记账;空库存不积累欠额。 */
var WeightedTurns = exports("W",class {
credits = /* @__PURE__ */ new Map();
advance(units, chosen) {
const live = new Set(units.map((u) => u.id));
for (const id of this.credits.keys()) if (!live.has(id)) this.credits.delete(id);
let best = units[0].id;
let total = 0;
for (const u of units) {
const weight = Math.min(5, Math.max(1, Math.floor(u.weight) || 1));
total += weight;
this.credits.set(u.id, (this.credits.get(u.id) ?? 0) + weight);
if (this.credits.get(u.id) > this.credits.get(best)) best = u.id;
}
const id = chosen ?? best;
this.credits.set(id, this.credits.get(id) - total);
return id;
}
});
/** 与库存同会话共享,普通刷新不会重置已交付份额。 */
var TagRecFairState = exports("j",class TagRecFairState {
parents = new WeightedTurns();
children = /* @__PURE__ */ new Map();
clear() {
this.parents.credits.clear();
this.children.clear();
}
inner(parentId) {
let turns = this.children.get(parentId);
if (!turns) this.children.set(parentId, turns = new WeightedTurns());
return turns;
}
clone() {
const copy = new TagRecFairState();
for (const [id, credit] of this.parents.credits) copy.parents.credits.set(id, credit);
for (const [parent, turns] of this.children) for (const [id, credit] of turns.credits) copy.inner(parent).credits.set(id, credit);
return copy;
}
pick(groups, l1s) {
const parents = [...new Set(groups.map((g) => g.l1Id))].map((id) => ({
id,
weight: l1s.get(id).weight
}));
const parentId = this.parents.advance(parents);
const children = groups.filter((g) => g.l1Id === parentId).map((g) => ({
id: g.id,
weight: g.weight
}));
const groupId = this.inner(parentId).advance(children);
return {
group: groups.find((g) => g.id === groupId),
parents,
children
};
}
commit(turns, delivered) {
for (const turn of turns) {
if (!turn.group || !delivered.has(turn.item)) continue;
let group = turn.group;
if (turn.item.api === EApiType.TagRec && turn.item.eligibleGroupKeys) {
const keys = turn.item.eligibleGroupKeys;
const allowed = (g) => keys.includes(tagRecGroupKey({ id: g.id })) || keys.includes(tagRecGroupKey(g.name));
if (!allowed(group)) {
const alternate = turn.groups?.find(allowed);
if (!alternate) continue;
group = alternate;
}
}
const assigned = assignTurnGroup(turn, group);
this.parents.advance(assigned.parents, group.l1Id);
this.inner(group.l1Id).advance(assigned.children, group.id);
}
}
});
/** 每条视频只存一份;多归因条目的调度归属不改动完整的过滤/卡片归因。 */
function partitionStock(queue, groups) {
const byId = new Map(groups.map((g) => [g.id, g]));
const byName = new Map(groups.map((g) => [g.name, g]));
const buckets = /* @__PURE__ */ new Map();
const other = [];
for (const item of queue) {
if (item.api !== EApiType.TagRec) {
other.push(item);
continue;
}
const owners = (item.source.groupIds?.length ? item.source.groupIds.flatMap((id) => byId.has(id) ? [byId.get(id)] : []) : item.source.groupNames.flatMap((name) => {
const g = byName.get(name.replace(/·相关$/, ""));
return g ? [g] : [];
})).filter((g) => !item.eligibleGroupKeys || item.eligibleGroupKeys.includes(tagRecGroupKey({ id: g.id })) || item.eligibleGroupKeys.includes(tagRecGroupKey(g.name)));
if (!owners.length) {
if (item.eligibleGroupKeys?.length !== 0 && item.activeSourceClaims?.length !== 0) other.push(item);
continue;
}
const owner = owners.reduce((best, g) => (buckets.get(g.id)?.length ?? 0) / g.weight < (buckets.get(best.id)?.length ?? 0) / best.weight ? g : best);
let bucket = buckets.get(owner.id);
if (!bucket) buckets.set(owner.id, bucket = []);
bucket.push(item);
}
return {
buckets,
other
};
}
function activeGroupIds(item) {
const claims = item.activeSourceClaims ?? item.source.claims ?? [item.source];
return [...new Set(claims.flatMap((claim) => claim.input ? [] : claim.groupIds ?? []))].filter((id) => !item.eligibleGroupKeys || item.eligibleGroupKeys.includes(tagRecGroupKey({ id })));
}
/** 先预演再严格校验;中止不移除库存,拒绝条目不消耗展示份额。 */
function previewTagRecFairSlice(queue, groups, l1s, state, count, guard, preceding = []) {
const parents = new Map(l1s.map((l) => [l.id, l]));
const eligible = groups.filter((g) => g.l1Id && parents.has(g.l1Id));
const { buckets, other } = partitionStock(queue, eligible);
for (const [id, bucket] of buckets) bucket.sort((a, b) => compareTagRecGroupItems(id, a, b));
const draft = state.clone();
const turns = [];
const groupCounts = /* @__PURE__ */ new Map();
const upCounts = /* @__PURE__ */ new Map();
for (const item of preceding) {
if (item.api !== EApiType.TagRec) continue;
for (const id of activeGroupIds(item)) groupCounts.set(id, (groupCounts.get(id) ?? 0) + 1);
if (item.owner?.mid !== void 0) upCounts.set(item.owner.mid, (upCounts.get(item.owner.mid) ?? 0) + 1);
}
const groupMax = Math.max(1, Math.floor((count + preceding.length) * (guard?.group?.maxShare ?? .6)));
const upMax = Math.max(1, guard?.up?.max ?? 4);
const allowedUp = (item) => !guard?.up?.enabled || item.owner?.mid === void 0 || (upCounts.get(item.owner.mid) ?? 0) < upMax;
const allowed = (item) => (!guard?.group?.enabled || activeGroupIds(item).every((id) => (groupCounts.get(id) ?? 0) < groupMax)) && allowedUp(item);
while (turns.length < count) {
const ready = eligible.filter((g) => buckets.get(g.id)?.length);
if (!ready.length) break;
let match = allowed;
let guarded = ready.filter((g) => buckets.get(g.id).some(match));
if (!guarded.length) {
match = allowedUp;
guarded = ready.filter((g) => buckets.get(g.id).some(match));
}
const turn = draft.pick(guarded.length ? guarded : ready, parents);
const bucket = buckets.get(turn.group.id);
const at = guarded.length ? bucket.findIndex(match) : 0;
const [item] = bucket.splice(at, 1);
for (const id of activeGroupIds(item)) groupCounts.set(id, (groupCounts.get(id) ?? 0) + 1);
if (item.owner?.mid !== void 0) upCounts.set(item.owner.mid, (upCounts.get(item.owner.mid) ?? 0) + 1);
turns.push({
...turn,
item,
groups: eligible,
readyGroups: guarded.length ? guarded : ready,
l1s: parents
});
}
for (const item of other.slice(0, count - turns.length)) turns.push({
item,
parents: [],
children: []
});
return {
items: turns.map((t) => t.item),
turns
};
}
function assignTurnGroup(turn, group) {
let parents = turn.parents;
if (!parents.some((p) => p.id === group.l1Id)) parents = [...parents, {
id: group.l1Id,
weight: turn.l1s?.get(group.l1Id)?.weight ?? 1
}];
let children = turn.group?.l1Id === group.l1Id ? turn.children : (turn.readyGroups ?? []).filter((g) => g.l1Id === group.l1Id).map((g) => ({
id: g.id,
weight: g.weight
}));
if (!children.some((g) => g.id === group.id)) children = [...children, {
id: group.id,
weight: group.weight
}];
return {
...turn,
group,
parents,
children
};
}
/** 补查后只在已有组名额内重排,不用切片大小重新计算公平欠额。 */
function rankTagRecFairSlice(turns, checked) {
const byBvid = new Map(checked.flatMap((item) => item.api === EApiType.TagRec ? [[item.bvid, item]] : []));
const retained = new Set(checked);
const passed = turns.flatMap((turn) => {
if (turn.item.api !== EApiType.TagRec) return retained.has(turn.item) ? [turn] : [];
const item = byBvid.get(turn.item.bvid);
if (!item) return [];
const keys = item.eligibleGroupKeys;
if (keys?.length === 0) return [];
const allowed = (g) => !keys || keys.includes(tagRecGroupKey({ id: g.id })) || keys.includes(tagRecGroupKey(g.name));
const group = turn.group && !allowed(turn.group) ? turn.groups?.find(allowed) : turn.group;
if (turn.group && !group) return [];
const updated = {
...turn,
item
};
return [group ? assignTurnGroup(updated, group) : updated];
});
const buckets = /* @__PURE__ */ new Map();
for (const turn of passed) {
if (turn.item.api !== EApiType.TagRec) continue;
const id = turn.group?.id ?? "";
const bucket = buckets.get(id) ?? [];
bucket.push(turn.item);
buckets.set(id, bucket);
}
for (const [id, bucket] of buckets) bucket.sort(id ? (a, b) => compareTagRecGroupItems(id, a, b) : compareTagRecItems);
const ranked = passed.map((turn) => turn.item.api === EApiType.TagRec ? {
...turn,
item: buckets.get(turn.group?.id ?? "").shift()
} : turn);
return {
items: ranked.map((turn) => turn.item),
turns: ranked
};
}
/** 深挖与主轮共用库存总量上限;组低水位目标与补货高水位闸同用此口径做联合可达性折算。 */
var TAG_REC_STOCK_TOTAL = exports("H",5e3);
/** 深挖与主轮共用库存边界;淘汰的未展示候选退还取数去重资格。 */
function trimTagRecStock(queue, groups, perGroup = 1e3, total = TAG_REC_STOCK_TOTAL) {
const { buckets } = partitionStock(queue, groups);
const evicted = /* @__PURE__ */ new Set();
for (const [id, bucket] of buckets) {
bucket.sort((a, b) => compareTagRecGroupItems(id, a, b));
for (const item of bucket.slice(perGroup)) evicted.add(item);
}
const assigned = new Set([...buckets.values()].flat());
const videos = queue.filter((item) => item.api === EApiType.TagRec);
const unassigned = videos.filter((item) => !assigned.has(item)).sort(compareTagRecItems);
let remaining = videos.length - evicted.size;
const sizes = [...buckets.values(), unassigned].map((items) => items.filter((it) => !evicted.has(it)));
while (remaining > total) {
const item = sizes.reduce((best, items) => items.length > best.length ? items : best, []).pop();
if (!item) break;
evicted.add(item);
remaining--;
}
if (evicted.size) {
let write = 0;
for (const item of queue) {
if (item.api === EApiType.TagRec && evicted.has(item)) continue;
if (queue[write] !== item) queue[write] = item;
write++;
}
queue.length = write;
}
return [...evicted];
}
/**
* 组合组内原子级公平选片 (同 TagRecFairState 的欠额记账): 条目按当前组合下命中的条件原子分桶,
* 多命中条归当前最短板 (同 partitionStock 哲学), 桶内先按质量序, 再欠额轮转取条 ——
* 新加的条件原子不再被源序/命中数压在队尾。只决定展示顺序, 不触碰判定/证据语义;
* 无命中条不参与轮转, 兜底按原序补足。原地移除已取条目 (同 splice 语义)。
*/
function spliceTagRecAtomFairSlice(working, count, turns, atomKeysOf, guard) {
const buckets = /* @__PURE__ */ new Map();
for (const item of working) {
if (item.api !== EApiType.TagRec) continue;
const keys = atomKeysOf(item);
if (!keys.length) continue;
const owner = keys.reduce((best, key) => (buckets.get(key)?.length ?? 0) < (buckets.get(best)?.length ?? 0) ? key : best);
const bucket = buckets.get(owner) ?? [];
bucket.push(item);
buckets.set(owner, bucket);
}
for (const bucket of buckets.values()) bucket.sort((a, b) => compareTagRecItems(a, b));
const picked = /* @__PURE__ */ new Set();
const out = [];
const upMax = Math.max(1, guard?.up?.max ?? 4);
const upCounts = /* @__PURE__ */ new Map();
const allowedUp = (item) => !guard?.up?.enabled || item.api !== EApiType.TagRec || item.owner?.mid === void 0 || (upCounts.get(item.owner.mid) ?? 0) < upMax;
while (out.length < count) {
const ready = [...buckets].filter(([, bucket]) => bucket.length);
if (!ready.length) break;
const guarded = ready.filter(([, bucket]) => bucket.some(allowedUp));
const pool = guarded.length ? guarded : ready;
const id = turns.advance(pool.map(([id]) => ({
id,
weight: 1
})));
const bucket = buckets.get(id);
const at = guarded.length ? bucket.findIndex(allowedUp) : 0;
const [item] = bucket.splice(at, 1);
picked.add(item);
out.push(item);
if (item.api === EApiType.TagRec && item.owner?.mid !== void 0) upCounts.set(item.owner.mid, (upCounts.get(item.owner.mid) ?? 0) + 1);
}
for (const item of working) {
if (out.length >= count) break;
if (!picked.has(item)) {
picked.add(item);
out.push(item);
}
}
for (let i = working.length - 1; i >= 0; i--) if (picked.has(working[i])) working.splice(i, 1);
return out;
}
/** 先保护候选最稀缺的归属池;池深相同时按调用方的淘汰顺序选择。 */
function pickTagRecCapacityVictim(bucket, scopes, ordered) {
const smallest = /* @__PURE__ */ new Map();
for (const pool of scopes.values()) for (const item of pool) if (bucket.has(item)) smallest.set(item, Math.min(smallest.get(item) ?? Infinity, pool.size));
let victim;
let depth = -1;
for (const item of ordered) {
if (!bucket.has(item)) continue;
const size = smallest.get(item) ?? 0;
if (size > depth) {
victim = item;
depth = size;
}
}
return victim;
}
function upSearchMids(resolve, key) {
const eff = resolve(key, "allV2");
if (eff.combination && tagRecCombinationRequiresUp(eff.combination)) {
const ups = tagRecCombinationSeeds(eff.combination).ups;
if (ups.length && ups.every((entry) => Number.isSafeInteger(entry.mid) && entry.mid > 0)) return [...new Set(ups.map((entry) => entry.mid))];
}
const allow = eff.cfg.enabled ? eff.neg.upAllow?.list : void 0;
if (!allow?.length || allow.some((it) => !Number.isSafeInteger(it.mid) || it.mid <= 0)) return;
return [...new Set(allow.map((it) => it.mid))];
}
/**
* 源的消费者集。`input: true` 的源通常只有输入消费者 (key=undefined) —— 但深挖流表里
* 锚点流会被同名「组簇流」就地合并 (ensureCliqueStreams 按 streamIdentity 撞车合并,
* 只并 attribution/groupIds, 不翻 input 位): 合并后该流同时服务「输入词」与「组」两个消费者。
* 若仍只返回输入消费者, 工作台切换 (inputText 清空、组消费者就位) 后本流会因消费者不在
* 作用域内被 plannedStream 判为不可挖 —— 深挖整链静默停摆, 小池只剩 tag/top 那几条
* (实机 kingexit: 10 条后硬见底, 搜索池 0 请求)。故 input 源带 groupIds 时两者都返回。
*/
function tagRecConsumers(source) {
const names = source.groupNames ?? source.attribution ?? [];
const groupConsumers = source.groupIds?.length ? source.groupIds.map((id, i) => ({
key: { id },
name: names[i] ?? "",
attributionLabels: source.attributionLabels
})) : (names.length ? names : [""]).map((name) => ({
key: name || void 0,
name,
attributionLabels: source.attributionLabels
}));
if (source.input) return [{
key: void 0,
name: names[0] ?? "",
input: true,
attributionLabels: source.attributionLabels
}, ...source.groupIds?.length ? groupConsumers : []];
return groupConsumers;
}
function mergeTagRecConsumers(consumers) {
return [...new Map(consumers.map((c) => [tagRecGroupKey(c.key), c])).values()];
}
function tagRecConsumerSource(consumers) {
return {
groupNames: consumers.map((c) => c.name).filter(Boolean),
groupIds: consumers.flatMap((c) => typeof c.key === "object" && c.key?.id ? [c.key.id] : []),
...consumers.every((c) => c.input) ? { input: true } : {}
};
}
function tagRecSourceSignature(consumers, resolve) {
return JSON.stringify(mergeTagRecConsumers(consumers).map((c) => {
const pools = resolve(c.key, "allV2").poolsEnabled;
return [
tagRecGroupKey(c.key),
upSearchMids(resolve, c.key) ?? null,
TAG_REC_ALL_POOL_KEYS.map((p) => pools[p])
];
}).sort((a, b) => String(a[0]).localeCompare(String(b[0]))));
}
function tagRecReopenedTopTids(previous, next, groups) {
if (!previous || previous === next) return [];
const before = new Map(JSON.parse(previous).map(([key, route, pools]) => [key, !route && pools[0]]));
const reopened = new Set(JSON.parse(next).filter(([key, route, pools]) => !route && pools[0] && before.get(key) !== true).map(([key]) => key));
return groups.filter((g) => reopened.has(tagRecGroupKey({ id: g.id }))).flatMap((g) => g.tag_ids.map((e) => e.id));
}
/** 先逐消费者求路线和来源,再合并物理请求;兼容旧 shallow 数组调用。 */
function planTagRecRecall(input, resolve) {
const { roundSources, digSources = [], scopeConsumers } = Array.isArray(input) ? {
roundSources: input,
digSources: [],
scopeConsumers: void 0
} : input;
const top = /* @__PURE__ */ new Map();
const up = /* @__PURE__ */ new Map();
const dig = /* @__PURE__ */ new Map();
const scope = mergeTagRecConsumers(scopeConsumers ?? [...roundSources, ...digSources].flatMap(tagRecConsumers));
const result = {
tagTop: [],
up: [],
dig: [],
archiveConsumers: [],
blocked: []
};
const ordinary = (c) => !upSearchMids(resolve, c.key);
for (const c of scope) {
const pools = resolve(c.key, "allV2").poolsEnabled;
if (!ordinary(c)) {
if (!pools.allV2) result.blocked.push({
consumer: c,
reason: "route-off"
});
} else {
if (pools.archiveRelated) result.archiveConsumers.push(c);
if (TAG_REC_ALL_POOL_KEYS.every((p) => !pools[p])) result.blocked.push({
consumer: c,
reason: "pools-off"
});
}
const combination = resolve(c.key, "allV2").combination;
if (combination && pools.allV2) {
const seeds = tagRecCombinationSeeds(combination);
for (const owner of seeds.ups) if (owner.mid) for (const keyword of tagRecCombinationRequiresUp(combination) && !seeds.tags.length && !tagRecConditionAtoms(combination.include).some((atom) => atom.field === "partition") ? seeds.keywords : [""]) {
const identity = JSON.stringify([owner.mid, keyword]);
const consumers = mergeTagRecConsumers([...up.get(identity)?.consumers ?? [], c]);
up.set(identity, {
tid: 0,
mid: owner.mid,
keyword,
...tagRecConsumerSource(consumers),
consumers
});
}
}
}
for (const source of roundSources) for (const rawConsumer of tagRecConsumers(source)) {
const c = {
...rawConsumer,
queryKeyword: source.keyword,
name: typeof rawConsumer.key === "object" && rawConsumer.key?.id ? resolve(rawConsumer.key, "allV2").groupName ?? rawConsumer.name : rawConsumer.name
};
const pools = resolve(c.key, "allV2").poolsEnabled;
const mids = upSearchMids(resolve, c.key);
if (mids) {
if (!pools.allV2 || !source.keyword?.trim()) continue;
for (const mid of mids) {
const keyword = source.keyword.trim();
const key = JSON.stringify([mid, keyword]);
const consumers = mergeTagRecConsumers([...up.get(key)?.consumers ?? [], c]);
up.set(key, {
...source,
mid,
keyword,
...tagRecConsumerSource(consumers),
consumers
});
}
} else if (pools.tagTop) {
const consumers = mergeTagRecConsumers([...top.get(source.tid)?.consumers ?? [], c]);
top.set(source.tid, {
...source,
...tagRecConsumerSource(consumers),
consumers,
attributionLabels: [...new Set(consumers.flatMap((v) => v.attributionLabels ?? []))]
});
}
}
for (const source of digSources) {
const consumers = tagRecConsumers(source).filter(ordinary);
const exact = consumers.filter((c) => resolve(c.key, "allV2").poolsEnabled.allV2);
const related = consumers.filter((c) => resolve(c.key, "allV2Related").poolsEnabled.allV2Related);
if (!exact.length && !related.length) continue;
const identity = JSON.stringify([source.key, source.keyword]);
const old = dig.get(identity);
const exactConsumers = mergeTagRecConsumers([...old?.exactConsumers ?? [], ...exact]);
const relatedConsumers = mergeTagRecConsumers([...old?.relatedConsumers ?? [], ...related]);
const all = mergeTagRecConsumers([...exactConsumers, ...relatedConsumers]);
dig.set(identity, {
...source,
...tagRecConsumerSource(all),
attribution: [...new Set(all.map((c) => c.name).filter(Boolean))],
consumers: all,
exactConsumers,
relatedConsumers
});
}
return {
...result,
tagTop: [...top.values()],
up: [...up.values()],
dig: [...dig.values()]
};
}
/** A cancelled wait releases its timer and listener immediately, including injected test sleepers. */
function abortableDelay(ms, signal, sleep) {
if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
return new Promise((resolve, reject) => {
let timer;
const detach = () => {
signal?.removeEventListener("abort", onAbort);
if (timer !== void 0) clearTimeout(timer);
};
const onAbort = () => {
detach();
reject(new DOMException("Aborted", "AbortError"));
};
signal?.addEventListener("abort", onAbort, { once: true });
if (sleep) Promise.resolve().then(() => {
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
return sleep(ms);
}).then(() => {
detach();
resolve();
}, (error) => {
detach();
reject(error);
});
else timer = setTimeout(() => {
detach();
resolve();
}, ms);
});
}
var UpSearchPausedError = class extends Error {
retryAt;
constructor(retryAt) {
super("UP 投稿搜索已暂停,请稍后重试;如 B 站要求验证,请先在 B 站页面完成验证。");
this.retryAt = retryAt;
this.name = "UpSearchPausedError";
}
};
var upSearchGate = new class UpSearchGate {
now;
sleep;
static INTERVAL_MS = 2e3;
static COOLDOWN_MS = 3e5;
active = false;
pending = /* @__PURE__ */ new Set();
nextAt = 0;
pausedUntil = 0;
constructor(now = Date.now, sleep) {
this.now = now;
this.sleep = sleep;
}
pause(retryAfterMs = 0) {
const serverDelay = Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? retryAfterMs : 0;
this.pausedUntil = Math.max(this.pausedUntil, this.now() + Math.max(UpSearchGate.COOLDOWN_MS, serverDelay));
return new UpSearchPausedError(this.pausedUntil);
}
run(request, signal) {
if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
if (this.now() < this.pausedUntil) return Promise.reject(new UpSearchPausedError(this.pausedUntil));
const check = () => {
if (signal?.aborted) throw new DOMException("Aborted", "AbortError");
if (this.now() < this.pausedUntil) throw new UpSearchPausedError(this.pausedUntil);
};
const { promise, resolve, reject } = Promise.withResolvers();
const detach = () => signal?.removeEventListener("abort", onAbort);
const onAbort = () => {
if (!this.pending.delete(start)) return;
detach();
reject(new DOMException("Aborted", "AbortError"));
};
const start = () => {
this.active = true;
detach();
Promise.resolve().then(async () => {
for (;;) {
check();
const delay = this.nextAt - this.now();
if (delay <= 0) break;
await abortableDelay(Math.min(delay, 2147483647), signal, this.sleep);
}
check();
this.nextAt = this.now() + UpSearchGate.INTERVAL_MS;
return request();
}).then(resolve, reject).finally(() => {
this.active = false;
this.drain();
});
};
this.pending.add(start);
signal?.addEventListener("abort", onAbort, { once: true });
this.drain();
return promise;
}
drain() {
if (this.active) return;
const next = this.pending.values().next().value;
if (!next) return;
this.pending.delete(next);
next();
}
}();
var PAGE_SIZE = 30;
var cache = new SessionFetchCache({
dedupeMs: 0,
maxAgeMs: 3e5,
maxEntries: 200
});
/** 包含 code=0 的验证码/风控空页,不能误报成关键词没有结果。 */
function isUpSearchRisk(response) {
const data = response.data;
const body = data?.data;
return response.status === 412 || response.status === 429 || !data || typeof data !== "object" || [
-401,
-352,
-412,
-101
].includes(data.code) || !!data.v_voucher || !!body?.is_risk || !!body?.gaia_res_type || !!body?.gaia_data || !!body?.v_voucher;
}
function checkRecoveryResponse(response) {
const retryAfterMs = retryAfterDelay(response.headers);
if (isUpSearchRisk(response) || response.status === 503 && retryAfterMs !== void 0) throw upSearchGate.pause(retryAfterMs);
}
function requestPage(mid, keyword, page, signal) {
return upSearchGate.run(async () => {
const { request, isWebApiSuccess } = await module.import('./request-DatC6abj-uCHRJZHB.js');
let response;
try {
response = await request.get("/x/space/wbi/arc/search", {
params: {
mid,
keyword,
pn: page,
ps: PAGE_SIZE,
order: "pubdate",
tid: 0
},
signal,
timeout: 15e3
});
} catch (err) {
if (isAxiosError(err) && err.response) checkRecoveryResponse(err.response);
throw err;
}
checkRecoveryResponse(response);
if (!isWebApiSuccess(response.data)) throw new Error(`UP 投稿搜索失败:${response.data?.message ?? response.data?.code}`);
const data = response.data.data;
if (!Array.isArray(data?.list?.vlist) || !data.page) throw new Error("UP 投稿搜索返回了不完整的数据,请稍后重试。");
const items = data.list.vlist.filter((it) => !!it.bvid && !it.is_lesson_video && !(it.is_avoided && it.meta)).map((it) => ({
aid: it.aid,
bvid: it.bvid,
title: it.title,
desc: it.description,
pic: it.pic,
pubdate: it.created,
duration: parseAllV2Duration(it.length),
tid: it.typeid,
owner: {
mid: it.mid,
name: it.author
},
stat: {
view: it.play,
reply: it.comment,
danmaku: it.video_review
}
}));
const { pn, ps, count } = data.page;
if (!Number.isSafeInteger(pn) || pn < 1 || !Number.isSafeInteger(ps) || ps < 1 || !Number.isSafeInteger(count) || count < 0) throw new Error("UP 投稿搜索返回了不完整的分页信息,请稍后重试。");
return {
items,
hasMore: data.list.vlist.length > 0 && pn * ps < count
};
}, signal);
}
/**
* 同 UP/词/页并发合并;最后一位等待者离开时取消排队/在途请求,不继续刷旧查询。
* 完成页缓存 TTL=CACHE_TTL_MS; opts.fresh (定向重查) 弃完成档换新页 —— 在途档不算陈旧照常合并。
*/
function fetchUpSearchPage(mid, keyword, page, signal, opts) {
if (signal?.aborted) return Promise.reject(new DOMException("Aborted", "AbortError"));
if (!Number.isSafeInteger(mid) || mid <= 0 || typeof keyword !== "string" || !Number.isSafeInteger(page) || page < 1) return Promise.reject(/* @__PURE__ */ new Error("UP 投稿搜索需要有效的 UID 和页码。"));
const key = JSON.stringify([
mid,
keyword.trim(),
page
]);
if (opts?.fresh) cache.invalidateSettled(key);
return cache.fetch(key, (entrySignal) => requestPage(mid, keyword.trim(), page, entrySignal), signal);
}
function tagRecRecheckOptions(plan) {
const options = [];
const add = (key, name, pool, keyword) => {
const labels = {
tagTop: "标签池",
allV2: "搜索池",
allV2Related: "搜索相关",
archiveRelated: "推荐相关"
};
const selection = {
key,
pools: [pool],
...keyword ? { keyword } : {}
};
options.push({
...selection,
value: JSON.stringify(selection),
label: `${name || "本次输入"} · ${labels[pool]}${keyword ? " · " + keyword : ""}`
});
};
for (const s of plan.tagTop) for (const c of s.consumers) add(c.key, c.name, "tagTop");
for (const s of plan.up) for (const c of s.consumers ?? []) add(c.key, c.name, "allV2", s.keyword);
for (const s of plan.dig) {
for (const c of s.exactConsumers) add(c.key, c.name, "allV2", s.keyword);
for (const c of s.relatedConsumers) add(c.key, c.name, "allV2Related", s.keyword);
}
for (const c of plan.archiveConsumers) add(c.key, c.name, "archiveRelated");
return [...new Map(options.map((option) => [option.value, option])).values()];
}
/** 正常续页和首屏探测分开;单次至多4个请求身份、每词1页,不写普通游标。 */
var TagRecRecheckExecutor = class {
running = /* @__PURE__ */ new Map();
run(identity, options) {
const existing = this.running.get(identity);
if (existing) return existing;
options.onStart?.();
const promise = this.execute(options).finally(() => {
if (this.running.get(identity) === promise) this.running.delete(identity);
});
this.running.set(identity, promise);
return promise;
}
async execute(options) {
const { plan, signal } = options;
const result = {
requests: 0,
added: 0,
empty: true,
failed: false,
stale: false
};
const current = () => !signal?.aborted && options.current();
const requests = [];
for (const source of plan.tagTop) requests.push(async () => (await fetchTagTop(source.tid, { signal })).map((raw, srcRank) => {
const item = normalizeTagTopItem(raw, {
...tagRecConsumerSource(source.consumers),
srcRank
});
item.source.tagIds = [source.tid];
item.source.claims = source.consumers.map((c) => ({
...tagRecConsumerSource([c]),
via: "tag-top",
tagIds: [source.tid],
queryKeyword: c.queryKeyword ?? source.keyword
}));
return item;
}));
for (const source of plan.up) requests.push(async () => (await fetchUpSearchPage(source.mid, source.keyword, 1, signal, { fresh: true })).items.map((raw, srcRank) => {
const item = normalizeTagTopItem(raw, {
...tagRecConsumerSource(source.consumers ?? []),
srcRank
});
item.source.via = "up-search";
item.source.queryKeyword = source.keyword;
item.source.input = source.input;
item.source.claims = source.consumers?.map((c) => ({
...tagRecConsumerSource([c]),
via: "up-search",
queryKeyword: source.keyword
}));
return item;
}));
for (const source of plan.dig) requests.push(async () => {
const res = await runPhase2Round({
keyword: source.keyword,
inputNorm: source.key,
matchNorms: source.crossNorms,
fromPage: 1,
pageCount: 1,
signal,
withSuggest: false,
resolveVariants: false
});
if (res.allPagesFailed) {
if (current()) options.noteSearch?.(source.key, source.keyword, void 0);
throw new Error("首屏重查失败");
}
if (!current()) return [];
if (source.crossNorms) for (const n of source.crossNorms) options.mergeCorpus?.(n, res);
else options.mergeCorpus?.(source.key, res);
options.noteSearch?.(source.key, source.keyword, res.videos.length === 0);
if (res.videos.length) result.empty = false;
return [[
"all-v2-exact",
res.analysis.exact,
source.exactConsumers
], [
"all-v2-related",
res.analysis.related,
source.relatedConsumers
]].flatMap(([via, raws, consumers]) => raws.map((raw, srcRank) => {
const item = normalizeAllV2Item(raw, {
...tagRecConsumerSource(consumers),
via,
srcRank
});
item.source.claims = consumers.map((c) => ({
...tagRecConsumerSource([c]),
via,
queryKeyword: source.keyword
}));
return item;
})).filter((item) => item.source.claims?.length);
});
for (const seed of (options.seeds ?? []).slice(0, 3)) {
const claims = seed.activeSourceClaims ?? seed.source.claims ?? [seed.source];
const matchClaim = (c) => claims.find((claim) => typeof c.key === "object" && c.key?.id ? claim.groupIds?.includes(c.key.id) : tagRecGroupKey(c.key) === tagRecGroupKey(claim.input ? void 0 : claim.groupNames[0]));
const allowed = plan.archiveConsumers.filter((c) => matchClaim(c) !== void 0);
if (!allowed.length) continue;
requests.push(async () => (await fetchArchiveRelated(seed.bvid, { signal })).map((raw, srcRank) => {
const item = normalizeTagTopItem(raw, {
...tagRecConsumerSource(allowed),
via: "archive-related",
srcRank
});
item.source.claims = allowed.map((c) => {
const seed = matchClaim(c);
return {
...tagRecConsumerSource([c]),
via: "archive-related",
queryKeyword: seed?.queryKeyword,
...seed?.tagIds?.length ? { tagIds: seed.tagIds } : {}
};
});
item.source.queryKeyword = allowed.map((c) => matchClaim(c)?.queryKeyword).find(Boolean);
return item;
}));
}
for (const request of requests.slice(0, 4)) {
if (!current()) {
result.stale = true;
break;
}
result.requests++;
try {
const items = await request();
if (!current()) {
result.stale = true;
break;
}
if (items.length) result.empty = false;
result.added += options.accept(items);
} catch {
if (!current()) {
result.stale = true;
break;
}
result.failed = true;
result.empty = false;
}
}
return result;
}
};
/** ladder 旋钮取数: 覆盖值 > 常量出厂值 */
var LADDER_DEFAULTS = {
seedPoolMax: 12,
seedCount: 3,
seedMinScore: 0,
fallbackPage: 20};
var LADDER_STREAMS_MAX = 64;
/**
* W3-6: settings.tagRec.ladder 七键可 GM 直改/导入注入绕过 UI 校验 (对照 seenCap 同款面):
* 消费点统一钳制 —— digStreamWindow/autoDigMax/seedCount 放任值 = 并发请求洪泛。
*/
var LADDER_BOUNDS = {
triggerYield: [1, 50],
autoDigMax: [1, 20],
digStreamWindow: [1, 8],
seedPoolMax: [10, 200],
seedCount: [1, 10],
seedMinScore: [0, 100],
fallbackPage: [5, 50]
};
var clampLadderNum = (k, v) => {
if (v === void 0) return void 0;
if (!Number.isFinite(v)) return void 0;
const [lo, hi] = LADDER_BOUNDS[k];
return Math.min(hi, Math.max(lo, Math.round(v)));
};
function clampTagRecLadderOpts(o) {
const out = {};
for (const k of Object.keys(LADDER_BOUNDS)) {
const v = clampLadderNum(k, o[k]);
if (v !== void 0) out[k] = v;
}
return out;
}
/** tab1 保守档 (= 票 13 现状): 仅干轮起梯队 + 自动深挖 K=3 转手动横幅 */
var FALLBACK_LADDER_STRATEGY_CONSERVATIVE = {
triggerYield: 1,
autoDig: {
mode: "fixed",
max: 3
}
};
/** tab2 激进档 (票 17): 浅水位 <10 预挖 + 自适应无限深挖 (连续 2 轮 <3 条低产停) */
var FALLBACK_LADDER_STRATEGY_AGGRESSIVE = exports("F",{
triggerYield: 10,
autoDig: {
mode: "adaptive",
lowYield: 3,
lowYieldStreakLimit: 2
}
});
/** 兜底堆写版本: heap 数组经 Proxy 拦写自增 —— bvid 懒索引的等长换血检出依据 */
var tagRecHeapSeq = 0;
function tagRecHeapVersion() {
return tagRecHeapSeq;
}
var wrapTagRecHeap = (init = []) => tagRecVersionedArray(() => tagRecHeapSeq++, init);
function newTagRecLadderState() {
return {
streams: /* @__PURE__ */ new Map(),
heap: wrapTagRecHeap(),
poolItems: [],
exactPoolItems: [],
autoDigs: 0,
earlyStopped: false,
lowYieldStreak: 0,
relatedSeeded: false,
pagesFetched: 0,
lastDigYield: 0,
lastDigError: false,
seededBvids: /* @__PURE__ */ new Set(),
seedRequests: /* @__PURE__ */ new Map(),
seedScanCursor: 0,
producedTotal: 0,
digRunning: void 0,
tagCursors: /* @__PURE__ */ new Map(),
queryCursor: 0,
wordCursor: 0,
wordWindow: /* @__PURE__ */ new Set(),
rechecks: new TagRecRecheckExecutor(),
epoch: 0,
stockEpoch: 0,
digCursorEpoch: 0
};
}
/** 语境签名变 => 原地重置 (共享引用不换壳, 持有方无感) */
function resetTagRecLadderState(s, preserveBudget = false) {
s.streams.clear();
s.heap.length = 0;
s.poolItems.length = 0;
s.exactPoolItems.length = 0;
if (!preserveBudget) s.autoDigs = 0;
s.earlyStopped = false;
s.earlyStopReason = void 0;
s.lowYieldStreak = 0;
s.relatedSeeded = false;
s.pagesFetched = 0;
s.lastDigYield = 0;
s.lastDigError = false;
s.seededBvids.clear();
s.seedRequests.clear();
s.seedScanCursor = 0;
s.producedTotal = 0;
s.digRunning = void 0;
s.queryCursor = 0;
s.wordCursor = 0;
s.wordWindow.clear();
s.epoch++;
s.stockEpoch++;
}
var TagRecFallbackLadder = exports("d",class {
hooks;
strategy;
s;
streamIdentity(stream) {
return JSON.stringify([stream.key, stream.keyword]);
}
/** 兜底堆: all/v2 related + archive/related (放行时按分排序取页) */
get heap() {
return this.s.heap;
}
set heap(v) {
this.s.heap = wrapTagRecHeap(v);
}
/** 堆内 bvid 懒索引: acceptNormalized 每条到达物 findIndex 全扫 => O(items×heap) */
heapBvidIndex = createTagRecBvidIndex(() => this.s.heap, tagRecHeapVersion);
/** all/v2 深挖流 (norm => 流): 输入锚点流 / 组同义词簇流 */
get streams() {
return this.s.streams;
}
/** tag/top 池累计条目 (选种扫描原料; 热池判据已改 producedTotal, 本池只管「扫谁」) */
get poolItems() {
return this.s.poolItems;
}
/** all/v2 exact 归一条目累计 (自带 tag 名单的免费种材) */
get exactPoolItems() {
return this.s.exactPoolItems;
}
get autoDigs() {
return this.s.autoDigs;
}
set autoDigs(v) {
this.s.autoDigs = v;
}
get earlyStopped() {
return this.s.earlyStopped;
}
set earlyStopped(v) {
this.s.earlyStopped = v;
}
/** adaptive 低产连停计数 (票 17): 连续 0<净增 0;
}
/** 梯队旋钮合并读 (票10): strategy.ladder getter 快照, 缺省={}; W3-6 出参过钳制 */
ladderOpts() {
return clampTagRecLadderOpts(this.strategy.ladder?.() ?? {});
}
autoPolicy() {
const configured = this.strategy.ladder?.();
if (!configured) return this.strategy.autoDig;
const p = tagRecDiscoveryPolicy(configured);
return p.mode === "fixed" ? {
mode: "fixed",
max: p.autoDigMax
} : {
mode: "adaptive",
lowYield: p.lowYield,
lowYieldStreakLimit: p.lowYieldStreakLimit
};
}
/** 只提升未交付原料;已看/已交付仍由原有去重阶段裁掉。 */
acceptRecheck(items) {
const accepted = items.flatMap((item) => {
const candidate = this.acceptNormalized(item);
return candidate ? [candidate] : [];
});
const staged = this.hooks.applyStages(accepted, {
poolItems: [...this.hooks.poolItems?.() ?? [], ...accepted],
mergeStats: true
});
this.hooks.pushQueue(staged);
return staged.length;
}
recheck(selection, sources, signal) {
const epoch = this.s.epoch;
const stockEpoch = this.s.stockEpoch;
const cursorEpoch = this.s.digCursorEpoch;
const match = (c) => tagRecGroupKey(c.key) === tagRecGroupKey(selection.key);
const plan = {
...sources,
tagTop: selection.pools.includes("tagTop") ? sources.tagTop.flatMap((s) => {
const consumers = s.consumers.filter(match);
return consumers.length ? [{
...s,
...tagRecConsumerSource(consumers),
consumers
}] : [];
}) : [],
up: selection.pools.includes("allV2") ? sources.up.flatMap((s) => {
const consumers = s.consumers?.filter(match) ?? [];
return consumers.length && (!selection.keyword || s.keyword === selection.keyword) ? [{
...s,
...tagRecConsumerSource(consumers),
consumers
}] : [];
}) : [],
dig: sources.dig.flatMap((s) => {
if (selection.keyword && s.keyword !== selection.keyword) return [];
const exactConsumers = selection.pools.includes("allV2") ? s.exactConsumers.filter(match) : [];
const relatedConsumers = selection.pools.includes("allV2Related") ? s.relatedConsumers.filter(match) : [];
return exactConsumers.length || relatedConsumers.length ? [{
...s,
exactConsumers,
relatedConsumers
}] : [];
}),
archiveConsumers: selection.pools.includes("archiveRelated") ? sources.archiveConsumers.filter(match) : []
};
const seedPool = plan.archiveConsumers.length ? this.hooks.poolItems?.() ?? [] : [];
const seedVocab = seedPool.length ? cooccurVocab(sediment.map, [...this.streams.keys()]) : /* @__PURE__ */ new Set();
const seedMin = seedPool.length ? this.ladderOpts().seedMinScore ?? 0 : 0;
const seedResolve = seedPool.length ? this.hooks.resolve?.() : void 0;
const seeds = seedPool.flatMap((item) => {
const source = {
...item.source,
via: "archive-related",
claims: tagRecSourceClaims(item).map((claim) => ({
...claim,
via: "archive-related"
}))
};
const seed = {
...item,
source,
eligibleGroupKeys: void 0,
eligiblePoolScopes: void 0
};
if (seedResolve && !projectTagRecActiveClaims(seed, seedResolve)) return [];
if (!seed.ownTagNames?.length || !countCooccurDegree(seed.ownTagNames, seedVocab)) return [];
const min = Math.max(seedMin, this.hooks.seedMinScoreOf?.(seed) ?? 0);
const score = computeScore(seed, this.hooks.scoreParams(seed)).score;
return min > 0 && (score === void 0 || score < min) ? [] : [seed];
});
const probe = this.probeWriter(plan);
const current = () => this.s.stockEpoch === stockEpoch && this.s.digCursorEpoch === cursorEpoch && probe.current();
return this.s.rechecks.run(JSON.stringify([
epoch,
cursorEpoch,
selection
]), {
plan,
seeds,
signal,
current,
accept: (items) => this.acceptRecheck(items),
mergeCorpus: (key, res) => sediment.merge(key, res.analysis.cliques),
noteSearch: probe.note
});
}
probeWriter(plan) {
const version = (key, keyword, scope) => {
const dig = sediment.map.get(key)?.dig;
return dig?.keyword === keyword ? dig.probes?.[scope]?.revision ?? 0 : 0;
};
const scopes = plan.dig.flatMap((source) => [["allV2", source.exactConsumers], ["allV2Related", source.relatedConsumers]].flatMap(([pool, consumers]) => consumers.map((c) => {
const scope = tagRecPoolScopeKey(c.key, pool);
return {
key: source.key,
keyword: source.keyword,
scope,
revision: version(source.key, source.keyword, scope)
};
})));
return {
current: () => scopes.every((s) => version(s.key, s.keyword, s.scope) === s.revision),
note: (key, keyword, empty) => {
const ttl = (empty === void 0 ? 5 : tagRecDiscoveryPolicy(this.strategy.ladder?.()).emptyTtlMinutes) * 6e4;
for (const s of scopes.filter((s) => s.key === key && s.keyword === keyword)) {
sedimentNoteProbe(sediment.map, key, keyword, s.scope, empty, ttl, s.revision);
s.revision = version(key, keyword, s.scope);
}
}
};
}
expiredProbe(stream) {
if (!stream.exhausted || stream.stopReason === "cap") return false;
const source = this.plannedStream(stream);
if (!source) return false;
const dig = sediment.map.get(stream.key)?.dig;
if (dig && dig.keyword !== stream.keyword) return false;
const fallback = stream.retryAt ?? (dig?.lastAt ?? Date.now()) + tagRecDiscoveryPolicy(this.strategy.ladder?.()).emptyTtlMinutes * 6e4;
return (this.hooks.resolve ? planTagRecRecall({
roundSources: [],
digSources: [source]
}, this.hooks.resolve()).dig.flatMap((s) => [...s.exactConsumers.map((c) => tagRecPoolScopeKey(c.key, "allV2")), ...s.relatedConsumers.map((c) => tagRecPoolScopeKey(c.key, "allV2Related"))]) : ["default"]).some((scope) => Date.now() >= (dig?.probes?.[scope]?.retryAt ?? fallback));
}
trimSeedRequests() {
let size = 0;
for (const entry of this.s.seedRequests.values()) size += entry.size;
while (size > 500) {
let removed = false;
for (const [bvid, entry] of this.s.seedRequests) {
if (!entry.settled) continue;
this.s.seedRequests.delete(bvid);
size -= entry.size;
removed = true;
break;
}
if (!removed) break;
}
}
findStream(predicate) {
for (const stream of this.streams.values()) if (predicate(stream)) return stream;
}
async reprobeExpired(signal, manual = false) {
if (!this.hooks.resolve || signal?.aborted || tagRecBackoff.isEngaged || this.stale() || !manual && this.earlyStopped && this.s.earlyStopReason !== "empty") return 0;
const p = this.autoPolicy();
if (!manual && (p.mode === "fixed" ? this.autoDigs >= p.max : this.lowYieldStreak >= p.lowYieldStreakLimit)) return 0;
const stream = this.findStream((s) => this.expiredProbe(s));
if (!stream) return 0;
const plan = planTagRecRecall({
roundSources: [],
digSources: [this.plannedStream(stream)]
}, this.hooks.resolve());
const epoch = this.s.epoch;
const stockEpoch = this.s.stockEpoch;
const cursorEpoch = this.s.digCursorEpoch;
const probe = this.probeWriter(plan);
const result = await this.s.rechecks.run(JSON.stringify([
"expiry",
epoch,
cursorEpoch,
stream.key,
stream.keyword
]), {
plan,
signal,
onStart: () => {
if (!manual) this.autoDigs++;
},
current: () => this.s.stockEpoch === stockEpoch && this.s.digCursorEpoch === cursorEpoch && probe.current(),
accept: (items) => this.acceptRecheck(items),
mergeCorpus: (key, res) => sediment.merge(key, res.analysis.cliques),
noteSearch: probe.note
});
if (!result.stale && stockEpoch === this.s.stockEpoch && cursorEpoch === this.s.digCursorEpoch) this.lastDigError = result.failed;
return result.added;
}
/**
* 超 LADDER_HEAP_MAX 才重排截断;先削最大组×来源池,保护共享候选的稀缺归属。
* 归属池深相同时丢排序尾部;exact 历史池 FIFO 截头 (只供选种扫描, 新料优先)。
*/
pushHeap(items) {
if (!items.length) return;
this.heap.push(...items);
if (this.heap.length > 500) {
this.rankItems(this.heap);
this.heap.sort(compareFallbackItems);
const buckets = /* @__PURE__ */ new Map();
for (const item of this.heap) for (const key of tagRecChainKeysOf(item)) for (const pool of tagRecPoolsForChain(item, key)) {
const scope = tagRecPoolScopeKey(key, pool);
const bucket = buckets.get(scope) ?? /* @__PURE__ */ new Set();
bucket.add(item);
buckets.set(scope, bucket);
}
while (this.heap.length > 500) {
const largest = [...buckets.values()].sort((a, b) => b.size - a.size)[0];
const victim = pickTagRecCapacityVictim(largest, buckets, this.heap.toReversed());
if (!victim) break;
const at = this.heap.indexOf(victim);
const [removed] = this.heap.splice(at, 1);
for (const bucket of buckets.values()) bucket.delete(removed);
}
}
}
rankItems(items) {
if (this.hooks.rankItems) {
this.hooks.rankItems(items);
return;
}
for (const it of items) {
const r = computeScore(it, this.hooks.scoreParams(it));
it.score = r.score;
it.partial = r.partial;
}
const scores = items.flatMap((it) => it.score === void 0 ? [] : [it.score]).sort((a, b) => a - b);
const neutral = scores.length ? (scores[Math.floor(scores.length / 2)] + scores[Math.floor((scores.length - 1) / 2)]) / 2 : 50;
for (const it of items) it.qualitySortScore = it.score ?? neutral;
}
matchesCandidateScope(item, scope) {
if (!scope) return true;
const keys = tagRecChainKeysOf(item).map(tagRecGroupKey);
return keys.some((key) => scope.needed.has(key)) || !item.source.groupIds?.length && keys.every((key) => !scope.all.has(key));
}
pushExactPool(items) {
if (!items.length) return;
this.exactPoolItems.push(...items);
if (this.exactPoolItems.length > 500) this.exactPoolItems.splice(0, this.exactPoolItems.length - 500);
}
/** 干轮触发线生效值: settings.ladder.triggerYield > 策略档出厂 (service 消费) */
effTriggerYield() {
return this.ladderOpts().triggerYield ?? this.strategy.triggerYield;
}
/**
* 登记深挖流 (输入锚点流 / 手工构造); key 撞车不覆盖
* 票 36: 建流时经 `sediment.map.get(key)?.dig` 水合页游标 —— keyword 精确等值才续
* (跨 F5/签名变的「已付费页」不再重付; 撞已有活流走 key 撞车早退, 不碰游标)
*/
addStream(s) {
if (this.findStream((stream) => this.streamIdentity(stream) === this.streamIdentity(s))) return;
this.streams.set(this.streams.has(s.key) ? this.streamIdentity(s) : s.key, newDigStream(s, this.bypassSeen() ? void 0 : digResumeFromSediment(sediment.map.get(s.key), s.keyword)));
}
/**
* D7/D8: 组同义词簇补流 —— 组正向成员规范名归一去重成簇, 每簇一条 all/v2 流
* 已存在的 key 合并归属组名 (跨组同簇共享一条流, 归因并集 —— 流已在游标是活的, 不水合不新建);
* 返回新建流数; 票 36: 新建时同款 sediment dig 水合
*/
ensureCliqueStreams(groups) {
let created = 0;
const resolve = this.hooks.resolve?.();
const all = deriveGroupCliques(groups.map((g) => ({
...g,
extra: resolve ? tagRecSearchExtraOf(resolve(g.id ? { id: g.id } : g.name, "allV2")) : void 0
})), Infinity, this.autoDigs);
const allowed = this.hooks.resolve ? planTagRecRecall({
roundSources: [],
digSources: all
}, this.hooks.resolve()).dig : all;
const scope = this.hooks.candidateScope?.();
const demanded = scope?.all.size ? allowed.filter((c) => tagRecConsumers(c).some((consumer) => scope.needed.has(tagRecGroupKey(consumer.key)))) : allowed;
const start = demanded.length ? this.s.wordCursor % demanded.length : 0;
const full = this.hooks.recallPlan?.().dig;
const selected = Array.from({ length: Math.min(5, demanded.length) }, (_, i) => {
const c = demanded[(start + i) % demanded.length];
return full?.find((source) => this.streamIdentity(source) === this.streamIdentity(c)) ?? c;
});
this.s.wordWindow = new Set(selected.map((c) => this.streamIdentity(c)));
let crossLive = [...this.streams.values()].filter((s) => s.crossNorms && !s.exhausted).length;
for (const c of selected) {
const exist = this.findStream((stream) => this.streamIdentity(stream) === this.streamIdentity(c));
if (exist) {
for (const n of c.attribution) if (!exist.attribution.includes(n)) exist.attribution.push(n);
if (c.groupIds?.length) exist.groupIds = [.../* @__PURE__ */ new Set([...exist.groupIds ?? [], ...c.groupIds])];
if (c.pageCap && !exist.pageCap && !exist.capLifted) {
exist.pageCap = c.pageCap;
if (exist.nextPage > c.pageCap) {
exist.exhausted = true;
exist.stopReason = "cap";
}
}
continue;
}
if (c.crossNorms && crossLive >= 8) continue;
if (c.crossNorms) crossLive++;
const stream = newDigStream({
key: c.key,
keyword: c.keyword,
attribution: c.attribution,
...c.groupIds && { groupIds: c.groupIds },
...c.pageCap && { pageCap: c.pageCap },
...c.crossNorms && { crossNorms: c.crossNorms }
}, this.bypassSeen() ? void 0 : digResumeFromSediment(sediment.map.get(c.key), c.keyword));
if (stream.pageCap && stream.nextPage > stream.pageCap) {
stream.exhausted = true;
stream.stopReason = "cap";
}
this.streams.set(this.streams.has(c.key) ? this.streamIdentity(c) : c.key, stream);
created++;
}
while (this.streams.size > LADDER_STREAMS_MAX) {
const victim = [...this.streams].find(([, s]) => !s.input && !this.s.wordWindow.has(this.streamIdentity(s)));
if (!victim) break;
this.streams.delete(victim[0]);
}
return created;
}
/** 仍有可挖的流 (未 exhausted) */
plannedStream(stream) {
if (this.s.wordWindow.size && !stream.input && stream.groupIds?.length && !this.s.wordWindow.has(this.streamIdentity(stream))) return;
if (!this.hooks.resolve) return stream;
const selected = planTagRecRecall({
roundSources: [],
digSources: [stream]
}, this.hooks.resolve()).dig[0];
if (!selected) return;
const scope = this.hooks.candidateScope?.();
if (scope?.all.size && !selected.consumers.some((c) => scope.needed.has(tagRecGroupKey(c.key)))) return;
return {
...stream,
...selected
};
}
hasDueStreams() {
for (const s of this.streams.values()) if (!s.exhausted && this.plannedStream(s) || this.expiredProbe(s)) return true;
return false;
}
/**
* 「重新发掘」(硬见底出口): 把当前全部深挖流复位到第 1 页, 清 exhausted/停止原因/冷却,
* 并清掉持久沉淀里对应的深挖游标 (票 36 —— 不清会在下次建流时把 exhausted 水合回来),
* 同时复位自动深挖的三停纪律 (早停/低产连停)。共现边与候选/存货一概不动。
* 请求仍有界: 由随后的一次 digPass 按既有窗口 (≤digStreamWindow 流 ×3 页) 与退避纪律执行。
*/
resetDigCursors() {
this.s.digCursorEpoch++;
this.s.digRunning = void 0;
for (const stream of this.streams.values()) {
stream.nextPage = 1;
stream.exhausted = false;
stream.stopReason = void 0;
stream.retryAt = void 0;
stream.okPages?.clear();
sedimentClearDig(sediment.map, stream.key);
}
this.s.earlyStopped = false;
this.s.earlyStopReason = void 0;
this.s.lowYieldStreak = 0;
}
/**
* D4 自动深挖门 (票 17 策略分家):
* 三停共用 —— 零产出早停 (earlyStopped) / 风控退避 (tagRecBackoff) / 无流可挖;
* 上限分策略 —— fixed: autoDigs {
if (this.s.digRunning === p) this.s.digRunning = void 0;
});
this.s.digRunning = p;
}
return this.s.digRunning;
}
/**
* 票 32: 深挖流间并行 —— 每条流内部 3 页本就并发, 总闸仍是下游 anonGetJson tagRecLimit(4);
* 拉取并行放, 副作用 (页游标/归因/sediment/出入队堆) 按流序串行落 (结果序不敏感, 保确定性)
*
* 票 38 A: due 流窗口 —— 单 pass 只挖 nextPage 升序头 DIG_STREAM_WINDOW 条 (页少先挖=公平轮转,
* 手动/自动同限; 单 pass all/v2 发数封顶 PHASE2_PAGE_BATCH×窗口), 窗外流留待下个 digPass;
* 票 38 D: 退避恢复后首个 pass 窗口减半探活 (按恢复时刻一次性消费, 挖完即过, pageCount 不变)
*/
async doDigPass(mode, signal) {
if (this.streams.size > LADDER_STREAMS_MAX) {
for (const [k, s] of this.streams) if (s.exhausted) this.streams.delete(k);
}
const due = [...this.streams.values()].filter((s) => !s.exhausted && s.nextPage <= (s.pageCap ?? 50) && this.plannedStream(s));
if (signal?.aborted) return 0;
if (!due.length) return mode === "manual" ? this.reprobeExpired(signal, true) : 0;
let windowSize = this.ladderOpts().digStreamWindow ?? 4;
const recoveredAt = tagRecBackoff.lastRecoveredAt ?? 0;
if (recoveredAt !== this.digRecoveryConsumedAt && tagRecBackoff.recentlyRecovered?.(1e4)) {
this.digRecoveryConsumedAt = recoveredAt;
windowSize = Math.max(1, Math.ceil(windowSize / 2));
}
const batch = due.sort((a, b) => a.nextPage - b.nextPage).slice(0, windowSize);
const requestPlans = new Map(batch.map((stream) => [stream, this.plannedStream(stream)]));
const revisionOf = (stream) => {
const dig = sediment.map.get(stream.key)?.dig;
return dig?.keyword === stream.keyword ? dig.revision ?? 0 : 0;
};
const revisions = new Map(batch.map((stream) => [stream, revisionOf(stream)]));
if (batch.some((stream) => !stream.input)) this.s.wordCursor += batch.filter((stream) => !stream.input).length;
const stockEpoch = this.s.stockEpoch;
const cursorEpoch = this.s.digCursorEpoch;
const paidPolicy = this.autoPolicy();
const current = () => this.s.stockEpoch === stockEpoch && this.s.digCursorEpoch === cursorEpoch;
if (mode === "auto") this.autoDigs++;
const batchPages = /* @__PURE__ */ new Map();
for (const stream of batch) {
const pageCap = stream.pageCap ?? 50;
const pages = [];
for (let pn = stream.nextPage; pn <= pageCap && pages.length < 3; pn++) if (!stream.okPages?.has(pn)) pages.push(pn);
if (pages.length) batchPages.set(stream, pages);
}
const settled = await Promise.all([...batchPages].map(([stream, pages]) => Promise.all(pages.map((page, i) => runPhase2Round({
keyword: stream.keyword,
inputNorm: stream.key,
matchNorms: stream.crossNorms,
anchor: stream.anchor,
fromPage: page,
pageCount: 1,
withSuggest: !!stream.withSuggest && stream.nextPage === 1 && i === 0,
resolveVariants: !!stream.withVariants,
signal
}).then((res) => ({
res,
page
}), (err) => ({
err,
page
})))).then((pageSettled) => ({
stream,
pageSettled
}))));
let yieldN = 0;
let attempted = 0;
let failed = 0;
for (const { stream, pageSettled } of settled) {
if (!current()) break;
if (revisionOf(stream) !== revisions.get(stream)) continue;
attempted++;
const requestSource = requestPlans.get(stream);
let gap = false;
let streamPagesFetched = 0;
let anyVideo = false;
const okPages = [];
const okPageNos = [];
for (const p of pageSettled) {
if ("err" in p) {
appWarn("[tag-rec] dig stream %s page failed: %o", stream.key, p.err);
gap = true;
continue;
}
if (p.res.allPagesFailed) {
gap = true;
continue;
}
okPages.push(p.res);
okPageNos.push(p.page);
streamPagesFetched += p.res.pagesFetched;
if (p.res.videos.length) anyVideo = true;
}
if (!okPages.length) {
failed++;
continue;
}
if (current()) {
const okBitmap = stream.okPages ??= /* @__PURE__ */ new Set();
for (const pn of okPageNos) okBitmap.add(pn);
while (okBitmap.delete(stream.nextPage)) stream.nextPage++;
this.pagesFetched += streamPagesFetched;
const pageCap = stream.pageCap ?? 50;
stream.exhausted = !gap && (stream.nextPage > pageCap || streamPagesFetched > 0 && !anyVideo);
stream.stopReason = stream.exhausted ? stream.nextPage > pageCap ? "cap" : "empty" : void 0;
stream.retryAt = stream.stopReason === "empty" ? Date.now() + tagRecDiscoveryPolicy(this.strategy.ladder?.()).emptyTtlMinutes * 6e4 : void 0;
if (!this.bypassSeen()) sediment.noteDig(stream.key, {
keyword: stream.keyword,
nextPage: stream.nextPage,
...stream.exhausted ? { exhausted: true } : {},
schemaVersion: 2,
stopReason: stream.stopReason,
capLifted: stream.capLifted,
retryAt: stream.retryAt,
expectedRevision: revisions.get(stream)
});
}
let streamStaged = 0;
for (const res of okPages) {
if (current()) {
if (stream.withVariants) this.hooks.mergeVariants?.(res.variantEntries);
this.hooks.mergeCandidates?.(res.newCandidates);
}
if (current()) {
const exact = this.normalizeAllV2(res.analysis.exact, "all-v2-exact", requestSource.attribution, requestSource.groupIds, stream.keyword, requestSource.input, tagRecConsumers(requestSource));
this.pushExactPool(exact);
const staged = this.hooks.applyStages(exact, {
poolSize: this.hooks.queueSize() + this.heap.length + exact.length,
poolItems: [
...this.hooks.poolItems?.() ?? [],
...exact,
...this.heap
],
mergeStats: true
});
this.hooks.pushQueue(staged);
streamStaged += staged.length;
const related = this.normalizeAllV2(res.analysis.related, "all-v2-related", requestSource.attribution.map((n) => `${n}·相关`), requestSource.groupIds, stream.keyword, requestSource.input, tagRecConsumers(requestSource));
this.pushHeap(related);
yieldN += staged.length + related.length;
}
for (const anchor of stream.crossNorms ?? [stream.key]) sediment.merge(anchor, res.analysis.cliques);
}
if (current() && stream.pageCap && !stream.capLifted && streamStaged >= 5) {
stream.pageCap = void 0;
stream.capLifted = true;
if (stream.stopReason === "cap") {
stream.exhausted = false;
stream.stopReason = void 0;
}
if (!this.bypassSeen()) sediment.noteDig(stream.key, {
keyword: stream.keyword,
nextPage: stream.nextPage,
schemaVersion: 2,
capLifted: true,
expectedRevision: revisionOf(stream)
});
}
}
if (!current() || !attempted) return yieldN;
this.lastDigYield = yieldN;
this.lastDigError = attempted > 0 && failed === attempted;
if (mode === "auto") {
if (!yieldN && !(attempted > 0 && failed === attempted)) {
this.earlyStopped = true;
this.s.earlyStopReason = batch.every((s) => s.stopReason === "empty") ? "empty" : "yield";
}
const p = paidPolicy;
if (p.mode === "adaptive") this.lowYieldStreak = yieldN > 0 && yieldN < p.lowYield ? this.lowYieldStreak + 1 : 0;
}
return yieldN;
}
/**
* 浅水位预挖 = digPass('auto') 一档, 非整条梯队:
* - prepareStreams 补登记深挖流同 D7 (组簇流借此前移成立; 无流 => 空转返回 0);
* - related 选种 (D2) / 兜底堆放行 (D3) 一律不跑 —— 梯队前段干轮专属, 不提前倒存货;
* - 深挖产出照常落 queue/heap/sediment: queue 货下轮直放, heap 留待干轮放行;
* - 门槛与深挖档完全同构 (canAutoDig: 早停/风控/策略上限);
* - 队列有货 (在途深挖刚落) 或在途挖 => 不重复挖 (digPass 单飞共享在途 promise)
*/
preDig({ signal, prepareStreams } = {}) {
prepareStreams?.();
if (this.hasDemandStock() || !this.canAutoDig()) return Promise.resolve(0);
return this.digPass("auto", signal);
}
/**
* 已知标签按共现取窗口;未知标签限量轮转补查,同共现再比质量。
* 排除已成功选过的 BV,显式质量门槛仍生效;空池、退避与失败不锁死后续重评。
* 有组库存信息时仅给缺货组补料,其他组的历史产出不占冷池资格。
*/
async maybeSeedRelated(signal, stockEpoch = this.s.stockEpoch) {
const current = () => this.s.stockEpoch === stockEpoch;
const archiveSeed = (item) => {
if (!this.hooks.resolve) return item;
const seed = {
...item,
source: {
...item.source,
via: "archive-related",
claims: (item.source.claims ?? [item.source]).map((claim) => ({
...claim,
via: "archive-related"
}))
},
eligibleGroupKeys: void 0,
eligiblePoolScopes: void 0
};
return projectTagRecActiveClaims(seed, this.hooks.resolve()) ? seed : void 0;
};
if (this.relatedSeeded) return;
if (tagRecBackoff.isEngaged) return;
if (this.hooks.relatedSeedingAllowed?.() === false) return;
const seedPoolMax = this.ladderOpts().seedPoolMax ?? LADDER_DEFAULTS.seedPoolMax;
const vocab = cooccurVocab(sediment.map, [...this.streams.keys()]);
if (!vocab.size) return;
const pool = [...new Map([
...this.poolItems,
...this.exactPoolItems,
...this.hooks.poolItems?.() ?? []
].map((i) => [i.bvid, i])).values()];
if (!pool.length) return;
const scope = this.hooks.candidateScope?.();
const seedResolve = this.hooks.resolve?.();
const available = pool.flatMap((it) => {
const seed = archiveSeed(it);
if (!seed || !this.matchesCandidateScope(seed, scope)) return [];
if (seedResolve) {
const intersectionEffs = tagRecChainKeysOf(it).map((key) => seedResolve(key, "allV2")).filter((eff) => eff.intersection && eff.combination);
if (intersectionEffs.length && !intersectionEffs.some((eff) => tagRecDeliverable(it, eff.combination) === "match")) return [];
}
const paid = this.s.seedRequests.get(it.bvid);
const freshOwner = paid && tagRecChainKeysOf(seed).some((key) => !paid.owners.has(tagRecGroupKey(key)));
return !this.s.seededBvids.has(it.bvid) || freshOwner ? [seed] : [];
});
this.rankItems(available);
const known = available.filter((it) => it.ownTagNames !== void 0).sort((a, b) => countCooccurDegree(b.ownTagNames, vocab) - countCooccurDegree(a.ownTagNames, vocab) || (b.qualitySortScore ?? 0) - (a.qualitySortScore ?? 0));
const unknown = available.filter((it) => it.ownTagNames === void 0);
const scanCount = Math.min(seedPoolMax, 30, unknown.length);
const start = unknown.length ? this.s.seedScanCursor % unknown.length : 0;
const rotated = Array.from({ length: scanCount }, (_, i) => unknown[(start + i) % unknown.length]);
const scanned = [...known.slice(0, seedPoolMax), ...rotated].map((it) => ({
it,
score: computeScore(it, this.hooks.scoreParams(it)).score
}));
if (!scanned.length) return;
const seedMinUnified = this.ladderOpts().seedMinScore ?? LADDER_DEFAULTS.seedMinScore;
let fetchBudget = 30;
const nameLists = await Promise.all(scanned.map(({ it, score }) => {
if (it.ownTagNames !== void 0) return it.ownTagNames;
const min = Math.max(seedMinUnified, this.hooks.seedMinScoreOf?.(it) ?? 0);
if (min > 0 && (score === void 0 || score < min)) return void 0;
if (fetchBudget <= 0 || tagRecBackoff.isEngaged) return void 0;
fetchBudget--;
return fetchVideoTagNames(it.bvid, signal).catch(() => void 0);
}));
if (!current()) return;
this.s.seedScanCursor = start + scanCount;
if (tagRecBackoff.isEngaged) return;
const candidates = [];
scanned.forEach(({ it, score }, i) => {
const names = nameLists[i];
if (names !== void 0) it.ownTagNames = names;
if (!names?.length) return;
const minScore = this.hooks.seedMinScoreOf?.(it) ?? 0;
if (minScore > 0 && (score === void 0 || score < minScore)) return;
candidates.push({
bvid: it.bvid,
cooccur: countCooccurDegree(names, vocab),
score
});
});
const byBvid = new Map(available.map((i) => [i.bvid, i]));
const seedBvids = selectCooccurSeeds(candidates, {
count: this.ladderOpts().seedCount ?? LADDER_DEFAULTS.seedCount,
minScore: this.ladderOpts().seedMinScore ?? LADDER_DEFAULTS.seedMinScore
});
const raws = await Promise.all(seedBvids.map((bvid) => {
const paid = this.s.seedRequests.get(bvid);
if (paid) return paid.promise;
const entry = {
promise: Promise.resolve(void 0),
owners: /* @__PURE__ */ new Set(),
settled: false,
size: 1
};
entry.promise = fetchArchiveRelated(bvid, { signal }).then((v) => v, () => void 0).then((raw) => {
entry.settled = true;
entry.size = Math.max(1, raw?.length ?? 0);
if (!raw && this.s.seedRequests.get(bvid) === entry) this.s.seedRequests.delete(bvid);
this.trimSeedRequests();
return raw;
});
this.s.seedRequests.set(bvid, entry);
this.trimSeedRequests();
return entry.promise;
}));
if (!current()) return;
seedBvids.forEach((bvid, i) => {
const res = raws[i];
if (!res) return;
this.s.seededBvids.add(bvid);
const seed = byBvid.get(bvid);
for (const key of seed ? tagRecChainKeysOf(seed) : []) this.s.seedRequests.get(bvid)?.owners.add(tagRecGroupKey(key));
const seedClaims = seed?.activeSourceClaims ?? seed?.source.claims;
const consumers = (seedClaims ?? []).flatMap(tagRecConsumers);
const owners = consumers.length ? tagRecConsumerSource(consumers) : seed?.source;
const names = owners?.groupNames.map((n) => `${n}·相关`) ?? [];
this.pushHeap(this.normalizeArchiveRelated(res, names, owners?.groupIds, seed?.source.input, seedClaims));
});
}
/**
* 每干轮排序一次,依次检查物理页,首批非空即放行;全部被拒才耗尽。
* 按当前算分参数 (逐条按所属池槽, 票05) 重排堆 => 取页 => 经同一管线
* (滤除原料由 service 候补缓存保留)。applyStages 注入未消费原料池计算组×来源池深 ——
* 页长 ≤fallbackPage 是切片伪影, 堆深才是真池规模
*/
releasePage(epoch = this.s.epoch) {
if (!this.heap.length) return [];
if (this.s.epoch !== epoch) return [];
this.rankItems(this.heap);
this.heap.sort(compareFallbackItems);
const pageSize = this.ladderOpts().fallbackPage ?? LADDER_DEFAULTS.fallbackPage;
const heap = this.heap;
const scope = this.hooks.candidateScope?.();
const eligible = scope ? heap.filter((it) => this.matchesCandidateScope(it, scope)) : heap;
const depth = eligible.length;
let consumed = 0;
const used = /* @__PURE__ */ new Set();
try {
while (consumed < depth) {
if (this.s.epoch !== epoch) return [];
const poolSize = depth - consumed;
const page = eligible.slice(consumed, consumed + pageSize);
consumed += page.length;
const items = this.hooks.applyStages(page, {
poolSize,
poolItems: eligible.slice(consumed - page.length),
mergeStats: true
});
if (this.s.epoch !== epoch) return [];
for (const item of page) used.add(item);
if (items.length) return items;
}
return [];
} finally {
if (this.s.epoch === epoch && this.heap === heap) {
for (let i = heap.length - 1; i >= 0; i--) if (used.has(heap[i])) heap.splice(i, 1);
}
}
}
/**
* 干轮编排 (service fetchMore 主池产出 0 后调用):
* 等在途深挖 => prepareStreams 补簇流 => 选种 => 放行 => 自动深挖 => 手动待决/硬见底
* 队列有货 (在途深挖刚落) 直接判放行轮, 不重复挖
*/
runLadder(options = {}) {
if (options.signal?.aborted) return Promise.resolve({ kind: "aborted" });
return tagRecRecallScheduler.withForegroundSupply(() => this.runForegroundLadder(options));
}
async runForegroundLadder({ signal, prepareStreams } = {}) {
const epoch = this.s.epoch;
const cursorEpoch = this.s.digCursorEpoch;
const current = () => this.s.epoch === epoch && this.s.digCursorEpoch === cursorEpoch;
if (signal?.aborted) return { kind: "aborted" };
if (this.hasDemandStock()) return {
kind: "items",
items: []
};
if (this.digRunning) {
await waitForAbortable(this.digRunning, signal).catch(() => void 0);
if (signal?.aborted) return { kind: "aborted" };
if (!current()) return { kind: "aborted" };
}
if (signal?.aborted || !current()) return { kind: "aborted" };
if (this.hasDemandStock()) return {
kind: "items",
items: []
};
prepareStreams?.();
if (signal?.aborted || !current()) return { kind: "aborted" };
if (this.hasDemandStock()) return {
kind: "items",
items: []
};
await this.reprobeExpired(signal);
if (signal?.aborted || !current()) return { kind: "aborted" };
if (this.hasDemandStock()) return {
kind: "items",
items: []
};
await this.maybeSeedRelated(signal, this.s.stockEpoch);
if (signal?.aborted || !current()) return { kind: "aborted" };
let items = this.releasePage(epoch);
if (signal?.aborted || !current()) return { kind: "aborted" };
if (!items.length && !this.hasDemandStock() && this.canAutoDig()) {
await this.digPass("auto", signal);
if (signal?.aborted || !current()) return { kind: "aborted" };
items = this.releasePage(epoch);
}
if (signal?.aborted || !current()) return { kind: "aborted" };
if (items.length || this.hasDemandStock()) return {
kind: "items",
items
};
if (this.hasDueStreams()) return { kind: "manual-pending" };
if (this.findStream((stream) => {
const source = this.plannedStream(stream);
if (!source || !this.hooks.resolve) return false;
const dig = sediment.map.get(stream.key)?.dig;
return planTagRecRecall({
roundSources: [],
digSources: [source]
}, this.hooks.resolve()).dig.flatMap((s) => [...s.exactConsumers.map((c) => tagRecPoolScopeKey(c.key, "allV2")), ...s.relatedConsumers.map((c) => tagRecPoolScopeKey(c.key, "allV2Related"))]).some((scope) => {
const probe = dig?.probes?.[scope];
return probe && probe.empty === void 0 && Date.now() < probe.retryAt;
});
})) return {
kind: "retry-later",
reason: "上次重查失败,正在冷却;可稍后刷新或选择定向重查"
};
const plan = this.hooks.recallPlan?.();
if (plan?.archiveConsumers.length && !plan.tagTop.length && !plan.up.length && !plan.dig.length && (!cooccurVocab(sediment.map, [...this.streams.keys()]).size || !(this.hooks.poolItems?.().length || this.poolItems.length || this.exactPoolItems.length))) return {
kind: "source-unavailable",
reason: "推荐相关需要已有种子和标签词表;可开启标签池或搜索池"
};
return { kind: "hard-bottom" };
}
get phase2() {
return {
status: this.digRunning ? "running" : this.lastDigError ? "error" : this.pagesFetched > 0 ? "done" : "idle",
pagesFetched: this.pagesFetched,
exhausted: this.streams.size > 0 && !this.hasDueStreams(),
autoDigs: this.autoDigs,
lastDigYield: this.lastDigYield
};
}
normalizeAllV2(items, via, groupNames, groupIds, queryKeyword, input, consumers) {
const out = [];
for (const it of items) {
if (!it.bvid) continue;
const item = normalizeAllV2Item(it, {
via,
groupNames,
groupIds,
srcRank: out.length
});
item.source.queryKeyword = queryKeyword;
item.source.input = input;
item.source.claims = consumers?.map((c) => ({
...tagRecConsumerSource([c]),
via,
queryKeyword
}));
const candidate = this.acceptNormalized(item);
if (candidate) out.push(candidate);
}
return out;
}
normalizeArchiveRelated(items, groupNames, groupIds, input, claims) {
const out = [];
for (const it of items) {
if (!it.bvid) continue;
const item = normalizeTagTopItem(it, {
via: "archive-related",
groupNames,
groupIds,
srcRank: out.length
});
item.source.input = input;
if (claims?.length) {
item.source.claims = claims.map((claim) => ({
...claim,
via: "archive-related"
}));
item.source.queryKeyword = claims.find((c) => c.queryKeyword)?.queryKeyword;
}
const candidate = this.acceptNormalized(item);
if (candidate) out.push(candidate);
}
return out;
}
acceptNormalized(item) {
if (this.hooks.resolve && !projectTagRecActiveClaims(item, this.hooks.resolve())) {
if (!this.hooks.seenBvids.has(item.bvid)) this.hooks.observe?.([item]);
else this.hooks.mergeExisting?.(item);
return;
}
if (!this.hooks.seenBvids.has(item.bvid)) {
this.hooks.seenBvids.add(item.bvid);
return item;
}
const heapHit = this.heapBvidIndex.map().get(item.bvid);
const at = heapHit ? this.heap.indexOf(heapHit) : -1;
if (at !== -1) {
const existing = mergeTagRecCandidate(this.heap[at], item);
if (item.source.via === "all-v2-exact") {
this.heap.splice(at, 1);
return existing;
}
return;
}
return this.hooks.mergeExisting?.(item);
}
});
/**
* [tag-rec] 负向校验的两条异步路径 (票 14 决议: 负向成员绑定标签组)
*
* 同步两条免费路径在 filter-engine 黑名单维 (all/v2 自报名名单匹配 + 负向池交集):
* - all/v2 条目自带 tag 名名单 (ownTagNames), 按名匹配源组负向成员 —— 零请求
* - 负向池交集: 本模块会话级维护 tid -> bvid 集合快照 (tag/top 浅池), fetchMore 预热
*
* 本模块仅承担严格校验路径: 组开关 negative_strict 开启的源组条目,
* 出队切片逐条 view/detail/tag 核查 (每上屏视频至多 1 次匿名请求, 会话缓存+弹层共用)
* - 退避态 (tagRecBackoff 已 engaged) => 整切片直通不校验 (风控可疑期零新增压力)
* - 常态加 STRICT_BUDGET_MS 时间盒, 超时未完成的条目 fail-open 放行
* - 单条拉取失败 => fail-open 放行; 未校验条数经 unchecked 回报
*/
var NEG_POOL_DEDUPE_MS = 3e3;
/** 票 39: 空负向池 TTL (票面区间 10~30min 取 15min) —— 真空池也沉淀, 截停每轮恒定 1 发滴漏 */
var NEG_POOL_EMPTY_TTL_MS = 9e5;
/**
* A4-7: 非空负向池 TTL —— 恒沉淀期负向 tag 中途上新的 bvid 永远进不了交集快照;
* 到期经 ensure 既有路径重探一轮 (1 发/tid 有界), 空集仍走短 TTL 快回填
*/
var NEG_POOL_TTL_MS = 36e5;
var STRICT_BUDGET_MS = 5e3;
/**
* 显式严格标签名单: 仅上屏切片中缺标签的视频补查, 与右键浮窗共享缓存/在途。
* 补齐后同时复核两侧标签名单及组内负向, 防「仅看补查」绕过屏蔽。
* 失败/退避/5s 时间盒沿用组内严格校验的放行 + 未校验计数。
*/
async function dropTagRecStrictTagListHits(items, core, resolve, signal, queryMatches, allowFetch = true, budget) {
const stats = {
dropped: 0,
byDim: {},
byScope: {},
degraded: false
};
const checkedBvids = /* @__PURE__ */ new Set();
if (!core.enabled) return {
items,
stats,
checkedBvids
};
const pools = tagRecNegativePools();
const videos = items.flatMap((item, at) => {
if (item.api !== EApiType.TagRec) return [];
let strict = false;
for (const { key, pool } of tagRecChainKeysOf(item).flatMap((key) => tagRecPoolsForChain(item, key).map((pool) => ({
key,
pool
})))) {
const eff = resolve(key, pool);
const needsCheck = eff.cfg.enabled && (eff.neg.strict && eff.neg.tagIds.length > 0 || eff.neg.tagBlock?.some((rule) => rule.strict) || eff.neg.tagAllow?.strict);
strict ||= !!needsCheck;
if (evalTagRecChain(item, eff, {
poolSize: item.filterPoolSizes?.[tagRecPoolScopeKey(key, pool)] ?? item.filterPoolSize,
sourcePool: pool,
negative: { pools },
onlyTagLists: item.filterPoolSize === void 0
}) !== null) continue;
if (!needsCheck) return [];
}
return strict ? [{
item,
at
}] : [];
});
if (!videos.length) return {
items,
stats,
checkedBvids
};
const pending = videos.filter(({ item }) => item.ownTagIds === void 0 && (item.ownTagNames === void 0 || tagRecChainKeysOf(item).some((key) => tagRecPoolsForChain(item, key).some((pool) => {
const eff = resolve(key, pool);
return eff.neg.strict && eff.neg.tagIds.length > 0;
}))));
for (const { item } of pending) if (item.bvid) checkedBvids.add(item.bvid);
const checked = /* @__PURE__ */ new Map();
let unchecked = 0;
const applyTags = (item, at, tags) => checked.set(at, {
...item,
ownTagNames: tags.map((t) => t.tag_name).filter((name) => name !== void 0),
ownTagIds: tags.map((t) => t.tag_id).filter((id) => id !== void 0)
});
if (allowFetch && pending.length && !signal?.aborted && tagRecBackoff.currentIntervalMs === 0) {
if (budget) {
const requests = /* @__PURE__ */ new Map();
await Promise.all(pending.map(async ({ item, at }) => {
let request = item.bvid ? requests.get(item.bvid) : void 0;
if (item.bvid && !request) {
request = budget.lookupTags(item.bvid);
requests.set(item.bvid, request);
}
const tags = await request;
if (tags === void 0) {
unchecked++;
return;
}
applyTags(item, at, tags);
}));
} else {
const controller = new AbortController();
const onAbort = () => controller.abort();
signal?.addEventListener("abort", onAbort, { once: true });
const timer = setTimeout(() => controller.abort(), STRICT_BUDGET_MS);
try {
const requests = /* @__PURE__ */ new Map();
await Promise.all(pending.map(async ({ item, at }) => {
let request = item.bvid ? requests.get(item.bvid) : void 0;
if (item.bvid && !request) {
request = fetchVideoTags(item.bvid, { signal: controller.signal }).catch(() => void 0);
requests.set(item.bvid, request);
}
const tags = await request;
if (tags === void 0) {
unchecked++;
return;
}
applyTags(item, at, tags);
}));
} finally {
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
}
}
} else unchecked = pending.length;
stats.unchecked = unchecked || void 0;
if (signal?.aborted) return {
items: [],
stats,
checkedBvids
};
const checkable = new Set(videos.map(({ at }) => at));
return {
items: items.flatMap((item, at) => {
if (item.api !== EApiType.TagRec || !checkable.has(at)) return [item];
const video = checked.get(at) ?? item;
const verdict = evalTagRecItem(video, resolve, {
queryMatches,
negative: { pools },
poolSize: video.filterPoolSize,
poolSizes: video.filterPoolSizes,
onlyTagLists: video.filterPoolSize === void 0,
confirmPartitions: true
});
if (verdict.pass) return [video];
const { dim, scope } = verdict.drop;
stats.dropped++;
stats.byDim[dim] = (stats.byDim[dim] ?? 0) + 1;
const key = `${dim}@${scope}`;
stats.byScope[key] = (stats.byScope[key] ?? 0) + 1;
return [];
}),
stats,
checkedBvids
};
}
var pools = /* @__PURE__ */ new Map();
var poolFetch = new SessionFetchCache({
dedupeMs: NEG_POOL_DEDUPE_MS,
isCacheable: () => true
});
/** 过滤维读的负向池快照视图 (fetchMore 预热后直接同步读); 空池条目读时过期即弃 */
function tagRecNegativePools() {
const now = Date.now();
const view = /* @__PURE__ */ new Map();
for (const [tid, e] of pools) {
if (e.expiresAt <= now) {
pools.delete(tid);
continue;
}
view.set(tid, e.set);
}
return view;
}
/** 预热负向池: 每个负向 tid 一次 tag/top; 已缓存/在途自动去重; 空池 TTL 到期弃快照重探一轮 */
async function ensureTagRecNegativePools(tids, signal) {
const load = (tid) => poolFetch.fetch(tid, async (entrySignal) => {
const items = await fetchTagTop(tid, { signal: entrySignal });
const set = new Set(items.map((i) => i.bvid).filter((b) => !!b));
const entry = {
set,
expiresAt: Date.now() + (set.size ? NEG_POOL_TTL_MS : NEG_POOL_EMPTY_TTL_MS)
};
pools.set(tid, entry);
return entry;
}, signal).catch(() => void 0);
await Promise.all([...new Set(tids)].map(async (tid) => {
const hit = await load(tid);
if (hit && hit.expiresAt <= Date.now()) {
poolFetch.invalidate(tid);
pools.delete(tid);
await load(tid);
}
}));
}
function hasTagRecPartitionRules(items, resolve) {
return items.some((item) => item.api === EApiType.TagRec && tagRecChainKeysOf(item).some((key) => tagRecPoolsForChain(item, key).some((pool) => {
const eff = resolve(key, pool);
return !!eff.combination || !!eff.globalExclusions?.length || !!eff.globalExclusionsInvalid || eff.cfg.enabled && (eff.neg.partitionBlock.length > 0 || !!eff.neg.partitionAllow?.list.length);
})));
}
/** 先处理尚未核验的库存;都处理过后再重试待检,不让排序反复推回失败条目。 */
function tagRecPartitionWorkQueue(queue, held, isStaleContext, heldRetryQuota = 0, canRetryHeld) {
const fresh = [];
const heldRetry = [];
for (const item of queue) {
if (item.api !== EApiType.TagRec) {
fresh.push(item);
continue;
}
if (isStaleContext?.(item)) continue;
if (!held.has(item.bvid)) {
fresh.push(item);
continue;
}
const cached = getCachedTagRecPartition(item.bvid);
if (cached?.tidv2 !== void 0 && cached.tidv2 !== item.tidv2 || cached?.pid_v2 !== void 0 && cached.pid_v2 !== item.pid_v2) fresh.push(item);
else if (canRetryHeld?.(item.bvid) !== false) heldRetry.push(item);
}
if (!fresh.length) {
const rest = isStaleContext ? queue.filter((item) => item.api !== EApiType.TagRec || !isStaleContext(item)) : queue;
if (canRetryHeld) return rest.filter((item) => item.api !== EApiType.TagRec || !held.has(item.bvid) || canRetryHeld(item.bvid));
return [...rest];
}
if (heldRetryQuota > 0) fresh.push(...heldRetry.slice(0, heldRetryQuota));
return fresh;
}
/**
* 票 005: 停牌账目推进 —— 本轮被暂停且**真核查过**的条目尝试数 +1 (封顶钳在上限)。
* `attempted` = 本切片真发过请求的 bvid (checkTagRecPartitionSlice 经 budget 记账回传):
* 闸挡/skipLookup 根本没查的条目 +0 —— "连扑"只算真烧过请求的轮次, 不冤枉等待位。
* 返回的是全量当前账目 (rePended 之外仍活着的条目原数携带)。
*/
function tagRecPartitionHeldNext(prev, rePended, remaining, maxAttempts, attempted) {
const next = /* @__PURE__ */ new Map();
for (const [bvid, attempts] of prev) if (remaining.has(bvid)) next.set(bvid, attempts);
for (const item of rePended) {
if (item.api !== EApiType.TagRec) continue;
const cur = next.get(item.bvid) ?? prev.get(item.bvid) ?? 0;
next.set(item.bvid, attempted.has(item.bvid) ? Math.min(cur + 1, maxAttempts) : cur);
}
return next;
}
/** 同一次刷新补足、切片补位共用预算,避免外层循环不断重新取得预算。 */
var TagRecPartitionBudget = exports("a",class {
signal;
limits;
controller = new AbortController();
timer;
/** 去重表: 同 BV 并发/跨 pass 复用同一承诺; 条目留存与「是否付费」无关 (spent 单独记账) */
requests = /* @__PURE__ */ new Map();
evidenceRequests = /* @__PURE__ */ new Map();
tagRequests = /* @__PURE__ */ new Map();
/** 已实际发出的取数承诺数 —— 三种核验通道共用同一额度 (审计 §1); 持久/会话命中不计 */
spent = 0;
checked = 0;
onAbort = () => this.controller.abort();
constructor(signal, limits = {
requests: 20,
candidates: 40,
ms: 5e3
}) {
this.signal = signal;
this.limits = limits;
if (signal.aborted) this.controller.abort();
else signal.addEventListener("abort", this.onAbort, { once: true });
}
get exhausted() {
return this.controller.signal.aborted || this.spent >= this.limits.requests || this.checked >= this.limits.candidates;
}
countCandidate() {
this.checked++;
}
lookup(bvid) {
const known = getCachedTagRecPartition(bvid);
if (known?.tidv2 !== void 0) return Promise.resolve(known);
const shared = this.requests.get(bvid);
if (shared) return shared;
if (this.exhausted || tagRecBackoff.currentIntervalMs > 0) return Promise.resolve(void 0);
this.timer ??= setTimeout(this.onAbort, this.limits.ms);
this.spent++;
const request = fetchVideoPartition(bvid, { signal: this.controller.signal }).catch(() => void 0);
this.requests.set(bvid, request);
return request;
}
lookupEvidence(bvid) {
const known = getCachedTagRecVideoEvidence(bvid);
if (known) return Promise.resolve(known);
const shared = this.evidenceRequests.get(bvid);
if (shared) return shared;
if (this.exhausted || tagRecBackoff.currentIntervalMs > 0) return Promise.resolve(void 0);
this.timer ??= setTimeout(this.onAbort, this.limits.ms);
this.spent++;
const request = fetchTagRecVideoEvidence(bvid, this.controller.signal).catch(() => void 0);
this.evidenceRequests.set(bvid, request);
return request;
}
/**
* 严格标签核查的逐视频 tag 名单拉取 —— 与分区/素材核验共享整次刷新请求预算 (审计 §1):
* 持久层完整命中免费; 预算耗尽/退避态 => undefined (调用侧按未校验 fail-open)。
* 同步占位 + spent 记账: 同 BV 并发只发一次, 免费命中不烧额度。
* 传输层经整包 /view/detail 共享在途 (§8-2): 与分区/证据补查同 BV 在途合一,
* 严格核查自己付费的那次也把分区字段+简介证据一并入库。
*/
lookupTags(bvid) {
const shared = this.tagRequests.get(bvid);
if (shared) return shared;
const request = (async () => {
const persisted = await getPersistedVideoTags(bvid).catch(() => void 0);
if (persisted !== void 0 && hasCompleteTagIds(persisted)) return persisted;
if (this.exhausted || tagRecBackoff.currentIntervalMs > 0) return void 0;
this.timer ??= setTimeout(this.onAbort, this.limits.ms);
this.spent++;
return fetchVideoTags(bvid, { signal: this.controller.signal }).catch(() => void 0);
})();
this.tagRequests.set(bvid, request);
return request;
}
dispose() {
clearTimeout(this.timer);
this.signal.removeEventListener("abort", this.onAbort);
}
/** 票 005: 本预算生命周期内真发过请求的 bvid (闸挡/缓存命中不进) —— 持账"真尝试"判据 */
requestedBvids() {
return /* @__PURE__ */ new Set([
...this.requests.keys(),
...this.evidenceRequests.keys(),
...this.tagRequests.keys()
]);
}
});
/** 分区是三态:命中交付,不符滤除,未知留在库存;不占公平调度的交付名额。 */
async function checkTagRecPartitionSlice(items, resolve, budget, ctx = {}) {
const stats = {
dropped: 0,
byDim: {},
byScope: {},
degraded: false
};
const requestedBefore = budget.requestedBvids();
const results = /* @__PURE__ */ new Map();
const evaluate = (item) => {
const context = {
...ctx,
poolSize: item.filterPoolSize,
poolSizes: item.filterPoolSizes
};
const possible = evalTagRecItem(item, resolve, {
...context,
deferPartitions: true
});
if (!possible.pass) return {
state: "reject",
drop: possible.drop
};
if (evalTagRecItem(item, resolve, {
...context,
deferPartitions: false,
confirmPartitions: true
}).pass) {
if (tagRecChainKeysOf(item).some((key) => tagRecPoolsForChain(item, key).some((pool) => resolve(key, pool).allowUnverified))) {
const verified = evalTagRecItem(item, (key, pool) => ({
...resolve(key, pool),
allowUnverified: false
}), {
...context,
deferPartitions: false,
confirmPartitions: true
});
if (!verified.pass && verified.drop?.dim === "combination") item.combinationUnverified = true;
else delete item.combinationUnverified;
evalTagRecItem(item, resolve, {
...context,
deferPartitions: false,
confirmPartitions: true
});
} else delete item.combinationUnverified;
return { state: "pass" };
}
evalTagRecItem(item, resolve, {
...context,
deferPartitions: true
});
return { state: "pending" };
};
const videos = items.filter((item) => item.api === EApiType.TagRec);
let cursor = 0;
await Promise.all(Array.from({ length: Math.min(2, videos.length) }, async () => {
while (cursor < videos.length) {
const original = videos[cursor++];
let item = mergeTagRecVideoEvidence(mergeTagRecPartitionFields(original, getCachedTagRecPartition(original.bvid) ?? {}), getCachedTagRecVideoEvidence(original.bvid));
let verdict = evaluate(item);
if (verdict.state === "pending") {
const cells = tagRecChainKeysOf(item).flatMap((key) => tagRecPoolsForChain(item, key).map((pool) => resolve(key, pool)));
const combination = cells.some((eff) => !!eff.combination || !!eff.globalExclusions?.length);
const fields = item.source.via === "archive-related" && cells.length > 0 && cells.every((eff) => eff.intersection) ? void 0 : combination ? await budget.lookupEvidence(item.bvid) : await budget.lookup(item.bvid);
if (fields) {
item = combination ? mergeTagRecVideoEvidence(item, fields) : mergeTagRecPartitionFields(item, fields);
verdict = evaluate(item);
}
}
if (hasTagRecPartitionRules([item], resolve)) budget.countCandidate();
if (verdict.state === "reject" && verdict.drop) {
stats.dropped++;
const { dim, scope } = verdict.drop;
stats.byDim[dim] = (stats.byDim[dim] ?? 0) + 1;
const key = `${dim}@${scope}`;
stats.byScope[key] = (stats.byScope[key] ?? 0) + 1;
}
results.set(original, {
item,
state: verdict.state
});
}
}));
const attempted = /* @__PURE__ */ new Set();
for (const bvid of budget.requestedBvids()) if (!requestedBefore.has(bvid)) attempted.add(bvid);
return {
items: items.flatMap((original) => {
const result = results.get(original);
return !result ? [original] : result.state === "pass" ? [result.item] : [];
}),
pending: new Map([...results].filter(([, result]) => result.state === "pending").map(([original, result]) => [original, result.item])),
updated: [...results.values()].map(({ item }) => item),
attempted,
stats
};
}
/** 交付前更新未展示库存;已看阶段不重跑,分隔符保留,筛选和排序采用同一当前配置。 */
function refilterTagRecStock(queue, core, resolve, opts = {}) {
if (opts.candidates) opts.candidates.needsRefilter = true;
const queued = queue.filter((item) => item.api === EApiType.TagRec);
opts.candidates?.absorbRetouched();
const byBvid = new Map(opts.candidates?.values().map((item) => [item.bvid, item]));
for (const item of queued) byBvid.set(item.bvid, item);
const videos = [...byBvid.values()];
if (!videos.length) {
if (opts.candidates) opts.candidates.needsRefilter = false;
return;
}
const result = runTagRecFilter(videos, {
resolve,
pools: opts.pools,
queryMatches: opts.queryMatches,
poolSize: videos.length,
poolItems: videos,
deferPartitions: true,
relatedFallback: opts.relatedFallback
});
const filtered = result.items;
const queuedIds = new Set(queued.map((item) => item.bvid));
const passedSet = new Set(filtered);
const restored = (opts.recover ?? ((items) => items))(filtered.filter((item) => !queuedIds.has(item.bvid)));
const passed = [...filtered.filter((item) => queuedIds.has(item.bvid)), ...restored];
result.stats.seenDropped = filtered.length - passed.length || void 0;
const failed = queued.filter((item) => !passedSet.has(item));
if (failed.length) opts.candidates?.record(failed);
else opts.candidates?.flushMerged();
const keep = new Set(passed);
const scored = runTagRecScoreStage(passed, core, {
resolve,
diversityGuard: opts.diversityGuard,
sliceSize: opts.sliceSize
}).items;
let at = 0;
const updated = [];
const placed = /* @__PURE__ */ new Set();
for (const item of queue) if (item.api !== EApiType.TagRec) updated.push(item);
else if (keep.has(item) && !placed.has(item.bvid)) {
placed.add(item.bvid);
updated.push(scored[at++]);
}
while (at < scored.length) updated.push(scored[at++]);
queue.splice(0, queue.length, ...updated);
if (opts.candidates) opts.candidates.needsRefilter = false;
return result.stats;
}
/**
* [tag-rec] 轮次管线「去已看」阶段 (spec §3.4 / §4.5; 实装归票 08)
* - bypassSeen 关 => 滤掉 seen-set 命中条目 (已看不再入队)
* - bypassSeen 开 => 直通 (不删 set, 会话 flag 刷新即丢)
* - bypass 取值按调用方 scope: 主题推荐读 tagRecFavSession / 标签发掘读 tagRecCustomSession
* - 黑名单条目渲染前已被过滤管线滤除 => 不计已看, 本阶段不另处理 (§4.5)
* 纯判定逻辑在 model/seen.ts (filterTagRecSeenItems), 本文件只做 store 绑定
*/
var tagRecSeenStage = exports("E",(items, bypassSeen) => filterTagRecSeenItems(items, (bvid) => seen.has(bvid), bypassSeen));
/** 显示已看跳过数,让“滤除 0 条但没有结果”能解释清楚;旁路批沿用过滤阶段的累计账。 */
function createTagRecSeenStage(target, bypassSeen) {
return (items) => {
const out = tagRecSeenStage(items, bypassSeen());
target.filterStats.seenDropped = (target.filterStats.seenDropped ?? 0) + items.length - out.length || void 0;
return out;
};
}
/** 池深与可交付库存用不同分母;每个组及组×来源内按实际 BV 去重。 */
function tagRecSupplyView(raw, stock, resolve, lowWater = 10) {
const depth = /* @__PURE__ */ new Map();
const inventory = /* @__PURE__ */ new Map();
const groups = /* @__PURE__ */ new Map();
const add = (map, key, bvid) => {
const bucket = map.get(key) ?? /* @__PURE__ */ new Set();
bucket.add(bvid);
map.set(key, bucket);
};
for (const item of raw) {
projectTagRecActiveClaims(item, resolve);
for (const key of tagRecChainKeysOf(item)) for (const pool of tagRecPoolsForChain(item, key)) add(depth, tagRecPoolScopeKey(key, pool), item.bvid);
}
for (const item of stock) {
projectTagRecActiveClaims(item, resolve);
for (const key of tagRecChainKeysOf(item)) {
const group = tagRecGroupKey(key);
if (item.eligibleGroupKeys && !item.eligibleGroupKeys.includes(group)) continue;
for (const pool of tagRecPoolsForChain(item, key)) {
const scope = tagRecPoolScopeKey(key, pool);
if (item.eligiblePoolScopes && !item.eligiblePoolScopes.includes(scope)) continue;
add(inventory, scope, item.bvid);
add(groups, group, item.bvid);
}
}
}
return {
depth,
inventory,
groups,
/** 可交付库存总条数 (队列内 TagRec 条目, bvid 唯一) —— 高水位闸输入 */
stockTotal: stock.length,
needs: (key) => (groups.get(key)?.size ?? 0) < lowWater
};
}
/** 两个服务分别持有共享分页态;页面缓存与网络节流在 API 层跨服务共用。 */
var TagRecUpSearchPool = exports("T",class TagRecUpSearchPool {
static PAGE_BUDGET = 3;
pages = /* @__PURE__ */ new Map();
sources = /* @__PURE__ */ new Map();
budget = TagRecUpSearchPool.PAGE_BUDGET;
cursor = 0;
epoch = 0;
pendingLoad;
active = false;
reset(preserveBudget = false) {
this.pages.clear();
this.sources.clear();
if (!preserveBudget) this.budget = TagRecUpSearchPool.PAGE_BUDGET;
this.cursor = 0;
this.pendingLoad = void 0;
this.epoch++;
this.active = false;
}
key(source) {
return JSON.stringify([source.mid, source.keyword]);
}
hasMore(sources) {
for (const source of sources) this.sources.set(this.key(source), source);
return sources.some((s) => !this.pages.get(this.key(s))?.exhausted);
}
sync(sources) {
this.sources = new Map(sources.map((source) => [this.key(source), source]));
this.active = sources.length > 0;
}
invalidate() {
this.pendingLoad = void 0;
this.epoch++;
}
snapshot() {
return {
pages: new Map([...this.pages].map(([k, v]) => [k, {
...v,
seen: new Set(v.seen)
}])),
sources: new Map(this.sources),
budget: this.budget,
cursor: this.cursor,
active: this.active
};
}
restore(snap) {
this.pages = new Map([...snap.pages].map(([k, v]) => [k, {
...v,
seen: new Set(v.seen)
}]));
this.sources = new Map(snap.sources);
this.budget = snap.budget;
this.cursor = snap.cursor;
this.active = snap.active;
this.pendingLoad = void 0;
this.epoch++;
}
get hasAnyMore() {
return this.hasMore([...this.sources.values()]);
}
get pending() {
return this.active && this.budget <= 0;
}
continue() {
this.budget = TagRecUpSearchPool.PAGE_BUDGET;
}
/** Serialize shared-pool loads; transport dedupe alone cannot protect cursor/budget commits. */
load(sources, signal, isCurrent) {
const epoch = this.epoch;
const run = () => {
if (signal?.aborted || epoch !== this.epoch || isCurrent?.() === false) return Promise.resolve([]);
return this.loadNext(sources, signal, isCurrent);
};
const task = this.pendingLoad ? this.pendingLoad.then(run, run) : run();
this.pendingLoad = task;
const clear = () => {
if (this.pendingLoad === task) this.pendingLoad = void 0;
};
task.then(clear, clear);
return waitForAbortable(task, signal);
}
async loadNext(sources, signal, isCurrent) {
this.hasMore(sources);
if (!sources.length || !this.budget || signal?.aborted) return [];
this.active = true;
const open = sources.filter((s) => !this.pages.get(this.key(s))?.exhausted);
if (!open.length) return [];
const source = open[this.cursor++ % open.length];
const key = this.key(source);
const state = this.pages.get(key) ?? {
next: 1,
exhausted: false,
seen: /* @__PURE__ */ new Set()
};
const epoch = this.epoch;
this.budget--;
let page;
try {
page = await fetchUpSearchPage(source.mid, source.keyword, state.next, signal);
} catch (err) {
if (epoch === this.epoch && !signal?.aborted && isCurrent?.() !== false) this.budget = 0;
throw err;
}
if (signal?.aborted || epoch !== this.epoch || isCurrent?.() === false) return [];
const fresh = page.items.filter((it) => {
if (!it.bvid || state.seen.has(it.bvid)) return false;
state.seen.add(it.bvid);
return true;
});
state.exhausted = !page.hasMore || !page.items.length || !fresh.length;
state.next++;
this.pages.set(key, state);
return fresh.map((it, srcRank) => {
const item = normalizeTagTopItem(it, {
groupNames: source.groupNames,
groupIds: source.groupIds,
srcRank
});
item.fieldEvidence = {
...item.fieldEvidence,
desc: it.desc === void 0 ? "missing" : "partial"
};
item.source.via = "up-search";
item.source.queryKeyword = source.keyword;
item.source.claims = source.consumers?.map((c) => ({
via: "up-search",
queryKeyword: source.keyword,
...tagRecConsumerSource([c])
}));
return item;
});
}
});
/** UP 定向页与普通标签池共用过滤/算分,但 UP 页不进入全站兜底选种原料池。 */
async function runTagRecRecallRound(opts) {
const plan = opts.plan ?? planTagRecRecall(opts.sources, opts.resolve);
const topSources = plan.tagTop.filter((s) => !opts.dryTids.isDry(s.tid));
const fetched = await fetchTagRecRoundSources(topSources, opts.signal, opts.isCurrent);
const cancelled = () => opts.signal?.aborted || opts.isCurrent?.() === false;
const empty = () => ({
...emptyTagRecRoundResult(),
upSources: plan.up,
hasTagTop: !!plan.tagTop.length
});
if (cancelled()) return empty();
let upItems = [];
let upFailure;
try {
upItems = await opts.upPool.load(plan.up, opts.signal, opts.isCurrent);
} catch (error) {
upFailure = { error };
}
if (cancelled()) return empty();
const top = finishTagRecRound({
sources: topSources,
seenBvids: opts.seenBvids,
signal: opts.signal,
stages: { scoreStage: (items) => items },
mergeExisting: opts.mergeExisting,
hasPending: opts.hasPending,
isCurrent: opts.isCurrent,
commitSeen: false
}, fetched);
if (upFailure && !top.poolItems.length) throw upFailure.error;
const byBvid = new Map(top.poolItems.map((it) => [it.bvid, it]));
for (const item of upItems) {
const existing = byBvid.get(item.bvid);
if (existing) mergeTagRecCandidate(existing, item);
else if (!opts.seenBvids.has(item.bvid)) byBvid.set(item.bvid, item);
else {
const updated = opts.mergeExisting?.(item);
if (updated) byBvid.set(item.bvid, updated);
}
}
const pool = [...byBvid.values()];
const items = applyTagRecStages(pool, {
fetchedCount: top.fetchedCount + upItems.length,
poolSize: pool.length,
poolItems: pool,
signal: opts.signal
}, opts.stages);
if (cancelled()) return empty();
for (const item of pool) opts.seenBvids.add(item.bvid);
return {
...top,
items,
producedCount: items.length,
fetchedCount: top.fetchedCount + upItems.length,
upSources: plan.up,
hasTagTop: !!plan.tagTop.length
};
}
/**
* 巨型收藏夹规模修复 (535 组/930 标签实测): tab1 单次 fetchMore 曾新建 ~1150 个
* scopeResolver —— hasAllowedSupply/needsSupply 逐组调用时各建三张全表 Map, 求解器
* 内部 (组,池) 缓存随实例同弃; 签名函数同量级重算。版本号键记忆化后整轮至多一建。
* valtio 子代理深变向上冒泡版本号; 任一输入非代理 (测试替身) => key undefined =>
* 逐次重建 (与缓存前语义一致, 不引入静态过期风险)。
*/
var identities = /* @__PURE__ */ new WeakMap();
var nextIdentity = 1;
/**
* 代理身份+版号二段读数: getVersion 是每 proxy 独立计数器 —— 对象整体换壳
* (favoritesStore.list = [...] 整表替换路径存在) 新 proxy 从 0 重计会与旧值碰撞,
* 身份序号把「不同对象的同版号」区分开; WeakMap 不阻回收。
*/
var ver = (o) => {
if (!o || typeof o !== "object") return void 0;
const v = getVersion(o);
if (v === void 0) return void 0;
let id = identities.get(o);
if (id === void 0) identities.set(o, id = nextIdentity++);
return `${id}@${v}`;
};
/** (身份,版号) 元组 -> 缓存键; 任一输入拿不到版号 => undefined (调用侧应不缓存直算) */
function tagRecVersionKey(inputs) {
const parts = [];
for (const o of inputs) {
const v = ver(o);
if (v === void 0) return void 0;
parts.push(v);
}
return parts.join(":");
}
/**
* 单槽版号记忆化: 每槽位存 (key, value); key undefined => 不缓存恒重算;
* key 漂移 => 重建并换槽。适合 resolver/签名这类「输入集固定、按内容版号失效」的值。
*/
function versionedSlot() {
let slot;
return (key, build) => {
if (key !== void 0 && slot?.key === key) return slot.value;
const value = build();
if (key !== void 0) slot = {
key,
value
};
return value;
};
}})}}));
System.register("./discovery-policy-BBjOB0CZ-BWQIVxg7.js", ['./risk-log-ZLVUftoN-DnHKear8.js','./routes-B0HXSp2g-8gD5jzg6.js','./___monkey.entry.js'],(function(exports){'use strict';var __toESM,require_react,tagRecBackoff,require_react_dom,proxy,jsxs,jsx,r,ForwardRef$26,clsx,button_default,Popconfirm,useSnapshot,Switch,pickSettings,initialSettings,updateSettings,Input,toast,normalizeTagName,notifyTagRecSeenCleared,useComponentConfig,isPlainObject,isNonNullable$1,useSemanticRootStyle,useMergeSemantic,isReactRenderable,pickAttrs,es_default$5,composeRef,useSettingsSnapshot,settings,subscribe,isEqual$1,snapshot,applyTagRecPresetToCore,Select,applyTagRecPreset,Fragment,removeTagRecPreset,renameTagRecPreset,TAG_REC_ALL_POOL_KEYS,Checkbox,Tag,HelpInfo,resolveTagRecEffective,clampTagRecK,groupState,hasTagRecCombination,TAG_REC_PATCHABLE_DIM_KEYS,Modal,genStyleHooks,restoreTagRecBuiltinPreset,upsertTagRecPreset,cloneTagRecFilterCore,TypedInputNumber,PARTITION_NAME_TO_PARENT,pagination_default,parseTagInput,fetchTagInfoById,fetchTagInfoByName,fetchAllV2Search,parseAllV2TagField,persistVideoTags,fetchSuggest,cloneDeep,diffTagRecScopeOverride,normalizeTagRecNegativeDraft,hasTagRecOverride,Badge,antModal,unit$1,resetComponent,genFocusStyle,RefIcon$3$1,RefIcon$5$1,RefIcon$2$1,RefIcon$6$1,RefIcon$4$1,require_dayjs_min,stripHighlightTags,findGroupsByTagId,findGroupsByNegativeTagId,useBreakpoint,omit,ConfigContext,localeValues,useSize,useLocale,useToken,isFunction,isNumber,DefaultRenderEmpty,Spin,TableMeasureRowContext,ConfigProvider,shuffle,AntdTooltip,toArray$2,merge,useCSSVarCls,mergeProps,useMultipleSelect,useControlledState,_toConsumableArray,clearFix,genCssVar,operationUnit,textEllipsis,FastColor,warningOnce$1,devUseWarning,mergeStyles,resolveStyleOrClass,mergeClassNames,KeyCode,Tooltip,Button,Dropdown,useEvent,get$1,isEqual,useLayoutEffect$2,getTargetScrollBarSize,Icon,useForceUpdate,Empty,Menu,supportRef,getDOM,useMemo$13,Input$1,RefIcon$10,DisabledContext,initCollapseMotion,Radio,es_default$7,canUseDom$1,fillRef,wrapperRaf,getScrollBarSize,getStyle,genCollapseMotion,getId,useId_default,RefIcon$1$1,cloneElement,RefResizeObserver,isVisible_default,Keyframe,genFocusOutline,_inherits,_createClass,RefIcon$11,OverrideProvider,_classCallCheck,_getPrototypeOf,_possibleConstructorReturn,_isNativeReflectConstruct,Typography,sediment,seen,tagRecSharedSession,TAG_REC_POOL_LABELS,TAG_REC_POOL_KEYS,TAG_REC_DROP_DIM_LABELS,Tabs,favoritesStore,favoritesL1Store,themesStore,scrollTo;return{setters:[function(module){__toESM=module._;require_react=module.r;tagRecBackoff=module.t;require_react_dom=module.b;},function(module){proxy=module.aP;jsxs=module.j;jsx=module.a;r=module.a8;ForwardRef$26=module.ei;clsx=module.m;button_default=module.n;Popconfirm=module.a2;useSnapshot=module.u;Switch=module.bj;pickSettings=module.aj;initialSettings=module.ej;updateSettings=module.q;Input=module.Y;toast=module.ak;normalizeTagName=module.bB;notifyTagRecSeenCleared=module.ek;useComponentConfig=module.ao;isPlainObject=module.el;isNonNullable$1=module.em;useSemanticRootStyle=module.at;useMergeSemantic=module.au;isReactRenderable=module.en;pickAttrs=module.ba;es_default$5=module.eo;composeRef=module.ep;useSettingsSnapshot=module.d;settings=module.t;subscribe=module.c_;isEqual$1=module.al;snapshot=module.dv;applyTagRecPresetToCore=module.eq;Select=module.S;applyTagRecPreset=module.bM;Fragment=module.F;removeTagRecPreset=module.bO;renameTagRecPreset=module.bP;TAG_REC_ALL_POOL_KEYS=module.cq;Checkbox=module._;Tag=module.T;HelpInfo=module.H;resolveTagRecEffective=module.bq;clampTagRecK=module.cx;groupState=module.bm;hasTagRecCombination=module.aE;TAG_REC_PATCHABLE_DIM_KEYS=module.bD;Modal=module.$;genStyleHooks=module.b3;restoreTagRecBuiltinPreset=module.bN;upsertTagRecPreset=module.bH;cloneTagRecFilterCore=module.bI;TypedInputNumber=module.a0;PARTITION_NAME_TO_PARENT=module.b$;pagination_default=module.er;parseTagInput=module.cK;fetchTagInfoById=module.bF;fetchTagInfoByName=module.bG;fetchAllV2Search=module.c$;parseAllV2TagField=module.es;persistVideoTags=module.et;fetchSuggest=module.d0;cloneDeep=module.aQ;diffTagRecScopeOverride=module.eu;normalizeTagRecNegativeDraft=module.ev;hasTagRecOverride=module.ew;Badge=module.bk;antModal=module.aH;unit$1=module.bv;resetComponent=module.ex;genFocusStyle=module.ey;RefIcon$3$1=module.ez;RefIcon$5$1=module.eA;RefIcon$2$1=module.eB;RefIcon$6$1=module.eC;RefIcon$4$1=module.eD;require_dayjs_min=module.bt;stripHighlightTags=module.eE;findGroupsByTagId=module.eF;findGroupsByNegativeTagId=module.eG;useBreakpoint=module.eH;omit=module.eI;ConfigContext=module.eJ;localeValues=module.eK;useSize=module.as;useLocale=module.b0;useToken=module.eL;isFunction=module.b6;isNumber=module.eM;DefaultRenderEmpty=module.eN;Spin=module.dP;TableMeasureRowContext=module.eO;ConfigProvider=module.eP;shuffle=module.eQ;AntdTooltip=module.A;toArray$2=module.eR;merge=module.b4;useCSSVarCls=module.b5;mergeProps=module.eS;useMultipleSelect=module.eT;useControlledState=module.av;_toConsumableArray=module.bU;clearFix=module.eU;genCssVar=module.eV;operationUnit=module.eW;textEllipsis=module.eX;FastColor=module.eY;warningOnce$1=module.eZ;devUseWarning=module.e_;mergeStyles=module.e$;resolveStyleOrClass=module.f0;mergeClassNames=module.f1;KeyCode=module.f2;Tooltip=module.d7;Button=module.f3;Dropdown=module.bh;useEvent=module.b1;get$1=module.f4;isEqual=module.f5;useLayoutEffect$2=module.by;getTargetScrollBarSize=module.f6;Icon=module.f7;useForceUpdate=module.bz;Empty=module.aD;Menu=module.f8;supportRef=module.f9;getDOM=module.fa;useMemo$13=module.fb;Input$1=module.bW;RefIcon$10=module.fc;DisabledContext=module.ap;initCollapseMotion=module.fd;Radio=module.a9;es_default$7=module.fe;canUseDom$1=module.ff;fillRef=module.fg;wrapperRaf=module.fh;getScrollBarSize=module.fi;getStyle=module.fj;genCollapseMotion=module.fk;getId=module.fl;useId_default=module.fm;RefIcon$1$1=module.fn;cloneElement=module.fo;RefResizeObserver=module.fp;isVisible_default=module.fq;Keyframe=module.fr;genFocusOutline=module.fs;_inherits=module.ft;_createClass=module.fu;RefIcon$11=module.fv;OverrideProvider=module.fw;_classCallCheck=module.fx;_getPrototypeOf=module.fy;_possibleConstructorReturn=module.fz;_isNativeReflectConstruct=module.fA;},function(module){Typography=module.e;sediment=module.y;seen=module.x;tagRecSharedSession=module.t;TAG_REC_POOL_LABELS=module.M;TAG_REC_POOL_KEYS=module.L;TAG_REC_DROP_DIM_LABELS=module.Q;Tabs=module.T;favoritesStore=module.n;favoritesL1Store=module.o;themesStore=module.p;scrollTo=module.ba;}],execute:(function(){exports({B:TagRecPresetRow,C:CandidateResolvePicker,D:DimsSection,R:ResetPartialSettingsButton,S:SettingsGroup,a:TagRecFilterForm,b:withTagRecGroupScope,c:clearTagRecSeenRecords,d:clearTagRecSedimentRecords,e:tagRecDiscoveryPolicy,f:TagRecCandidatePickList,g:TagRecScopeConfigEditor,h:AdvancedSection,i:TagRecPoolSlotsBlock,k:pickTagIdsForRound,l:isDeckDrawable,n:isL1Drawable,o:mergeCandidateRows,q:resolveAnchor,r:resolveCandidateRows,u:unitsFingerprint,v:analyzeAllV2Videos,w:withTagRecTempEnables,x:mergeTagRecCooccurAliases,y:tagRecPoolSlotSummary,z:runPhase2Round});var import_react = /* @__PURE__ */ __toESM(require_react());
var genAlertTypeStyle = (bgColor, iconColor, alertCls) => ({
background: bgColor,
[`${alertCls}-icon`]: { color: iconColor }
});
var genBaseStyle$1 = (token) => {
const { componentCls, motionDurationSlow: duration, marginXS, marginSM, fontSize, fontSizeLG, lineHeight, motionEaseInOutCirc, borderRadius, withDescriptionIconSize, colorText, colorTextHeading, withDescriptionPadding, defaultPadding, lineWidth, lineType, colorSuccessBorder, colorWarningBorder, colorErrorBorder, colorInfoBorder } = token;
return { [componentCls]: {
...resetComponent(token),
position: "relative",
display: "flex",
alignItems: "center",
padding: defaultPadding,
wordWrap: "break-word",
borderRadius,
borderWidth: unit$1(lineWidth),
borderStyle: lineType,
[`&${componentCls}-success`]: { borderColor: colorSuccessBorder },
[`&${componentCls}-info`]: { borderColor: colorInfoBorder },
[`&${componentCls}-warning`]: { borderColor: colorWarningBorder },
[`&${componentCls}-error`]: { borderColor: colorErrorBorder },
[`&${componentCls}-filled`]: { borderColor: "transparent" },
[`&${componentCls}-rtl`]: { direction: "rtl" },
[`${componentCls}-section`]: {
flex: 1,
minWidth: 0
},
[`${componentCls}-icon`]: {
marginInlineEnd: marginXS,
lineHeight: 0
},
"&-description": {
display: "none",
fontSize,
lineHeight
},
"&-title": { color: colorTextHeading },
[`&${componentCls}-motion-leave`]: {
overflow: "hidden",
opacity: 1,
transition: [
`max-height`,
`opacity`,
`padding-top`,
`padding-bottom`,
`margin-bottom`
].map((prop) => `${prop} ${duration} ${motionEaseInOutCirc}`).join(", ")
},
[`&${componentCls}-motion-leave-active`]: {
maxHeight: 0,
marginBottom: "0 !important",
paddingTop: 0,
paddingBottom: 0,
opacity: 0
},
[`&${componentCls}-with-description`]: {
alignItems: "flex-start",
padding: withDescriptionPadding,
[`${componentCls}-icon`]: {
marginInlineEnd: marginSM,
fontSize: withDescriptionIconSize,
lineHeight: 0
},
[`${componentCls}-title`]: {
display: "block",
marginBottom: marginXS,
color: colorTextHeading,
fontSize: fontSizeLG
},
[`${componentCls}-description`]: {
display: "block",
color: colorText
}
},
[`&${componentCls}-banner`]: {
marginBottom: 0,
border: "0 !important",
borderRadius: 0
}
} };
};
var genTypeStyle = (token) => {
const { componentCls, colorSuccess, colorSuccessBg, colorWarning, colorWarningBg, colorError, colorErrorBg, colorInfo, colorInfoBg } = token;
return { [componentCls]: {
"&-success": genAlertTypeStyle(colorSuccessBg, colorSuccess, componentCls),
"&-info": genAlertTypeStyle(colorInfoBg, colorInfo, componentCls),
"&-warning": genAlertTypeStyle(colorWarningBg, colorWarning, componentCls),
"&-error": {
...genAlertTypeStyle(colorErrorBg, colorError, componentCls),
[`${componentCls}-description > pre`]: {
margin: 0,
padding: 0
}
}
} };
};
var genActionStyle = (token) => {
const { componentCls, iconCls, motionDurationMid, marginXS, fontSizeIcon, colorIcon, colorIconHover } = token;
return { [componentCls]: {
[`${componentCls}-actions`]: { marginInlineStart: marginXS },
[`${componentCls}-close-icon`]: {
marginInlineStart: marginXS,
padding: 0,
overflow: "hidden",
fontSize: fontSizeIcon,
lineHeight: unit$1(fontSizeIcon),
backgroundColor: "transparent",
border: "none",
cursor: "pointer",
...genFocusStyle(token),
[`${iconCls}-close`]: {
color: colorIcon,
transition: `color ${motionDurationMid}`,
"&:hover": { color: colorIconHover }
}
},
"&-close-text": {
color: colorIcon,
transition: `color ${motionDurationMid}`,
"&:hover": { color: colorIconHover }
}
} };
};
var prepareComponentToken$2 = (token) => {
return {
borderRadius: token.borderRadiusLG,
withDescriptionIconSize: token.fontSizeHeading3,
defaultPadding: `${token.paddingContentVerticalSM}px 12px`,
withDescriptionPadding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`
};
};
var style_default$2 = genStyleHooks("Alert", (token) => [
genBaseStyle$1(token),
genTypeStyle(token),
genActionStyle(token)
], prepareComponentToken$2);
var IconNode = (props) => {
const { icon, type, className, style, successIcon, infoIcon, warningIcon, errorIcon } = props;
const iconMapFilled = {
success: successIcon ?? /*#__PURE__*/ import_react.createElement(RefIcon$6$1, null),
info: infoIcon ?? /*#__PURE__*/ import_react.createElement(RefIcon$2$1, null),
error: errorIcon ?? /*#__PURE__*/ import_react.createElement(RefIcon$5$1, null),
warning: warningIcon ?? /*#__PURE__*/ import_react.createElement(RefIcon$3$1, null)
};
return /*#__PURE__*/ import_react.createElement("span", {
className,
style
}, icon ?? iconMapFilled[type]);
};
var CloseIconNode = (props) => {
const { isClosable, prefixCls, closeIcon, handleClose, ariaProps, className, style } = props;
const mergedCloseIcon = closeIcon === true || closeIcon === void 0 ? /*#__PURE__*/ import_react.createElement(RefIcon$4$1, null) : closeIcon;
return isClosable ? /*#__PURE__*/ import_react.createElement("button", {
type: "button",
onClick: handleClose,
className: clsx(`${prefixCls}-close-icon`, className),
tabIndex: 0,
style,
...ariaProps
}, mergedCloseIcon) : null;
};
var Alert$1 = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { description, prefixCls: customizePrefixCls, message, title, banner, className, rootClassName, style, onMouseEnter, onMouseLeave, onClick, afterClose, showIcon, closable, closeText, closeIcon, action, id, styles, classNames, ...otherProps } = props;
const mergedTitle = title ?? message;
const [closed, setClosed] = import_react.useState(false);
const internalRef = import_react.useRef(null);
import_react.useImperativeHandle(ref, () => ({ nativeElement: internalRef.current }));
const { getPrefixCls, direction, variant: contextVariant, closable: contextClosable, closeIcon: contextCloseIcon, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles, successIcon, infoIcon, warningIcon, errorIcon } = useComponentConfig("alert");
const prefixCls = getPrefixCls("alert", customizePrefixCls);
const [hashId, cssVarCls] = style_default$2(prefixCls);
const { onClose: closableOnClose, afterClose: closableAfterClose } = isPlainObject(closable) ? closable : {};
const handleClose = (e) => {
setClosed(true);
(closableOnClose ?? props.onClose)?.(e);
};
const type = import_react.useMemo(() => {
if (props.type !== void 0) return props.type;
return banner ? "warning" : "info";
}, [props.type, banner]);
const mergedVariant = props.variant ?? contextVariant ?? "outlined";
const isClosable = import_react.useMemo(() => {
if (isPlainObject(closable)) return true;
if (closeText) return true;
if (typeof closable === "boolean") return closable;
if (closeIcon !== false && isNonNullable$1(closeIcon)) return true;
return !!contextClosable;
}, [
closeText,
closeIcon,
closable,
contextClosable
]);
const isShowIcon = banner && showIcon === void 0 ? true : showIcon;
const mergedProps = {
...props,
prefixCls,
variant: mergedVariant,
type,
showIcon: isShowIcon,
closable: isClosable
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps });
const alertCls = clsx(prefixCls, `${prefixCls}-${type}`, `${prefixCls}-${mergedVariant}`, {
[`${prefixCls}-with-description`]: isReactRenderable(description),
[`${prefixCls}-no-icon`]: !isShowIcon,
[`${prefixCls}-banner`]: !!banner,
[`${prefixCls}-rtl`]: direction === "rtl"
}, contextClassName, className, rootClassName, mergedClassNames.root, cssVarCls, hashId);
const restProps = pickAttrs(otherProps, {
aria: true,
data: true
});
const mergedCloseIcon = import_react.useMemo(() => {
if (isPlainObject(closable) && closable.closeIcon) return closable.closeIcon;
if (closeText) return closeText;
if (closeIcon !== void 0) return closeIcon;
if (isPlainObject(contextClosable) && contextClosable.closeIcon) return contextClosable.closeIcon;
return contextCloseIcon;
}, [
closeIcon,
closable,
contextClosable,
closeText,
contextCloseIcon
]);
const mergedAriaProps = import_react.useMemo(() => {
const merged = closable ?? contextClosable;
if (isPlainObject(merged)) return pickAttrs(merged, {
data: true,
aria: true
});
return {};
}, [closable, contextClosable]);
return /*#__PURE__*/ import_react.createElement(es_default$5, {
visible: !closed,
motionName: `${prefixCls}-motion`,
motionAppear: false,
motionEnter: false,
onLeaveStart: (node) => ({ maxHeight: node.offsetHeight }),
onLeaveEnd: closableAfterClose ?? afterClose
}, ({ className: motionClassName, style: motionStyle }, setRef) => /*#__PURE__*/ import_react.createElement("div", {
id,
ref: composeRef(internalRef, setRef),
"data-show": !closed,
className: clsx(alertCls, motionClassName),
style: {
...mergedStyles.root,
...motionStyle
},
onMouseEnter,
onMouseLeave,
onClick,
role: "alert",
...restProps
}, isShowIcon ? /*#__PURE__*/ import_react.createElement(IconNode, {
className: clsx(`${prefixCls}-icon`, mergedClassNames.icon),
style: mergedStyles.icon,
description,
icon: props.icon,
prefixCls,
type,
successIcon,
infoIcon,
warningIcon,
errorIcon
}) : null, /*#__PURE__*/ import_react.createElement("div", {
className: clsx(`${prefixCls}-section`, mergedClassNames.section),
style: mergedStyles.section
}, isReactRenderable(mergedTitle) ? /*#__PURE__*/ import_react.createElement("div", {
className: clsx(`${prefixCls}-title`, mergedClassNames.title),
style: mergedStyles.title
}, mergedTitle) : null, isReactRenderable(description) ? /*#__PURE__*/ import_react.createElement("div", {
className: clsx(`${prefixCls}-description`, mergedClassNames.description),
style: mergedStyles.description
}, description) : null), isReactRenderable(action) ? /*#__PURE__*/ import_react.createElement("div", {
className: clsx(`${prefixCls}-actions`, mergedClassNames.actions),
style: mergedStyles.actions
}, action) : null, /*#__PURE__*/ import_react.createElement(CloseIconNode, {
className: mergedClassNames.close,
style: mergedStyles.close,
isClosable,
prefixCls,
closeIcon: mergedCloseIcon,
handleClose,
ariaProps: mergedAriaProps
})));
});
function _callSuper(t, o, e) {
return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e));
}
var ErrorBoundary = /*#__PURE__*/ function(_React$PureComponent) {
function ErrorBoundary() {
var _this;
_classCallCheck(this, ErrorBoundary);
_this = _callSuper(this, ErrorBoundary, arguments);
_this.state = {
error: void 0,
info: {}
};
return _this;
}
_inherits(ErrorBoundary, _React$PureComponent);
return _createClass(ErrorBoundary, [{
key: "componentDidCatch",
value: function componentDidCatch(error, info) {
this.setState({
error,
info
});
}
}, {
key: "render",
value: function render() {
const { message, title, description, id, children } = this.props;
const { error, info } = this.state;
const mergedTitle = title ?? message;
const componentStack = info?.componentStack || null;
const errorMessage = isNonNullable$1(mergedTitle) ? mergedTitle : error?.toString();
const errorDescription = isNonNullable$1(description) ? description : componentStack;
if (error) return /*#__PURE__*/ import_react.createElement(Alert$1, {
id,
type: "error",
title: errorMessage,
description: /*#__PURE__*/ import_react.createElement("pre", { style: {
fontSize: "0.9em",
overflowX: "auto"
} }, errorDescription)
});
return children;
}
}]);
}(import_react.PureComponent);
var Alert = exports("A",Alert$1);
Alert.ErrorBoundary = ErrorBoundary;
var fillProxy = (element, handler) => {
element._antProxy = element._antProxy || {};
Object.keys(handler).forEach((key) => {
if (!(key in element._antProxy)) {
const ori = element[key];
element._antProxy[key] = ori;
element[key] = handler[key];
}
});
return element;
};
var useProxyImperativeHandle = (ref, init) => {
return (0, import_react.useImperativeHandle)(ref, () => {
const refObj = init();
const { nativeElement } = refObj;
if (typeof Proxy !== "undefined") return new Proxy(nativeElement, { get(obj, prop) {
if (refObj[prop]) return refObj[prop];
return Reflect.get(obj, prop);
} });
return fillProxy(nativeElement, refObj);
});
};
var useSyncState = (initialValue) => {
const ref = import_react.useRef(initialValue);
const [, forceUpdate] = useForceUpdate();
return [() => ref.current, (newValue) => {
ref.current = newValue;
forceUpdate();
}];
};
var import_react_dom = /* @__PURE__ */ __toESM(require_react_dom());
/**
* Webpack has bug for import loop, which is not the same behavior as ES module.
* When util.js imports the TreeNode for tree generate will cause treeContextTypes be empty.
*/
var TreeContext = /*#__PURE__*/ import_react.createContext(null);
/** Internal usage, safe to remove. Do not use in prod */
var UnstableContext = /*#__PURE__*/ import_react.createContext({});
var DropIndicator = (props) => {
const { dropPosition, dropLevelOffset, indent } = props;
const style = {
pointerEvents: "none",
position: "absolute",
right: 0,
backgroundColor: "red",
height: 2
};
switch (dropPosition) {
case -1:
style.top = 0;
style.left = -dropLevelOffset * indent;
break;
case 1:
style.bottom = 0;
style.left = -dropLevelOffset * indent;
break;
case 0:
style.bottom = 0;
style.left = indent;
}
return /*#__PURE__*/ import_react.createElement("div", { style });
};
var Indent = ({ prefixCls, level, isStart, isEnd }) => {
const baseClassName = `${prefixCls}-indent-unit`;
const list = [];
for (let i = 0; i < level; i += 1) list.push(/*#__PURE__*/ import_react.createElement("span", {
key: i,
className: clsx(baseClassName, {
[`${baseClassName}-start`]: isStart[i],
[`${baseClassName}-end`]: isEnd[i]
})
}));
return /*#__PURE__*/ import_react.createElement("span", {
"aria-hidden": "true",
className: `${prefixCls}-indent`
}, list);
};
var Indent_default = /*#__PURE__*/ import_react.memo(Indent);
function getEntity(keyEntities, key) {
return keyEntities[key];
}
function getPosition(level, index) {
return `${level}-${index}`;
}
function isTreeNode(node) {
return node && node.type && node.type.isTreeNode;
}
function getKey(key, pos) {
if (key !== null && key !== void 0) return key;
return pos;
}
function fillFieldNames(fieldNames) {
const { title, _title, key, children } = fieldNames || {};
const mergedTitle = title || "title";
return {
title: mergedTitle,
_title: _title || [mergedTitle],
key: key || "key",
children: children || "children"
};
}
/**
* Convert `children` of Tree into `treeData` structure.
*/
function convertTreeToData(rootNodes) {
function dig(node) {
return toArray$2(node).map((treeNode) => {
if (!isTreeNode(treeNode)) {
warningOnce$1(!treeNode, "Tree/TreeNode can only accept TreeNode as children.");
return null;
}
const { key } = treeNode;
const { children, ...rest } = treeNode.props;
const dataNode = {
key,
...rest
};
const parsedChildren = dig(children);
if (parsedChildren.length) dataNode.children = parsedChildren;
return dataNode;
}).filter((dataNode) => dataNode);
}
return dig(rootNodes);
}
/**
* Flat nest tree data into flatten list. This is used for virtual list render.
* @param treeNodeList Origin data node list
* @param expandedKeys
* need expanded keys, provides `true` means all expanded (used in `rc-tree-select`).
*/
function flattenTreeData(treeNodeList, expandedKeys, fieldNames) {
const { _title: fieldTitles, key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames);
const expandedKeySet = new Set(expandedKeys === true ? [] : expandedKeys);
const flattenList = [];
function dig(list, parent = null) {
return list.map((treeNode, index) => {
const pos = getPosition(parent ? parent.pos : "0", index);
const mergedKey = getKey(treeNode[fieldKey], pos);
let mergedTitle;
for (let i = 0; i < fieldTitles.length; i += 1) {
const fieldTitle = fieldTitles[i];
if (treeNode[fieldTitle] !== void 0) {
mergedTitle = treeNode[fieldTitle];
break;
}
}
const flattenNode = Object.assign(omit(treeNode, [
...fieldTitles,
fieldKey,
fieldChildren
]), {
title: mergedTitle,
key: mergedKey,
parent,
pos,
children: null,
data: treeNode,
isStart: [...parent ? parent.isStart : [], index === 0],
isEnd: [...parent ? parent.isEnd : [], index === list.length - 1]
});
flattenList.push(flattenNode);
if (expandedKeys === true || expandedKeySet.has(mergedKey)) flattenNode.children = dig(treeNode[fieldChildren] || [], flattenNode);
else flattenNode.children = [];
return flattenNode;
});
}
dig(treeNodeList);
return flattenList;
}
/**
* Traverse all the data by `treeData`.
* Please not use it out of the `rc-tree` since we may refactor this code.
*/
function traverseDataNodes(dataNodes, callback, config) {
const { childrenPropName, externalGetKey, fieldNames } = typeof config === "object" ? config || {} : { externalGetKey: config };
const { key: fieldKey, children: fieldChildren } = fillFieldNames(fieldNames);
const mergeChildrenPropName = childrenPropName || fieldChildren;
let syntheticGetKey;
if (externalGetKey) {
if (typeof externalGetKey === "string") syntheticGetKey = (node) => node[externalGetKey];
else if (typeof externalGetKey === "function") syntheticGetKey = (node) => externalGetKey(node);
} else syntheticGetKey = (node, pos) => getKey(node[fieldKey], pos);
function processNode(node, index, parent, pathNodes) {
const children = node ? node[mergeChildrenPropName] : dataNodes;
const pos = node ? getPosition(parent.pos, index) : "0";
const connectNodes = node ? [...pathNodes, node] : [];
if (node) callback({
node,
index,
pos,
key: syntheticGetKey(node, pos),
parentPos: parent.node ? parent.pos : null,
level: parent.level + 1,
nodes: connectNodes
});
if (children) children.forEach((subNode, subIndex) => {
processNode(subNode, subIndex, {
node,
pos,
level: parent ? parent.level + 1 : -1
}, connectNodes);
});
}
processNode(null);
}
/**
* Convert `treeData` into entity records.
*/
function convertDataToEntities(dataNodes, { initWrapper, processEntity, onProcessFinished, externalGetKey, childrenPropName, fieldNames } = {}, legacyExternalGetKey) {
const mergedExternalGetKey = externalGetKey || legacyExternalGetKey;
const posEntities = {};
const keyEntities = {};
let wrapper = {
posEntities,
keyEntities
};
if (initWrapper) wrapper = initWrapper(wrapper) || wrapper;
traverseDataNodes(dataNodes, (item) => {
const { node, index, pos, key, parentPos, level, nodes } = item;
const entity = {
node,
nodes,
index,
key,
pos,
level
};
const mergedKey = getKey(key, pos);
posEntities[pos] = entity;
keyEntities[mergedKey] = entity;
entity.parent = posEntities[parentPos];
if (entity.parent) {
entity.parent.children = entity.parent.children || [];
entity.parent.children.push(entity);
}
if (processEntity) processEntity(entity, wrapper);
}, {
externalGetKey: mergedExternalGetKey,
childrenPropName,
fieldNames
});
if (onProcessFinished) onProcessFinished(wrapper);
return wrapper;
}
function isLeafNode(isLeaf, loadData, hasChildren, loaded) {
if (isLeaf === false) return false;
return isLeaf || !loadData && !hasChildren || loadData && loaded && !hasChildren;
}
/**
* Get TreeNode props with Tree props.
*/
function getTreeNodeProps(key, { expandedKeys, selectedKeys, loadedKeys, loadingKeys, checkedKeys, halfCheckedKeys, dragOverNodeKey, dropPosition, keyEntities }) {
const entity = getEntity(keyEntities, key);
return {
eventKey: key,
expanded: expandedKeys.indexOf(key) !== -1,
selected: selectedKeys.indexOf(key) !== -1,
loaded: loadedKeys.indexOf(key) !== -1,
loading: loadingKeys.indexOf(key) !== -1,
checked: checkedKeys.indexOf(key) !== -1,
halfChecked: halfCheckedKeys.indexOf(key) !== -1,
pos: String(entity ? entity.pos : ""),
dragOver: dragOverNodeKey === key && dropPosition === 0,
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1
};
}
function convertNodePropsToEventData(props) {
const { data, expanded, selected, checked, loaded, loading, halfChecked, dragOver, dragOverGapTop, dragOverGapBottom, pos, active, eventKey } = props;
const eventData = {
...data,
expanded,
selected,
checked,
loaded,
loading,
halfChecked,
dragOver,
dragOverGapTop,
dragOverGapBottom,
pos,
active,
key: eventKey
};
if (!("props" in eventData)) Object.defineProperty(eventData, "props", { get() {
warningOnce$1(false, "Second param return from event is node data instead of TreeNode instance. Please read value directly instead of reading from `props`.");
return props;
} });
return eventData;
}
function _extends$23() {
_extends$23 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$23.apply(this, arguments);
}
var ICON_OPEN = "open";
var ICON_CLOSE = "close";
var defaultTitle = "---";
var TreeNode = (props) => {
const { eventKey, className, style, dragOver, dragOverGapTop, dragOverGapBottom, isLeaf, isStart, isEnd, expanded, selected, checked, halfChecked, loading, domRef, active, data, onMouseMove, selectable, treeId, ...otherProps } = props;
const nodeId = getId(treeId, eventKey);
const context = import_react.useContext(TreeContext);
const { classNames: treeClassNames, styles } = context || {};
const unstableContext = import_react.useContext(UnstableContext);
const selectHandleRef = import_react.useRef(null);
const [dragNodeHighlight, setDragNodeHighlight] = import_react.useState(false);
const isDisabled = !!(context.disabled || props.disabled || unstableContext.nodeDisabled?.(data));
const isCheckable = import_react.useMemo(() => {
if (!context.checkable || props.checkable === false) return false;
return context.checkable;
}, [context.checkable, props.checkable]);
const onSelect = (e) => {
if (isDisabled) return;
context.onNodeSelect(e, convertNodePropsToEventData(props));
};
const onCheck = (e) => {
if (isDisabled) return;
if (!isCheckable || props.disableCheckbox) return;
context.onNodeCheck(e, convertNodePropsToEventData(props), !checked);
};
const isSelectable = import_react.useMemo(() => {
if (typeof selectable === "boolean") return selectable;
return context.selectable;
}, [selectable, context.selectable]);
const onSelectorClick = (e) => {
context.onNodeClick(e, convertNodePropsToEventData(props));
if (isSelectable) onSelect(e);
else onCheck(e);
};
const onSelectorDoubleClick = (e) => {
context.onNodeDoubleClick(e, convertNodePropsToEventData(props));
};
const onMouseEnter = (e) => {
context.onNodeMouseEnter(e, convertNodePropsToEventData(props));
};
const onMouseLeave = (e) => {
context.onNodeMouseLeave(e, convertNodePropsToEventData(props));
};
const onContextMenu = (e) => {
context.onNodeContextMenu(e, convertNodePropsToEventData(props));
};
const isDraggable = import_react.useMemo(() => {
return !!(context.draggable && (!context.draggable.nodeDraggable || context.draggable.nodeDraggable(data)));
}, [context.draggable, data]);
const onDragStart = (e) => {
e.stopPropagation();
setDragNodeHighlight(true);
context.onNodeDragStart(e, props);
try {
e.dataTransfer.setData("text/plain", "");
} catch {}
};
const onDragEnter = (e) => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragEnter(e, props);
};
const onDragOver = (e) => {
e.preventDefault();
e.stopPropagation();
context.onNodeDragOver(e, props);
};
const onDragLeave = (e) => {
e.stopPropagation();
context.onNodeDragLeave(e, props);
};
const onDragEnd = (e) => {
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDragEnd(e, props);
};
const onDrop = (e) => {
e.preventDefault();
e.stopPropagation();
setDragNodeHighlight(false);
context.onNodeDrop(e, props);
};
const onExpand = (e) => {
if (loading) return;
context.onNodeExpand(e, convertNodePropsToEventData(props));
};
const hasChildren = import_react.useMemo(() => {
const { children } = getEntity(context.keyEntities, eventKey) || {};
return Boolean((children || []).length);
}, [context.keyEntities, eventKey]);
const memoizedIsLeaf = import_react.useMemo(() => {
return isLeafNode(isLeaf, context.loadData, hasChildren, props.loaded);
}, [
isLeaf,
context.loadData,
hasChildren,
props.loaded
]);
import_react.useEffect(() => {
if (loading) return;
if (typeof context.loadData === "function" && expanded && !memoizedIsLeaf && !props.loaded) context.onNodeLoad(convertNodePropsToEventData(props));
}, [
loading,
context.loadData,
context.onNodeLoad,
expanded,
memoizedIsLeaf,
props
]);
const dragHandlerNode = import_react.useMemo(() => {
if (!context.draggable?.icon) return null;
return /*#__PURE__*/ import_react.createElement("span", { className: `${context.prefixCls}-draggable-icon` }, context.draggable.icon);
}, [context.draggable]);
const renderSwitcherIconDom = (isInternalLeaf) => {
const switcherIcon = props.switcherIcon || context.switcherIcon;
if (typeof switcherIcon === "function") return switcherIcon({
...props,
isLeaf: isInternalLeaf
});
return switcherIcon;
};
const renderSwitcher = () => {
if (memoizedIsLeaf) {
const switcherIconDom = renderSwitcherIconDom(true);
return switcherIconDom !== false ? /*#__PURE__*/ import_react.createElement("span", {
className: clsx(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher-noop`, treeClassNames?.itemSwitcher),
style: styles?.itemSwitcher
}, switcherIconDom) : null;
}
const switcherIconDom = renderSwitcherIconDom(false);
return switcherIconDom !== false ? /*#__PURE__*/ import_react.createElement("span", {
onClick: onExpand,
className: clsx(`${context.prefixCls}-switcher`, `${context.prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`, treeClassNames?.itemSwitcher),
style: styles?.itemSwitcher
}, switcherIconDom) : null;
};
const checkboxNode = import_react.useMemo(() => {
if (!isCheckable) return null;
const $custom = typeof isCheckable !== "boolean" ? isCheckable : null;
return /*#__PURE__*/ import_react.createElement("span", {
className: clsx(`${context.prefixCls}-checkbox`, {
[`${context.prefixCls}-checkbox-checked`]: checked,
[`${context.prefixCls}-checkbox-indeterminate`]: !checked && halfChecked,
[`${context.prefixCls}-checkbox-disabled`]: isDisabled || props.disableCheckbox
}),
onClick: onCheck,
role: "checkbox",
"aria-checked": halfChecked ? "mixed" : checked,
"aria-disabled": isDisabled || props.disableCheckbox,
"aria-labelledby": nodeId
}, $custom);
}, [
isCheckable,
checked,
halfChecked,
isDisabled,
props.disableCheckbox,
nodeId
]);
const nodeState = import_react.useMemo(() => {
if (memoizedIsLeaf) return null;
return expanded ? ICON_OPEN : ICON_CLOSE;
}, [memoizedIsLeaf, expanded]);
const iconNode = import_react.useMemo(() => {
return /*#__PURE__*/ import_react.createElement("span", {
className: clsx(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__${nodeState || "docu"}`, { [`${context.prefixCls}-icon_loading`]: loading }),
style: styles?.itemIcon
});
}, [
context.prefixCls,
nodeState,
loading
]);
const dropIndicatorNode = import_react.useMemo(() => {
const rootDraggable = Boolean(context.draggable);
if (!(!props.disabled && rootDraggable && context.dragOverNodeKey === eventKey)) return null;
return context.dropIndicatorRender({
dropPosition: context.dropPosition,
dropLevelOffset: context.dropLevelOffset,
indent: context.indent,
prefixCls: context.prefixCls,
direction: context.direction
});
}, [
context.dropPosition,
context.dropLevelOffset,
context.indent,
context.prefixCls,
context.direction,
context.draggable,
context.dragOverNodeKey,
context.dropIndicatorRender
]);
const selectorNode = import_react.useMemo(() => {
const { title = defaultTitle } = props;
const wrapClass = `${context.prefixCls}-node-content-wrapper`;
let $icon;
if (context.showIcon) {
const currentIcon = props.icon || context.icon;
$icon = currentIcon ? /*#__PURE__*/ import_react.createElement("span", {
className: clsx(treeClassNames?.itemIcon, `${context.prefixCls}-iconEle`, `${context.prefixCls}-icon__customize`),
style: styles?.itemIcon
}, typeof currentIcon === "function" ? currentIcon(props) : currentIcon) : iconNode;
} else if (context.loadData && loading) $icon = iconNode;
let titleNode;
if (typeof title === "function") titleNode = title(data);
else if (context.titleRender) titleNode = context.titleRender(data);
else titleNode = title;
return /*#__PURE__*/ import_react.createElement("span", {
ref: selectHandleRef,
title: typeof title === "string" ? title : "",
className: clsx(wrapClass, `${wrapClass}-${nodeState || "normal"}`, { [`${context.prefixCls}-node-selected`]: !isDisabled && (selected || dragNodeHighlight) }),
onMouseEnter,
onMouseLeave,
onContextMenu,
onClick: onSelectorClick,
onDoubleClick: onSelectorDoubleClick
}, $icon, /*#__PURE__*/ import_react.createElement("span", {
className: clsx(`${context.prefixCls}-title`, treeClassNames?.itemTitle),
style: styles?.itemTitle
}, titleNode), dropIndicatorNode);
}, [
context.prefixCls,
context.showIcon,
props,
context.icon,
iconNode,
context.titleRender,
data,
nodeState,
onMouseEnter,
onMouseLeave,
onContextMenu,
onSelectorClick,
onSelectorDoubleClick
]);
const dataOrAriaAttributeProps = pickAttrs(otherProps, {
aria: true,
data: true
});
const { level } = getEntity(context.keyEntities, eventKey) || {};
const isEndNode = isEnd[isEnd.length - 1];
const draggableWithoutDisabled = !isDisabled && isDraggable;
const dragging = context.draggingNodeKey === eventKey;
return /*#__PURE__*/ import_react.createElement("div", _extends$23({
ref: domRef,
role: "treeitem",
id: nodeId,
"aria-expanded": memoizedIsLeaf ? void 0 : expanded,
"aria-selected": isSelectable && !isDisabled ? selected : void 0,
"aria-checked": isCheckable && !isDisabled ? halfChecked ? "mixed" : checked : void 0,
"aria-disabled": isDisabled,
className: clsx(className, `${context.prefixCls}-treenode`, treeClassNames?.item, {
[`${context.prefixCls}-treenode-disabled`]: isDisabled,
[`${context.prefixCls}-treenode-switcher-${expanded ? "open" : "close"}`]: !isLeaf,
[`${context.prefixCls}-treenode-checkbox-checked`]: checked,
[`${context.prefixCls}-treenode-checkbox-indeterminate`]: halfChecked,
[`${context.prefixCls}-treenode-selected`]: selected,
[`${context.prefixCls}-treenode-loading`]: loading,
[`${context.prefixCls}-treenode-active`]: active,
[`${context.prefixCls}-treenode-leaf-last`]: isEndNode,
[`${context.prefixCls}-treenode-draggable`]: isDraggable,
dragging,
"drop-target": context.dropTargetKey === eventKey,
"drop-container": context.dropContainerKey === eventKey,
"drag-over": !isDisabled && dragOver,
"drag-over-gap-top": !isDisabled && dragOverGapTop,
"drag-over-gap-bottom": !isDisabled && dragOverGapBottom,
"filter-node": context.filterTreeNode?.(convertNodePropsToEventData(props)),
[`${context.prefixCls}-treenode-leaf`]: memoizedIsLeaf
}),
style: {
...style,
...styles?.item
},
draggable: draggableWithoutDisabled,
onDragStart: draggableWithoutDisabled ? onDragStart : void 0,
onDragEnter: isDraggable ? onDragEnter : void 0,
onDragOver: isDraggable ? onDragOver : void 0,
onDragLeave: isDraggable ? onDragLeave : void 0,
onDrop: isDraggable ? onDrop : void 0,
onDragEnd: isDraggable ? onDragEnd : void 0,
onMouseMove
}, dataOrAriaAttributeProps), /*#__PURE__*/ import_react.createElement(Indent_default, {
prefixCls: context.prefixCls,
level,
isStart,
isEnd
}), dragHandlerNode, renderSwitcher(), checkboxNode, selectorNode);
};
TreeNode.isTreeNode = 1;
/**
* Trigger only when component unmount
*/
function useUnmount(triggerStart, triggerEnd) {
const [firstMount, setFirstMount] = import_react.useState(false);
useLayoutEffect$2(() => {
if (firstMount) {
triggerStart();
return () => {
triggerEnd();
};
}
}, [firstMount]);
useLayoutEffect$2(() => {
setFirstMount(true);
return () => {
setFirstMount(false);
};
}, []);
}
function _extends$22() {
_extends$22 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$22.apply(this, arguments);
}
var MotionTreeNode = /*#__PURE__*/ import_react.forwardRef((oriProps, ref) => {
const { className, style, motion, motionNodes, motionType, onMotionStart: onOriginMotionStart, onMotionEnd: onOriginMotionEnd, active, treeNodeRequiredProps, ...props } = oriProps;
const [visible, setVisible] = import_react.useState(true);
const { prefixCls } = import_react.useContext(TreeContext);
const targetVisible = motionNodes && motionType !== "hide";
useLayoutEffect$2(() => {
if (motionNodes) {
if (targetVisible !== visible) setVisible(targetVisible);
}
}, [motionNodes]);
const triggerMotionStart = () => {
if (motionNodes) onOriginMotionStart();
};
const triggerMotionEndRef = import_react.useRef(false);
const triggerMotionEnd = () => {
if (motionNodes && !triggerMotionEndRef.current) {
triggerMotionEndRef.current = true;
onOriginMotionEnd();
}
};
useUnmount(triggerMotionStart, triggerMotionEnd);
const onVisibleChanged = (nextVisible) => {
if (targetVisible === nextVisible) triggerMotionEnd();
};
if (motionNodes) return /*#__PURE__*/ import_react.createElement(es_default$5, _extends$22({
ref,
visible
}, motion, {
motionAppear: motionType === "show",
onVisibleChanged
}), ({ className: motionClassName, style: motionStyle }, motionRef) => /*#__PURE__*/ import_react.createElement("div", {
ref: motionRef,
className: clsx(`${prefixCls}-treenode-motion`, motionClassName),
style: motionStyle
}, motionNodes.map((treeNode) => {
const { data: { ...restProps }, title, key, isStart, isEnd } = treeNode;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(key, treeNodeRequiredProps);
return /*#__PURE__*/ import_react.createElement(TreeNode, _extends$22({}, restProps, treeNodeProps, {
title,
active,
data: treeNode.data,
key,
isStart,
isEnd
}));
})));
return /*#__PURE__*/ import_react.createElement(TreeNode, _extends$22({
domRef: ref,
className,
style
}, props, { active }));
});
function findExpandedKeys(prev = [], next = []) {
const prevLen = prev.length;
const nextLen = next.length;
if (Math.abs(prevLen - nextLen) !== 1) return {
add: false,
key: null
};
function find(shorter, longer) {
const cache = /* @__PURE__ */ new Map();
shorter.forEach((key) => {
cache.set(key, true);
});
const keys = longer.filter((key) => !cache.has(key));
return keys.length === 1 ? keys[0] : null;
}
if (prevLen < nextLen) return {
add: true,
key: find(prev, next)
};
return {
add: false,
key: find(next, prev)
};
}
function getExpandRange(shorter, longer, key) {
const shorterEndNode = shorter[shorter.findIndex((data) => data.key === key) + 1];
const longerStartIndex = longer.findIndex((data) => data.key === key);
if (shorterEndNode) {
const longerEndIndex = longer.findIndex((data) => data.key === shorterEndNode.key);
return longer.slice(longerStartIndex + 1, longerEndIndex);
}
return longer.slice(longerStartIndex + 1);
}
/**
* Handle virtual list of the TreeNodes.
*/
function _extends$21() {
_extends$21 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$21.apply(this, arguments);
}
var MOTION_KEY = `RC_TREE_MOTION_${Math.random()}`;
var MotionNode = { key: MOTION_KEY };
var MotionEntity = {
key: MOTION_KEY,
level: 0,
index: 0,
pos: "0",
node: MotionNode,
nodes: [MotionNode]
};
var MotionFlattenData = {
parent: null,
children: [],
pos: MotionEntity.pos,
data: MotionNode,
title: null,
key: MOTION_KEY,
/** Hold empty list here since we do not use it */
isStart: [],
isEnd: []
};
/**
* We only need get visible content items to play the animation.
*/
function getMinimumRangeTransitionRange(list, virtual, height, itemHeight) {
if (virtual === false || !height) return list;
return list.slice(0, Math.ceil(height / itemHeight) + 1);
}
function itemKey(item) {
const { key, pos } = item;
return getKey(key, pos);
}
var NodeList = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { prefixCls, data, selectable, checkable, expandedKeys, selectedKeys, checkedKeys, loadedKeys, loadingKeys, halfCheckedKeys, keyEntities, disabled, dragging, dragOverNodeKey, dropPosition, motion, height, itemHeight, virtual, scrollWidth, focusable, activeItem, tabIndex, onKeyDown, onFocus, onBlur, onMouseDown, onActiveChange, onListChangeStart, onListChangeEnd, ...domProps } = props;
const treeId = useId_default();
const listRef = import_react.useRef(null);
const indentMeasurerRef = import_react.useRef(null);
import_react.useImperativeHandle(ref, () => ({
scrollTo: (scroll) => {
listRef.current.scrollTo(scroll);
},
getIndentWidth: () => indentMeasurerRef.current.offsetWidth
}));
const [prevExpandedKeys, setPrevExpandedKeys] = import_react.useState(expandedKeys);
const [prevData, setPrevData] = import_react.useState(data);
const [transitionData, setTransitionData] = import_react.useState(data);
const [transitionRange, setTransitionRange] = import_react.useState([]);
const [motionType, setMotionType] = import_react.useState(null);
const dataRef = import_react.useRef(data);
dataRef.current = data;
function onMotionEnd() {
const latestData = dataRef.current;
setPrevData(latestData);
setTransitionData(latestData);
setTransitionRange([]);
setMotionType(null);
onListChangeEnd();
}
useLayoutEffect$2(() => {
setPrevExpandedKeys(expandedKeys);
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
if (diffExpanded.key !== null) {
if (diffExpanded.add) {
const keyIndex = prevData.findIndex(({ key }) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange(getExpandRange(prevData, data, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = prevData.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType("show");
} else {
const keyIndex = data.findIndex(({ key }) => key === diffExpanded.key);
const rangeNodes = getMinimumRangeTransitionRange(getExpandRange(data, prevData, diffExpanded.key), virtual, height, itemHeight);
const newTransitionData = data.slice();
newTransitionData.splice(keyIndex + 1, 0, MotionFlattenData);
setTransitionData(newTransitionData);
setTransitionRange(rangeNodes);
setMotionType("hide");
}
} else if (prevData !== data) {
setPrevData(data);
setTransitionData(data);
}
}, [expandedKeys, data]);
import_react.useEffect(() => {
if (!dragging) onMotionEnd();
}, [dragging]);
const mergedData = motion ? transitionData : data;
const treeNodeRequiredProps = {
expandedKeys,
selectedKeys,
loadedKeys,
loadingKeys,
checkedKeys,
halfCheckedKeys,
dragOverNodeKey,
dropPosition,
keyEntities
};
return /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement("div", {
className: `${prefixCls}-treenode`,
"aria-hidden": true,
style: {
position: "absolute",
pointerEvents: "none",
visibility: "hidden",
height: 0,
overflow: "hidden",
border: 0,
padding: 0
}
}, /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-indent` }, /*#__PURE__*/ import_react.createElement("div", {
ref: indentMeasurerRef,
className: `${prefixCls}-indent-unit`
}))), /*#__PURE__*/ import_react.createElement(es_default$7, _extends$21({}, domProps, {
data: mergedData,
itemKey,
height,
fullHeight: false,
virtual,
itemHeight,
scrollWidth,
prefixCls: `${prefixCls}-list`,
ref: listRef,
role: "tree",
tabIndex: focusable !== false && !disabled ? tabIndex : void 0,
"aria-activedescendant": activeItem ? getId(treeId, activeItem.key) : void 0,
onKeyDown,
onFocus,
onBlur,
onMouseDown,
onVisibleChange: (originList) => {
if (originList.every((item) => itemKey(item) !== MOTION_KEY)) onMotionEnd();
}
}), (treeNode) => {
const { pos, data: { ...restProps }, title, key, isStart, isEnd } = treeNode;
const mergedKey = getKey(key, pos);
delete restProps.key;
delete restProps.children;
const treeNodeProps = getTreeNodeProps(mergedKey, treeNodeRequiredProps);
return /*#__PURE__*/ import_react.createElement(MotionTreeNode, _extends$21({}, restProps, treeNodeProps, {
title,
active: !!activeItem && key === activeItem.key,
pos,
data: treeNode.data,
isStart,
isEnd,
motion,
motionNodes: key === MOTION_KEY ? transitionRange : null,
motionType,
onMotionStart: onListChangeStart,
onMotionEnd,
treeNodeRequiredProps,
treeId,
onMouseMove: () => {
onActiveChange(null);
}
}));
}));
});
/**
* Legacy code. Should avoid to use if you are new to import these code.
*/
function arrDel(list, value) {
if (!list) return [];
const clone = list.slice();
const index = clone.indexOf(value);
if (index >= 0) clone.splice(index, 1);
return clone;
}
function arrAdd(list, value) {
const clone = (list || []).slice();
if (clone.indexOf(value) === -1) clone.push(value);
return clone;
}
function posToArr(pos) {
return pos.split("-");
}
function getDragChildrenKeys(dragNodeKey, keyEntities) {
const dragChildrenKeys = [];
const entity = getEntity(keyEntities, dragNodeKey);
function dig(list = []) {
list.forEach(({ key, children }) => {
dragChildrenKeys.push(key);
dig(children);
});
}
dig(entity.children);
return dragChildrenKeys;
}
function isLastChild(treeNodeEntity) {
if (treeNodeEntity.parent) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === treeNodeEntity.parent.children.length - 1;
}
return false;
}
function isFirstChild(treeNodeEntity) {
const posArr = posToArr(treeNodeEntity.pos);
return Number(posArr[posArr.length - 1]) === 0;
}
function calcDropPosition(event, dragNodeProps, targetNodeProps, indent, startMousePosition, allowDrop, flattenedNodes, keyEntities, expandKeys, direction) {
const { clientX, clientY } = event;
const { top, height } = event.target.getBoundingClientRect();
const rawDropLevelOffset = ((direction === "rtl" ? -1 : 1) * ((startMousePosition?.x || 0) - clientX) - 12) / indent;
const filteredExpandKeys = expandKeys.filter((key) => keyEntities[key]?.children?.length);
let abstractDropNodeEntity = getEntity(keyEntities, targetNodeProps.eventKey);
if (clientY < top + height / 2) {
const nodeIndex = flattenedNodes.findIndex((flattenedNode) => flattenedNode.key === abstractDropNodeEntity.key);
const prevNodeKey = flattenedNodes[nodeIndex <= 0 ? 0 : nodeIndex - 1].key;
abstractDropNodeEntity = getEntity(keyEntities, prevNodeKey);
}
const initialAbstractDropNodeKey = abstractDropNodeEntity.key;
const abstractDragOverEntity = abstractDropNodeEntity;
const dragOverNodeKey = abstractDropNodeEntity.key;
let dropPosition = 0;
let dropLevelOffset = 0;
if (!filteredExpandKeys.includes(initialAbstractDropNodeKey)) for (let i = 0; i < rawDropLevelOffset; i += 1) if (isLastChild(abstractDropNodeEntity)) {
abstractDropNodeEntity = abstractDropNodeEntity.parent;
dropLevelOffset += 1;
} else break;
const abstractDragDataNode = dragNodeProps.data;
const abstractDropDataNode = abstractDropNodeEntity.node;
let dropAllowed = true;
if (isFirstChild(abstractDropNodeEntity) && abstractDropNodeEntity.level === 0 && clientY < top + height / 2 && allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: -1
}) && abstractDropNodeEntity.key === targetNodeProps.eventKey) dropPosition = -1;
else if ((abstractDragOverEntity.children || []).length && filteredExpandKeys.includes(dragOverNodeKey)) {
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) dropPosition = 0;
else dropAllowed = false;
} else if (dropLevelOffset === 0) {
if (rawDropLevelOffset > -1.5) {
if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) dropPosition = 1;
else dropAllowed = false;
} else if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 0
})) dropPosition = 0;
else if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) dropPosition = 1;
else dropAllowed = false;
} else if (allowDrop({
dragNode: abstractDragDataNode,
dropNode: abstractDropDataNode,
dropPosition: 1
})) dropPosition = 1;
else dropAllowed = false;
return {
dropPosition,
dropLevelOffset,
dropTargetKey: abstractDropNodeEntity.key,
dropTargetPos: abstractDropNodeEntity.pos,
dragOverNodeKey,
dropContainerKey: dropPosition === 0 ? null : abstractDropNodeEntity.parent?.key || null,
dropAllowed
};
}
/**
* Return selectedKeys according with multiple prop
* @param selectedKeys
* @param props
* @returns [string]
*/
function calcSelectedKeys(selectedKeys, props) {
if (!selectedKeys) return void 0;
const { multiple } = props;
if (multiple) return selectedKeys.slice();
if (selectedKeys.length) return [selectedKeys[0]];
return selectedKeys;
}
/**
* Parse `checkedKeys` to { checkedKeys, halfCheckedKeys } style
*/
function parseCheckedKeys(keys) {
if (!keys) return null;
let keyProps;
if (Array.isArray(keys)) keyProps = {
checkedKeys: keys,
halfCheckedKeys: void 0
};
else if (typeof keys === "object") keyProps = {
checkedKeys: keys.checked || void 0,
halfCheckedKeys: keys.halfChecked || void 0
};
else {
warningOnce$1(false, "`checkedKeys` is not an array or an object");
return null;
}
return keyProps;
}
/**
* If user use `autoExpandParent` we should get the list of parent node
* @param keyList
* @param keyEntities
*/
function conductExpandParent(keyList, keyEntities) {
const expandedKeys = /* @__PURE__ */ new Set();
function conductUp(key) {
if (expandedKeys.has(key)) return;
const entity = getEntity(keyEntities, key);
if (!entity) return;
expandedKeys.add(key);
const { parent, node } = entity;
if (node.disabled) return;
if (parent) conductUp(parent.key);
}
(keyList || []).forEach((key) => {
conductUp(key);
});
return [...expandedKeys];
}
function removeFromCheckedKeys(halfCheckedKeys, checkedKeys) {
const filteredKeys = /* @__PURE__ */ new Set();
halfCheckedKeys.forEach((key) => {
if (!checkedKeys.has(key)) filteredKeys.add(key);
});
return filteredKeys;
}
function isCheckDisabled(node) {
const { disabled, disableCheckbox, checkable } = node || {};
return !!(disabled || disableCheckbox) || checkable === false;
}
function fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
const halfCheckedKeys = /* @__PURE__ */ new Set();
for (let level = 0; level <= maxLevel; level += 1) (levelEntities.get(level) || /* @__PURE__ */ new Set()).forEach((entity) => {
const { key, node, children = [] } = entity;
if (checkedKeys.has(key) && !syntheticGetCheckDisabled(node)) children.filter((childEntity) => !syntheticGetCheckDisabled(childEntity.node)).forEach((childEntity) => {
checkedKeys.add(childEntity.key);
});
});
const visitedKeys = /* @__PURE__ */ new Set();
for (let level = maxLevel; level >= 0; level -= 1) (levelEntities.get(level) || /* @__PURE__ */ new Set()).forEach((entity) => {
const { parent, node } = entity;
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) return;
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter((childEntity) => !syntheticGetCheckDisabled(childEntity.node)).forEach(({ key }) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) allChecked = false;
if (!partialChecked && (checked || halfCheckedKeys.has(key))) partialChecked = true;
});
if (allChecked) checkedKeys.add(parent.key);
if (partialChecked) halfCheckedKeys.add(parent.key);
visitedKeys.add(parent.key);
});
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
function cleanConductCheck(keys, halfKeys, levelEntities, maxLevel, syntheticGetCheckDisabled) {
const checkedKeys = new Set(keys);
let halfCheckedKeys = new Set(halfKeys);
for (let level = 0; level <= maxLevel; level += 1) (levelEntities.get(level) || /* @__PURE__ */ new Set()).forEach((entity) => {
const { key, node, children = [] } = entity;
if (!checkedKeys.has(key) && !halfCheckedKeys.has(key) && !syntheticGetCheckDisabled(node)) children.filter((childEntity) => !syntheticGetCheckDisabled(childEntity.node)).forEach((childEntity) => {
checkedKeys.delete(childEntity.key);
});
});
halfCheckedKeys = /* @__PURE__ */ new Set();
const visitedKeys = /* @__PURE__ */ new Set();
for (let level = maxLevel; level >= 0; level -= 1) (levelEntities.get(level) || /* @__PURE__ */ new Set()).forEach((entity) => {
const { parent, node } = entity;
if (syntheticGetCheckDisabled(node) || !entity.parent || visitedKeys.has(entity.parent.key)) return;
if (syntheticGetCheckDisabled(entity.parent.node)) {
visitedKeys.add(parent.key);
return;
}
let allChecked = true;
let partialChecked = false;
(parent.children || []).filter((childEntity) => !syntheticGetCheckDisabled(childEntity.node)).forEach(({ key }) => {
const checked = checkedKeys.has(key);
if (allChecked && !checked) allChecked = false;
if (!partialChecked && (checked || halfCheckedKeys.has(key))) partialChecked = true;
});
if (!allChecked) checkedKeys.delete(parent.key);
if (partialChecked) halfCheckedKeys.add(parent.key);
visitedKeys.add(parent.key);
});
return {
checkedKeys: Array.from(checkedKeys),
halfCheckedKeys: Array.from(removeFromCheckedKeys(halfCheckedKeys, checkedKeys))
};
}
/**
* Conduct with keys.
* @param keyList current key list
* @param keyEntities key - dataEntity map
* @param mode `fill` to fill missing key, `clean` to remove useless key
*/
function conductCheck(keyList, checked, keyEntities, getCheckDisabled) {
const warningMissKeys = [];
let syntheticGetCheckDisabled;
if (getCheckDisabled) syntheticGetCheckDisabled = getCheckDisabled;
else syntheticGetCheckDisabled = isCheckDisabled;
const keys = new Set(keyList.filter((key) => {
const hasEntity = !!getEntity(keyEntities, key);
if (!hasEntity) warningMissKeys.push(key);
return hasEntity;
}));
const levelEntities = /* @__PURE__ */ new Map();
let maxLevel = 0;
Object.keys(keyEntities).forEach((key) => {
const entity = keyEntities[key];
const { level } = entity;
let levelSet = levelEntities.get(level);
if (!levelSet) {
levelSet = /* @__PURE__ */ new Set();
levelEntities.set(level, levelSet);
}
levelSet.add(entity);
maxLevel = Math.max(maxLevel, level);
});
warningOnce$1(!warningMissKeys.length, `Tree missing follow keys: ${warningMissKeys.slice(0, 100).map((key) => `'${key}'`).join(", ")}`);
let result;
if (checked === true) result = fillConductCheck(keys, levelEntities, maxLevel, syntheticGetCheckDisabled);
else result = cleanConductCheck(keys, checked.halfCheckedKeys, levelEntities, maxLevel, syntheticGetCheckDisabled);
return result;
}
function _extends$20() {
_extends$20 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$20.apply(this, arguments);
}
var MAX_RETRY_TIMES = 10;
var Tree$2 = class extends import_react.Component {
static defaultProps = {
prefixCls: "rc-tree",
showLine: false,
showIcon: true,
selectable: true,
multiple: false,
checkable: false,
disabled: false,
checkStrictly: false,
draggable: false,
defaultExpandParent: true,
autoExpandParent: false,
defaultExpandAll: false,
defaultExpandedKeys: [],
defaultCheckedKeys: [],
defaultSelectedKeys: [],
dropIndicatorRender: DropIndicator,
allowDrop: () => true,
expandAction: false
};
static TreeNode = TreeNode;
destroyed = false;
delayedDragEnterLogic;
loadingRetryTimes = {};
state = {
keyEntities: {},
indent: null,
selectedKeys: [],
checkedKeys: [],
halfCheckedKeys: [],
loadedKeys: [],
loadingKeys: [],
expandedKeys: [],
draggingNodeKey: null,
dragChildrenKeys: [],
dropTargetKey: null,
dropPosition: null,
dropContainerKey: null,
dropLevelOffset: null,
dropTargetPos: null,
dropAllowed: true,
dragOverNodeKey: null,
treeData: [],
flattenNodes: [],
activeKey: null,
listChanging: false,
prevProps: null,
fieldNames: fillFieldNames()
};
dragStartMousePosition = null;
dragNodeProps = null;
currentMouseOverDroppableNodeKey = null;
focusedByMouse = false;
listRef = /*#__PURE__*/ import_react.createRef();
componentDidMount() {
this.destroyed = false;
this.onUpdated();
window.addEventListener("mouseup", this.onGlobalMouseUp);
}
componentDidUpdate() {
this.onUpdated();
}
onUpdated() {
const { activeKey, itemScrollOffset = 0 } = this.props;
if (activeKey !== void 0 && activeKey !== this.state.activeKey) {
this.setState({ activeKey });
if (activeKey !== null) this.scrollTo({
key: activeKey,
offset: itemScrollOffset
});
}
}
componentWillUnmount() {
window.removeEventListener("dragend", this.onWindowDragEnd);
window.removeEventListener("mouseup", this.onGlobalMouseUp);
this.destroyed = true;
}
static getDerivedStateFromProps(props, prevState) {
const { prevProps } = prevState;
const newState = { prevProps: props };
function needSync(name) {
return !prevProps && props.hasOwnProperty(name) || prevProps && prevProps[name] !== props[name];
}
let treeData;
let { fieldNames } = prevState;
if (needSync("fieldNames")) {
fieldNames = fillFieldNames(props.fieldNames);
newState.fieldNames = fieldNames;
}
if (needSync("treeData")) ({treeData} = props);
else if (needSync("children")) {
warningOnce$1(false, "`children` of Tree is deprecated. Please use `treeData` instead.");
treeData = convertTreeToData(props.children);
}
if (treeData) {
newState.treeData = treeData;
const entitiesMap = convertDataToEntities(treeData, { fieldNames });
newState.keyEntities = {
[MOTION_KEY]: MotionEntity,
...entitiesMap.keyEntities
};
}
const keyEntities = newState.keyEntities || prevState.keyEntities;
if (needSync("expandedKeys") || prevProps && needSync("autoExpandParent")) newState.expandedKeys = props.autoExpandParent || !prevProps && props.defaultExpandParent ? conductExpandParent(props.expandedKeys, keyEntities) : props.expandedKeys;
else if (!prevProps && props.defaultExpandAll) {
const cloneKeyEntities = { ...keyEntities };
delete cloneKeyEntities[MOTION_KEY];
const nextExpandedKeys = [];
Object.keys(cloneKeyEntities).forEach((key) => {
const entity = cloneKeyEntities[key];
if (entity.children && entity.children.length) nextExpandedKeys.push(entity.key);
});
newState.expandedKeys = nextExpandedKeys;
} else if (!prevProps && props.defaultExpandedKeys) newState.expandedKeys = props.autoExpandParent || props.defaultExpandParent ? conductExpandParent(props.defaultExpandedKeys, keyEntities) : props.defaultExpandedKeys;
if (!newState.expandedKeys) delete newState.expandedKeys;
if (treeData || newState.expandedKeys) newState.flattenNodes = flattenTreeData(treeData || prevState.treeData, newState.expandedKeys || prevState.expandedKeys, fieldNames);
if (props.selectable) {
if (needSync("selectedKeys")) newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props);
else if (!prevProps && props.defaultSelectedKeys) newState.selectedKeys = calcSelectedKeys(props.defaultSelectedKeys, props);
}
if (props.checkable) {
let checkedKeyEntity;
if (needSync("checkedKeys")) checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {};
else if (!prevProps && props.defaultCheckedKeys) checkedKeyEntity = parseCheckedKeys(props.defaultCheckedKeys) || {};
else if (treeData) checkedKeyEntity = parseCheckedKeys(props.checkedKeys) || {
checkedKeys: prevState.checkedKeys,
halfCheckedKeys: prevState.halfCheckedKeys
};
if (checkedKeyEntity) {
let { checkedKeys = [], halfCheckedKeys = [] } = checkedKeyEntity;
if (!props.checkStrictly) {
const conductKeys = conductCheck(checkedKeys, true, keyEntities);
({checkedKeys, halfCheckedKeys} = conductKeys);
}
newState.checkedKeys = checkedKeys;
newState.halfCheckedKeys = halfCheckedKeys;
}
}
if (needSync("loadedKeys")) newState.loadedKeys = props.loadedKeys;
return newState;
}
onNodeDragStart = (event, nodeProps) => {
const { expandedKeys, keyEntities } = this.state;
const { onDragStart } = this.props;
const { eventKey } = nodeProps;
this.dragNodeProps = nodeProps;
this.dragStartMousePosition = {
x: event.clientX,
y: event.clientY
};
const newExpandedKeys = arrDel(expandedKeys, eventKey);
this.setState({
draggingNodeKey: eventKey,
dragChildrenKeys: getDragChildrenKeys(eventKey, keyEntities),
indent: this.listRef.current.getIndentWidth()
});
this.setExpandedKeys(newExpandedKeys);
window.addEventListener("dragend", this.onWindowDragEnd);
onDragStart?.({
event,
node: convertNodePropsToEventData(nodeProps)
});
};
/**
* [Legacy] Select handler is smaller than node,
* so that this will trigger when drag enter node or select handler.
* This is a little tricky if customize css without padding.
* Better for use mouse move event to refresh drag state.
* But let's just keep it to avoid event trigger logic change.
*/
onNodeDragEnter = (event, nodeProps) => {
const { expandedKeys, keyEntities, dragChildrenKeys, flattenNodes, indent } = this.state;
const { onDragEnter, onExpand, allowDrop, direction } = this.props;
const { pos, eventKey } = nodeProps;
if (this.currentMouseOverDroppableNodeKey !== eventKey) this.currentMouseOverDroppableNodeKey = eventKey;
if (!this.dragNodeProps) {
this.resetDragState();
return;
}
const { dropPosition, dropLevelOffset, dropTargetKey, dropContainerKey, dropTargetPos, dropAllowed, dragOverNodeKey } = calcDropPosition(event, this.dragNodeProps, nodeProps, indent, this.dragStartMousePosition, allowDrop, flattenNodes, keyEntities, expandedKeys, direction);
if (dragChildrenKeys.includes(dropTargetKey) || !dropAllowed) {
this.resetDragState();
return;
}
if (!this.delayedDragEnterLogic) this.delayedDragEnterLogic = {};
Object.keys(this.delayedDragEnterLogic).forEach((key) => {
clearTimeout(this.delayedDragEnterLogic[key]);
});
if (this.dragNodeProps.eventKey !== nodeProps.eventKey) {
event.persist();
this.delayedDragEnterLogic[pos] = window.setTimeout(() => {
if (this.state.draggingNodeKey === null) return;
let newExpandedKeys = [...expandedKeys];
const entity = getEntity(keyEntities, nodeProps.eventKey);
if (entity && (entity.children || []).length) newExpandedKeys = arrAdd(expandedKeys, nodeProps.eventKey);
this.setExpandedKeys(newExpandedKeys);
onExpand?.(newExpandedKeys, {
node: convertNodePropsToEventData(nodeProps),
expanded: true,
nativeEvent: event.nativeEvent
});
}, 800);
}
if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
this.resetDragState();
return;
}
this.setState({
dragOverNodeKey,
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed
});
onDragEnter?.({
event,
node: convertNodePropsToEventData(nodeProps),
expandedKeys
});
};
onNodeDragOver = (event, nodeProps) => {
const { dragChildrenKeys, flattenNodes, keyEntities, expandedKeys, indent } = this.state;
const { onDragOver, allowDrop, direction } = this.props;
if (!this.dragNodeProps) return;
const { dropPosition, dropLevelOffset, dropTargetKey, dropContainerKey, dropTargetPos, dropAllowed, dragOverNodeKey } = calcDropPosition(event, this.dragNodeProps, nodeProps, indent, this.dragStartMousePosition, allowDrop, flattenNodes, keyEntities, expandedKeys, direction);
if (dragChildrenKeys.includes(dropTargetKey) || !dropAllowed) return;
if (this.dragNodeProps.eventKey === dropTargetKey && dropLevelOffset === 0) {
if (!(this.state.dropPosition === null && this.state.dropLevelOffset === null && this.state.dropTargetKey === null && this.state.dropContainerKey === null && this.state.dropTargetPos === null && this.state.dropAllowed === false && this.state.dragOverNodeKey === null)) this.resetDragState();
} else if (!(dropPosition === this.state.dropPosition && dropLevelOffset === this.state.dropLevelOffset && dropTargetKey === this.state.dropTargetKey && dropContainerKey === this.state.dropContainerKey && dropTargetPos === this.state.dropTargetPos && dropAllowed === this.state.dropAllowed && dragOverNodeKey === this.state.dragOverNodeKey)) this.setState({
dropPosition,
dropLevelOffset,
dropTargetKey,
dropContainerKey,
dropTargetPos,
dropAllowed,
dragOverNodeKey
});
onDragOver?.({
event,
node: convertNodePropsToEventData(nodeProps)
});
};
onNodeDragLeave = (event, nodeProps) => {
if (this.currentMouseOverDroppableNodeKey === nodeProps.eventKey && !event.currentTarget.contains(event.relatedTarget)) {
this.resetDragState();
this.currentMouseOverDroppableNodeKey = null;
}
const { onDragLeave } = this.props;
onDragLeave?.({
event,
node: convertNodePropsToEventData(nodeProps)
});
};
onWindowDragEnd = (event) => {
this.onNodeDragEnd(event, null, true);
window.removeEventListener("dragend", this.onWindowDragEnd);
};
onNodeDragEnd = (event, nodeProps) => {
const { onDragEnd } = this.props;
this.setState({ dragOverNodeKey: null });
this.cleanDragState();
onDragEnd?.({
event,
node: convertNodePropsToEventData(nodeProps)
});
this.dragNodeProps = null;
window.removeEventListener("dragend", this.onWindowDragEnd);
};
onNodeDrop = (event, _, outsideTree = false) => {
const { dragChildrenKeys, dropPosition, dropTargetKey, dropTargetPos, dropAllowed } = this.state;
if (!dropAllowed) return;
const { onDrop } = this.props;
this.setState({ dragOverNodeKey: null });
this.cleanDragState();
if (dropTargetKey === null) return;
const abstractDropNodeProps = {
...getTreeNodeProps(dropTargetKey, this.getTreeNodeRequiredProps()),
active: this.getActiveItem()?.key === dropTargetKey,
data: getEntity(this.state.keyEntities, dropTargetKey).node
};
const dropToChild = dragChildrenKeys.includes(dropTargetKey);
warningOnce$1(!dropToChild, "Can not drop to dragNode's children node. This is a bug of rc-tree. Please report an issue.");
const posArr = posToArr(dropTargetPos);
const dropResult = {
event,
node: convertNodePropsToEventData(abstractDropNodeProps),
dragNode: this.dragNodeProps ? convertNodePropsToEventData(this.dragNodeProps) : null,
dragNodesKeys: [this.dragNodeProps.eventKey].concat(dragChildrenKeys),
dropToGap: dropPosition !== 0,
dropPosition: dropPosition + Number(posArr[posArr.length - 1])
};
if (!outsideTree) onDrop?.(dropResult);
this.dragNodeProps = null;
};
resetDragState() {
this.setState({
dragOverNodeKey: null,
dropPosition: null,
dropLevelOffset: null,
dropTargetKey: null,
dropContainerKey: null,
dropTargetPos: null,
dropAllowed: false
});
}
cleanDragState = () => {
const { draggingNodeKey } = this.state;
if (draggingNodeKey !== null) this.setState({
draggingNodeKey: null,
dropPosition: null,
dropContainerKey: null,
dropTargetKey: null,
dropLevelOffset: null,
dropAllowed: true,
dragOverNodeKey: null
});
this.dragStartMousePosition = null;
this.currentMouseOverDroppableNodeKey = null;
};
triggerExpandActionExpand = (e, treeNode) => {
const { expandedKeys, flattenNodes } = this.state;
const { expanded, key, isLeaf } = treeNode;
if (isLeaf || e.shiftKey || e.metaKey || e.ctrlKey) return;
const node = flattenNodes.filter((nodeItem) => nodeItem.key === key)[0];
const eventNode = convertNodePropsToEventData({
...getTreeNodeProps(key, this.getTreeNodeRequiredProps()),
data: node.data
});
this.setExpandedKeys(expanded ? arrDel(expandedKeys, key) : arrAdd(expandedKeys, key));
this.onNodeExpand(e, eventNode);
};
onNodeClick = (e, treeNode) => {
const { onClick, expandAction } = this.props;
if (expandAction === "click") this.triggerExpandActionExpand(e, treeNode);
onClick?.(e, treeNode);
};
onNodeDoubleClick = (e, treeNode) => {
const { onDoubleClick, expandAction } = this.props;
if (expandAction === "doubleClick") this.triggerExpandActionExpand(e, treeNode);
onDoubleClick?.(e, treeNode);
};
onNodeSelect = (e, treeNode) => {
let { selectedKeys } = this.state;
const { keyEntities, fieldNames } = this.state;
const { onSelect, multiple } = this.props;
const { selected } = treeNode;
const key = treeNode[fieldNames.key];
const targetSelected = !selected;
if (!targetSelected) selectedKeys = arrDel(selectedKeys, key);
else if (!multiple) selectedKeys = [key];
else selectedKeys = arrAdd(selectedKeys, key);
const selectedNodes = selectedKeys.map((selectedKey) => {
const entity = getEntity(keyEntities, selectedKey);
return entity ? entity.node : null;
}).filter(Boolean);
this.setUncontrolledState({ selectedKeys });
onSelect?.(selectedKeys, {
event: "select",
selected: targetSelected,
node: treeNode,
selectedNodes,
nativeEvent: e.nativeEvent
});
};
onNodeCheck = (e, treeNode, checked) => {
const { keyEntities, checkedKeys: oriCheckedKeys, halfCheckedKeys: oriHalfCheckedKeys } = this.state;
const { checkStrictly, onCheck } = this.props;
const { key } = treeNode;
let checkedObj;
const eventObj = {
event: "check",
node: treeNode,
checked,
nativeEvent: e.nativeEvent
};
if (checkStrictly) {
const checkedKeys = checked ? arrAdd(oriCheckedKeys, key) : arrDel(oriCheckedKeys, key);
checkedObj = {
checked: checkedKeys,
halfChecked: arrDel(oriHalfCheckedKeys, key)
};
eventObj.checkedNodes = checkedKeys.map((checkedKey) => getEntity(keyEntities, checkedKey)).filter(Boolean).map((entity) => entity.node);
this.setUncontrolledState({ checkedKeys });
} else {
let { checkedKeys, halfCheckedKeys } = conductCheck([...oriCheckedKeys, key], true, keyEntities);
if (!checked) {
const keySet = new Set(checkedKeys);
keySet.delete(key);
({checkedKeys, halfCheckedKeys} = conductCheck(Array.from(keySet), {
halfCheckedKeys
}, keyEntities));
}
checkedObj = checkedKeys;
eventObj.checkedNodes = [];
eventObj.checkedNodesPositions = [];
eventObj.halfCheckedKeys = halfCheckedKeys;
checkedKeys.forEach((checkedKey) => {
const entity = getEntity(keyEntities, checkedKey);
if (!entity) return;
const { node, pos } = entity;
eventObj.checkedNodes.push(node);
eventObj.checkedNodesPositions.push({
node,
pos
});
});
this.setUncontrolledState({ checkedKeys }, false, { halfCheckedKeys });
}
onCheck?.(checkedObj, eventObj);
};
onNodeLoad = (treeNode) => {
const { key } = treeNode;
const { keyEntities } = this.state;
if (getEntity(keyEntities, key)?.children?.length) return;
const loadPromise = new Promise((resolve, reject) => {
this.setState(({ loadedKeys = [], loadingKeys = [] }) => {
const { loadData, onLoad } = this.props;
if (!loadData || loadedKeys.includes(key) || loadingKeys.includes(key)) return null;
loadData(treeNode).then(() => {
const { loadedKeys: currentLoadedKeys } = this.state;
const newLoadedKeys = arrAdd(currentLoadedKeys, key);
onLoad?.(newLoadedKeys, {
event: "load",
node: treeNode
});
this.setUncontrolledState({ loadedKeys: newLoadedKeys });
this.setState((prevState) => ({ loadingKeys: arrDel(prevState.loadingKeys, key) }));
resolve();
}).catch((e) => {
this.setState((prevState) => ({ loadingKeys: arrDel(prevState.loadingKeys, key) }));
this.loadingRetryTimes[key] = (this.loadingRetryTimes[key] || 0) + 1;
if (this.loadingRetryTimes[key] >= MAX_RETRY_TIMES) {
const { loadedKeys: currentLoadedKeys } = this.state;
warningOnce$1(false, "Retry for `loadData` many times but still failed. No more retry.");
this.setUncontrolledState({ loadedKeys: arrAdd(currentLoadedKeys, key) });
resolve();
}
reject(e);
});
return { loadingKeys: arrAdd(loadingKeys, key) };
});
});
loadPromise.catch(() => {});
return loadPromise;
};
onNodeMouseEnter = (event, node) => {
const { onMouseEnter } = this.props;
onMouseEnter?.({
event,
node
});
};
onNodeMouseLeave = (event, node) => {
const { onMouseLeave } = this.props;
onMouseLeave?.({
event,
node
});
};
onNodeContextMenu = (event, node) => {
const { onRightClick } = this.props;
if (onRightClick) {
event.preventDefault();
onRightClick({
event,
node
});
}
};
onMouseDown = (event) => {
this.focusedByMouse = true;
const { onMouseDown } = this.props;
onMouseDown?.(event);
};
onGlobalMouseUp = () => {
this.focusedByMouse = false;
};
onFocus = (...args) => {
const { onFocus, disabled } = this.props;
const { activeKey, selectedKeys, flattenNodes } = this.state;
if (!this.focusedByMouse && !disabled && activeKey === null) {
const visibleSelectedKey = selectedKeys.find((key) => {
return flattenNodes.some((nodeItem) => nodeItem.key === key);
});
if (visibleSelectedKey !== void 0) this.onActiveChange(visibleSelectedKey);
else this.onActiveChange(flattenNodes?.[0]?.key || null);
}
onFocus?.(...args);
};
onBlur = (...args) => {
const { onBlur } = this.props;
this.onActiveChange(null);
onBlur?.(...args);
};
getTreeNodeRequiredProps = () => {
const { expandedKeys, selectedKeys, loadedKeys, loadingKeys, checkedKeys, halfCheckedKeys, dragOverNodeKey, dropPosition, keyEntities } = this.state;
return {
expandedKeys: expandedKeys || [],
selectedKeys: selectedKeys || [],
loadedKeys: loadedKeys || [],
loadingKeys: loadingKeys || [],
checkedKeys: checkedKeys || [],
halfCheckedKeys: halfCheckedKeys || [],
dragOverNodeKey,
dropPosition,
keyEntities
};
};
/** Set uncontrolled `expandedKeys`. This will also auto update `flattenNodes`. */
setExpandedKeys = (expandedKeys) => {
if (this.props.hasOwnProperty("expandedKeys")) return;
const { treeData, fieldNames } = this.state;
const flattenNodes = flattenTreeData(treeData, expandedKeys, fieldNames);
this.setUncontrolledState({
expandedKeys,
flattenNodes
}, true);
};
onNodeExpand = (e, treeNode) => {
let { expandedKeys } = this.state;
const { listChanging, fieldNames } = this.state;
const { onExpand, loadData } = this.props;
const { expanded } = treeNode;
const key = treeNode[fieldNames.key];
if (listChanging) return;
const certain = expandedKeys.includes(key);
const targetExpanded = !expanded;
warningOnce$1(expanded && certain || !expanded && !certain, "Expand state not sync with index check");
expandedKeys = targetExpanded ? arrAdd(expandedKeys, key) : arrDel(expandedKeys, key);
this.setExpandedKeys(expandedKeys);
onExpand?.(expandedKeys, {
node: treeNode,
expanded: targetExpanded,
nativeEvent: e.nativeEvent
});
if (targetExpanded && loadData) {
const loadPromise = this.onNodeLoad(treeNode);
if (loadPromise) loadPromise.then(() => {
const newFlattenTreeData = flattenTreeData(this.state.treeData, expandedKeys, fieldNames);
this.setUncontrolledState({ flattenNodes: newFlattenTreeData });
}).catch(() => {
const { expandedKeys: currentExpandedKeys } = this.state;
const expandedKeysToRestore = arrDel(currentExpandedKeys, key);
this.setExpandedKeys(expandedKeysToRestore);
});
}
};
onListChangeStart = () => {
this.setUncontrolledState({ listChanging: true });
};
onListChangeEnd = () => {
setTimeout(() => {
this.setUncontrolledState({ listChanging: false });
});
};
onActiveChange = (newActiveKey) => {
const { activeKey } = this.state;
const { onActiveChange, itemScrollOffset = 0 } = this.props;
if (activeKey === newActiveKey) return;
this.setState({ activeKey: newActiveKey });
if (newActiveKey !== null) this.scrollTo({
key: newActiveKey,
offset: itemScrollOffset
});
onActiveChange?.(newActiveKey);
};
getActiveItem = () => {
const { activeKey, flattenNodes } = this.state;
if (activeKey === null) return null;
return flattenNodes.find(({ key }) => key === activeKey) || null;
};
offsetActiveKey = (offset) => {
const { flattenNodes, activeKey } = this.state;
let index = flattenNodes.findIndex(({ key }) => key === activeKey);
if (index === -1 && offset < 0) index = flattenNodes.length;
index = (index + offset + flattenNodes.length) % flattenNodes.length;
const item = flattenNodes[index];
if (item) {
const { key } = item;
this.onActiveChange(key);
} else this.onActiveChange(null);
};
onKeyDown = (event) => {
const { activeKey, expandedKeys, checkedKeys, flattenNodes, keyEntities } = this.state;
const { onKeyDown, checkable, selectable, disabled, loadData } = this.props;
if (disabled) return;
switch (event.key) {
case "ArrowUp":
this.offsetActiveKey(-1);
event.preventDefault();
break;
case "ArrowDown":
this.offsetActiveKey(1);
event.preventDefault();
break;
case "Home":
this.onActiveChange(flattenNodes?.[0]?.key);
event.preventDefault();
break;
case "End":
this.onActiveChange(flattenNodes?.[flattenNodes.length - 1]?.key);
event.preventDefault();
}
const activeItem = this.getActiveItem();
if (activeItem && activeItem.data) {
const eventNode = convertNodePropsToEventData({
...getTreeNodeProps(activeKey, this.getTreeNodeRequiredProps()),
data: activeItem.data,
active: true
});
const hasChildren = !!getEntity(keyEntities, activeKey)?.children?.length;
const expandable = !isLeafNode(activeItem.data.isLeaf, loadData, hasChildren, eventNode.loaded);
const canCheck = checkable && !eventNode.disabled && eventNode.checkable !== false && !eventNode.disableCheckbox;
const canSelect = !checkable && selectable && !eventNode.disabled && eventNode.selectable !== false;
switch (event.key) {
case "ArrowLeft":
if (expandable && expandedKeys.includes(activeKey)) this.onNodeExpand({}, eventNode);
else if (activeItem.parent) this.onActiveChange(activeItem.parent.key);
event.preventDefault();
break;
case "ArrowRight":
if (expandable && !expandedKeys.includes(activeKey)) this.onNodeExpand({}, eventNode);
else if (activeItem.children && activeItem.children.length) this.onActiveChange(activeItem.children[0].key);
event.preventDefault();
break;
case "Enter":
if (expandable) {
event.preventDefault();
this.onNodeExpand({}, eventNode);
} else if (canCheck) {
if (!checkedKeys.includes(activeKey)) {
event.preventDefault();
this.onNodeCheck({}, eventNode, true);
}
} else if (canSelect && !eventNode.selected) {
event.preventDefault();
this.onNodeSelect({}, eventNode);
}
break;
case " ": if (canCheck) {
event.preventDefault();
this.onNodeCheck({}, eventNode, !checkedKeys.includes(activeKey));
} else if (canSelect) {
event.preventDefault();
this.onNodeSelect({}, eventNode);
}
}
}
onKeyDown?.(event);
};
/**
* Only update the value which is not in props
*/
setUncontrolledState = (state, atomic = false, forceState = null) => {
if (!this.destroyed) {
let needSync = false;
let allPassed = true;
const newState = {};
Object.keys(state).forEach((name) => {
if (this.props.hasOwnProperty(name)) {
allPassed = false;
return;
}
needSync = true;
newState[name] = state[name];
});
if (needSync && (!atomic || allPassed)) this.setState({
...newState,
...forceState
});
}
};
scrollTo = (scroll) => {
if (scroll && typeof scroll === "object" && scroll.autoExpand) this.setExpandedKeys(arrAdd(this.state.expandedKeys, scroll.key));
this.listRef.current.scrollTo(scroll);
};
render() {
const { flattenNodes, keyEntities, draggingNodeKey, dropLevelOffset, dropContainerKey, dropTargetKey, dropPosition, dragOverNodeKey, indent } = this.state;
const { prefixCls, className, style, styles, classNames: treeClassNames, showLine, focusable, tabIndex = 0, selectable, showIcon, icon, switcherIcon, draggable, checkable, checkStrictly, disabled, motion, loadData, filterTreeNode, height, itemHeight, scrollWidth, virtual, titleRender, dropIndicatorRender, onContextMenu, onScroll, direction, rootClassName, rootStyle } = this.props;
const domProps = pickAttrs(this.props, {
aria: true,
data: true
});
let draggableConfig;
if (draggable) {
if (typeof draggable === "object") draggableConfig = draggable;
else if (typeof draggable === "function") draggableConfig = { nodeDraggable: draggable };
else draggableConfig = {};
}
const contextValue = {
styles,
classNames: treeClassNames,
prefixCls,
selectable,
showIcon,
icon,
switcherIcon,
draggable: draggableConfig,
draggingNodeKey,
checkable,
checkStrictly,
disabled,
keyEntities,
dropLevelOffset,
dropContainerKey,
dropTargetKey,
dropPosition,
dragOverNodeKey,
indent,
direction,
dropIndicatorRender,
loadData,
filterTreeNode,
titleRender,
onNodeClick: this.onNodeClick,
onNodeDoubleClick: this.onNodeDoubleClick,
onNodeExpand: this.onNodeExpand,
onNodeSelect: this.onNodeSelect,
onNodeCheck: this.onNodeCheck,
onNodeLoad: this.onNodeLoad,
onNodeMouseEnter: this.onNodeMouseEnter,
onNodeMouseLeave: this.onNodeMouseLeave,
onNodeContextMenu: this.onNodeContextMenu,
onNodeDragStart: this.onNodeDragStart,
onNodeDragEnter: this.onNodeDragEnter,
onNodeDragOver: this.onNodeDragOver,
onNodeDragLeave: this.onNodeDragLeave,
onNodeDragEnd: this.onNodeDragEnd,
onNodeDrop: this.onNodeDrop
};
return /*#__PURE__*/ import_react.createElement(TreeContext.Provider, { value: contextValue }, /*#__PURE__*/ import_react.createElement("div", {
className: clsx(prefixCls, className, rootClassName, { [`${prefixCls}-show-line`]: showLine }),
style: rootStyle
}, /*#__PURE__*/ import_react.createElement(NodeList, _extends$20({
ref: this.listRef,
prefixCls,
style,
data: flattenNodes,
disabled,
selectable,
checkable: !!checkable,
motion,
dragging: draggingNodeKey !== null,
height,
itemHeight,
virtual,
focusable,
tabIndex,
activeItem: this.getActiveItem(),
onFocus: this.onFocus,
onMouseDown: this.onMouseDown,
onBlur: this.onBlur,
onKeyDown: this.onKeyDown,
onActiveChange: this.onActiveChange,
onListChangeStart: this.onListChangeStart,
onListChangeEnd: this.onListChangeEnd,
onContextMenu,
onScroll,
scrollWidth
}, this.getTreeNodeRequiredProps(), domProps))));
}
};
function useTree(treeData, config) {
const { fieldNames } = config;
const keyEntities = import_react.useMemo(() => {
const { keyEntities } = convertDataToEntities(treeData, { fieldNames });
return keyEntities;
}, [treeData, fieldNames]);
return { getPath: useEvent((key) => {
const path = [];
let entity = getEntity(keyEntities, key);
while (entity) {
path.unshift(entity);
entity = entity.parent;
}
return path;
}) };
}
var es_default = Tree$2;
var EXPAND_COLUMN = {};
var INTERNAL_HOOKS = "rc-table-internal-hook";
function createContext(defaultValue) {
const Context = /*#__PURE__*/ import_react.createContext(void 0);
const Provider = ({ value, children }) => {
const valueRef = import_react.useRef(value);
valueRef.current = value;
const [context] = import_react.useState(() => ({
getValue: () => valueRef.current,
listeners: /* @__PURE__ */ new Set()
}));
useLayoutEffect$2(() => {
(0, import_react_dom.unstable_batchedUpdates)(() => {
context.listeners.forEach((listener) => {
listener(value);
});
});
}, [value]);
return /*#__PURE__*/ import_react.createElement(Context.Provider, { value: context }, children);
};
return {
Context,
Provider,
defaultValue
};
}
/** e.g. useSelect(userContext) => user */
/** e.g. useSelect(userContext, user => user.name) => user.name */
/** e.g. useSelect(userContext, ['name', 'age']) => user { name, age } */
/** e.g. useSelect(userContext, 'name') => user.name */
function useContext(holder, selector) {
const eventSelector = useEvent(typeof selector === "function" ? selector : (ctx) => {
if (selector === void 0) return ctx;
if (!Array.isArray(selector)) return ctx[selector];
const obj = {};
selector.forEach((key) => {
obj[key] = ctx[key];
});
return obj;
});
const context = import_react.useContext(holder?.Context);
const { listeners, getValue } = context || {};
const valueRef = import_react.useRef();
valueRef.current = eventSelector(context ? getValue() : holder?.defaultValue);
const [, forceUpdate] = import_react.useState({});
useLayoutEffect$2(() => {
if (!context) return;
function trigger(nextValue) {
const nextSelectorValue = eventSelector(nextValue);
if (!isEqual(valueRef.current, nextSelectorValue, true)) forceUpdate({});
}
listeners.add(trigger);
return () => {
listeners.delete(trigger);
};
}, [context]);
return valueRef.current;
}
function _extends$19() {
_extends$19 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$19.apply(this, arguments);
}
/**
* Create Immutable pair for `makeImmutable` and `responseImmutable`.
*/
function createImmutable() {
const ImmutableContext = /*#__PURE__*/ import_react.createContext(null);
/**
* Get render update mark by `makeImmutable` root.
* Do not deps on the return value as render times
* but only use for `useMemo` or `useCallback` deps.
*/
function useImmutableMark() {
return import_react.useContext(ImmutableContext);
}
/**
* Wrapped Component will be marked as Immutable.
* When Component parent trigger render,
* it will notice children component (use with `responseImmutable`) node that parent has updated.
* @param Component Passed Component
* @param triggerRender Customize trigger `responseImmutable` children re-render logic. Default will always trigger re-render when this component re-render.
*/
function makeImmutable(Component, shouldTriggerRender) {
const refAble = supportRef(Component);
const ImmutableComponent = (props, ref) => {
const refProps = refAble ? { ref } : {};
const renderTimesRef = import_react.useRef(0);
const prevProps = import_react.useRef(props);
if (useImmutableMark() !== null) return /*#__PURE__*/ import_react.createElement(Component, _extends$19({}, props, refProps));
if (!shouldTriggerRender || shouldTriggerRender(prevProps.current, props)) renderTimesRef.current += 1;
prevProps.current = props;
return /*#__PURE__*/ import_react.createElement(ImmutableContext.Provider, { value: renderTimesRef.current }, /*#__PURE__*/ import_react.createElement(Component, _extends$19({}, props, refProps)));
};
return refAble ? /*#__PURE__*/ import_react.forwardRef(ImmutableComponent) : ImmutableComponent;
}
/**
* Wrapped Component with `React.memo`.
* But will rerender when parent with `makeImmutable` rerender.
*/
function responseImmutable(Component, propsAreEqual) {
const refAble = supportRef(Component);
const ImmutableComponent = (props, ref) => {
const refProps = refAble ? { ref } : {};
useImmutableMark();
return /*#__PURE__*/ import_react.createElement(Component, _extends$19({}, props, refProps));
};
return /*#__PURE__*/ import_react.memo(refAble ? /*#__PURE__*/ import_react.forwardRef(ImmutableComponent) : ImmutableComponent, propsAreEqual);
}
return {
makeImmutable,
responseImmutable,
useImmutableMark
};
}
var { makeImmutable, responseImmutable, useImmutableMark } = createImmutable();
var TableContext = createContext();
/* istanbul ignore file */
var PerfContext = /*#__PURE__*/ import_react.createContext({ renderWithProps: false });
var INTERNAL_KEY_PREFIX = "RC_TABLE_KEY";
function toArray(arr) {
if (arr === void 0 || arr === null) return [];
return Array.isArray(arr) ? arr : [arr];
}
function getColumnsKey(columns) {
const columnKeys = [];
const keys = {};
columns.forEach((column) => {
const { key, dataIndex } = column || {};
let mergedKey = key || toArray(dataIndex).join("-") || INTERNAL_KEY_PREFIX;
while (keys[mergedKey]) mergedKey = `${mergedKey}_next`;
keys[mergedKey] = true;
columnKeys.push(mergedKey);
});
return columnKeys;
}
function validateValue(val) {
return val !== null && val !== void 0;
}
function validNumberValue(value) {
return typeof value === "number" && !Number.isNaN(value);
}
function isRenderCell(data) {
return data && typeof data === "object" && !Array.isArray(data) && !/*#__PURE__*/ import_react.isValidElement(data);
}
function useCellRender(record, dataIndex, renderIndex, children, render, shouldCellUpdate) {
const perfRecord = import_react.useContext(PerfContext);
const mark = useImmutableMark();
return useMemo$13(() => {
if (validateValue(children)) return [children];
const value = get$1(record, dataIndex === null || dataIndex === void 0 || dataIndex === "" ? [] : Array.isArray(dataIndex) ? dataIndex : [dataIndex]);
let returnChildNode = value;
let returnCellProps = void 0;
if (render) {
const renderData = render(value, record, renderIndex);
if (isRenderCell(renderData)) {
returnChildNode = renderData.children;
returnCellProps = renderData.props;
perfRecord.renderWithProps = true;
} else returnChildNode = renderData;
}
return [returnChildNode, returnCellProps];
}, [
mark,
record,
children,
dataIndex,
render,
renderIndex
], (prev, next) => {
if (shouldCellUpdate) {
const [, prevRecord] = prev;
const [, nextRecord] = next;
return shouldCellUpdate(nextRecord, prevRecord);
}
if (perfRecord.renderWithProps) return true;
return !isEqual(prev, next, true);
});
}
/** Check if cell is in hover range */
function inHoverRange(cellStartRow, cellRowSpan, startRow, endRow) {
const cellEndRow = cellStartRow + cellRowSpan - 1;
return cellStartRow <= endRow && cellEndRow >= startRow;
}
function useHoverState(rowIndex, rowSpan) {
return useContext(TableContext, (ctx) => {
return [inHoverRange(rowIndex, rowSpan || 1, ctx.hoverStartRow, ctx.hoverEndRow), ctx.onHover];
});
}
function _extends$18() {
_extends$18 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$18.apply(this, arguments);
}
var getTitleFromCellRenderChildren = ({ ellipsis, rowType, children }) => {
let title;
const ellipsisConfig = ellipsis === true ? { showTitle: true } : ellipsis;
if (ellipsisConfig && (ellipsisConfig.showTitle || rowType === "header")) {
if (typeof children === "string" || typeof children === "number") title = children.toString();
else if (/*#__PURE__*/ import_react.isValidElement(children) && typeof children.props?.children === "string") title = children.props?.children;
}
return title;
};
var Cell = (props) => {
const { component: Component, children, ellipsis, scope, prefixCls, className, style, align, record, render, dataIndex, renderIndex, shouldCellUpdate, index, rowType, colSpan, rowSpan, fixStart, fixEnd, fixedStartShadow, fixedEndShadow, offsetFixedStartShadow, offsetFixedEndShadow, zIndex, zIndexReverse, appendNode, additionalProps = {}, originRowSpan, isSticky } = props;
const cellPrefixCls = `${prefixCls}-cell`;
const { allColumnsFixedLeft, rowHoverable } = useContext(TableContext, ["allColumnsFixedLeft", "rowHoverable"]);
const [childNode, legacyCellProps] = useCellRender(record, dataIndex, renderIndex, children, render, shouldCellUpdate);
const fixedStyle = {};
const isFixStart = typeof fixStart === "number" && !allColumnsFixedLeft;
const isFixEnd = typeof fixEnd === "number" && !allColumnsFixedLeft;
const [showFixStartShadow, showFixEndShadow] = useContext(TableContext, ({ scrollInfo }) => {
if (!isFixStart && !isFixEnd) return [false, false];
const [absScroll, scrollWidth] = scrollInfo;
return [(isFixStart && fixedStartShadow && absScroll) - offsetFixedStartShadow >= 1, (isFixEnd && fixedEndShadow && scrollWidth - absScroll) - offsetFixedEndShadow > 1];
});
if (isFixStart) {
fixedStyle.insetInlineStart = fixStart;
fixedStyle["--z-offset"] = zIndex;
fixedStyle["--z-offset-reverse"] = zIndexReverse;
}
if (isFixEnd) {
fixedStyle.insetInlineEnd = fixEnd;
fixedStyle["--z-offset"] = zIndex;
fixedStyle["--z-offset-reverse"] = zIndexReverse;
}
const mergedColSpan = legacyCellProps?.colSpan ?? additionalProps.colSpan ?? colSpan ?? 1;
const mergedRowSpan = legacyCellProps?.rowSpan ?? additionalProps.rowSpan ?? rowSpan ?? 1;
const mergedHoverRowSpan = legacyCellProps?.rowSpan ?? originRowSpan ?? mergedRowSpan;
const [hovering, onHover] = useHoverState(index, mergedHoverRowSpan);
const onMouseEnter = useEvent((event) => {
if (record) onHover(index, index + mergedHoverRowSpan - 1);
additionalProps?.onMouseEnter?.(event);
});
const onMouseLeave = useEvent((event) => {
if (record) onHover(-1, -1);
additionalProps?.onMouseLeave?.(event);
});
if (mergedColSpan === 0 || mergedRowSpan === 0) return null;
const title = additionalProps.title ?? getTitleFromCellRenderChildren({
rowType,
ellipsis,
children: childNode
});
const mergedClassName = clsx(cellPrefixCls, className, {
[`${cellPrefixCls}-fix`]: isFixStart || isFixEnd,
[`${cellPrefixCls}-fix-start`]: isFixStart,
[`${cellPrefixCls}-fix-end`]: isFixEnd,
[`${cellPrefixCls}-fix-start-shadow`]: fixedStartShadow,
[`${cellPrefixCls}-fix-start-shadow-show`]: fixedStartShadow && showFixStartShadow,
[`${cellPrefixCls}-fix-end-shadow`]: fixedEndShadow,
[`${cellPrefixCls}-fix-end-shadow-show`]: fixedEndShadow && showFixEndShadow,
[`${cellPrefixCls}-ellipsis`]: ellipsis,
[`${cellPrefixCls}-with-append`]: appendNode,
[`${cellPrefixCls}-fix-sticky`]: (isFixStart || isFixEnd) && isSticky,
[`${cellPrefixCls}-row-hover`]: !legacyCellProps && hovering
}, additionalProps.className, legacyCellProps?.className);
const alignStyle = {};
if (align) alignStyle.textAlign = align;
const mergedStyle = {
...legacyCellProps?.style,
...fixedStyle,
...alignStyle,
...additionalProps.style,
...style
};
let mergedChildNode = childNode;
if (typeof mergedChildNode === "object" && !Array.isArray(mergedChildNode) && !/*#__PURE__*/ import_react.isValidElement(mergedChildNode)) mergedChildNode = null;
if (ellipsis && (fixedStartShadow || fixedEndShadow)) mergedChildNode = /*#__PURE__*/ import_react.createElement("span", { className: `${cellPrefixCls}-content` }, mergedChildNode);
return /*#__PURE__*/ import_react.createElement(Component, _extends$18({}, legacyCellProps, additionalProps, {
className: mergedClassName,
style: mergedStyle,
title,
scope,
onMouseEnter: rowHoverable ? onMouseEnter : void 0,
onMouseLeave: rowHoverable ? onMouseLeave : void 0,
colSpan: mergedColSpan !== 1 ? mergedColSpan : null,
rowSpan: mergedRowSpan !== 1 ? mergedRowSpan : null
}), appendNode, mergedChildNode);
};
var Cell_default = /*#__PURE__*/ import_react.memo(Cell);
function isFixedStart(column) {
return column.fixed === "start";
}
function isFixedEnd(column) {
return column.fixed === "end";
}
function getCellFixedInfo(colStart, colEnd, columns, stickyOffsets) {
const startColumn = columns[colStart] || {};
const endColumn = columns[colEnd] || {};
let fixStart = null;
let fixEnd = null;
if (isFixedStart(startColumn) && isFixedStart(endColumn)) fixStart = stickyOffsets.start[colStart];
else if (isFixedEnd(endColumn) && isFixedEnd(startColumn)) fixEnd = stickyOffsets.end[colEnd];
let fixedStartShadow = false;
let fixedEndShadow = false;
let zIndex = 0;
let zIndexReverse = 0;
if (fixStart !== null) {
fixedStartShadow = !columns[colEnd + 1] || !isFixedStart(columns[colEnd + 1]);
zIndex = columns.length * 2 - colStart;
zIndexReverse = columns.length + colStart;
}
if (fixEnd !== null) {
fixedEndShadow = !columns[colStart - 1] || !isFixedEnd(columns[colStart - 1]);
zIndex = colEnd;
zIndexReverse = columns.length - colEnd;
}
let offsetFixedStartShadow = 0;
let offsetFixedEndShadow = 0;
if (fixedStartShadow) {
for (let i = 0; i < colStart; i += 1) if (!isFixedStart(columns[i])) offsetFixedStartShadow += stickyOffsets.widths[i] || 0;
}
if (fixedEndShadow) {
for (let i = columns.length - 1; i > colEnd; i -= 1) if (!isFixedEnd(columns[i])) offsetFixedEndShadow += stickyOffsets.widths[i] || 0;
}
return {
fixStart,
fixEnd,
fixedStartShadow,
fixedEndShadow,
offsetFixedStartShadow,
offsetFixedEndShadow,
isSticky: stickyOffsets.isSticky,
zIndex,
zIndexReverse
};
}
var SummaryContext = /*#__PURE__*/ import_react.createContext({});
function _extends$17() {
_extends$17 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$17.apply(this, arguments);
}
var SummaryCell = (props) => {
const { className, index, children, colSpan = 1, rowSpan, align } = props;
const { prefixCls } = useContext(TableContext, ["prefixCls"]);
const { scrollColumnIndex, stickyOffsets, flattenColumns } = import_react.useContext(SummaryContext);
const mergedColSpan = index + colSpan - 1 + 1 === scrollColumnIndex ? colSpan + 1 : colSpan;
const fixedInfo = import_react.useMemo(() => getCellFixedInfo(index, index + mergedColSpan - 1, flattenColumns, stickyOffsets), [
index,
mergedColSpan,
flattenColumns,
stickyOffsets
]);
return /*#__PURE__*/ import_react.createElement(Cell_default, _extends$17({
className,
index,
component: "td",
prefixCls,
record: null,
dataIndex: null,
align,
colSpan: mergedColSpan,
rowSpan,
render: () => children
}, fixedInfo));
};
var FooterRow = (props) => {
const { children, ...restProps } = props;
return /*#__PURE__*/ import_react.createElement("tr", restProps, children);
};
/**
* Syntactic sugar. Do not support HOC.
*/
var Summary = (props) => {
const { children } = props;
return children;
};
Summary.Row = FooterRow;
Summary.Cell = SummaryCell;
var Footer = (props) => {
const { children, stickyOffsets, flattenColumns } = props;
const prefixCls = useContext(TableContext, "prefixCls");
const lastColumnIndex = flattenColumns.length - 1;
const scrollColumn = flattenColumns[lastColumnIndex];
const summaryContext = import_react.useMemo(() => ({
stickyOffsets,
flattenColumns,
scrollColumnIndex: scrollColumn?.scrollbar ? lastColumnIndex : null
}), [
scrollColumn,
flattenColumns,
lastColumnIndex,
stickyOffsets
]);
return /*#__PURE__*/ import_react.createElement(SummaryContext.Provider, { value: summaryContext }, /*#__PURE__*/ import_react.createElement("tfoot", { className: `${prefixCls}-summary` }, children));
};
var Footer_default = responseImmutable(Footer);
var FooterComponents = Summary;
/* istanbul ignore next */
/**
* This is a syntactic sugar for `columns` prop.
* So HOC will not work on this.
*/
function Column$1(_) {
return null;
}
/* istanbul ignore next */
/**
* This is a syntactic sugar for `columns` prop.
* So HOC will not work on this.
*/
function ColumnGroup$1(_) {
return null;
}
function fillRecords(list, record, indent, childrenColumnName, expandedKeys, getRowKey, index) {
const key = getRowKey(record, index);
list.push({
record,
indent,
index,
rowKey: key
});
const expanded = expandedKeys?.has(key);
if (record && Array.isArray(record[childrenColumnName]) && expanded) for (let i = 0; i < record[childrenColumnName].length; i += 1) fillRecords(list, record[childrenColumnName][i], indent + 1, childrenColumnName, expandedKeys, getRowKey, i);
}
/**
* flat tree data on expanded state
*
* @export
* @template T
* @param {*} data : table data
* @param {string} childrenColumnName : 指定树形结构的列名
* @param {Set} expandedKeys : 展开的行对应的keys
* @param {GetRowKey} getRowKey : 获取当前rowKey的方法
* @returns flattened data
*/
function useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey) {
return import_react.useMemo(() => {
if (expandedKeys?.size) {
const list = [];
for (let i = 0; i < data?.length; i += 1) {
const record = data[i];
fillRecords(list, record, 0, childrenColumnName, expandedKeys, getRowKey, i);
}
return list;
}
return data?.map((item, index) => {
return {
record: item,
indent: 0,
index,
rowKey: getRowKey(item, index)
};
});
}, [
data,
childrenColumnName,
expandedKeys,
getRowKey
]);
}
function useRowInfo(record, rowKey, recordIndex, indent) {
const context = useContext(TableContext, [
"prefixCls",
"fixedInfoList",
"flattenColumns",
"expandableType",
"expandRowByClick",
"onTriggerExpand",
"rowClassName",
"expandedRowClassName",
"indentSize",
"expandIcon",
"expandedRowRender",
"forceRender",
"expandIconColumnIndex",
"expandedKeys",
"childrenColumnName",
"rowExpandable",
"onRow"
]);
const { flattenColumns, expandableType, expandedKeys, childrenColumnName, onTriggerExpand, rowExpandable, onRow, expandRowByClick, rowClassName } = context;
const nestExpandable = expandableType === "nest";
const rowSupportExpand = expandableType === "row" && (!rowExpandable || rowExpandable(record));
const mergedExpandable = rowSupportExpand || nestExpandable;
const expanded = expandedKeys && expandedKeys.has(rowKey);
const hasNestChildren = childrenColumnName && record && record[childrenColumnName];
const onInternalTriggerExpand = useEvent(onTriggerExpand);
const rowProps = onRow?.(record, recordIndex);
const onRowClick = rowProps?.onClick;
const onClick = (event, ...args) => {
if (expandRowByClick && mergedExpandable) onTriggerExpand(record, event);
onRowClick?.(event, ...args);
};
let computeRowClassName;
if (typeof rowClassName === "string") computeRowClassName = rowClassName;
else if (typeof rowClassName === "function") computeRowClassName = rowClassName(record, recordIndex, indent);
const columnsKey = getColumnsKey(flattenColumns);
return {
...context,
columnsKey,
nestExpandable,
expanded,
hasNestChildren,
record,
onTriggerExpand: onInternalTriggerExpand,
rowSupportExpand,
expandable: mergedExpandable,
rowProps: {
...rowProps,
className: clsx(computeRowClassName, rowProps?.className),
onClick
}
};
}
var ExpandedRow = (props) => {
const { prefixCls, children, component: Component, cellComponent, className, expanded, colSpan, isEmpty, stickyOffset = 0 } = props;
const { scrollbarSize, fixHeader, fixColumn, componentWidth, horizonScroll } = useContext(TableContext, [
"scrollbarSize",
"fixHeader",
"fixColumn",
"componentWidth",
"horizonScroll"
]);
let contentNode = children;
if (isEmpty ? horizonScroll && componentWidth : fixColumn) contentNode = /*#__PURE__*/ import_react.createElement("div", {
style: {
width: componentWidth - stickyOffset - (fixHeader && !isEmpty ? scrollbarSize : 0),
position: "sticky",
left: stickyOffset,
overflow: "hidden"
},
className: `${prefixCls}-expanded-row-fixed`
}, contentNode);
return /*#__PURE__*/ import_react.createElement(Component, {
className,
style: { display: expanded ? null : "none" }
}, /*#__PURE__*/ import_react.createElement(Cell_default, {
component: cellComponent,
prefixCls,
colSpan
}, contentNode));
};
function renderExpandIcon$1({ prefixCls, record, onExpand, expanded, expandable }) {
const expandClassName = `${prefixCls}-row-expand-icon`;
if (!expandable) return /*#__PURE__*/ import_react.createElement("span", { className: clsx(expandClassName, `${prefixCls}-row-spaced`) });
const onClick = (event) => {
onExpand(record, event);
event.stopPropagation();
};
return /*#__PURE__*/ import_react.createElement("span", {
className: clsx(expandClassName, {
[`${prefixCls}-row-expanded`]: expanded,
[`${prefixCls}-row-collapsed`]: !expanded
}),
onClick
});
}
function findAllChildrenKeys(data, getRowKey, childrenColumnName) {
const keys = [];
function dig(list) {
(list || []).forEach((item, index) => {
keys.push(getRowKey(item, index));
dig(item[childrenColumnName]);
});
}
dig(data);
return keys;
}
function computedExpandedClassName(cls, record, index, indent) {
if (typeof cls === "string") return cls;
if (typeof cls === "function") return cls(record, index, indent);
return "";
}
function _extends$16() {
_extends$16 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$16.apply(this, arguments);
}
function getCellProps(rowInfo, column, colIndex, indent, index, rowKeys = [], expandedRowOffset = 0) {
const { record, prefixCls, columnsKey, fixedInfoList, expandIconColumnIndex, nestExpandable, indentSize, expandIcon, expanded, hasNestChildren, onTriggerExpand, expandable, expandedKeys } = rowInfo;
const key = columnsKey[colIndex];
const fixedInfo = fixedInfoList[colIndex];
let appendCellNode;
if (colIndex === (expandIconColumnIndex || 0) && nestExpandable) appendCellNode = /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement("span", {
style: { paddingLeft: `${indentSize * indent}px` },
className: `${prefixCls}-row-indent indent-level-${indent}`
}), expandIcon({
prefixCls,
expanded,
expandable: hasNestChildren,
record,
onExpand: onTriggerExpand
}));
const additionalCellProps = { ...column.onCell?.(record, index) };
let originRowSpan;
if (expandedRowOffset) {
const { rowSpan = 1 } = additionalCellProps;
if (expandable && rowSpan && colIndex < expandedRowOffset) {
originRowSpan = rowSpan;
let currentRowSpan = rowSpan;
for (let i = index; i < index + rowSpan; i += 1) {
const rowKey = rowKeys[i];
if (expandedKeys.has(rowKey)) currentRowSpan += 1;
}
additionalCellProps.rowSpan = currentRowSpan;
}
}
return {
key,
fixedInfo,
appendCellNode,
additionalCellProps,
originRowSpan
};
}
var BodyRow = (props) => {
const { className, style, classNames, styles, record, index, renderIndex, rowKey, rowKeys, indent = 0, rowComponent: RowComponent, cellComponent, scopeCellComponent, expandedRowInfo } = props;
const rowInfo = useRowInfo(record, rowKey, index, indent);
const { prefixCls, flattenColumns, expandedRowClassName, expandedRowRender, forceRender, rowProps, expanded, rowSupportExpand } = rowInfo;
const expandedRef = import_react.useRef(false);
expandedRef.current ||= expanded;
const expandedClsName = computedExpandedClassName(expandedRowClassName, record, index, indent);
const baseRowNode = /*#__PURE__*/ import_react.createElement(RowComponent, _extends$16({}, rowProps, {
"data-row-key": rowKey,
className: clsx(className, `${prefixCls}-row`, `${prefixCls}-row-level-${indent}`, rowProps?.className, classNames.row, { [expandedClsName]: indent >= 1 }),
style: {
...style,
...rowProps?.style,
...styles.row
}
}), flattenColumns.map((column, colIndex) => {
const { render, dataIndex, className: columnClassName } = column;
const { key, fixedInfo, appendCellNode, additionalCellProps, originRowSpan } = getCellProps(rowInfo, column, colIndex, indent, index, rowKeys, expandedRowInfo?.offset);
return /*#__PURE__*/ import_react.createElement(Cell_default, _extends$16({
className: clsx(columnClassName, classNames.cell),
style: styles.cell,
ellipsis: column.ellipsis,
align: column.align,
scope: column.rowScope,
component: column.rowScope ? scopeCellComponent : cellComponent,
prefixCls,
key,
record,
index,
renderIndex,
dataIndex,
render,
shouldCellUpdate: column.shouldCellUpdate
}, fixedInfo, {
appendNode: appendCellNode,
additionalProps: additionalCellProps,
originRowSpan
}));
}));
let expandRowNode;
if (rowSupportExpand && (forceRender || expandedRef.current || expanded)) {
const expandContent = expandedRowRender(record, index, indent + 1, expanded);
expandRowNode = /*#__PURE__*/ import_react.createElement(ExpandedRow, {
expanded,
className: clsx(`${prefixCls}-expanded-row`, `${prefixCls}-expanded-row-level-${indent + 1}`, expandedClsName),
prefixCls,
component: RowComponent,
cellComponent,
colSpan: expandedRowInfo ? expandedRowInfo.colSpan : flattenColumns.length,
isEmpty: false,
stickyOffset: expandedRowInfo?.sticky
}, expandContent);
}
return /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, baseRowNode, expandRowNode);
};
var BodyRow_default = responseImmutable(BodyRow);
var MeasureCell = (props) => {
const { columnKey, onColumnResize, title } = props;
const cellRef = import_react.useRef(null);
useLayoutEffect$2(() => {
if (cellRef.current) onColumnResize(columnKey, cellRef.current.offsetWidth);
}, []);
return /*#__PURE__*/ import_react.createElement(RefResizeObserver, { data: columnKey }, /*#__PURE__*/ import_react.createElement("td", {
ref: cellRef,
style: {
paddingTop: 0,
paddingBottom: 0,
borderTop: 0,
borderBottom: 0,
height: 0
}
}, /*#__PURE__*/ import_react.createElement("div", { style: {
height: 0,
overflow: "hidden",
fontWeight: "bold"
} }, title || "\xA0")));
};
var MeasureRow = ({ prefixCls, columnsKey, onColumnResize, columns }) => {
const ref = import_react.useRef(null);
const { measureRowRender } = useContext(TableContext, ["measureRowRender"]);
const measureRow = /*#__PURE__*/ import_react.createElement("tr", {
"aria-hidden": "true",
className: `${prefixCls}-measure-row`,
style: { height: 0 },
ref
}, /*#__PURE__*/ import_react.createElement(RefResizeObserver.Collection, { onBatchResize: (infoList) => {
if (isVisible_default(ref.current)) infoList.forEach(({ data: columnKey, size }) => {
onColumnResize(columnKey, size.offsetWidth);
});
} }, columnsKey.map((columnKey) => {
const rawTitle = columns.find((col) => col.key === columnKey)?.title;
const titleForMeasure = /*#__PURE__*/ import_react.isValidElement(rawTitle) ? /*#__PURE__*/ import_react.cloneElement(rawTitle, { ref: null }) : rawTitle;
return /*#__PURE__*/ import_react.createElement(MeasureCell, {
key: columnKey,
columnKey,
onColumnResize,
title: titleForMeasure
});
})));
return typeof measureRowRender === "function" ? measureRowRender(measureRow) : measureRow;
};
var Body = (props) => {
const { data, measureColumnWidth } = props;
const { prefixCls, getComponent, onColumnResize, flattenColumns, getRowKey, expandedKeys, childrenColumnName, emptyNode, classNames, styles, expandedRowOffset = 0, colWidths } = useContext(TableContext, [
"prefixCls",
"getComponent",
"onColumnResize",
"flattenColumns",
"getRowKey",
"expandedKeys",
"childrenColumnName",
"emptyNode",
"classNames",
"styles",
"expandedRowOffset",
"fixedInfoList",
"colWidths"
]);
const { body: bodyCls = {} } = classNames || {};
const { body: bodyStyles = {} } = styles || {};
const flattenData = useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey);
const rowKeys = import_react.useMemo(() => flattenData.map((item) => item.rowKey), [flattenData]);
const perfRef = import_react.useRef({ renderWithProps: false });
const expandedRowInfo = import_react.useMemo(() => {
const expandedColSpan = flattenColumns.length - expandedRowOffset;
let expandedStickyStart = 0;
for (let i = 0; i < expandedRowOffset; i += 1) expandedStickyStart += colWidths[i] || 0;
return {
offset: expandedRowOffset,
colSpan: expandedColSpan,
sticky: expandedStickyStart
};
}, [
flattenColumns.length,
expandedRowOffset,
colWidths
]);
const WrapperComponent = getComponent(["body", "wrapper"], "tbody");
const trComponent = getComponent(["body", "row"], "tr");
const tdComponent = getComponent(["body", "cell"], "td");
const thComponent = getComponent(["body", "cell"], "th");
let rows;
if (data.length) rows = flattenData.map((item, idx) => {
const { record, indent, index: renderIndex, rowKey } = item;
return /*#__PURE__*/ import_react.createElement(BodyRow_default, {
classNames: bodyCls,
styles: bodyStyles,
key: rowKey,
rowKey,
rowKeys,
record,
index: idx,
renderIndex,
rowComponent: trComponent,
cellComponent: tdComponent,
scopeCellComponent: thComponent,
indent,
expandedRowInfo
});
});
else rows = /*#__PURE__*/ import_react.createElement(ExpandedRow, {
expanded: true,
className: `${prefixCls}-placeholder`,
prefixCls,
component: trComponent,
cellComponent: tdComponent,
colSpan: flattenColumns.length,
isEmpty: true
}, emptyNode);
const columnsKey = getColumnsKey(flattenColumns);
return /*#__PURE__*/ import_react.createElement(PerfContext.Provider, { value: perfRef.current }, /*#__PURE__*/ import_react.createElement(WrapperComponent, {
style: bodyStyles.wrapper,
className: clsx(`${prefixCls}-tbody`, bodyCls.wrapper)
}, measureColumnWidth && /*#__PURE__*/ import_react.createElement(MeasureRow, {
prefixCls,
columnsKey,
onColumnResize,
columns: flattenColumns
}), rows));
};
var Body_default = responseImmutable(Body);
var INTERNAL_COL_DEFINE = "RC_TABLE_INTERNAL_COL_DEFINE";
function getExpandableProps(props) {
const { expandable, ...legacyExpandableConfig } = props;
let config;
if ("expandable" in props) config = {
...legacyExpandableConfig,
...expandable
};
else config = legacyExpandableConfig;
if (config.showExpandColumn === false) config.expandIconColumnIndex = -1;
return config;
}
function _extends$15() {
_extends$15 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$15.apply(this, arguments);
}
var ColGroup = (props) => {
const { colWidths, columns, columCount } = props;
const { tableLayout } = useContext(TableContext, ["tableLayout"]);
const cols = [];
const len = columCount || columns.length;
let mustInsert = false;
for (let i = len - 1; i >= 0; i -= 1) {
const width = colWidths[i];
const column = columns && columns[i];
let additionalProps;
let minWidth;
if (column) {
additionalProps = column[INTERNAL_COL_DEFINE];
if (tableLayout === "auto") minWidth = column.minWidth;
}
if (width || minWidth || additionalProps || mustInsert) {
const { columnType, ...restAdditionalProps } = additionalProps || {};
cols.unshift(/*#__PURE__*/ import_react.createElement("col", _extends$15({
key: i,
style: {
width,
minWidth
}
}, restAdditionalProps)));
mustInsert = true;
}
}
return cols.length > 0 ? /*#__PURE__*/ import_react.createElement("colgroup", null, cols) : null;
};
function useColumnWidth(colWidths, columCount) {
return (0, import_react.useMemo)(() => {
const cloneColumns = [];
for (let i = 0; i < columCount; i += 1) {
const val = colWidths[i];
if (val !== void 0) cloneColumns[i] = val;
else return null;
}
return cloneColumns;
}, [colWidths.join("_"), columCount]);
}
var FixedHolder = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { className, style, noData, columns, flattenColumns, colWidths, colGroup, columCount, stickyOffsets, direction, fixHeader, stickyTopOffset, stickyBottomOffset, stickyClassName, scrollX, tableLayout = "fixed", onScroll, maxContentScroll, children, ...restProps } = props;
const { prefixCls, scrollbarSize, isSticky, getComponent } = useContext(TableContext, [
"prefixCls",
"scrollbarSize",
"isSticky",
"getComponent"
]);
const TableComponent = getComponent(["header", "table"], "table");
const combinationScrollBarSize = isSticky && !fixHeader ? 0 : scrollbarSize;
const scrollRef = import_react.useRef(null);
const setScrollRef = import_react.useCallback((element) => {
fillRef(ref, element);
fillRef(scrollRef, element);
}, []);
import_react.useEffect(() => {
function onWheel(e) {
const { currentTarget, deltaX } = e;
if (deltaX) {
const { scrollLeft, scrollWidth, clientWidth } = currentTarget;
const maxScrollWidth = scrollWidth - clientWidth;
let nextScroll = scrollLeft + deltaX;
if (direction === "rtl") {
nextScroll = Math.max(-maxScrollWidth, nextScroll);
nextScroll = Math.min(0, nextScroll);
} else {
nextScroll = Math.min(maxScrollWidth, nextScroll);
nextScroll = Math.max(0, nextScroll);
}
onScroll({
currentTarget,
scrollLeft: nextScroll
});
e.preventDefault();
}
}
const scrollEle = scrollRef.current;
scrollEle?.addEventListener("wheel", onWheel, { passive: false });
return () => {
scrollEle?.removeEventListener("wheel", onWheel);
};
}, []);
const lastColumn = flattenColumns[flattenColumns.length - 1];
const ScrollBarColumn = {
fixed: lastColumn ? lastColumn.fixed : null,
scrollbar: true,
onHeaderCell: () => ({ className: `${prefixCls}-cell-scrollbar` })
};
const columnsWithScrollbar = (0, import_react.useMemo)(() => combinationScrollBarSize ? [...columns, ScrollBarColumn] : columns, [combinationScrollBarSize, columns]);
const flattenColumnsWithScrollbar = (0, import_react.useMemo)(() => combinationScrollBarSize ? [...flattenColumns, ScrollBarColumn] : flattenColumns, [combinationScrollBarSize, flattenColumns]);
const headerStickyOffsets = (0, import_react.useMemo)(() => {
const { start, end } = stickyOffsets;
return {
...stickyOffsets,
start,
end: [...end.map((width) => width + combinationScrollBarSize), 0],
isSticky
};
}, [
combinationScrollBarSize,
stickyOffsets,
isSticky
]);
const mergedColumnWidth = useColumnWidth(colWidths, columCount);
const isColGroupEmpty = (0, import_react.useMemo)(() => {
const noWidth = !mergedColumnWidth || !mergedColumnWidth.length || mergedColumnWidth.every((w) => !w);
return noData || noWidth;
}, [noData, mergedColumnWidth]);
return /*#__PURE__*/ import_react.createElement("div", {
style: {
overflow: "hidden",
...isSticky ? {
top: stickyTopOffset,
bottom: stickyBottomOffset
} : {},
...style
},
ref: setScrollRef,
className: clsx(className, { [stickyClassName]: !!stickyClassName })
}, /*#__PURE__*/ import_react.createElement(TableComponent, { style: {
tableLayout,
minWidth: "100%",
width: scrollX
} }, isColGroupEmpty ? colGroup : /*#__PURE__*/ import_react.createElement(ColGroup, {
colWidths: [...mergedColumnWidth, combinationScrollBarSize],
columCount: columCount + 1,
columns: flattenColumnsWithScrollbar
}), children({
...restProps,
stickyOffsets: headerStickyOffsets,
columns: columnsWithScrollbar,
flattenColumns: flattenColumnsWithScrollbar
})));
});
/** Return a table in div as fixed element which contains sticky info */
var FixedHolder_default = /*#__PURE__*/ import_react.memo(FixedHolder);
function _extends$14() {
_extends$14 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$14.apply(this, arguments);
}
var HeaderRow = (props) => {
const { cells, stickyOffsets, flattenColumns, rowComponent: RowComponent, cellComponent: CellComponent, onHeaderRow, index, classNames, styles } = props;
const { prefixCls } = useContext(TableContext, ["prefixCls"]);
let rowProps;
if (onHeaderRow) rowProps = onHeaderRow(cells.map((cell) => cell.column), index);
const columnsKey = getColumnsKey(cells.map((cell) => cell.column));
return /*#__PURE__*/ import_react.createElement(RowComponent, _extends$14({}, rowProps, {
className: classNames.row,
style: styles.row
}), cells.map((cell, cellIndex) => {
const { column, colStart, colEnd, colSpan } = cell;
const fixedInfo = getCellFixedInfo(colStart, colEnd, flattenColumns, stickyOffsets);
const additionalProps = column?.onHeaderCell?.(column) || {};
return /*#__PURE__*/ import_react.createElement(Cell_default, _extends$14({}, cell, {
scope: column.title ? colSpan > 1 ? "colgroup" : "col" : null,
ellipsis: column.ellipsis,
align: column.align,
component: CellComponent,
prefixCls,
key: columnsKey[cellIndex]
}, fixedInfo, {
additionalProps,
rowType: "header"
}));
}));
};
function parseHeaderRows(rootColumns, classNames, styles) {
const rows = [];
function fillRowCells(columns, colIndex, rowIndex = 0) {
rows[rowIndex] = rows[rowIndex] || [];
let currentColIndex = colIndex;
return columns.filter(Boolean).map((column) => {
const cell = {
key: column.key,
className: clsx(column.className, classNames.cell) || "",
style: styles.cell,
children: column.title,
column,
colStart: currentColIndex
};
let colSpan = 1;
const subColumns = column.children;
if (subColumns && subColumns.length > 0) {
colSpan = fillRowCells(subColumns, currentColIndex, rowIndex + 1).reduce((total, count) => total + count, 0);
cell.hasSubColumns = true;
}
if ("colSpan" in column) ({colSpan} = column);
if ("rowSpan" in column) cell.rowSpan = column.rowSpan;
cell.colSpan = colSpan;
cell.colEnd = cell.colStart + colSpan - 1;
rows[rowIndex].push(cell);
currentColIndex += colSpan;
return colSpan;
});
}
fillRowCells(rootColumns, 0);
const rowCount = rows.length;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex += 1) rows[rowIndex].forEach((cell) => {
if (!("rowSpan" in cell) && !cell.hasSubColumns) cell.rowSpan = rowCount - rowIndex;
});
return rows;
}
var Header = (props) => {
const { stickyOffsets, columns, flattenColumns, onHeaderRow } = props;
const { prefixCls, getComponent, classNames, styles } = useContext(TableContext, [
"prefixCls",
"getComponent",
"classNames",
"styles"
]);
const { header: headerCls = {} } = classNames || {};
const { header: headerStyles = {} } = styles || {};
const rows = import_react.useMemo(() => parseHeaderRows(columns, headerCls, headerStyles), [
columns,
headerCls,
headerStyles
]);
const WrapperComponent = getComponent(["header", "wrapper"], "thead");
const trComponent = getComponent(["header", "row"], "tr");
const thComponent = getComponent(["header", "cell"], "th");
return /*#__PURE__*/ import_react.createElement(WrapperComponent, {
className: clsx(`${prefixCls}-thead`, headerCls.wrapper),
style: headerStyles.wrapper
}, rows.map((row, rowIndex) => {
return /* @__PURE__ */ import_react.createElement(HeaderRow, {
classNames: headerCls,
styles: headerStyles,
key: rowIndex,
flattenColumns,
cells: row,
stickyOffsets,
rowComponent: trComponent,
cellComponent: thComponent,
onHeaderRow,
index: rowIndex
});
}));
};
var Header_default = responseImmutable(Header);
function parseColWidth(totalWidth, width = "") {
if (typeof width === "number") return width;
if (width.endsWith("%")) return totalWidth * parseFloat(width) / 100;
return null;
}
/**
* Fill all column with width
*/
function useWidthColumns(flattenColumns, scrollWidth, clientWidth) {
return import_react.useMemo(() => {
if (scrollWidth && scrollWidth > 0) {
let totalWidth = 0;
let missWidthCount = 0;
flattenColumns.forEach((col) => {
const colWidth = parseColWidth(scrollWidth, col.width);
if (colWidth) totalWidth += colWidth;
else missWidthCount += 1;
});
const maxFitWidth = Math.max(scrollWidth, clientWidth);
let restWidth = Math.max(maxFitWidth - totalWidth, missWidthCount);
let restCount = missWidthCount;
const avgWidth = restWidth / missWidthCount;
let realTotal = 0;
const filledColumns = flattenColumns.map((col) => {
const clone = { ...col };
const colWidth = parseColWidth(scrollWidth, clone.width);
if (colWidth) clone.width = colWidth;
else {
const colAvgWidth = Math.floor(avgWidth);
clone.width = restCount === 1 ? restWidth : colAvgWidth;
restWidth -= colAvgWidth;
restCount -= 1;
}
realTotal += clone.width;
return clone;
});
if (realTotal < maxFitWidth) {
const scale = maxFitWidth / realTotal;
restWidth = maxFitWidth;
filledColumns.forEach((col, index) => {
const colWidth = Math.floor(col.width * scale);
col.width = index === filledColumns.length - 1 ? restWidth : colWidth;
restWidth -= colWidth;
});
}
return [filledColumns, Math.max(realTotal, maxFitWidth)];
}
return [flattenColumns, scrollWidth];
}, [
flattenColumns,
scrollWidth,
clientWidth
]);
}
function convertChildrenToColumns(children) {
return toArray$2(children).filter((node) => /*#__PURE__*/ import_react.isValidElement(node)).map((node) => {
const { key, props } = node;
const { children: nodeChildren, ...restProps } = props;
const column = {
key,
...restProps
};
if (nodeChildren) column.children = convertChildrenToColumns(nodeChildren);
return column;
});
}
function filterHiddenColumns(columns) {
return columns.filter((column) => column && typeof column === "object" && !column.hidden).map((column) => {
const subColumns = column.children;
if (subColumns && subColumns.length > 0) return {
...column,
children: filterHiddenColumns(subColumns)
};
return column;
});
}
function flatColumns(columns, parentKey = "key") {
return columns.filter((column) => column && typeof column === "object").reduce((list, column, index) => {
const { fixed } = column;
const parsedFixed = fixed === true || fixed === "left" ? "start" : fixed === "right" ? "end" : fixed;
const mergedKey = `${parentKey}-${index}`;
const subColumns = column.children;
if (subColumns && subColumns.length > 0) return [...list, ...flatColumns(subColumns, mergedKey).map((subColum) => ({
...subColum,
fixed: subColum.fixed ?? parsedFixed
}))];
return [...list, {
key: mergedKey,
...column,
fixed: parsedFixed
}];
}, []);
}
/**
* Parse `columns` & `children` into `columns`.
*/
function useColumns({ prefixCls, columns, children, expandable, expandedKeys, columnTitle, getRowKey, onTriggerExpand, expandIcon, rowExpandable, expandIconColumnIndex, expandedRowOffset = 0, direction, expandRowByClick, columnWidth, fixed, scrollWidth, clientWidth }, transformColumns) {
const baseColumns = import_react.useMemo(() => {
return filterHiddenColumns((columns || convertChildrenToColumns(children) || []).slice());
}, [columns, children]);
const withExpandColumns = import_react.useMemo(() => {
if (expandable) {
let cloneColumns = baseColumns.slice();
if (!cloneColumns.includes(EXPAND_COLUMN)) {
const expandColIndex = expandIconColumnIndex || 0;
const insertIndex = expandColIndex === 0 && (fixed === "right" || fixed === "end") ? baseColumns.length : expandColIndex;
if (insertIndex >= 0) cloneColumns.splice(insertIndex, 0, EXPAND_COLUMN);
}
const expandColumnIndex = cloneColumns.indexOf(EXPAND_COLUMN);
cloneColumns = cloneColumns.filter((column, index) => column !== EXPAND_COLUMN || index === expandColumnIndex);
const prevColumn = baseColumns[expandColumnIndex];
let fixedColumn;
if (fixed) fixedColumn = fixed;
else fixedColumn = prevColumn ? prevColumn.fixed : null;
const expandColumn = {
[INTERNAL_COL_DEFINE]: {
className: `${prefixCls}-expand-icon-col`,
columnType: "EXPAND_COLUMN"
},
title: columnTitle,
fixed: fixedColumn,
className: `${prefixCls}-row-expand-icon-cell`,
width: columnWidth,
render: (_, record, index) => {
const rowKey = getRowKey(record, index);
const icon = expandIcon({
prefixCls,
expanded: expandedKeys.has(rowKey),
expandable: rowExpandable ? rowExpandable(record) : true,
record,
onExpand: onTriggerExpand
});
if (expandRowByClick) return /*#__PURE__*/ import_react.createElement("span", { onClick: (e) => e.stopPropagation() }, icon);
return icon;
}
};
return cloneColumns.map((col, index) => {
const column = col === EXPAND_COLUMN ? expandColumn : col;
if (index < expandedRowOffset) return {
...column,
fixed: column.fixed || "start"
};
return column;
});
}
return baseColumns.filter((col) => col !== EXPAND_COLUMN);
}, [
expandable,
baseColumns,
getRowKey,
expandedKeys,
expandIcon,
direction,
expandedRowOffset
]);
const mergedColumns = import_react.useMemo(() => {
let finalColumns = withExpandColumns;
if (transformColumns) finalColumns = transformColumns(finalColumns);
if (!finalColumns.length) finalColumns = [{ render: () => null }];
return finalColumns;
}, [
transformColumns,
withExpandColumns,
direction
]);
const [filledColumns, realScrollWidth] = useWidthColumns(import_react.useMemo(() => flatColumns(mergedColumns), [
mergedColumns,
direction,
scrollWidth
]), scrollWidth, clientWidth);
return [
mergedColumns,
filledColumns,
realScrollWidth
];
}
function useExpand(props, mergedData, getRowKey) {
const expandableConfig = getExpandableProps(props);
const { expandIcon, expandedRowKeys, defaultExpandedRowKeys, defaultExpandAllRows, expandedRowRender, onExpand, onExpandedRowsChange, childrenColumnName } = expandableConfig;
const mergedExpandIcon = expandIcon || renderExpandIcon$1;
const mergedChildrenColumnName = childrenColumnName || "children";
const expandableType = import_react.useMemo(() => {
if (expandedRowRender) return "row";
/**
* Fix https://github.com/ant-design/ant-design/issues/21154
* This is a workaround to not to break current behavior.
* We can remove follow code after final release.
*
* To other developer:
* Do not use `__PARENT_RENDER_ICON__` in prod since we will remove this when refactor
*/
if (props.expandable && props.internalHooks === "rc-table-internal-hook" && props.expandable.__PARENT_RENDER_ICON__ || mergedData.some((record) => record && typeof record === "object" && record[mergedChildrenColumnName])) return "nest";
return false;
}, [!!expandedRowRender, mergedData]);
const [innerExpandedKeys, setInnerExpandedKeys] = import_react.useState(() => {
if (defaultExpandedRowKeys) return defaultExpandedRowKeys;
if (defaultExpandAllRows) return findAllChildrenKeys(mergedData, getRowKey, mergedChildrenColumnName);
return [];
});
const mergedExpandedKeys = import_react.useMemo(() => new Set(expandedRowKeys || innerExpandedKeys || []), [expandedRowKeys, innerExpandedKeys]);
return [
expandableConfig,
expandableType,
mergedExpandedKeys,
mergedExpandIcon,
mergedChildrenColumnName,
import_react.useCallback((record) => {
const key = getRowKey(record, mergedData.indexOf(record));
let newExpandedKeys;
const hasKey = mergedExpandedKeys.has(key);
if (hasKey) {
mergedExpandedKeys.delete(key);
newExpandedKeys = [...mergedExpandedKeys];
} else newExpandedKeys = [...mergedExpandedKeys, key];
setInnerExpandedKeys(newExpandedKeys);
if (onExpand) onExpand(!hasKey, record);
if (onExpandedRowsChange) onExpandedRowsChange(newExpandedKeys);
}, [
getRowKey,
mergedExpandedKeys,
mergedData,
onExpand,
onExpandedRowsChange
])
];
}
function useFixedInfo(flattenColumns, stickyOffsets) {
const fixedInfoList = import_react.useMemo(() => flattenColumns.map((_, colIndex) => getCellFixedInfo(colIndex, colIndex, flattenColumns, stickyOffsets)), [flattenColumns, stickyOffsets]);
return useMemo$13(() => fixedInfoList, [fixedInfoList], (prev, next) => !isEqual(prev, next));
}
/**
* Execute code before next frame but async
*/
function useLayoutState(defaultState) {
const stateRef = (0, import_react.useRef)(defaultState);
const [, forceUpdate] = (0, import_react.useState)({});
const lastPromiseRef = (0, import_react.useRef)(null);
const updateBatchRef = (0, import_react.useRef)([]);
function setFrameState(updater) {
updateBatchRef.current.push(updater);
const promise = Promise.resolve();
lastPromiseRef.current = promise;
promise.then(() => {
if (lastPromiseRef.current === promise) {
const prevBatch = updateBatchRef.current;
const prevState = stateRef.current;
updateBatchRef.current = [];
prevBatch.forEach((batchUpdater) => {
stateRef.current = batchUpdater(stateRef.current);
});
lastPromiseRef.current = null;
if (prevState !== stateRef.current) forceUpdate({});
}
});
}
(0, import_react.useEffect)(() => () => {
lastPromiseRef.current = null;
}, []);
return [stateRef.current, setFrameState];
}
/** Lock frame, when frame pass reset the lock. */
function useTimeoutLock(defaultState) {
const frameRef = (0, import_react.useRef)(null);
const timeoutRef = (0, import_react.useRef)(null);
function cleanUp() {
clearTimeout(timeoutRef.current);
}
function setState(newState) {
frameRef.current = newState;
cleanUp();
timeoutRef.current = setTimeout(() => {
frameRef.current = null;
timeoutRef.current = void 0;
}, 100);
}
function getState() {
return frameRef.current;
}
(0, import_react.useEffect)(() => cleanUp, []);
return [setState, getState];
}
function useHover() {
const [startRow, setStartRow] = import_react.useState(-1);
const [endRow, setEndRow] = import_react.useState(-1);
return [
startRow,
endRow,
import_react.useCallback((start, end) => {
setStartRow(start);
setEndRow(end);
}, [])
];
}
var defaultContainer = canUseDom$1() ? window : null;
/** Sticky header hooks */
function useSticky(sticky, prefixCls) {
const { offsetHeader = 0, offsetSummary = 0, offsetScroll = 0, getContainer = () => defaultContainer } = typeof sticky === "object" ? sticky : {};
const container = getContainer() || defaultContainer;
const isSticky = !!sticky;
return import_react.useMemo(() => {
return {
isSticky,
stickyClassName: isSticky ? `${prefixCls}-sticky-holder` : "",
offsetHeader,
offsetSummary,
offsetScroll,
container
};
}, [
isSticky,
offsetScroll,
offsetHeader,
offsetSummary,
prefixCls,
container
]);
}
/**
* Get sticky column offset width
*/
function useStickyOffsets(colWidths, flattenColumns) {
return (0, import_react.useMemo)(() => {
const columnCount = flattenColumns.length;
const getOffsets = (startIndex, endIndex, offset) => {
const offsets = [];
let total = 0;
for (let i = startIndex; i !== endIndex; i += offset) {
offsets.push(total);
if (flattenColumns[i].fixed) total += colWidths[i] || 0;
}
return offsets;
};
return {
start: getOffsets(0, columnCount, 1),
end: getOffsets(columnCount - 1, -1, -1).reverse(),
widths: colWidths
};
}, [colWidths, flattenColumns]);
}
var Panel = (props) => {
const { children, className, style } = props;
return /*#__PURE__*/ import_react.createElement("div", {
className,
style
}, children);
};
function getOffset(node) {
const box = getDOM(node).getBoundingClientRect();
const docElem = document.documentElement;
return {
left: box.left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || document.body.clientLeft || 0),
top: box.top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || document.body.clientTop || 0)
};
}
var MOUSEUP_EVENT = "mouseup";
var MOUSEMOVE_EVENT = "mousemove";
var SCROLL_EVENT = "scroll";
var RESIZE_EVENT = "resize";
var StickyScrollBar = (props, ref) => {
const { scrollBodyRef, onScroll, offsetScroll, container, direction } = props;
const prefixCls = useContext(TableContext, "prefixCls");
const bodyScrollWidth = scrollBodyRef.current?.scrollWidth || 0;
const bodyWidth = scrollBodyRef.current?.clientWidth || 0;
const scrollBarWidth = bodyScrollWidth && bodyWidth * (bodyWidth / bodyScrollWidth);
const scrollBarRef = import_react.useRef(null);
const [scrollState, setScrollState] = useLayoutState({
scrollLeft: 0,
isHiddenScrollBar: true
});
const refState = import_react.useRef({
delta: 0,
x: 0
});
const [isActive, setActive] = import_react.useState(false);
const rafRef = import_react.useRef(null);
import_react.useEffect(() => () => {
wrapperRaf.cancel(rafRef.current);
}, []);
const onMouseUp = () => {
setActive(false);
};
const onMouseDown = (event) => {
event.persist();
refState.current.delta = event.pageX - scrollState.scrollLeft;
refState.current.x = 0;
setActive(true);
event.preventDefault();
};
const onMouseMove = (event) => {
const { buttons } = event || window?.event;
if (!isActive || buttons === 0) {
if (isActive) setActive(false);
return;
}
let left = refState.current.x + event.pageX - refState.current.x - refState.current.delta;
const isRTL = direction === "rtl";
left = Math.max(isRTL ? scrollBarWidth - bodyWidth : 0, Math.min(isRTL ? 0 : bodyWidth - scrollBarWidth, left));
if (!isRTL || Math.abs(left) + Math.abs(scrollBarWidth) < bodyWidth) {
onScroll({ scrollLeft: left / bodyWidth * (bodyScrollWidth + 2) });
refState.current.x = event.pageX;
}
};
const checkScrollBarVisible = () => {
wrapperRaf.cancel(rafRef.current);
rafRef.current = wrapperRaf(() => {
if (!scrollBodyRef.current) return;
const tableOffsetTop = getOffset(scrollBodyRef.current).top;
const tableBottomOffset = tableOffsetTop + scrollBodyRef.current.offsetHeight;
const currentClientOffset = container === window ? document.documentElement.scrollTop + window.innerHeight : getOffset(container).top + container.clientHeight;
if (tableBottomOffset - getScrollBarSize() <= currentClientOffset || tableOffsetTop >= currentClientOffset - offsetScroll) setScrollState((state) => ({
...state,
isHiddenScrollBar: true
}));
else setScrollState((state) => ({
...state,
isHiddenScrollBar: false
}));
});
};
const setScrollLeft = (left) => {
setScrollState((state) => {
return {
...state,
scrollLeft: left / bodyScrollWidth * bodyWidth || 0
};
});
};
import_react.useImperativeHandle(ref, () => ({
setScrollLeft,
checkScrollBarVisible
}));
import_react.useEffect(() => {
document.body.addEventListener(MOUSEUP_EVENT, onMouseUp, false);
document.body.addEventListener(MOUSEMOVE_EVENT, onMouseMove, false);
checkScrollBarVisible();
return () => {
document.body.removeEventListener(MOUSEUP_EVENT, onMouseUp);
document.body.removeEventListener(MOUSEMOVE_EVENT, onMouseMove);
};
}, [scrollBarWidth, isActive]);
import_react.useEffect(() => {
if (scrollBodyRef.current) {
const scrollParents = [];
let parent = getDOM(scrollBodyRef.current);
while (parent) {
scrollParents.push(parent);
parent = parent.parentElement;
}
scrollParents.forEach((p) => {
p.addEventListener(SCROLL_EVENT, checkScrollBarVisible, false);
});
window.addEventListener(RESIZE_EVENT, checkScrollBarVisible, false);
window.addEventListener(SCROLL_EVENT, checkScrollBarVisible, false);
container.addEventListener(SCROLL_EVENT, checkScrollBarVisible, false);
return () => {
scrollParents.forEach((p) => {
p.removeEventListener(SCROLL_EVENT, checkScrollBarVisible);
});
window.removeEventListener(RESIZE_EVENT, checkScrollBarVisible);
window.removeEventListener(SCROLL_EVENT, checkScrollBarVisible);
container.removeEventListener(SCROLL_EVENT, checkScrollBarVisible);
};
}
}, [container]);
import_react.useEffect(() => {
if (!scrollState.isHiddenScrollBar) setScrollState((state) => {
const bodyNode = scrollBodyRef.current;
if (!bodyNode) return state;
return {
...state,
scrollLeft: bodyNode.scrollLeft / bodyNode.scrollWidth * bodyNode.clientWidth
};
});
}, [scrollState.isHiddenScrollBar]);
if (bodyScrollWidth <= bodyWidth || !scrollBarWidth || scrollState.isHiddenScrollBar) return null;
return /*#__PURE__*/ import_react.createElement("div", {
style: {
height: getScrollBarSize(),
width: bodyWidth,
bottom: offsetScroll
},
className: `${prefixCls}-sticky-scroll`
}, /*#__PURE__*/ import_react.createElement("div", {
onMouseDown,
ref: scrollBarRef,
className: clsx(`${prefixCls}-sticky-scroll-bar`, { [`${prefixCls}-sticky-scroll-bar-active`]: isActive }),
style: {
width: `${scrollBarWidth}px`,
transform: `translate3d(${scrollState.scrollLeft}px, 0, 0)`
}
}));
};
var stickyScrollBar_default = /*#__PURE__*/ import_react.forwardRef(StickyScrollBar);
/**
* Feature:
* - fixed not need to set width
* - support `rowExpandable` to config row expand logic
* - add `summary` to support `() => ReactNode`
*
* Update:
* - `dataIndex` is `array[]` now
* - `expandable` wrap all the expand related props
*
* Removed:
* - expandIconAsCell
* - useFixedHeader
* - rowRef
* - columns[number].onCellClick
* - onRowClick
* - onRowDoubleClick
* - onRowMouseEnter
* - onRowMouseLeave
* - getBodyWrapper
* - bodyStyle
*
* Deprecated:
* - All expanded props, move into expandable
*/
function _extends$13() {
_extends$13 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$13.apply(this, arguments);
}
var DEFAULT_PREFIX = "rc-table";
var EMPTY_DATA = [];
var EMPTY_SCROLL_TARGET = {};
function defaultEmpty() {
return "No Data";
}
var Table$1 = (tableProps, ref) => {
const props = {
rowKey: "key",
prefixCls: DEFAULT_PREFIX,
emptyText: defaultEmpty,
...tableProps
};
const { prefixCls, className, rowClassName, style, classNames, styles, data, rowKey, scroll, tableLayout, direction, title, footer, summary, caption, id, showHeader, components, emptyText, onRow, onHeaderRow, measureRowRender, onScroll, internalHooks, transformColumns, internalRefs, tailor, getContainerWidth, sticky, rowHoverable = true } = props;
const mergedData = data || EMPTY_DATA;
const hasData = !!mergedData.length;
const useInternalHooks = internalHooks === INTERNAL_HOOKS;
const getComponent = import_react.useCallback((path, defaultComponent) => get$1(components, path) || defaultComponent, [components]);
const getRowKey = import_react.useMemo(() => {
if (typeof rowKey === "function") return rowKey;
return (record) => {
return record && record[rowKey];
};
}, [rowKey]);
const customizeScrollBody = getComponent(["body"]);
const [startRow, endRow, onHover] = useHover();
const [expandableConfig, expandableType, mergedExpandedKeys, mergedExpandIcon, mergedChildrenColumnName, onTriggerExpand] = useExpand(props, mergedData, getRowKey);
const scrollX = scroll?.x;
const [componentWidth, setComponentWidth] = import_react.useState(0);
const [columns, flattenColumns, flattenScrollX] = useColumns({
...props,
...expandableConfig,
expandable: !!expandableConfig.expandedRowRender,
columnTitle: expandableConfig.columnTitle,
expandedKeys: mergedExpandedKeys,
getRowKey,
onTriggerExpand,
expandIcon: mergedExpandIcon,
expandIconColumnIndex: expandableConfig.expandIconColumnIndex,
direction,
scrollWidth: useInternalHooks && tailor && typeof scrollX === "number" ? scrollX : null,
clientWidth: componentWidth
}, useInternalHooks ? transformColumns : null);
const mergedScrollX = flattenScrollX ?? scrollX;
const columnContext = import_react.useMemo(() => ({
columns,
flattenColumns
}), [columns, flattenColumns]);
const fullTableRef = import_react.useRef(null);
const scrollHeaderRef = import_react.useRef(null);
const scrollBodyRef = import_react.useRef(null);
const scrollBodyContainerRef = import_react.useRef(null);
import_react.useImperativeHandle(ref, () => {
return {
nativeElement: fullTableRef.current,
scrollTo: (config) => {
if (scrollBodyRef.current instanceof HTMLElement) {
const { index, top, key, offset, align = "nearest" } = config;
if (validNumberValue(top)) scrollBodyRef.current?.scrollTo({ top });
else {
const mergedKey = key ?? getRowKey(mergedData[index]);
const targetElement = scrollBodyRef.current.querySelector(`[data-row-key="${mergedKey}"]`);
if (targetElement) {
targetElement.scrollIntoView({ block: align });
if (offset) {
const container = scrollBodyRef.current;
container.scrollTo({ top: container.scrollTop + offset });
}
}
}
} else if (scrollBodyRef.current?.scrollTo) scrollBodyRef.current.scrollTo(config);
}
};
});
const scrollSummaryRef = import_react.useRef(null);
const [shadowStart, setShadowStart] = import_react.useState(false);
const [shadowEnd, setShadowEnd] = import_react.useState(false);
const [colsWidths, updateColsWidths] = import_react.useState(/* @__PURE__ */ new Map());
const pureColWidths = getColumnsKey(flattenColumns).map((columnKey) => colsWidths.get(columnKey));
const colWidths = import_react.useMemo(() => pureColWidths, [pureColWidths.join("_")]);
const stickyOffsets = useStickyOffsets(colWidths, flattenColumns);
const fixHeader = scroll && validateValue(scroll.y);
const horizonScroll = scroll && validateValue(mergedScrollX) || Boolean(expandableConfig.fixed);
const fixColumn = horizonScroll && flattenColumns.some(({ fixed }) => fixed);
const stickyRef = import_react.useRef(null);
const { isSticky, offsetHeader, offsetSummary, offsetScroll, stickyClassName, container } = useSticky(sticky, prefixCls);
const summaryNode = import_react.useMemo(() => summary?.(mergedData), [summary, mergedData]);
const fixFooter = (fixHeader || isSticky) && /*#__PURE__*/ import_react.isValidElement(summaryNode) && summaryNode.type === Summary && summaryNode.props.fixed;
let scrollXStyle;
let scrollYStyle;
let scrollTableStyle;
if (fixHeader) scrollYStyle = {
overflowY: hasData ? "scroll" : "auto",
maxHeight: scroll.y
};
if (horizonScroll) {
scrollXStyle = { overflowX: "auto" };
if (!fixHeader) scrollYStyle = { overflowY: "hidden" };
scrollTableStyle = {
width: mergedScrollX === true ? "auto" : mergedScrollX,
minWidth: "100%"
};
}
const onColumnResize = import_react.useCallback((columnKey, width) => {
updateColsWidths((widths) => {
if (widths.get(columnKey) !== width) {
const newWidths = new Map(widths);
newWidths.set(columnKey, width);
return newWidths;
}
return widths;
});
}, []);
const [setScrollTarget, getScrollTarget] = useTimeoutLock();
const [scrollRetryTimeoutMap] = import_react.useState(() => /* @__PURE__ */ new WeakMap());
function forceScroll(scrollLeft, target) {
if (!target) return;
if (typeof target === "function") {
target(scrollLeft);
return;
}
const scrollRetryTimeout = scrollRetryTimeoutMap.get(target);
if (scrollRetryTimeout) clearTimeout(scrollRetryTimeout);
if (target.scrollLeft !== scrollLeft) {
target.scrollLeft = scrollLeft;
const retryTimeout = setTimeout(() => {
if (target.scrollLeft !== scrollLeft) target.scrollLeft = scrollLeft;
}, 0);
scrollRetryTimeoutMap.set(target, retryTimeout);
}
}
const [scrollInfo, setScrollInfo] = import_react.useState([0, 0]);
const onInternalScroll = useEvent(({ currentTarget, scrollLeft }) => {
const mergedScrollLeft = typeof scrollLeft === "number" ? scrollLeft : currentTarget.scrollLeft;
const compareTarget = currentTarget || EMPTY_SCROLL_TARGET;
if (!getScrollTarget() || getScrollTarget() === compareTarget) {
setScrollTarget(compareTarget);
forceScroll(mergedScrollLeft, scrollHeaderRef.current);
forceScroll(mergedScrollLeft, scrollBodyRef.current);
forceScroll(mergedScrollLeft, scrollSummaryRef.current);
forceScroll(mergedScrollLeft, stickyRef.current?.setScrollLeft);
}
const measureTarget = currentTarget || scrollHeaderRef.current;
if (measureTarget) {
const scrollWidth = useInternalHooks && tailor && typeof mergedScrollX === "number" ? mergedScrollX : measureTarget.scrollWidth;
const clientWidth = measureTarget.clientWidth;
const absScrollStart = Math.abs(mergedScrollLeft);
setScrollInfo((ori) => {
const nextScrollInfo = [absScrollStart, scrollWidth - clientWidth];
return isEqual(ori, nextScrollInfo) ? ori : nextScrollInfo;
});
if (scrollWidth === clientWidth) {
setShadowStart(false);
setShadowEnd(false);
return;
}
setShadowStart(absScrollStart > 0);
setShadowEnd(absScrollStart < scrollWidth - clientWidth - 1);
}
});
const onBodyScroll = useEvent((e) => {
onInternalScroll(e);
onScroll?.(e);
});
const triggerOnScroll = () => {
if (horizonScroll && scrollBodyRef.current) onInternalScroll({
currentTarget: getDOM(scrollBodyRef.current),
scrollLeft: scrollBodyRef.current?.scrollLeft
});
else {
setShadowStart(false);
setShadowEnd(false);
}
};
const onFullTableResize = (offsetWidth) => {
stickyRef.current?.checkScrollBarVisible();
let mergedWidth = offsetWidth ?? fullTableRef.current?.offsetWidth ?? 0;
if (useInternalHooks && getContainerWidth && fullTableRef.current) mergedWidth = getContainerWidth(fullTableRef.current, mergedWidth) || mergedWidth;
if (mergedWidth !== componentWidth) {
triggerOnScroll();
setComponentWidth(mergedWidth);
}
};
useLayoutEffect$2(() => {
if (horizonScroll) onFullTableResize();
}, [horizonScroll]);
const mounted = import_react.useRef(false);
import_react.useEffect(() => {
if (mounted.current) triggerOnScroll();
}, [
horizonScroll,
data,
columns.length
]);
import_react.useEffect(() => {
mounted.current = true;
}, []);
const [scrollbarSize, setScrollbarSize] = import_react.useState(0);
useLayoutEffect$2(() => {
if (!tailor || !useInternalHooks) {
if (scrollBodyRef.current instanceof Element) setScrollbarSize(getTargetScrollBarSize(scrollBodyRef.current).width);
else setScrollbarSize(getTargetScrollBarSize(scrollBodyContainerRef.current).width);
}
}, []);
import_react.useEffect(() => {
if (useInternalHooks && internalRefs) internalRefs.body.current = scrollBodyRef.current;
});
const renderFixedHeaderTable = import_react.useCallback((fixedHolderPassProps) => /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement(Header_default, fixedHolderPassProps), fixFooter === "top" && /*#__PURE__*/ import_react.createElement(Footer_default, fixedHolderPassProps, summaryNode)), [fixFooter, summaryNode]);
const renderFixedFooterTable = import_react.useCallback((fixedHolderPassProps) => /*#__PURE__*/ import_react.createElement(Footer_default, fixedHolderPassProps, summaryNode), [summaryNode]);
const TableComponent = getComponent(["table"], "table");
const mergedTableLayout = import_react.useMemo(() => {
if (tableLayout) return tableLayout;
if (fixColumn) return mergedScrollX === "max-content" ? "auto" : "fixed";
if (fixHeader || isSticky || flattenColumns.some(({ ellipsis }) => ellipsis)) return "fixed";
return "auto";
}, [
fixHeader,
fixColumn,
flattenColumns,
tableLayout,
isSticky
]);
let groupTableNode;
const headerProps = {
colWidths,
columCount: flattenColumns.length,
stickyOffsets,
onHeaderRow,
fixHeader,
scroll
};
const emptyNode = import_react.useMemo(() => {
if (hasData) return null;
if (typeof emptyText === "function") return emptyText();
return emptyText;
}, [hasData, emptyText]);
const bodyTable = /*#__PURE__*/ import_react.createElement(Body_default, {
data: mergedData,
measureColumnWidth: fixHeader || horizonScroll || isSticky
});
const bodyColGroup = /*#__PURE__*/ import_react.createElement(ColGroup, {
colWidths: flattenColumns.map(({ width }) => width),
columns: flattenColumns
});
const captionElement = caption !== null && caption !== void 0 ? /*#__PURE__*/ import_react.createElement("caption", { className: `${prefixCls}-caption` }, caption) : void 0;
const dataProps = pickAttrs(props, { data: true });
const ariaProps = pickAttrs(props, { aria: true });
if (fixHeader || isSticky) {
let bodyContent;
if (typeof customizeScrollBody === "function") {
bodyContent = customizeScrollBody(mergedData, {
scrollbarSize,
ref: scrollBodyRef,
onScroll: onInternalScroll
});
headerProps.colWidths = flattenColumns.map(({ width }, index) => {
const colWidth = index === flattenColumns.length - 1 ? width - scrollbarSize : width;
if (typeof colWidth === "number" && !Number.isNaN(colWidth)) return colWidth;
return 0;
});
} else bodyContent = /*#__PURE__*/ import_react.createElement("div", {
style: {
...scrollXStyle,
...scrollYStyle
},
onScroll: onBodyScroll,
ref: scrollBodyRef,
className: `${prefixCls}-body`
}, /*#__PURE__*/ import_react.createElement(TableComponent, _extends$13({ style: {
...scrollTableStyle,
tableLayout: mergedTableLayout
} }, ariaProps), captionElement, bodyColGroup, bodyTable, !fixFooter && summaryNode && /*#__PURE__*/ import_react.createElement(Footer_default, {
stickyOffsets,
flattenColumns
}, summaryNode)));
const fixedHolderProps = {
noData: !mergedData.length,
maxContentScroll: horizonScroll && mergedScrollX === "max-content",
...headerProps,
...columnContext,
direction,
stickyClassName,
scrollX: mergedScrollX,
tableLayout: mergedTableLayout,
onScroll: onInternalScroll
};
groupTableNode = /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, showHeader !== false && /*#__PURE__*/ import_react.createElement(FixedHolder_default, _extends$13({}, fixedHolderProps, {
stickyTopOffset: offsetHeader,
className: `${prefixCls}-header`,
ref: scrollHeaderRef,
colGroup: bodyColGroup
}), renderFixedHeaderTable), bodyContent, fixFooter && fixFooter !== "top" && /*#__PURE__*/ import_react.createElement(FixedHolder_default, _extends$13({}, fixedHolderProps, {
stickyBottomOffset: offsetSummary,
className: `${prefixCls}-summary`,
ref: scrollSummaryRef,
colGroup: bodyColGroup
}), renderFixedFooterTable), isSticky && scrollBodyRef.current && scrollBodyRef.current instanceof Element && /*#__PURE__*/ import_react.createElement(stickyScrollBar_default, {
ref: stickyRef,
offsetScroll,
scrollBodyRef,
onScroll: onInternalScroll,
container,
direction
}));
} else groupTableNode = /*#__PURE__*/ import_react.createElement("div", {
style: {
...scrollXStyle,
...scrollYStyle,
...styles?.content
},
className: clsx(`${prefixCls}-content`, classNames?.content),
onScroll: onInternalScroll,
ref: scrollBodyRef
}, /*#__PURE__*/ import_react.createElement(TableComponent, _extends$13({ style: {
...scrollTableStyle,
tableLayout: mergedTableLayout
} }, ariaProps), captionElement, bodyColGroup, showHeader !== false && /*#__PURE__*/ import_react.createElement(Header_default, _extends$13({}, headerProps, columnContext)), bodyTable, summaryNode && /*#__PURE__*/ import_react.createElement(Footer_default, {
stickyOffsets,
flattenColumns
}, summaryNode)));
const tableStyle = { ...style };
if (isSticky) tableStyle["--columns-count"] = flattenColumns.length;
let fullTable = /*#__PURE__*/ import_react.createElement("div", _extends$13({
className: clsx(prefixCls, className, {
[`${prefixCls}-rtl`]: direction === "rtl",
[`${prefixCls}-fix-start-shadow`]: horizonScroll,
[`${prefixCls}-fix-end-shadow`]: horizonScroll,
[`${prefixCls}-fix-start-shadow-show`]: horizonScroll && shadowStart,
[`${prefixCls}-fix-end-shadow-show`]: horizonScroll && shadowEnd,
[`${prefixCls}-layout-fixed`]: tableLayout === "fixed",
[`${prefixCls}-fixed-header`]: fixHeader,
/** No used but for compatible */
[`${prefixCls}-fixed-column`]: fixColumn,
[`${prefixCls}-scroll-horizontal`]: horizonScroll,
[`${prefixCls}-has-fix-start`]: flattenColumns[0]?.fixed,
[`${prefixCls}-has-fix-end`]: flattenColumns[flattenColumns.length - 1]?.fixed === "end"
}),
style: tableStyle,
id,
ref: fullTableRef
}, dataProps), title && /*#__PURE__*/ import_react.createElement(Panel, {
className: clsx(`${prefixCls}-title`, classNames?.title),
style: styles?.title
}, title(mergedData)), /*#__PURE__*/ import_react.createElement("div", {
ref: scrollBodyContainerRef,
className: clsx(`${prefixCls}-container`, classNames?.section),
style: styles?.section
}, groupTableNode), footer && /*#__PURE__*/ import_react.createElement(Panel, {
className: clsx(`${prefixCls}-footer`, classNames?.footer),
style: styles?.footer
}, footer(mergedData)));
if (horizonScroll) fullTable = /*#__PURE__*/ import_react.createElement(RefResizeObserver, { onResize: ({ offsetWidth }) => onFullTableResize(offsetWidth) }, fullTable);
const fixedInfoList = useFixedInfo(flattenColumns, stickyOffsets);
const TableContextValue = import_react.useMemo(() => ({
scrollX: mergedScrollX,
scrollInfo,
classNames,
styles,
prefixCls,
getComponent,
scrollbarSize,
direction,
fixedInfoList,
isSticky,
componentWidth,
fixHeader,
fixColumn,
horizonScroll,
tableLayout: mergedTableLayout,
rowClassName,
expandedRowClassName: expandableConfig.expandedRowClassName,
expandIcon: mergedExpandIcon,
expandableType,
expandRowByClick: expandableConfig.expandRowByClick,
expandedRowRender: expandableConfig.expandedRowRender,
forceRender: expandableConfig.forceRender ?? false,
expandedRowOffset: expandableConfig.expandedRowOffset,
onTriggerExpand,
expandIconColumnIndex: expandableConfig.expandIconColumnIndex,
indentSize: expandableConfig.indentSize,
allColumnsFixedLeft: flattenColumns.every((col) => col.fixed === "start"),
emptyNode,
columns,
flattenColumns,
onColumnResize,
colWidths,
hoverStartRow: startRow,
hoverEndRow: endRow,
onHover,
rowExpandable: expandableConfig.rowExpandable,
onRow,
getRowKey,
expandedKeys: mergedExpandedKeys,
childrenColumnName: mergedChildrenColumnName,
rowHoverable,
measureRowRender
}), [
mergedScrollX,
scrollInfo,
classNames,
styles,
prefixCls,
getComponent,
scrollbarSize,
direction,
fixedInfoList,
isSticky,
componentWidth,
fixHeader,
fixColumn,
horizonScroll,
mergedTableLayout,
rowClassName,
expandableConfig.expandedRowClassName,
mergedExpandIcon,
expandableType,
expandableConfig.expandRowByClick,
expandableConfig.expandedRowRender,
expandableConfig.forceRender,
expandableConfig.expandedRowOffset,
onTriggerExpand,
expandableConfig.expandIconColumnIndex,
expandableConfig.indentSize,
emptyNode,
columns,
flattenColumns,
onColumnResize,
colWidths,
startRow,
endRow,
onHover,
expandableConfig.rowExpandable,
onRow,
getRowKey,
mergedExpandedKeys,
mergedChildrenColumnName,
rowHoverable,
measureRowRender
]);
return /*#__PURE__*/ import_react.createElement(TableContext.Provider, { value: TableContextValue }, fullTable);
};
var RefTable = /*#__PURE__*/ import_react.forwardRef(Table$1);
var genTable = (shouldTriggerRender) => {
return makeImmutable(RefTable, shouldTriggerRender);
};
var ImmutableTable = genTable();
ImmutableTable.EXPAND_COLUMN = EXPAND_COLUMN;
ImmutableTable.INTERNAL_HOOKS = INTERNAL_HOOKS;
ImmutableTable.Column = Column$1;
ImmutableTable.ColumnGroup = ColumnGroup$1;
ImmutableTable.Summary = FooterComponents;
var StaticContext = createContext(null);
var GridContext = createContext(null);
function _extends$12() {
_extends$12 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$12.apply(this, arguments);
}
/**
* Return the width of the column by `colSpan`.
* When `colSpan` is `0` will be trade as `1`.
*/
function getColumnWidth(colIndex, colSpan, columnsOffset) {
return columnsOffset[colIndex + (colSpan || 1)] - (columnsOffset[colIndex] || 0);
}
var VirtualCell = (props) => {
const { rowInfo, column, colIndex, indent, index, component, renderIndex, record, style, className, inverse, getHeight } = props;
const { render, dataIndex, className: columnClassName, width: colWidth } = column;
const { columnsOffset } = useContext(GridContext, ["columnsOffset"]);
const { key, fixedInfo, appendCellNode, additionalCellProps, originRowSpan } = getCellProps(rowInfo, column, colIndex, indent, index);
const { style: cellStyle, colSpan = 1, rowSpan = 1 } = additionalCellProps;
const concatColWidth = getColumnWidth(colIndex - 1, colSpan, columnsOffset);
const marginOffset = colSpan > 1 ? colWidth - concatColWidth : 0;
const mergedStyle = {
...cellStyle,
...style,
flex: `0 0 ${concatColWidth}px`,
width: `${concatColWidth}px`,
marginRight: marginOffset,
pointerEvents: "auto"
};
const needHide = import_react.useMemo(() => {
if (inverse) return rowSpan <= 1;
else return colSpan === 0 || rowSpan === 0 || rowSpan > 1;
}, [
rowSpan,
colSpan,
inverse
]);
if (needHide) mergedStyle.visibility = "hidden";
else if (inverse) mergedStyle.height = getHeight?.(rowSpan);
const mergedRender = needHide ? () => null : render;
const cellSpan = {};
if (rowSpan === 0 || colSpan === 0) {
cellSpan.rowSpan = 1;
cellSpan.colSpan = 1;
}
return /*#__PURE__*/ import_react.createElement(Cell_default, _extends$12({
className: clsx(columnClassName, className),
ellipsis: column.ellipsis,
align: column.align,
scope: column.rowScope,
component,
prefixCls: rowInfo.prefixCls,
key,
record,
index,
renderIndex,
dataIndex,
render: mergedRender,
shouldCellUpdate: column.shouldCellUpdate
}, fixedInfo, {
appendNode: appendCellNode,
originRowSpan,
additionalProps: {
...additionalCellProps,
style: mergedStyle,
...cellSpan
}
}));
};
function _extends$11() {
_extends$11 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$11.apply(this, arguments);
}
var ResponseBodyLine = responseImmutable(/* @__PURE__ */ import_react.forwardRef((props, ref) => {
const { data, index, className, rowKey, style, extra, getHeight, ...restProps } = props;
const { record, indent, index: renderIndex } = data;
const { scrollX, flattenColumns, prefixCls, fixColumn, componentWidth, classNames, styles } = useContext(TableContext, [
"prefixCls",
"flattenColumns",
"fixColumn",
"componentWidth",
"scrollX",
"classNames",
"styles"
]);
const { getComponent } = useContext(StaticContext, ["getComponent"]);
const rowInfo = useRowInfo(record, rowKey, index, indent);
const RowComponent = getComponent(["body", "row"], "div");
const cellComponent = getComponent(["body", "cell"], "div");
const { rowSupportExpand, expanded, rowProps, expandedRowRender, expandedRowClassName, forceRender } = rowInfo;
const expandedClsName = computedExpandedClassName(expandedRowClassName, record, index, indent);
let expandRowNode;
if (!extra && rowSupportExpand && (forceRender || expanded)) {
const expandContent = expandedRowRender(record, index, indent + 1, expanded);
let additionalProps = {};
if (fixColumn) additionalProps = { style: { ["--virtual-width"]: `${componentWidth}px` } };
const rowCellCls = `${prefixCls}-expanded-row-cell`;
expandRowNode = /*#__PURE__*/ import_react.createElement(RowComponent, {
className: clsx(`${prefixCls}-expanded-row`, `${prefixCls}-expanded-row-level-${indent + 1}`, expandedClsName),
style: { display: expanded ? void 0 : "none" }
}, /*#__PURE__*/ import_react.createElement(Cell_default, {
component: cellComponent,
prefixCls,
className: clsx(rowCellCls, { [`${rowCellCls}-fixed`]: fixColumn }),
additionalProps
}, expandContent));
}
const rowStyle = {
...style,
width: scrollX
};
if (extra) {
rowStyle.position = "absolute";
rowStyle.pointerEvents = "none";
}
const rowNode = /*#__PURE__*/ import_react.createElement(RowComponent, _extends$11({}, rowProps, restProps, {
"data-row-key": rowKey,
ref: rowSupportExpand ? null : ref,
className: clsx(className, `${prefixCls}-row`, rowProps?.className, classNames?.body?.row, {
[expandedClsName]: indent >= 1,
[`${prefixCls}-row-extra`]: extra
}),
style: {
...rowStyle,
...rowProps?.style,
...styles?.body?.row
}
}), flattenColumns.map((column, colIndex) => {
return /*#__PURE__*/ import_react.createElement(VirtualCell, {
key: colIndex,
className: classNames?.body?.cell,
style: styles?.body?.cell,
component: cellComponent,
rowInfo,
column,
colIndex,
indent,
index,
renderIndex,
record,
inverse: extra,
getHeight
});
}));
if (rowSupportExpand) return /*#__PURE__*/ import_react.createElement("div", { ref }, rowNode, expandRowNode);
return rowNode;
}));
var ALIGN_MAP = {
start: "top",
end: "bottom",
nearest: "auto"
};
var ResponseGrid = responseImmutable(/* @__PURE__ */ import_react.forwardRef((props, ref) => {
const { data, onScroll } = props;
const { flattenColumns, onColumnResize, getRowKey, expandedKeys, prefixCls, childrenColumnName, scrollX, direction } = useContext(TableContext, [
"flattenColumns",
"onColumnResize",
"getRowKey",
"prefixCls",
"expandedKeys",
"childrenColumnName",
"scrollX",
"direction"
]);
const { sticky, scrollY, listItemHeight, getComponent, onScroll: onTablePropScroll } = useContext(StaticContext);
const listRef = import_react.useRef(null);
const flattenData = useFlattenRecords(data, childrenColumnName, expandedKeys, getRowKey);
const columnsWidth = import_react.useMemo(() => {
let total = 0;
return flattenColumns.map(({ width, minWidth, key }) => {
const finalWidth = Math.max(width || 0, minWidth || 0);
total += finalWidth;
return [
key,
finalWidth,
total
];
});
}, [flattenColumns]);
const columnsOffset = import_react.useMemo(() => columnsWidth.map((colWidth) => colWidth[2]), [columnsWidth]);
import_react.useEffect(() => {
columnsWidth.forEach(([key, width]) => {
onColumnResize(key, width);
});
}, [columnsWidth]);
import_react.useImperativeHandle(ref, () => {
const obj = {
scrollTo: (config) => {
const { align, offset, ...restConfig } = config;
const virtualAlign = ALIGN_MAP[align] ?? (offset ? "top" : "auto");
listRef.current?.scrollTo({
...restConfig,
offset,
align: virtualAlign
});
},
nativeElement: listRef.current?.nativeElement
};
Object.defineProperty(obj, "scrollLeft", {
get: () => listRef.current?.getScrollInfo().x || 0,
set: (value) => {
listRef.current?.scrollTo({ left: value });
}
});
Object.defineProperty(obj, "scrollTop", {
get: () => listRef.current?.getScrollInfo().y || 0,
set: (value) => {
listRef.current?.scrollTo({ top: value });
}
});
return obj;
});
const getRowSpan = (column, index) => {
const record = flattenData[index]?.record;
const { onCell } = column;
if (onCell) return onCell(record, index)?.rowSpan ?? 1;
return 1;
};
const extraRender = (info) => {
const { start, end, getSize, offsetY } = info;
if (end < 0) return null;
let firstRowSpanColumns = flattenColumns.filter((column) => getRowSpan(column, start) === 0);
let startIndex = start;
for (let i = start; i >= 0; i -= 1) {
firstRowSpanColumns = firstRowSpanColumns.filter((column) => getRowSpan(column, i) === 0);
if (!firstRowSpanColumns.length) {
startIndex = i;
break;
}
}
let lastRowSpanColumns = flattenColumns.filter((column) => getRowSpan(column, end) !== 1);
let endIndex = end;
for (let i = end; i < flattenData.length; i += 1) {
lastRowSpanColumns = lastRowSpanColumns.filter((column) => getRowSpan(column, i) !== 1);
if (!lastRowSpanColumns.length) {
endIndex = Math.max(i - 1, end);
break;
}
}
const spanLines = [];
for (let i = startIndex; i <= endIndex; i += 1) {
if (!flattenData[i]) continue;
if (flattenColumns.some((column) => getRowSpan(column, i) > 1)) spanLines.push(i);
}
return spanLines.map((index) => {
const item = flattenData[index];
const rowKey = getRowKey(item.record, index);
const getHeight = (rowSpan) => {
const endItemIndex = index + rowSpan - 1;
const endItem = flattenData[endItemIndex];
if (!endItem || !endItem.record) {
const safeEndIndex = Math.min(endItemIndex, flattenData.length - 1);
const safeEndItem = flattenData[safeEndIndex];
const endItemKey = getRowKey(safeEndItem.record, safeEndIndex);
const sizeInfo = getSize(rowKey, endItemKey);
return sizeInfo.bottom - sizeInfo.top;
}
const endItemKey = getRowKey(endItem.record, endItemIndex);
const sizeInfo = getSize(rowKey, endItemKey);
return sizeInfo.bottom - sizeInfo.top;
};
const sizeInfo = getSize(rowKey);
return /*#__PURE__*/ import_react.createElement(ResponseBodyLine, {
key: index,
data: item,
rowKey,
index,
style: { top: -offsetY + sizeInfo.top },
extra: true,
getHeight
});
});
};
const gridContext = import_react.useMemo(() => ({ columnsOffset }), [columnsOffset]);
const tblPrefixCls = `${prefixCls}-tbody`;
const wrapperComponent = getComponent(["body", "wrapper"]);
const horizontalScrollBarStyle = {};
if (sticky) {
horizontalScrollBarStyle.position = "sticky";
horizontalScrollBarStyle.bottom = 0;
if (typeof sticky === "object" && sticky.offsetScroll) horizontalScrollBarStyle.bottom = sticky.offsetScroll;
}
return /*#__PURE__*/ import_react.createElement(GridContext.Provider, { value: gridContext }, /*#__PURE__*/ import_react.createElement(es_default$7, {
fullHeight: false,
ref: listRef,
prefixCls: `${tblPrefixCls}-virtual`,
styles: { horizontalScrollBar: horizontalScrollBarStyle },
className: tblPrefixCls,
height: scrollY,
itemHeight: listItemHeight || 24,
data: flattenData,
itemKey: (item) => getRowKey(item.record),
component: wrapperComponent,
scrollWidth: scrollX,
direction,
onVirtualScroll: ({ x }) => {
onScroll({
currentTarget: listRef.current?.nativeElement,
scrollLeft: x
});
},
onScroll: onTablePropScroll,
extraRender
}, (item, index, itemProps) => {
const rowKey = getRowKey(item.record, index);
return /*#__PURE__*/ import_react.createElement(ResponseBodyLine, {
data: item,
rowKey,
index,
style: itemProps.style
});
}));
}));
function _extends$10() {
_extends$10 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$10.apply(this, arguments);
}
var renderBody = (rawData, props) => {
const { ref, onScroll } = props;
return /*#__PURE__*/ import_react.createElement(ResponseGrid, {
ref,
data: rawData,
onScroll
});
};
var VirtualTable = (props, ref) => {
const { data, columns, scroll, sticky, prefixCls = DEFAULT_PREFIX, className, listItemHeight, components, onScroll } = props;
let { x: scrollX, y: scrollY } = scroll || {};
if (typeof scrollX !== "number") scrollX = 1;
if (typeof scrollY !== "number") scrollY = 500;
const getComponent = useEvent((path, defaultComponent) => get$1(components, path) || defaultComponent);
const onInternalScroll = useEvent(onScroll);
const context = import_react.useMemo(() => ({
sticky,
scrollY,
listItemHeight,
getComponent,
onScroll: onInternalScroll
}), [
sticky,
scrollY,
listItemHeight,
getComponent,
onInternalScroll
]);
return /*#__PURE__*/ import_react.createElement(StaticContext.Provider, { value: context }, /*#__PURE__*/ import_react.createElement(ImmutableTable, _extends$10({}, props, {
className: clsx(className, `${prefixCls}-virtual`),
scroll: {
...scroll,
x: scrollX
},
components: {
...components,
body: data?.length ? renderBody : void 0
},
columns,
internalHooks: INTERNAL_HOOKS,
tailor: true,
ref
})));
};
var RefVirtualTable = /*#__PURE__*/ import_react.forwardRef(VirtualTable);
var genVirtualTable = (shouldTriggerRender) => {
return makeImmutable(RefVirtualTable, shouldTriggerRender);
};
genVirtualTable();
/* istanbul ignore next */
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
var Column = (_) => null;
/* istanbul ignore next */
/** This is a syntactic sugar for `columns` prop. So HOC will not work on this. */
var ColumnGroup = (_) => null;
var SELECTION_COLUMN = {};
var SELECTION_ALL = "SELECT_ALL";
var SELECTION_INVERT = "SELECT_INVERT";
var SELECTION_NONE = "SELECT_NONE";
var EMPTY_LIST$1 = [];
var flattenData = (childrenColumnName, data, list = []) => {
(data || []).forEach((record) => {
list.push(record);
if (isPlainObject(record) && childrenColumnName in record) flattenData(childrenColumnName, record[childrenColumnName], list);
});
return list;
};
var useSelection = (config, rowSelection) => {
const { preserveSelectedRowKeys, selectedRowKeys, defaultSelectedRowKeys, getCheckboxProps, getTitleCheckboxProps, onChange: onSelectionChange, onSelect, onSelectAll, onSelectInvert, onSelectNone, onSelectMultiple, columnWidth: selectionColWidth, type: selectionType, selections, fixed, renderCell: customizeRenderCell, hideSelectAll, checkStrictly = true } = rowSelection || {};
const { prefixCls, data, pageData, getRecordByKey, getRowKey, expandType, childrenColumnName, locale: tableLocale, getPopupContainer } = config;
const warning = devUseWarning();
const [multipleSelect, updatePrevSelectedIndex] = useMultipleSelect((item) => item);
const [mergedSelectedKeys, setMergedSelectedKeys] = useControlledState(defaultSelectedRowKeys || EMPTY_LIST$1, selectedRowKeys);
const mergedSelectedKeyList = mergedSelectedKeys ?? EMPTY_LIST$1;
const preserveRecordsRef = import_react.useRef(/* @__PURE__ */ new Map());
const updatePreserveRecordsCache = (0, import_react.useCallback)((keys) => {
if (preserveSelectedRowKeys) {
const newCache = /* @__PURE__ */ new Map();
keys.forEach((key) => {
let record = getRecordByKey(key);
if (!record && preserveRecordsRef.current.has(key)) record = preserveRecordsRef.current.get(key);
newCache.set(key, record);
});
preserveRecordsRef.current = newCache;
}
}, [getRecordByKey, preserveSelectedRowKeys]);
import_react.useEffect(() => {
updatePreserveRecordsCache(mergedSelectedKeyList);
}, [mergedSelectedKeyList, updatePreserveRecordsCache]);
const flattedData = (0, import_react.useMemo)(() => flattenData(childrenColumnName, pageData), [childrenColumnName, pageData]);
const { keyEntities } = (0, import_react.useMemo)(() => {
if (checkStrictly) return { keyEntities: null };
let convertData = data;
if (preserveSelectedRowKeys) {
const keysSet = new Set(flattedData.map(getRowKey));
const preserveRecords = Array.from(preserveRecordsRef.current).reduce((total, [key, value]) => keysSet.has(key) ? total : total.concat(value), []);
convertData = [].concat(_toConsumableArray(convertData), _toConsumableArray(preserveRecords));
}
return convertDataToEntities(convertData, {
externalGetKey: getRowKey,
childrenPropName: childrenColumnName
});
}, [
data,
getRowKey,
checkStrictly,
childrenColumnName,
preserveSelectedRowKeys,
flattedData
]);
const checkboxPropsMap = (0, import_react.useMemo)(() => {
const map = /* @__PURE__ */ new Map();
flattedData.forEach((record, index) => {
const key = getRowKey(record, index);
const checkboxProps = (getCheckboxProps ? getCheckboxProps(record) : null) || {};
map.set(key, checkboxProps);
});
return map;
}, [
flattedData,
getRowKey,
getCheckboxProps
]);
const isCheckboxDisabled = (0, import_react.useCallback)((r) => {
const rowKey = getRowKey(r);
let checkboxProps;
if (checkboxPropsMap.has(rowKey)) checkboxProps = checkboxPropsMap.get(getRowKey(r));
else checkboxProps = getCheckboxProps ? getCheckboxProps(r) : void 0;
return !!checkboxProps?.disabled;
}, [checkboxPropsMap, getRowKey]);
const [derivedSelectedKeys, derivedHalfSelectedKeys] = (0, import_react.useMemo)(() => {
if (checkStrictly) return [mergedSelectedKeyList, []];
const { checkedKeys, halfCheckedKeys } = conductCheck(mergedSelectedKeyList, true, keyEntities, isCheckboxDisabled);
return [checkedKeys || [], halfCheckedKeys];
}, [
mergedSelectedKeyList,
checkStrictly,
keyEntities,
isCheckboxDisabled
]);
const derivedSelectedKeySet = (0, import_react.useMemo)(() => {
const keys = selectionType === "radio" ? derivedSelectedKeys.slice(0, 1) : derivedSelectedKeys;
return new Set(keys);
}, [derivedSelectedKeys, selectionType]);
const derivedHalfSelectedKeySet = (0, import_react.useMemo)(() => selectionType === "radio" ? /* @__PURE__ */ new Set() : new Set(derivedHalfSelectedKeys), [derivedHalfSelectedKeys, selectionType]);
import_react.useEffect(() => {
if (!rowSelection) setMergedSelectedKeys(EMPTY_LIST$1);
}, [!!rowSelection]);
const setSelectedKeys = (0, import_react.useCallback)((keys, method) => {
let availableKeys;
let records;
updatePreserveRecordsCache(keys);
if (preserveSelectedRowKeys) {
availableKeys = keys;
records = keys.map((key) => preserveRecordsRef.current.get(key));
} else {
availableKeys = [];
records = [];
keys.forEach((key) => {
const record = getRecordByKey(key);
if (record !== void 0) {
availableKeys.push(key);
records.push(record);
}
});
}
setMergedSelectedKeys(availableKeys);
onSelectionChange?.(availableKeys, records, { type: method });
}, [
setMergedSelectedKeys,
getRecordByKey,
onSelectionChange,
preserveSelectedRowKeys
]);
const triggerSingleSelection = (0, import_react.useCallback)((key, selected, keys, event) => {
if (onSelect) {
const rows = keys.map(getRecordByKey);
onSelect(getRecordByKey(key), selected, rows, event);
}
setSelectedKeys(keys, "single");
}, [
onSelect,
getRecordByKey,
setSelectedKeys
]);
const mergedSelections = (0, import_react.useMemo)(() => {
if (!selections || hideSelectAll) return null;
return (selections === true ? [
SELECTION_ALL,
SELECTION_INVERT,
SELECTION_NONE
] : selections).map((selection) => {
let mergedSelection;
if (selection === "SELECT_ALL") mergedSelection = {
key: "all",
text: tableLocale.selectionAll,
onSelect() {
setSelectedKeys(data.reduce((keys, record, index) => {
const key = getRowKey(record, index);
if (!isCheckboxDisabled(record) || derivedSelectedKeySet.has(key)) keys.push(key);
return keys;
}, []), "all");
}
};
else if (selection === "SELECT_INVERT") mergedSelection = {
key: "invert",
text: tableLocale.selectInvert,
onSelect() {
const keySet = new Set(derivedSelectedKeySet);
pageData.forEach((record, index) => {
const key = getRowKey(record, index);
if (!checkboxPropsMap.get(key)?.disabled) {
if (keySet.has(key)) keySet.delete(key);
else keySet.add(key);
}
});
const keys = Array.from(keySet);
if (onSelectInvert) {
warning.deprecated(false, "onSelectInvert", "onChange");
onSelectInvert(keys);
}
setSelectedKeys(keys, "invert");
}
};
else if (selection === "SELECT_NONE") mergedSelection = {
key: "none",
text: tableLocale.selectNone,
onSelect() {
onSelectNone?.();
setSelectedKeys(Array.from(derivedSelectedKeySet).filter((key) => {
return checkboxPropsMap.get(key)?.disabled;
}), "none");
}
};
else mergedSelection = selection;
return {
...mergedSelection,
onSelect: (currentRowKeys) => {
mergedSelection.onSelect?.(currentRowKeys);
updatePrevSelectedIndex(null);
}
};
});
}, [
selections,
hideSelectAll,
tableLocale.selectionAll,
tableLocale.selectInvert,
tableLocale.selectNone,
checkboxPropsMap,
isCheckboxDisabled,
derivedSelectedKeySet,
data,
pageData,
getRowKey,
onSelectInvert,
setSelectedKeys
]);
return [(0, import_react.useCallback)((columns) => {
if (!rowSelection) return columns.filter((col) => col !== SELECTION_COLUMN);
let cloneColumns = _toConsumableArray(columns);
const keySet = new Set(derivedSelectedKeySet);
const recordKeys = flattedData.reduce((keys, record, index) => {
const key = getRowKey(record, index);
if (!checkboxPropsMap.get(key).disabled) keys.push(key);
return keys;
}, []);
const checkedCurrentAll = recordKeys.every((key) => keySet.has(key));
const checkedCurrentSome = recordKeys.some((key) => keySet.has(key));
const onSelectAllChange = () => {
const changeKeys = [];
if (checkedCurrentAll) recordKeys.forEach((key) => {
keySet.delete(key);
changeKeys.push(key);
});
else recordKeys.forEach((key) => {
if (!keySet.has(key)) {
keySet.add(key);
changeKeys.push(key);
}
});
const keys = Array.from(keySet);
onSelectAll?.(!checkedCurrentAll, keys.map(getRecordByKey), changeKeys.map(getRecordByKey));
setSelectedKeys(keys, "all");
updatePrevSelectedIndex(null);
};
let title;
let columnTitleCheckbox;
if (selectionType !== "radio") {
let customizeSelections;
if (mergedSelections) {
const menu = {
getPopupContainer,
items: mergedSelections.map((selection, index) => {
const { key, text, onSelect: onSelectionClick } = selection;
return {
key: key ?? index,
onClick: () => {
onSelectionClick?.(recordKeys);
},
label: text
};
})
};
customizeSelections = /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-selection-extra` }, /*#__PURE__*/ import_react.createElement(Dropdown, {
menu,
getPopupContainer
}, /*#__PURE__*/ import_react.createElement("span", null, /*#__PURE__*/ import_react.createElement(RefIcon$11, null))));
}
const allDisabledData = flattedData.reduce((list, record, index) => {
const key = getRowKey(record, index);
const checkboxProps = checkboxPropsMap.get(key) || {};
const item = {
checked: keySet.has(key),
...checkboxProps
};
if (item.disabled) list.push(item);
return list;
}, []);
const allDisabled = !!allDisabledData.length && allDisabledData.length === flattedData.length;
const allDisabledAndChecked = allDisabled && allDisabledData.every(({ checked }) => checked);
const allDisabledSomeChecked = allDisabled && allDisabledData.some(({ checked }) => checked);
const customCheckboxProps = getTitleCheckboxProps?.() || {};
const { onChange, disabled } = customCheckboxProps;
columnTitleCheckbox = /*#__PURE__*/ import_react.createElement(Checkbox, {
"aria-label": customizeSelections ? "Custom selection" : "Select all",
...customCheckboxProps,
checked: !allDisabled ? !!flattedData.length && checkedCurrentAll : allDisabledAndChecked,
indeterminate: !allDisabled ? !checkedCurrentAll && checkedCurrentSome : !allDisabledAndChecked && allDisabledSomeChecked,
onChange: (e) => {
onSelectAllChange();
onChange?.(e);
},
disabled: disabled ?? (flattedData.length === 0 || allDisabled),
skipGroup: true
});
title = !hideSelectAll && /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-selection` }, columnTitleCheckbox, customizeSelections);
}
let renderCell;
if (selectionType === "radio") renderCell = (_, record, index) => {
const key = getRowKey(record, index);
const checked = keySet.has(key);
const checkboxProps = checkboxPropsMap.get(key);
const defaultAriaLabel = `Select row ${index + 1}`;
return {
node: /*#__PURE__*/ import_react.createElement(Radio, {
"aria-label": defaultAriaLabel,
...checkboxProps,
checked,
onClick: (e) => {
e.stopPropagation();
checkboxProps?.onClick?.(e);
},
onChange: (event) => {
if (!keySet.has(key)) triggerSingleSelection(key, true, [key], event.nativeEvent);
checkboxProps?.onChange?.(event);
}
}),
checked
};
};
else renderCell = (_, record, index) => {
const key = getRowKey(record, index);
const checked = keySet.has(key);
const indeterminate = derivedHalfSelectedKeySet.has(key);
const checkboxProps = checkboxPropsMap.get(key);
let mergedIndeterminate;
if (expandType === "nest") mergedIndeterminate = indeterminate;
else mergedIndeterminate = checkboxProps?.indeterminate ?? indeterminate;
const defaultAriaLabel = checked ? `Row ${index + 1} selected` : `Select row ${index + 1}`;
return {
node: /*#__PURE__*/ import_react.createElement(Checkbox, {
"aria-label": defaultAriaLabel,
...checkboxProps,
indeterminate: mergedIndeterminate,
checked,
skipGroup: true,
onClick: (e) => {
e.stopPropagation();
checkboxProps?.onClick?.(e);
},
onChange: (event) => {
const { nativeEvent } = event;
const { shiftKey } = nativeEvent;
const currentSelectedIndex = recordKeys.indexOf(key);
const isMultiple = derivedSelectedKeySet.size > 0 && recordKeys.some((key) => derivedSelectedKeySet.has(key));
if (shiftKey && checkStrictly && isMultiple) {
const changedKeys = multipleSelect(currentSelectedIndex, recordKeys, keySet);
const keys = Array.from(keySet);
onSelectMultiple?.(!checked, keys.map(getRecordByKey), changedKeys.map(getRecordByKey));
setSelectedKeys(keys, "multiple");
} else {
const originCheckedKeys = derivedSelectedKeys;
if (checkStrictly) {
const checkedKeys = checked ? arrDel(originCheckedKeys, key) : arrAdd(originCheckedKeys, key);
triggerSingleSelection(key, !checked, checkedKeys, nativeEvent);
} else {
const { checkedKeys, halfCheckedKeys } = conductCheck([].concat(_toConsumableArray(originCheckedKeys), [key]), true, keyEntities, isCheckboxDisabled);
let nextCheckedKeys = checkedKeys;
if (checked) {
const tempKeySet = new Set(checkedKeys);
tempKeySet.delete(key);
nextCheckedKeys = conductCheck(Array.from(tempKeySet), {
halfCheckedKeys
}, keyEntities, isCheckboxDisabled).checkedKeys;
}
triggerSingleSelection(key, !checked, nextCheckedKeys, nativeEvent);
}
}
if (checked) updatePrevSelectedIndex(null);
else updatePrevSelectedIndex(currentSelectedIndex);
checkboxProps?.onChange?.(event);
}
}),
checked
};
};
const renderSelectionCell = (_, record, index) => {
const { node, checked } = renderCell(_, record, index);
if (customizeRenderCell) return customizeRenderCell(checked, record, index, node);
return node;
};
if (!cloneColumns.includes(SELECTION_COLUMN)) {
if (cloneColumns.findIndex((col) => col["RC_TABLE_INTERNAL_COL_DEFINE"]?.columnType === "EXPAND_COLUMN") === 0) {
const [expandColumn, ...restColumns] = cloneColumns;
cloneColumns = [expandColumn, SELECTION_COLUMN].concat(_toConsumableArray(restColumns));
} else cloneColumns = [SELECTION_COLUMN].concat(_toConsumableArray(cloneColumns));
}
const selectionColumnIndex = cloneColumns.indexOf(SELECTION_COLUMN);
cloneColumns = cloneColumns.filter((column, index) => column !== SELECTION_COLUMN || index === selectionColumnIndex);
const prevCol = cloneColumns[selectionColumnIndex - 1];
const nextCol = cloneColumns[selectionColumnIndex + 1];
let mergedFixed = fixed;
if (mergedFixed === void 0) {
if (nextCol?.fixed !== void 0) mergedFixed = nextCol.fixed;
else if (prevCol?.fixed !== void 0) mergedFixed = prevCol.fixed;
}
if (mergedFixed && prevCol && prevCol["RC_TABLE_INTERNAL_COL_DEFINE"]?.columnType === "EXPAND_COLUMN" && prevCol.fixed === void 0) prevCol.fixed = mergedFixed;
const columnCls = clsx(`${prefixCls}-selection-col`, { [`${prefixCls}-selection-col-with-dropdown`]: selections && selectionType === "checkbox" });
const renderColumnTitle = () => {
if (!rowSelection?.columnTitle) return title;
if (isFunction(rowSelection.columnTitle)) return rowSelection.columnTitle(columnTitleCheckbox);
return rowSelection.columnTitle;
};
const selectionColumn = {
fixed: mergedFixed,
width: selectionColWidth,
className: `${prefixCls}-selection-column`,
title: renderColumnTitle(),
render: renderSelectionCell,
onCell: rowSelection.onCell,
align: rowSelection.align,
[INTERNAL_COL_DEFINE]: { className: columnCls }
};
return cloneColumns.map((col) => col === SELECTION_COLUMN ? selectionColumn : col);
}, [
getRowKey,
flattedData,
rowSelection,
derivedSelectedKeys,
derivedSelectedKeySet,
derivedHalfSelectedKeySet,
selectionColWidth,
mergedSelections,
expandType,
checkboxPropsMap,
onSelectMultiple,
triggerSingleSelection,
isCheckboxDisabled
]), derivedSelectedKeySet];
};
function renderExpandIcon(locale) {
return (props) => {
const { prefixCls, onExpand, record, expanded, expandable } = props;
const iconPrefix = `${prefixCls}-row-expand-icon`;
return /*#__PURE__*/ import_react.createElement("button", {
type: "button",
onClick: (e) => {
onExpand(record, e);
e.stopPropagation();
},
className: clsx(iconPrefix, {
[`${iconPrefix}-spaced`]: !expandable,
[`${iconPrefix}-expanded`]: expandable && expanded,
[`${iconPrefix}-collapsed`]: expandable && !expanded
}),
"aria-label": expanded ? locale.collapse : locale.expand,
"aria-expanded": expanded
});
};
}
var getMergedFilters = (filters) => {
const mergedFilters = {};
for (const [key, value] of Object.entries(filters)) if (isNonNullable$1(value)) mergedFilters[key] = value;
return mergedFilters;
};
var useColumnTitleProps = (sorterTitleProps, filters) => {
return import_react.useMemo(() => {
return {
...sorterTitleProps,
filters: getMergedFilters(filters)
};
}, [sorterTitleProps, filters]);
};
function useContainerWidth(prefixCls) {
const getContainerWidth = (ele, width) => {
const container = ele.querySelector(`.${prefixCls}-container`);
let returnWidth = width;
if (container) {
const style = getComputedStyle(container);
const borderLeft = Number.parseInt(style.borderLeftWidth, 10);
const borderRight = Number.parseInt(style.borderRightWidth, 10);
returnWidth = width - borderLeft - borderRight;
}
return returnWidth;
};
return getContainerWidth;
}
var useFilledColumns = (columns, column) => import_react.useMemo(() => {
if (!column) return columns;
const fillColumns = (currentColumns) => currentColumns.map((col) => {
if (col === SELECTION_COLUMN || col === EXPAND_COLUMN) return col;
if ("children" in col && Array.isArray(col.children)) return {
...mergeProps(column, col),
children: fillColumns(col.children)
};
const columnWithoutChildren = omit(column, ["children"]);
return mergeProps(columnWithoutChildren, col);
});
return fillColumns(columns);
}, [columns, column]);
var getColumnKey = (column, defaultKey) => {
if ("key" in column && isNonNullable$1(column.key)) return column.key;
if (column.dataIndex) return Array.isArray(column.dataIndex) ? column.dataIndex.join(".") : column.dataIndex;
return defaultKey;
};
function getColumnPos(index, pos) {
return pos ? `${pos}-${index}` : `${index}`;
}
var renderColumnTitle = (title, props) => {
if (isFunction(title)) return title(props);
return title;
};
/**
* @description Safe get column title, Should filter object
* @param title
*/
var safeColumnTitle = (title, props) => {
const result = renderColumnTitle(title, props);
if (isPlainObject(result) || Array.isArray(result)) return "";
return result;
};
var normalizePlacement = (pos) => {
const lowerPos = pos.toLowerCase();
if (lowerPos.includes("center")) return "center";
return lowerPos.includes("left") || lowerPos.includes("start") ? "start" : "end";
};
var getPaginationSize = (paginationSize, mergedSize) => {
if (paginationSize) return paginationSize;
if (mergedSize === "small" || mergedSize === "medium") return "small";
};
var FilterFilled$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M349 838c0 17.7 14.2 32 31.8 32h262.4c17.6 0 31.8-14.3 31.8-32V642H349v196zm531.1-684H143.9c-24.5 0-39.8 26.7-27.5 48l221.3 376h348.8l221.3-376c12.1-21.3-3.2-48-27.7-48z" }
}]
},
"name": "filter",
"theme": "filled"
};
function _extends$9() {
_extends$9 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$9.apply(this, arguments);
}
var FilterFilled = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$9({}, props, {
ref,
icon: FilterFilled$1
}));
/** */
var RefIcon$9 = /*#__PURE__*/ import_react.forwardRef(FilterFilled);
var FileOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326zm1.8 562H232V136h302v216a42 42 0 0042 42h216v494z" }
}]
},
"name": "file",
"theme": "outlined"
};
function _extends$8() {
_extends$8 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$8.apply(this, arguments);
}
var FileOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$8({}, props, {
ref,
icon: FileOutlined$1
}));
/** */
var RefIcon$8 = /*#__PURE__*/ import_react.forwardRef(FileOutlined);
var FolderOpenOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M928 444H820V330.4c0-17.7-14.3-32-32-32H473L355.7 186.2a8.15 8.15 0 00-5.5-2.2H96c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h698c13 0 24.8-7.9 29.7-20l134-332c1.5-3.8 2.3-7.9 2.3-12 0-17.7-14.3-32-32-32zM136 256h188.5l119.6 114.4H748V444H238c-13 0-24.8 7.9-29.7 20L136 643.2V256zm635.3 512H159l103.3-256h612.4L771.3 768z" }
}]
},
"name": "folder-open",
"theme": "outlined"
};
function _extends$7() {
_extends$7 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$7.apply(this, arguments);
}
var FolderOpenOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$7({}, props, {
ref,
icon: FolderOpenOutlined$1
}));
/** */
var RefIcon$7 = /*#__PURE__*/ import_react.forwardRef(FolderOpenOutlined);
var FolderOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M880 298.4H521L403.7 186.2a8.15 8.15 0 00-5.5-2.2H144c-17.7 0-32 14.3-32 32v592c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V330.4c0-17.7-14.3-32-32-32zM840 768H184V256h188.5l119.6 114.4H840V768z" }
}]
},
"name": "folder",
"theme": "outlined"
};
function _extends$6() {
_extends$6 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$6.apply(this, arguments);
}
var FolderOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$6({}, props, {
ref,
icon: FolderOutlined$1
}));
/** */
var RefIcon$6 = /*#__PURE__*/ import_react.forwardRef(FolderOutlined);
var HolderOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M300 276.5a56 56 0 1056-97 56 56 0 00-56 97zm0 284a56 56 0 1056-97 56 56 0 00-56 97zM640 228a56 56 0 10112 0 56 56 0 00-112 0zm0 284a56 56 0 10112 0 56 56 0 00-112 0zM300 844.5a56 56 0 1056-97 56 56 0 00-56 97zM640 796a56 56 0 10112 0 56 56 0 00-112 0z" }
}]
},
"name": "holder",
"theme": "outlined"
};
function _extends$5() {
_extends$5 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$5.apply(this, arguments);
}
var HolderOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$5({}, props, {
ref,
icon: HolderOutlined$1
}));
/** */
var RefIcon$5 = /*#__PURE__*/ import_react.forwardRef(HolderOutlined);
var genDirectoryStyle = ({ treeCls, treeNodeCls, directoryNodeSelectedBg, directoryNodeSelectedColor, motionDurationMid, borderRadius, controlItemBgHover }) => ({ [`${treeCls}${treeCls}-directory ${treeNodeCls}`]: {
[`${treeCls}-node-content-wrapper`]: {
position: "static",
[`&:has(${treeCls}-drop-indicator)`]: { position: "relative" },
[`> *:not(${treeCls}-drop-indicator)`]: { position: "relative" },
"&:hover": { background: "transparent" },
"&:before": {
position: "absolute",
inset: 0,
transition: `background-color ${motionDurationMid}`,
content: "\"\"",
borderRadius
},
"&:hover:before": { background: controlItemBgHover }
},
[`${treeCls}-switcher, ${treeCls}-checkbox, ${treeCls}-draggable-icon`]: { zIndex: 1 },
"&-selected": {
background: directoryNodeSelectedBg,
borderRadius,
[`${treeCls}-switcher, ${treeCls}-draggable-icon`]: { color: directoryNodeSelectedColor },
[`${treeCls}-node-content-wrapper`]: {
color: directoryNodeSelectedColor,
background: "transparent",
"&, &:hover": { color: directoryNodeSelectedColor },
"&:before, &:hover:before": { background: directoryNodeSelectedBg }
}
}
} });
var treeNodeFX = new Keyframe("ant-tree-node-fx-do-not-use", {
"0%": { opacity: 0 },
"100%": { opacity: 1 }
});
var getSwitchStyle = (prefixCls, token) => ({ [`.${prefixCls}-switcher-icon`]: {
display: "inline-block",
fontSize: 10,
verticalAlign: "baseline",
svg: { transition: `transform ${token.motionDurationSlow}` }
} });
var getDropIndicatorStyle = (prefixCls, token) => ({ [`.${prefixCls}-drop-indicator`]: {
position: "absolute",
zIndex: 1,
height: 2,
backgroundColor: token.colorPrimary,
borderRadius: 1,
pointerEvents: "none",
"&:after": {
position: "absolute",
top: -3,
insetInlineStart: -6,
width: 8,
height: 8,
backgroundColor: "transparent",
border: `${unit$1(token.lineWidthBold)} solid ${token.colorPrimary}`,
borderRadius: "50%",
content: "\"\""
}
} });
var genBaseStyle = (prefixCls, token) => {
const { treeCls, treeNodeCls, treeNodePadding, titleHeight, indentSize, switcherSize, motionDurationMid, nodeSelectedBg, nodeHoverBg, colorTextQuaternary, controlItemBgActiveDisabled } = token;
return { [treeCls]: {
...resetComponent(token),
["--rc-virtual-list-scrollbar-bg"]: token.colorSplit,
background: token.colorBgContainer,
borderRadius: token.borderRadius,
transition: `background-color ${token.motionDurationSlow}`,
"&-rtl": { direction: "rtl" },
[`&${treeCls}-rtl ${treeCls}-switcher_close ${treeCls}-switcher-icon svg`]: { transform: "rotate(90deg)" },
[`${treeCls}-list`]: { "&:focus-visible": {
outline: "none",
[`${treeNodeCls}-active ${treeCls}-node-content-wrapper`]: { ...genFocusOutline(token) }
} },
[`${treeCls}-list-scrollbar`]: {
cursor: "pointer",
"&:hover": { backgroundColor: token.colorFillQuaternary }
},
[`${treeCls}-list-holder-inner`]: { alignItems: "flex-start" },
[`&${treeCls}-block-node`]: { [`${treeCls}-list-holder-inner`]: {
alignItems: "stretch",
[`${treeCls}-node-content-wrapper`]: { flex: "auto" },
[`${treeNodeCls}.dragging:after`]: {
position: "absolute",
inset: 0,
border: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorPrimary}`,
opacity: 0,
animationName: treeNodeFX,
animationDuration: token.motionDurationSlow,
animationPlayState: "running",
animationFillMode: "forwards",
content: "\"\"",
pointerEvents: "none",
borderRadius: token.borderRadius
}
} },
[treeNodeCls]: {
display: "flex",
alignItems: "flex-start",
marginBottom: treeNodePadding,
lineHeight: unit$1(titleHeight),
position: "relative",
"&:before": {
content: "\"\"",
position: "absolute",
zIndex: 1,
insetInlineStart: 0,
width: "100%",
top: "100%",
height: treeNodePadding
},
[`&-disabled ${treeCls}-node-content-wrapper`]: {
color: token.colorTextDisabled,
cursor: "not-allowed",
"&:hover": { background: "transparent" }
},
[`${treeCls}-checkbox-disabled + ${treeCls}-node-selected,&${treeNodeCls}-disabled${treeNodeCls}-selected ${treeCls}-node-content-wrapper`]: { backgroundColor: controlItemBgActiveDisabled },
[`${treeCls}-checkbox-disabled`]: { pointerEvents: "unset" },
[`&:not(${treeNodeCls}-disabled)`]: { [`${treeCls}-node-content-wrapper`]: { "&:hover": { color: token.nodeHoverColor } } },
[`&-active ${treeCls}-node-content-wrapper`]: { background: token.controlItemBgHover },
[`&:not(${treeNodeCls}-disabled).filter-node ${treeCls}-title`]: {
color: token.colorPrimary,
fontWeight: token.fontWeightStrong
},
"&-draggable": {
cursor: "grab",
[`${treeCls}-draggable-icon`]: {
flexShrink: 0,
width: switcherSize,
textAlign: "center",
visibility: "visible",
color: colorTextQuaternary
},
[`&${treeNodeCls}-disabled ${treeCls}-draggable-icon`]: { visibility: "hidden" }
}
},
[`${treeCls}-indent`]: {
alignSelf: "stretch",
whiteSpace: "nowrap",
userSelect: "none",
"&-unit": {
display: "inline-block",
width: indentSize
}
},
[`${treeCls}-draggable-icon`]: { visibility: "hidden" },
[`${treeCls}-switcher, ${treeCls}-checkbox`]: { marginInlineEnd: token.calc(token.calc(switcherSize).sub(token.controlInteractiveSize)).div(2).equal() },
[`${treeCls}-checkbox`]: {
flexShrink: 0,
alignSelf: "flex-start",
marginBlockStart: token.calc(token.calc(titleHeight).sub(token.controlInteractiveSize)).div(2).equal()
},
[`${treeCls}-switcher`]: {
...getSwitchStyle(prefixCls, token),
position: "relative",
flex: "none",
alignSelf: "stretch",
width: switcherSize,
textAlign: "center",
cursor: "pointer",
userSelect: "none",
transition: `all ${token.motionDurationSlow}`,
"&-noop": { cursor: "unset" },
"&:before": {
pointerEvents: "none",
content: "\"\"",
width: switcherSize,
height: titleHeight,
position: "absolute",
left: {
_skip_check_: true,
value: 0
},
top: 0,
borderRadius: token.borderRadius,
transition: `all ${token.motionDurationSlow}`
},
[`&:not(${treeCls}-switcher-noop):hover:before`]: { backgroundColor: token.colorBgTextHover },
[`&_close ${treeCls}-switcher-icon svg`]: { transform: "rotate(-90deg)" },
"&-loading-icon": { color: token.colorPrimary },
"&-leaf-line": {
position: "relative",
zIndex: 1,
display: "inline-block",
width: "100%",
height: "100%",
"&:before": {
position: "absolute",
top: 0,
insetInlineEnd: token.calc(switcherSize).div(2).equal(),
bottom: token.calc(treeNodePadding).mul(-1).equal(),
marginInlineStart: token.calc(token.lineWidth).mul(-1).equal(),
borderInlineEnd: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorBorder}`,
content: "\"\""
},
"&:after": {
position: "absolute",
width: token.calc(token.calc(switcherSize).div(2).equal()).mul(.8).equal(),
height: token.calc(titleHeight).div(2).equal(),
borderBottom: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorBorder}`,
content: "\"\""
}
}
},
[`${treeCls}-node-content-wrapper`]: {
position: "relative",
minHeight: titleHeight,
paddingBlock: 0,
paddingInline: token.paddingXS,
background: "transparent",
borderRadius: token.borderRadius,
cursor: "pointer",
transition: [
`all ${motionDurationMid}`,
"border 0s",
"line-height 0s",
"box-shadow 0s"
].join(", "),
...getDropIndicatorStyle(prefixCls, token),
"&:hover": { backgroundColor: nodeHoverBg },
[`&${treeCls}-node-selected`]: {
color: token.nodeSelectedColor,
backgroundColor: nodeSelectedBg
},
[`${treeCls}-iconEle`]: {
display: "inline-block",
width: switcherSize,
height: titleHeight,
textAlign: "center",
verticalAlign: "top",
"&:empty": { display: "none" }
}
},
[`${treeCls}-unselectable ${treeCls}-node-content-wrapper:hover`]: { backgroundColor: "transparent" },
[`${treeNodeCls}.drop-container > [draggable]`]: { boxShadow: `0 0 0 2px ${token.colorPrimary}` },
"&-show-line": {
[`${treeCls}-indent-unit`]: {
position: "relative",
height: "100%",
"&:before": {
position: "absolute",
top: 0,
insetInlineEnd: token.calc(switcherSize).div(2).equal(),
bottom: token.calc(treeNodePadding).mul(-1).equal(),
borderInlineEnd: `${unit$1(token.lineWidth)} ${token.lineType} ${token.colorBorder}`,
content: "\"\""
},
"&-end:before": { display: "none" }
},
[`${treeCls}-switcher`]: {
background: "transparent",
"&-line-icon": { verticalAlign: "-0.15em" }
}
},
[`${treeNodeCls}-leaf-last ${treeCls}-switcher-leaf-line:before`]: {
top: "auto !important",
bottom: "auto !important",
height: `${unit$1(token.calc(titleHeight).div(2).equal())} !important`
}
} };
};
var genTreeStyle = (prefixCls, token, enableDirectory = true) => {
const treeCls = `.${prefixCls}`;
const treeNodeCls = `${treeCls}-treenode`;
const treeNodePadding = token.calc(token.paddingXS).div(2).equal();
const treeToken = merge(token, {
treeCls,
treeNodeCls,
treeNodePadding
});
return [genBaseStyle(prefixCls, treeToken), enableDirectory && genDirectoryStyle(treeToken)].filter(Boolean);
};
var initComponentToken = (token) => {
const { controlHeightSM, controlItemBgHover, controlItemBgActive } = token;
const titleHeight = controlHeightSM;
return {
titleHeight,
switcherSize: titleHeight,
indentSize: titleHeight,
nodeHoverBg: controlItemBgHover,
nodeHoverColor: token.colorText,
nodeSelectedBg: controlItemBgActive,
nodeSelectedColor: token.colorText
};
};
var prepareComponentToken$1 = (token) => {
const { colorTextLightSolid, colorPrimary } = token;
return {
...initComponentToken(token),
directoryNodeSelectedColor: colorTextLightSolid,
directoryNodeSelectedBg: colorPrimary
};
};
var style_default$1 = genStyleHooks("Tree", (token, { prefixCls }) => [
{ [token.componentCls]: getStyle(`${prefixCls}-checkbox`, token) },
genTreeStyle(prefixCls, token),
genCollapseMotion(token)
], prepareComponentToken$1);
var dropIndicatorRender = (props) => {
const { dropPosition, dropLevelOffset, prefixCls, indent, direction = "ltr" } = props;
const startPosition = direction === "ltr" ? "left" : "right";
const endPosition = direction === "ltr" ? "right" : "left";
const style = {
[startPosition]: -dropLevelOffset * indent + 4,
[endPosition]: 0
};
switch (dropPosition) {
case -1:
style.top = -3;
break;
case 1:
style.bottom = -3;
break;
default:
style.bottom = -3;
style[startPosition] = indent + 4;
}
return /*#__PURE__*/ import_react.createElement("div", {
style,
className: `${prefixCls}-drop-indicator`
});
};
var CaretDownFilled$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "0 0 1024 1024",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z" }
}]
},
"name": "caret-down",
"theme": "filled"
};
function _extends$4() {
_extends$4 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$4.apply(this, arguments);
}
var CaretDownFilled = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$4({}, props, {
ref,
icon: CaretDownFilled$1
}));
/** */
var RefIcon$4 = /*#__PURE__*/ import_react.forwardRef(CaretDownFilled);
var MinusSquareOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M328 544h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z" }
}, {
"tag": "path",
"attrs": { "d": "M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z" }
}]
},
"name": "minus-square",
"theme": "outlined"
};
function _extends$3() {
_extends$3 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$3.apply(this, arguments);
}
var MinusSquareOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$3({}, props, {
ref,
icon: MinusSquareOutlined$1
}));
/** */
var RefIcon$3 = /*#__PURE__*/ import_react.forwardRef(MinusSquareOutlined);
var PlusSquareOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "64 64 896 896",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M328 544h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8z" }
}, {
"tag": "path",
"attrs": { "d": "M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z" }
}]
},
"name": "plus-square",
"theme": "outlined"
};
function _extends$2() {
_extends$2 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$2.apply(this, arguments);
}
var PlusSquareOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$2({}, props, {
ref,
icon: PlusSquareOutlined$1
}));
/** */
var RefIcon$2 = /*#__PURE__*/ import_react.forwardRef(PlusSquareOutlined);
var SwitcherIconCom = (props) => {
const { prefixCls, switcherIcon, treeNodeProps, showLine, switcherLoadingIcon } = props;
const { isLeaf, expanded, loading } = treeNodeProps;
if (loading) {
if (/*#__PURE__*/ import_react.isValidElement(switcherLoadingIcon)) return switcherLoadingIcon;
return /*#__PURE__*/ import_react.createElement(RefIcon$1$1, { className: `${prefixCls}-switcher-loading-icon` });
}
let showLeafIcon;
if (isPlainObject(showLine)) showLeafIcon = showLine.showLeafIcon;
if (isLeaf) {
if (!showLine) return null;
if (typeof showLeafIcon !== "boolean" && showLeafIcon) {
const leafIcon = isFunction(showLeafIcon) ? showLeafIcon(treeNodeProps) : showLeafIcon;
const leafCls = `${prefixCls}-switcher-line-custom-icon`;
if (/*#__PURE__*/ import_react.isValidElement(leafIcon)) return cloneElement(leafIcon, { className: clsx(leafIcon.props?.className, leafCls) });
return leafIcon;
}
return showLeafIcon ? /*#__PURE__*/ import_react.createElement(RefIcon$8, { className: `${prefixCls}-switcher-line-icon` }) : /*#__PURE__*/ import_react.createElement("span", { className: `${prefixCls}-switcher-leaf-line` });
}
const switcherCls = `${prefixCls}-switcher-icon`;
const switcher = isFunction(switcherIcon) ? switcherIcon(treeNodeProps) : switcherIcon;
if (/*#__PURE__*/ import_react.isValidElement(switcher)) return cloneElement(switcher, { className: clsx(switcher.props?.className, showLine ? `${prefixCls}-switcher-line-icon` : switcherCls) });
if (switcher !== void 0) return switcher;
if (showLine) return expanded ? /*#__PURE__*/ import_react.createElement(RefIcon$3, { className: `${prefixCls}-switcher-line-icon` }) : /*#__PURE__*/ import_react.createElement(RefIcon$2, { className: `${prefixCls}-switcher-line-icon` });
return /*#__PURE__*/ import_react.createElement(RefIcon$4, { className: switcherCls });
};
var Tree$1 = /*#__PURE__*/ import_react.forwardRef((props, ref) => {
const { getPrefixCls, direction, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("tree");
const { virtual } = import_react.useContext(ConfigContext);
const { prefixCls: customizePrefixCls, className, showIcon = false, showLine, switcherIcon, switcherLoadingIcon, blockNode = false, children, checkable = false, selectable = true, draggable, disabled, motion: customMotion, style, rootClassName, rootStyle, classNames, styles, icon } = props;
const contextDisabled = import_react.useContext(DisabledContext);
const mergedDisabled = disabled ?? contextDisabled;
const prefixCls = getPrefixCls("tree", customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const motion = customMotion ?? {
...initCollapseMotion(rootPrefixCls),
motionAppear: false
};
const mergedProps = {
...props,
showIcon,
blockNode,
checkable,
selectable,
disabled: mergedDisabled,
motion
};
const rootStyleRoot = useSemanticRootStyle(rootStyle);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
rootStyleRoot,
styles
], { props: mergedProps });
const newProps = {
...mergedProps,
showLine: Boolean(showLine),
icon,
dropIndicatorRender
};
const [hashId, cssVarCls] = style_default$1(prefixCls);
const [, token] = useToken();
const itemHeight = token.paddingXS / 2 + (token.Tree?.titleHeight || token.controlHeightSM);
const draggableConfig = import_react.useMemo(() => {
if (!draggable) return false;
let mergedDraggable = {};
switch (typeof draggable) {
case "function":
mergedDraggable.nodeDraggable = draggable;
break;
case "object": mergedDraggable = { ...draggable };
}
if (mergedDraggable.icon !== false) mergedDraggable.icon = mergedDraggable.icon || /*#__PURE__*/ import_react.createElement(RefIcon$5, null);
return mergedDraggable;
}, [draggable]);
const renderSwitcherIcon = (nodeProps) => /*#__PURE__*/ import_react.createElement(SwitcherIconCom, {
prefixCls,
switcherIcon,
switcherLoadingIcon,
treeNodeProps: nodeProps,
showLine
});
return /*#__PURE__*/ import_react.createElement(es_default, {
itemHeight,
ref,
virtual,
...newProps,
prefixCls,
className: clsx({
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
[`${prefixCls}-unselectable`]: !selectable,
[`${prefixCls}-rtl`]: direction === "rtl",
[`${prefixCls}-disabled`]: mergedDisabled
}, contextClassName, className, hashId, cssVarCls),
style: {
...contextStyle,
...style
},
rootClassName: clsx(mergedClassNames.root, rootClassName),
rootStyle: mergedStyles.root,
classNames: mergedClassNames,
styles: mergedStyles,
direction,
checkable: checkable ? /*#__PURE__*/ import_react.createElement("span", { className: `${prefixCls}-checkbox-inner` }) : checkable,
selectable,
switcherIcon: renderSwitcherIcon,
draggable: draggableConfig
}, children);
});
var RECORD_NONE = 0;
var RECORD_START = 1;
var RECORD_END = 2;
function traverseNodesKey(treeData, callback, fieldNames) {
const { key: fieldKey, children: fieldChildren } = fieldNames;
function processNode(dataNode) {
const key = dataNode[fieldKey];
const children = dataNode[fieldChildren];
if (callback(key, dataNode) !== false) traverseNodesKey(children || [], callback, fieldNames);
}
treeData.forEach(processNode);
}
/** 计算选中范围,只考虑expanded情况以优化性能 */
function calcRangeKeys({ treeData, expandedKeys, startKey, endKey, fieldNames }) {
const keys = [];
let record = RECORD_NONE;
if (!isNonNullable$1(startKey) || !isNonNullable$1(endKey)) return [];
if (startKey === endKey) return [startKey];
function matchKey(key) {
return key === startKey || key === endKey;
}
traverseNodesKey(treeData, (key) => {
if (record === RECORD_END) return false;
if (matchKey(key)) {
keys.push(key);
if (record === RECORD_NONE) record = RECORD_START;
else if (record === RECORD_START) {
record = RECORD_END;
return false;
}
} else if (record === RECORD_START) keys.push(key);
return expandedKeys.includes(key);
}, fillFieldNames(fieldNames));
return keys;
}
function convertDirectoryKeysToNodes(treeData, keys, fieldNames) {
const restKeys = _toConsumableArray(keys);
const nodes = [];
traverseNodesKey(treeData, (key, node) => {
const index = restKeys.indexOf(key);
if (index !== -1) {
nodes.push(node);
restKeys.splice(index, 1);
}
return !!restKeys.length;
}, fillFieldNames(fieldNames));
return nodes;
}
function getIcon(props) {
const { isLeaf, expanded } = props;
if (isLeaf) return /*#__PURE__*/ import_react.createElement(RefIcon$8, null);
return expanded ? /*#__PURE__*/ import_react.createElement(RefIcon$7, null) : /*#__PURE__*/ import_react.createElement(RefIcon$6, null);
}
function getTreeData({ treeData, children }) {
return treeData || convertTreeToData(children);
}
var DirectoryTree = /*#__PURE__*/ import_react.forwardRef((oriProps, ref) => {
const { defaultExpandAll, defaultExpandParent = true, defaultExpandedKeys, ...props } = oriProps;
const lastSelectedKeyRef = import_react.useRef(null);
const cachedSelectedKeysRef = import_react.useRef(null);
const getInitExpandedKeys = () => {
const { keyEntities } = convertDataToEntities(getTreeData(props), { fieldNames: props.fieldNames });
let initExpandedKeys;
const mergedExpandedKeys = props.expandedKeys || defaultExpandedKeys || [];
if (defaultExpandAll) initExpandedKeys = Object.keys(keyEntities);
else if (defaultExpandParent) initExpandedKeys = conductExpandParent(mergedExpandedKeys, keyEntities);
else initExpandedKeys = mergedExpandedKeys;
return initExpandedKeys;
};
const [selectedKeys, setSelectedKeys] = useControlledState(props.defaultSelectedKeys || [], props.selectedKeys);
const [expandedKeys, setExpandedKeys] = useControlledState(getInitExpandedKeys, props.expandedKeys);
const onExpand = (keys, info) => {
setExpandedKeys(keys);
return props.onExpand?.(keys, info);
};
const onSelect = (keys, event) => {
const { multiple, fieldNames } = props;
const { node, nativeEvent } = event;
const { key = "" } = node;
const treeData = getTreeData(props);
const newEvent = {
...event,
selected: true
};
const ctrlPick = nativeEvent?.ctrlKey || nativeEvent?.metaKey;
const shiftPick = nativeEvent?.shiftKey;
let newSelectedKeys;
if (multiple && ctrlPick) {
newSelectedKeys = keys;
lastSelectedKeyRef.current = key;
cachedSelectedKeysRef.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
} else if (multiple && shiftPick) {
newSelectedKeys = Array.from(new Set([].concat(_toConsumableArray(cachedSelectedKeysRef.current || []), _toConsumableArray(calcRangeKeys({
treeData,
expandedKeys,
startKey: key,
endKey: lastSelectedKeyRef.current,
fieldNames
})))));
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
} else {
newSelectedKeys = [key];
lastSelectedKeyRef.current = key;
cachedSelectedKeysRef.current = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys, fieldNames);
}
props.onSelect?.(newSelectedKeys, newEvent);
setSelectedKeys(newSelectedKeys);
};
const { getPrefixCls, direction } = import_react.useContext(ConfigContext);
const { prefixCls: customizePrefixCls, className, showIcon = true, expandAction = "click", ...restProps } = props;
const prefixCls = getPrefixCls("tree", customizePrefixCls);
const connectClassName = clsx(`${prefixCls}-directory`, { [`${prefixCls}-directory-rtl`]: direction === "rtl" }, className);
return /*#__PURE__*/ import_react.createElement(Tree$1, {
icon: getIcon,
ref,
blockNode: true,
...restProps,
showIcon,
expandAction,
prefixCls,
className: connectClassName,
defaultExpandParent,
expandedKeys,
selectedKeys,
onSelect,
onExpand
});
});
var Tree = Tree$1;
Tree.DirectoryTree = DirectoryTree;
Tree.TreeNode = TreeNode;
Tree.useTree = useTree;
var FilterSearch = (props) => {
const { value, filterSearch, tablePrefixCls, locale, onChange } = props;
if (!filterSearch) return null;
return /*#__PURE__*/ import_react.createElement("div", { className: `${tablePrefixCls}-filter-dropdown-search` }, /*#__PURE__*/ import_react.createElement(Input$1, {
prefix: /*#__PURE__*/ import_react.createElement(RefIcon$10, null),
placeholder: locale.filterSearchPlaceholder,
onChange,
value,
htmlSize: 1,
className: `${tablePrefixCls}-filter-dropdown-search-input`
}));
};
var onKeyDown = (event) => {
const { keyCode } = event;
if (keyCode === KeyCode.ENTER) event.stopPropagation();
};
var FilterDropdownMenuWrapper = /*#__PURE__*/ import_react.forwardRef((props, ref) => /*#__PURE__*/ import_react.createElement("div", {
className: props.className,
onClick: (e) => e.stopPropagation(),
onKeyDown,
ref,
role: "presentation"
}, props.children));
function flattenKeys(filters) {
let keys = [];
(filters || []).forEach(({ value, children }) => {
keys.push(value);
if (children) keys = [].concat(_toConsumableArray(keys), _toConsumableArray(flattenKeys(children)));
});
return keys;
}
function hasSubMenu(filters) {
return filters.some(({ children }) => children);
}
var searchValueMatched = (normalizedSearchValue, text) => {
if (typeof text === "string" || isNumber(text)) return text.toString().toLowerCase().includes(normalizedSearchValue);
return false;
};
var renderFilterItems = (options) => {
const { filters, prefixCls, filteredKeys, filterMultiple, searchValue, normalizedSearchValue, filterSearch } = options;
return filters.map((filter, index) => {
const key = String(filter.value);
if (filter.children) return {
key: key || index,
label: filter.text,
popupClassName: `${prefixCls}-dropdown-submenu`,
children: renderFilterItems({
filters: filter.children,
prefixCls,
filteredKeys,
filterMultiple,
searchValue,
normalizedSearchValue,
filterSearch
})
};
const Component = filterMultiple ? Checkbox : Radio;
const item = {
key: filter.value !== void 0 ? key : index,
label: /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement(Component, { checked: filteredKeys.includes(key) }), /*#__PURE__*/ import_react.createElement("span", null, filter.text))
};
if (normalizedSearchValue) {
if (isFunction(filterSearch)) return filterSearch(normalizedSearchValue, filter) ? item : null;
return searchValueMatched(normalizedSearchValue, filter.text) ? item : null;
}
return item;
});
};
function wrapStringListType(keys) {
return keys || [];
}
var FilterDropdown = (props) => {
const { tablePrefixCls, prefixCls, column, dropdownPrefixCls, columnKey, filterOnClose, filterMultiple, filterMode = "menu", filterSearch = false, filterState, triggerFilter, locale, children, getPopupContainer, rootClassName } = props;
const { filterResetToDefaultFilteredValue, defaultFilteredValue, filterDropdownProps = {}, filterDropdownOpen, onFilterDropdownOpenChange } = column;
const [visible, setVisible] = import_react.useState(false);
const inMeasureRow = import_react.useContext(TableMeasureRowContext);
const filtered = !!(filterState && (filterState.filteredKeys?.length || filterState.forceFiltered));
const triggerOpen = (nextOpen) => {
setVisible(nextOpen);
filterDropdownProps.onOpenChange?.(nextOpen);
onFilterDropdownOpenChange?.(nextOpen);
};
const mergedVisible = filterDropdownProps.open ?? filterDropdownOpen ?? visible;
const propFilteredKeys = filterState?.filteredKeys;
const [getFilteredKeysSync, setFilteredKeysSync] = useSyncState(wrapStringListType(propFilteredKeys));
const onSelectKeys = ({ selectedKeys }) => {
setFilteredKeysSync(selectedKeys);
};
const onCheck = (keys, { node, checked }) => {
if (!filterMultiple) onSelectKeys({ selectedKeys: checked ? [node.key] : [] });
else onSelectKeys({ selectedKeys: keys });
};
import_react.useEffect(() => {
if (!mergedVisible) return;
onSelectKeys({ selectedKeys: wrapStringListType(propFilteredKeys) });
}, [propFilteredKeys, mergedVisible]);
const [openKeys, setOpenKeys] = import_react.useState([]);
const onOpenChange = (keys) => {
setOpenKeys(keys);
};
const [searchValue, setSearchValue] = import_react.useState("");
const normalizedSearchValue = import_react.useMemo(() => searchValue.trim().toLowerCase(), [searchValue]);
const onSearch = (e) => {
const { value } = e.target;
setSearchValue(value);
};
import_react.useEffect(() => {
if (!mergedVisible) setSearchValue("");
}, [mergedVisible]);
const internalTriggerFilter = (keys) => {
const mergedKeys = keys?.length ? keys : null;
if (mergedKeys === null && (!filterState || !filterState.filteredKeys)) return null;
if (isEqual(mergedKeys, filterState?.filteredKeys, true)) return null;
triggerFilter({
column,
key: columnKey,
filteredKeys: mergedKeys
});
};
const onConfirm = () => {
triggerOpen(false);
internalTriggerFilter(getFilteredKeysSync());
};
const onReset = ({ confirm, closeDropdown } = {
confirm: false,
closeDropdown: false
}) => {
if (confirm) internalTriggerFilter([]);
if (closeDropdown) triggerOpen(false);
setSearchValue("");
if (filterResetToDefaultFilteredValue) setFilteredKeysSync((defaultFilteredValue || []).map(String));
else setFilteredKeysSync([]);
};
const doFilter = ({ closeDropdown } = { closeDropdown: true }) => {
if (closeDropdown) triggerOpen(false);
internalTriggerFilter(getFilteredKeysSync());
};
const onDropdownOpenChange = (nextOpen, info) => {
if (info.source === "trigger") {
if (nextOpen && propFilteredKeys !== void 0) setFilteredKeysSync(wrapStringListType(propFilteredKeys));
if (!nextOpen && !column.filterDropdown && filterOnClose) onConfirm();
else triggerOpen(nextOpen);
}
};
const dropdownMenuClass = clsx({ [`${dropdownPrefixCls}-menu-without-submenu`]: !hasSubMenu(column.filters || []) });
const onCheckAll = (e) => {
if (e.target.checked) {
const allFilterKeys = flattenKeys(column?.filters).map(String);
setFilteredKeysSync(allFilterKeys);
} else setFilteredKeysSync([]);
};
const getTreeData = ({ filters }) => (filters || []).map((filter, index) => {
const key = String(filter.value);
const item = {
title: filter.text,
key: filter.value !== void 0 ? key : String(index)
};
if (filter.children) item.children = getTreeData({ filters: filter.children });
return item;
});
const getFilterData = (node) => ({
...node,
text: node.title,
value: node.key,
children: node.children?.map(getFilterData) || []
});
let dropdownContent;
const { direction, renderEmpty } = import_react.useContext(ConfigContext);
if (isFunction(column.filterDropdown)) dropdownContent = column.filterDropdown({
prefixCls: `${dropdownPrefixCls}-custom`,
setSelectedKeys: (selectedKeys) => onSelectKeys({ selectedKeys }),
selectedKeys: getFilteredKeysSync(),
confirm: doFilter,
clearFilters: onReset,
filters: column.filters,
visible: mergedVisible,
close: () => {
triggerOpen(false);
}
});
else if (column.filterDropdown) dropdownContent = column.filterDropdown;
else {
const selectedKeys = getFilteredKeysSync() || [];
const getFilterComponent = () => {
const empty = renderEmpty?.("Table.filter") ?? /*#__PURE__*/ import_react.createElement(Empty, {
image: Empty.PRESENTED_IMAGE_SIMPLE,
description: locale.filterEmptyText,
styles: { image: { height: 24 } },
style: {
margin: 0,
padding: "16px 0"
}
});
if ((column.filters || []).length === 0) return empty;
if (filterMode === "tree") return /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement(FilterSearch, {
filterSearch,
value: searchValue,
onChange: onSearch,
tablePrefixCls,
locale
}), /*#__PURE__*/ import_react.createElement("div", { className: `${tablePrefixCls}-filter-dropdown-tree` }, filterMultiple ? /*#__PURE__*/ import_react.createElement(Checkbox, {
checked: selectedKeys.length === flattenKeys(column.filters).length,
indeterminate: selectedKeys.length > 0 && selectedKeys.length < flattenKeys(column.filters).length,
className: `${tablePrefixCls}-filter-dropdown-checkall`,
onChange: onCheckAll
}, locale?.filterCheckall ?? locale?.filterCheckAll) : null, /*#__PURE__*/ import_react.createElement(Tree, {
checkable: true,
selectable: false,
blockNode: true,
multiple: filterMultiple,
checkStrictly: !filterMultiple,
className: `${dropdownPrefixCls}-menu`,
onCheck,
checkedKeys: selectedKeys,
selectedKeys,
showIcon: false,
treeData: getTreeData({ filters: column.filters }),
autoExpandParent: true,
defaultExpandAll: true,
filterTreeNode: normalizedSearchValue ? (node) => {
if (isFunction(filterSearch)) return filterSearch(searchValue, getFilterData(node));
return searchValueMatched(normalizedSearchValue, node.title);
} : void 0
})));
const items = renderFilterItems({
filters: column.filters || [],
filterSearch,
prefixCls,
filteredKeys: getFilteredKeysSync(),
filterMultiple,
searchValue,
normalizedSearchValue
});
const isEmpty = items.every((item) => item === null);
return /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, /*#__PURE__*/ import_react.createElement(FilterSearch, {
filterSearch,
value: searchValue,
onChange: onSearch,
tablePrefixCls,
locale
}), isEmpty ? empty : /*#__PURE__*/ import_react.createElement(Menu, {
selectable: true,
multiple: filterMultiple,
prefixCls: `${dropdownPrefixCls}-menu`,
className: dropdownMenuClass,
onSelect: onSelectKeys,
onDeselect: onSelectKeys,
selectedKeys,
getPopupContainer,
openKeys,
onOpenChange,
items
}));
};
const getResetDisabled = () => {
if (filterResetToDefaultFilteredValue) return isEqual((defaultFilteredValue || []).map(String), selectedKeys, true);
return selectedKeys.length === 0;
};
dropdownContent = /*#__PURE__*/ import_react.createElement(import_react.Fragment, null, getFilterComponent(), /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-dropdown-btns` }, /*#__PURE__*/ import_react.createElement(Button, {
type: "link",
size: "small",
disabled: getResetDisabled(),
onClick: () => onReset()
}, locale.filterReset), /*#__PURE__*/ import_react.createElement(Button, {
type: "primary",
size: "small",
onClick: onConfirm
}, locale.filterConfirm)));
}
if (column.filterDropdown) dropdownContent = /*#__PURE__*/ import_react.createElement(OverrideProvider, { selectable: void 0 }, dropdownContent);
dropdownContent = /*#__PURE__*/ import_react.createElement(FilterDropdownMenuWrapper, { className: `${prefixCls}-dropdown` }, dropdownContent);
const getDropdownTrigger = () => {
let filterIcon;
if (isFunction(column.filterIcon)) filterIcon = column.filterIcon(filtered);
else if (column.filterIcon) filterIcon = column.filterIcon;
else filterIcon = /*#__PURE__*/ import_react.createElement(RefIcon$9, null);
return /*#__PURE__*/ import_react.createElement("span", {
role: "button",
tabIndex: -1,
className: clsx(`${prefixCls}-trigger`, { active: filtered }),
onClick: (e) => {
e.stopPropagation();
}
}, filterIcon);
};
const triggerNode = getDropdownTrigger();
if (inMeasureRow) return /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-column` }, /*#__PURE__*/ import_react.createElement("span", { className: `${tablePrefixCls}-column-title` }, children), triggerNode);
const mergedDropdownProps = mergeProps({
trigger: ["click"],
placement: direction === "rtl" ? "bottomLeft" : "bottomRight",
children: triggerNode,
getPopupContainer
}, {
...filterDropdownProps,
rootClassName: clsx(rootClassName, filterDropdownProps.rootClassName),
open: mergedVisible,
onOpenChange: onDropdownOpenChange,
popupRender: () => {
if (isFunction(filterDropdownProps?.dropdownRender)) return filterDropdownProps.dropdownRender(dropdownContent);
return dropdownContent;
}
});
return /*#__PURE__*/ import_react.createElement("div", { className: `${prefixCls}-column` }, /*#__PURE__*/ import_react.createElement("span", { className: `${tablePrefixCls}-column-title` }, children), /*#__PURE__*/ import_react.createElement(Dropdown, { ...mergedDropdownProps }));
};
var collectFilterStates = (columns, init, pos) => {
let filterStates = [];
(columns || []).forEach((column, index) => {
const columnPos = getColumnPos(index, pos);
const filterDropdownIsDefined = column.filterDropdown !== void 0;
if (column.filters || filterDropdownIsDefined || "onFilter" in column) {
if ("filteredValue" in column) {
let filteredValues = column.filteredValue;
if (!filterDropdownIsDefined) filteredValues = filteredValues?.map(String) ?? filteredValues;
filterStates.push({
column,
key: getColumnKey(column, columnPos),
filteredKeys: filteredValues,
forceFiltered: column.filtered
});
} else filterStates.push({
column,
key: getColumnKey(column, columnPos),
filteredKeys: init && column.defaultFilteredValue ? column.defaultFilteredValue : void 0,
forceFiltered: column.filtered
});
}
if ("children" in column) filterStates = [].concat(_toConsumableArray(filterStates), _toConsumableArray(collectFilterStates(column.children, init, columnPos)));
});
return filterStates;
};
function injectFilter(prefixCls, dropdownPrefixCls, columns, filterStates, locale, triggerFilter, getPopupContainer, pos, rootClassName) {
return columns.map((column, index) => {
const columnPos = getColumnPos(index, pos);
const { filterOnClose = true, filterMultiple = true, filterMode, filterSearch } = column;
let newColumn = column;
if (newColumn.filters || newColumn.filterDropdown) {
const columnKey = getColumnKey(newColumn, columnPos);
const filterState = filterStates.find(({ key }) => columnKey === key);
newColumn = {
...newColumn,
title: (renderProps) => /*#__PURE__*/ import_react.createElement(FilterDropdown, {
tablePrefixCls: prefixCls,
prefixCls: `${prefixCls}-filter`,
dropdownPrefixCls,
column: newColumn,
columnKey,
filterState,
filterOnClose,
filterMultiple,
filterMode,
filterSearch,
triggerFilter,
locale,
getPopupContainer,
rootClassName
}, renderColumnTitle(column.title, renderProps))
};
}
if ("children" in newColumn) newColumn = {
...newColumn,
children: injectFilter(prefixCls, dropdownPrefixCls, newColumn.children, filterStates, locale, triggerFilter, getPopupContainer, columnPos, rootClassName)
};
return newColumn;
});
}
var generateFilterInfo = (filterStates) => {
const currentFilters = {};
filterStates.forEach(({ key, filteredKeys, column }) => {
const keyAsString = key;
const { filters, filterDropdown } = column;
if (filterDropdown) currentFilters[keyAsString] = filteredKeys || null;
else if (Array.isArray(filteredKeys)) {
const keys = flattenKeys(filters);
currentFilters[keyAsString] = keys.filter((originKey) => filteredKeys.includes(String(originKey)));
} else currentFilters[keyAsString] = null;
});
return currentFilters;
};
var getFilterData = (data, filterStates, childrenColumnName) => {
return filterStates.reduce((currentData, filterState) => {
const { column: { onFilter, filters }, filteredKeys } = filterState;
if (onFilter && filteredKeys && filteredKeys.length) {
const flatKeys = flattenKeys(filters);
const keyMap = /* @__PURE__ */ new Map();
flatKeys.forEach((k) => {
const strKey = String(k);
if (!keyMap.has(strKey)) keyMap.set(strKey, k);
});
const realKeys = filteredKeys.map((key) => {
const strKey = String(key);
return keyMap.get(strKey) ?? key;
});
const internalFilter = (subset) => subset.reduce((acc, record) => {
const clonedRecord = { ...record };
if (clonedRecord[childrenColumnName]) clonedRecord[childrenColumnName] = getFilterData(clonedRecord[childrenColumnName], filterStates, childrenColumnName);
if (realKeys.some((realKey) => onFilter(realKey, clonedRecord))) acc.push(clonedRecord);
return acc;
}, []);
return internalFilter(currentData);
}
return currentData;
}, data);
};
var getMergedColumns = (rawMergedColumns) => rawMergedColumns.flatMap((column) => {
if ("children" in column) return [column].concat(_toConsumableArray(getMergedColumns(column.children || [])));
return [column];
});
var useFilter = (props) => {
const { prefixCls, dropdownPrefixCls, mergedColumns: rawMergedColumns, baseColumns, onFilterChange, getPopupContainer, locale: tableLocale, rootClassName } = props;
devUseWarning();
const mergedColumns = import_react.useMemo(() => getMergedColumns(baseColumns ?? rawMergedColumns ?? []), [baseColumns, rawMergedColumns]);
const [filterStates, setFilterStates] = import_react.useState(() => collectFilterStates(mergedColumns, true));
const mergedFilterStates = import_react.useMemo(() => {
const collectedStates = collectFilterStates(mergedColumns, false);
if (collectedStates.length === 0) return collectedStates;
let filteredKeysIsAllNotControlled = true;
collectedStates.forEach(({ filteredKeys }) => {
if (filteredKeys !== void 0) filteredKeysIsAllNotControlled = false;
});
if (filteredKeysIsAllNotControlled) {
const keyList = (mergedColumns || []).map((column, index) => getColumnKey(column, getColumnPos(index)));
return filterStates.reduce((list, item) => {
const keyIndex = keyList.indexOf(item.key);
if (keyIndex !== -1) {
const col = mergedColumns[keyIndex];
list.push({
...item,
column: {
...item.column,
...col
},
forceFiltered: col.filtered
});
}
return list;
}, []);
}
return collectedStates;
}, [mergedColumns, filterStates]);
const filters = import_react.useMemo(() => generateFilterInfo(mergedFilterStates), [mergedFilterStates]);
const triggerFilter = (filterState) => {
const newFilterStates = mergedFilterStates.filter(({ key }) => key !== filterState.key);
newFilterStates.push(filterState);
setFilterStates(newFilterStates);
onFilterChange(generateFilterInfo(newFilterStates), newFilterStates);
};
const transformColumns = (innerColumns) => injectFilter(prefixCls, dropdownPrefixCls, innerColumns, mergedFilterStates, tableLocale, triggerFilter, getPopupContainer, void 0, rootClassName);
return [
transformColumns,
mergedFilterStates,
filters
];
};
var useLazyKVMap = (data, childrenColumnName, getRowKey) => {
const mapCacheRef = import_react.useRef({});
function getRecordByKey(key) {
if (!mapCacheRef.current || mapCacheRef.current.data !== data || mapCacheRef.current.childrenColumnName !== childrenColumnName || mapCacheRef.current.getRowKey !== getRowKey) {
const kvMap = /* @__PURE__ */ new Map();
function dig(records) {
records.forEach((record, index) => {
const rowKey = getRowKey(record, index);
kvMap.set(rowKey, record);
if (isPlainObject(record) && childrenColumnName in record) dig(record[childrenColumnName] || []);
});
}
dig(data);
mapCacheRef.current = {
data,
childrenColumnName,
kvMap,
getRowKey
};
}
return mapCacheRef.current.kvMap?.get(key);
}
return [getRecordByKey];
};
function getPaginationParam(mergedPagination, pagination) {
const param = {
current: mergedPagination.current,
pageSize: mergedPagination.pageSize
};
const paginationObj = isPlainObject(pagination) ? pagination : {};
Object.keys(paginationObj).forEach((pageProp) => {
const value = mergedPagination[pageProp];
if (!isFunction(value)) param[pageProp] = value;
});
return param;
}
function usePagination(total, onChange, pagination) {
const { total: paginationTotal = 0, ...paginationObj } = isPlainObject(pagination) ? pagination : {};
const [innerPagination, setInnerPagination] = (0, import_react.useState)(() => ({
current: "defaultCurrent" in paginationObj ? paginationObj.defaultCurrent : 1,
pageSize: "defaultPageSize" in paginationObj ? paginationObj.defaultPageSize : 10
}));
const mergedPagination = mergeProps(innerPagination, paginationObj, { total: paginationTotal > 0 ? paginationTotal : total });
const maxPage = Math.ceil((paginationTotal || total) / mergedPagination.pageSize);
if (mergedPagination.current > maxPage) mergedPagination.current = maxPage || 1;
const refreshPagination = (current, pageSize) => {
setInnerPagination({
current: current ?? 1,
pageSize: pageSize || mergedPagination.pageSize
});
};
const onInternalChange = (current, pageSize) => {
if (pagination) pagination.onChange?.(current, pageSize);
refreshPagination(current, pageSize);
onChange(current, pageSize || mergedPagination?.pageSize);
};
if (pagination === false) return [{}, () => {}];
return [{
...mergedPagination,
onChange: onInternalChange
}, refreshPagination];
}
var CaretDownOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "0 0 1024 1024",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z" }
}]
},
"name": "caret-down",
"theme": "outlined"
};
function _extends$1() {
_extends$1 = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends$1.apply(this, arguments);
}
var CaretDownOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends$1({}, props, {
ref,
icon: CaretDownOutlined$1
}));
/** */
var RefIcon$1 = /*#__PURE__*/ import_react.forwardRef(CaretDownOutlined);
var CaretUpOutlined$1 = {
"icon": {
"tag": "svg",
"attrs": {
"viewBox": "0 0 1024 1024",
"focusable": "false"
},
"children": [{
"tag": "path",
"attrs": { "d": "M858.9 689L530.5 308.2c-9.4-10.9-27.5-10.9-37 0L165.1 689c-12.2 14.2-1.2 35 18.5 35h656.8c19.7 0 30.7-20.8 18.5-35z" }
}]
},
"name": "caret-up",
"theme": "outlined"
};
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
}
return target;
};
return _extends.apply(this, arguments);
}
var CaretUpOutlined = (props, ref) => /*#__PURE__*/ import_react.createElement(Icon, _extends({}, props, {
ref,
icon: CaretUpOutlined$1
}));
/** */
var RefIcon = /*#__PURE__*/ import_react.forwardRef(CaretUpOutlined);
var ASCEND = "ascend";
var DESCEND = "descend";
var getMultiplePriority = (column) => {
if (isPlainObject(column.sorter) && isNumber(column.sorter.multiple)) return column.sorter.multiple;
return false;
};
var getSortFunction = (sorter) => {
if (isFunction(sorter)) return sorter;
if (isPlainObject(sorter) && sorter.compare) return sorter.compare;
return false;
};
var nextSortDirection = (sortDirections, current) => {
if (!current) return sortDirections[0];
return sortDirections[sortDirections.indexOf(current) + 1];
};
var collectSortStates = (columns, init, pos) => {
let sortStates = [];
const pushState = (column, columnPos) => {
sortStates.push({
column,
key: getColumnKey(column, columnPos),
multiplePriority: getMultiplePriority(column),
sortOrder: column.sortOrder
});
};
(columns || []).forEach((column, index) => {
const columnPos = getColumnPos(index, pos);
if (column.children) {
if ("sortOrder" in column) pushState(column, columnPos);
sortStates = [].concat(_toConsumableArray(sortStates), _toConsumableArray(collectSortStates(column.children, init, columnPos)));
} else if (column.sorter) {
if ("sortOrder" in column) pushState(column, columnPos);
else if (init && column.defaultSortOrder) sortStates.push({
column,
key: getColumnKey(column, columnPos),
multiplePriority: getMultiplePriority(column),
sortOrder: column.defaultSortOrder
});
}
});
return sortStates;
};
var injectSorter = (prefixCls, columns, sorterStates, triggerSorter, defaultSortDirections, tableLocale, tableShowSorterTooltip, pos, a11yLocale) => {
return (columns || []).map((column, index) => {
const columnPos = getColumnPos(index, pos);
let newColumn = column;
if (newColumn.sorter) {
const sortDirections = newColumn.sortDirections || defaultSortDirections;
const showSorterTooltip = newColumn.showSorterTooltip === void 0 ? tableShowSorterTooltip : newColumn.showSorterTooltip;
const columnKey = getColumnKey(newColumn, columnPos);
const sorterState = sorterStates.find(({ key }) => key === columnKey);
const sortOrder = sorterState ? sorterState.sortOrder : null;
const nextSortOrder = nextSortDirection(sortDirections, sortOrder);
let sorter;
if (column.sortIcon) sorter = column.sortIcon({ sortOrder });
else {
const upNode = sortDirections.includes(ASCEND) && /*#__PURE__*/ import_react.createElement(RefIcon, { className: clsx(`${prefixCls}-column-sorter-up`, { active: sortOrder === ASCEND }) });
const downNode = sortDirections.includes(DESCEND) && /*#__PURE__*/ import_react.createElement(RefIcon$1, { className: clsx(`${prefixCls}-column-sorter-down`, { active: sortOrder === DESCEND }) });
sorter = /*#__PURE__*/ import_react.createElement("span", { className: clsx(`${prefixCls}-column-sorter`, { [`${prefixCls}-column-sorter-full`]: !!(upNode && downNode) }) }, /*#__PURE__*/ import_react.createElement("span", {
className: `${prefixCls}-column-sorter-inner`,
"aria-hidden": "true"
}, upNode, downNode));
}
const { cancelSort, triggerAsc, triggerDesc } = tableLocale || {};
let sortTip = cancelSort;
if (nextSortOrder === DESCEND) sortTip = triggerDesc;
else if (nextSortOrder === ASCEND) sortTip = triggerAsc;
const tooltipProps = isPlainObject(showSorterTooltip) ? {
title: sortTip,
...showSorterTooltip
} : { title: sortTip };
newColumn = {
...newColumn,
className: clsx(newColumn.className, { [`${prefixCls}-column-sort`]: sortOrder }),
title: (renderProps) => {
const columnSortersClass = `${prefixCls}-column-sorters`;
const renderColumnTitleWrapper = /*#__PURE__*/ import_react.createElement("span", { className: `${prefixCls}-column-title` }, renderColumnTitle(column.title, renderProps));
const renderSortTitle = /*#__PURE__*/ import_react.createElement("div", { className: columnSortersClass }, renderColumnTitleWrapper, sorter);
if (showSorterTooltip) {
if (typeof showSorterTooltip !== "boolean" && showSorterTooltip?.target === "sorter-icon") return /*#__PURE__*/ import_react.createElement("div", { className: clsx(columnSortersClass, `${columnSortersClass}-tooltip-target-sorter`) }, renderColumnTitleWrapper, /*#__PURE__*/ import_react.createElement(Tooltip, { ...tooltipProps }, sorter));
return /*#__PURE__*/ import_react.createElement(Tooltip, { ...tooltipProps }, renderSortTitle);
}
return renderSortTitle;
},
onHeaderCell: (col) => {
const cell = column.onHeaderCell?.(col) || {};
const originOnClick = cell.onClick;
const originOKeyDown = cell.onKeyDown;
cell.onClick = (event) => {
triggerSorter({
column,
key: columnKey,
sortOrder: nextSortOrder,
multiplePriority: getMultiplePriority(column)
});
originOnClick?.(event);
};
cell.onKeyDown = (event) => {
if (event.keyCode === KeyCode.ENTER) triggerSorter({
column,
key: columnKey,
sortOrder: nextSortOrder,
multiplePriority: getMultiplePriority(column)
});
originOKeyDown?.(event);
};
const renderTitle = safeColumnTitle(column.title, {});
const displayTitle = renderTitle?.toString();
if (sortOrder) cell["aria-sort"] = sortOrder === "ascend" ? "ascending" : "descending";
cell["aria-description"] = a11yLocale?.sortable;
cell["aria-label"] = displayTitle || "";
cell.className = clsx(cell.className, `${prefixCls}-column-has-sorters`);
cell.tabIndex = 0;
if (column.ellipsis) cell.title = (renderTitle ?? "").toString();
return cell;
}
};
}
if ("children" in newColumn) newColumn = {
...newColumn,
children: injectSorter(prefixCls, newColumn.children, sorterStates, triggerSorter, defaultSortDirections, tableLocale, tableShowSorterTooltip, columnPos, a11yLocale)
};
return newColumn;
});
};
var stateToInfo = (sorterState) => {
const { column, sortOrder } = sorterState;
return {
column,
order: sortOrder,
field: column.dataIndex,
columnKey: column.key
};
};
var generateSorterInfo = (sorterStates) => {
const activeSorters = sorterStates.reduce((list, sorterState) => {
if (sorterState.sortOrder) list.push(stateToInfo(sorterState));
return list;
}, []);
if (activeSorters.length === 0 && sorterStates.length) return {
...stateToInfo(sorterStates[sorterStates.length - 1]),
column: void 0,
order: void 0,
field: void 0,
columnKey: void 0
};
if (activeSorters.length <= 1) return activeSorters[0] || {};
return activeSorters;
};
var getSortData = (data, sortStates, childrenColumnName) => {
const innerSorterStates = sortStates.slice().sort((a, b) => b.multiplePriority - a.multiplePriority);
const cloneData = data.slice();
const runningSorters = innerSorterStates.filter(({ column: { sorter }, sortOrder }) => getSortFunction(sorter) && sortOrder);
if (!runningSorters.length) return cloneData;
return cloneData.sort((record1, record2) => {
for (let i = 0; i < runningSorters.length; i += 1) {
const { column: { sorter }, sortOrder } = runningSorters[i];
const compareFn = getSortFunction(sorter);
if (compareFn && sortOrder) {
const compareResult = compareFn(record1, record2, sortOrder);
if (compareResult !== 0) return sortOrder === ASCEND ? compareResult : -compareResult;
}
}
return 0;
}).map((record) => {
const subRecords = record[childrenColumnName];
if (subRecords) return {
...record,
[childrenColumnName]: getSortData(subRecords, sortStates, childrenColumnName)
};
return record;
});
};
var useFilterSorter = (props) => {
const { prefixCls, mergedColumns, baseColumns, sortDirections, tableLocale, showSorterTooltip, onSorterChange, globalLocale } = props;
const collectColumns = baseColumns ?? mergedColumns;
const [sortStates, setSortStates] = import_react.useState(() => collectSortStates(collectColumns, true));
const getColumnKeys = (columns, pos) => {
const newKeys = [];
columns.forEach((item, index) => {
const columnPos = getColumnPos(index, pos);
newKeys.push(getColumnKey(item, columnPos));
if (Array.isArray(item.children)) {
const childKeys = getColumnKeys(item.children, columnPos);
newKeys.push.apply(newKeys, _toConsumableArray(childKeys));
}
});
return newKeys;
};
const mergedSorterStates = import_react.useMemo(() => {
let validate = true;
const collectedStates = collectSortStates(collectColumns, false);
if (!collectedStates.length) {
const collectColumnsKeys = getColumnKeys(collectColumns);
return sortStates.filter(({ key }) => collectColumnsKeys.includes(key));
}
const validateStates = [];
function patchStates(state) {
if (validate) validateStates.push(state);
else validateStates.push({
...state,
sortOrder: null
});
}
let multipleMode = null;
collectedStates.forEach((state) => {
if (multipleMode === null) {
patchStates(state);
if (state.sortOrder) {
if (state.multiplePriority === false) validate = false;
else multipleMode = true;
}
} else if (multipleMode && state.multiplePriority !== false) patchStates(state);
else {
validate = false;
patchStates(state);
}
});
return validateStates;
}, [collectColumns, sortStates]);
const columnTitleSorterProps = import_react.useMemo(() => {
const sortColumns = mergedSorterStates.map(({ column, sortOrder }) => ({
column,
order: sortOrder
}));
return {
sortColumns,
sortColumn: sortColumns[0]?.column,
sortOrder: sortColumns[0]?.order
};
}, [mergedSorterStates]);
const triggerSorter = (sortState) => {
let newSorterStates;
if (sortState.multiplePriority === false || !mergedSorterStates.length || mergedSorterStates[0].multiplePriority === false) newSorterStates = [sortState];
else newSorterStates = [].concat(_toConsumableArray(mergedSorterStates.filter(({ key }) => key !== sortState.key)), [sortState]);
setSortStates(newSorterStates);
onSorterChange(generateSorterInfo(newSorterStates), newSorterStates);
};
const transformColumns = (innerColumns) => injectSorter(prefixCls, innerColumns, mergedSorterStates, triggerSorter, sortDirections, tableLocale, showSorterTooltip, void 0, globalLocale);
const getSorters = () => generateSorterInfo(mergedSorterStates);
return [
transformColumns,
mergedSorterStates,
columnTitleSorterProps,
getSorters
];
};
var useSpinProps = (loading) => {
return import_react.useMemo(() => {
if (typeof loading === "boolean") return { spinning: loading };
if (isPlainObject(loading)) return {
spinning: true,
...loading
};
return {};
}, [loading]);
};
var fillTitle = (columns, columnTitleProps) => {
return columns.map((column) => {
const cloneColumn = { ...column };
cloneColumn.title = renderColumnTitle(column.title, columnTitleProps);
if ("children" in cloneColumn) cloneColumn.children = fillTitle(cloneColumn.children, columnTitleProps);
return cloneColumn;
});
};
var useTitleColumns = (columnTitleProps) => {
return [import_react.useCallback((columns) => fillTitle(columns, columnTitleProps), [columnTitleProps])];
};
/**
* Same as `rc-component/table` but we modify trigger children update logic instead.
*/
var RcTable = genTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev;
const { _renderTimes: nextRenderTimes } = next;
return prevRenderTimes !== nextRenderTimes;
});
/**
* Same as `rc-component/table` but we modify trigger children update logic instead.
*/
var RcVirtualTable = genVirtualTable((prev, next) => {
const { _renderTimes: prevRenderTimes } = prev;
const { _renderTimes: nextRenderTimes } = next;
return prevRenderTimes !== nextRenderTimes;
});
var genBorderedStyle = (token) => {
const { antCls, componentCls, lineWidth, lineType, tableBorderColor, tableHeaderBg, tablePaddingVertical, tablePaddingHorizontal, calc } = token;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
const [varName, varRef] = genCssVar(antCls, "table");
const getSizeBorderStyle = (size, paddingVertical, paddingHorizontal) => ({ [`&${componentCls}-${size}`]: { [`> ${componentCls}-container`]: { [`> ${componentCls}-content, > ${componentCls}-body`]: { "> table > tbody > tr > th, > table > tbody > tr > td": { [`> ${componentCls}-expanded-row-fixed`]: { margin: `${unit$1(calc(paddingVertical).mul(-1).equal())}
${unit$1(calc(calc(paddingHorizontal).add(lineWidth)).mul(-1).equal())}` } } } } } });
return { [`${componentCls}-wrapper`]: {
[varName("nested-border-top")]: tableBorder,
[`${componentCls}${componentCls}-bordered`]: {
[`> ${componentCls}-title`]: {
border: tableBorder,
borderBottom: 0
},
[`> ${componentCls}-container`]: {
borderInlineStart: tableBorder,
borderTop: tableBorder,
"&:first-child": { borderTop: varRef("nested-border-top", tableBorder) },
[`> ${componentCls}-header${componentCls}-sticky-holder`]: {
marginTop: calc(lineWidth).mul(-1).equal(),
borderTop: tableBorder
},
[`> ${componentCls}-content, > ${componentCls}-header, > ${componentCls}-body, > ${componentCls}-summary`]: { "> table": {
"> thead > tr > th, > thead > tr > td, > tbody > tr > th, > tbody > tr > td, > tfoot > tr > th, > tfoot > tr > td": { borderInlineEnd: tableBorder },
"> thead": {
"> tr:not(:last-child) > th": { borderBottom: tableBorder },
"> tr > th::before": { backgroundColor: "transparent !important" }
},
"> thead > tr, > tbody > tr, > tfoot > tr": { [`> ${componentCls}-cell-fix-right-first:not(${componentCls}-cell-fix-right-last)::after`]: { borderInlineEnd: tableBorder } },
"> tbody > tr > th, > tbody > tr > td": { [`> ${componentCls}-expanded-row-fixed`]: {
margin: `${unit$1(calc(tablePaddingVertical).mul(-1).equal())} ${unit$1(calc(calc(tablePaddingHorizontal).add(lineWidth)).mul(-1).equal())}`,
"&::after": {
position: "absolute",
top: 0,
insetInlineEnd: lineWidth,
bottom: 0,
borderInlineEnd: tableBorder,
content: "\"\""
}
} }
} }
},
[`&${componentCls}-scroll-horizontal`]: { [`> ${componentCls}-container > ${componentCls}-body`]: { "> table > tbody": { [`
> tr${componentCls}-expanded-row,
> tr${componentCls}-placeholder
`]: { "> th, > td": { borderInlineEnd: 0 } } } } },
...getSizeBorderStyle("medium", token.tablePaddingVerticalMiddle, token.tablePaddingHorizontalMiddle),
...getSizeBorderStyle("small", token.tablePaddingVerticalSmall, token.tablePaddingHorizontalSmall),
[`> ${componentCls}-footer`]: {
border: tableBorder,
borderTop: 0
}
},
[`${componentCls}-cell`]: {
[`
> ${componentCls}-wrapper:only-child,
> ${componentCls}-expanded-row-fixed > ${componentCls}-wrapper:only-child
`]: { [varName("nested-border-top")]: 0 },
"&-scrollbar:not([rowspan])": { boxShadow: `0 ${unit$1(lineWidth)} 0 ${unit$1(lineWidth)} ${tableHeaderBg}` }
},
[`${componentCls}-bordered ${componentCls}-cell-scrollbar`]: { borderInlineEnd: tableBorder }
} };
};
var genEllipsisStyle = (token) => {
const { componentCls } = token;
return { [`${componentCls}-wrapper`]: { [`${componentCls}-cell-ellipsis`]: {
...textEllipsis,
wordBreak: "keep-all",
[`
&${componentCls}-cell-fix-start-shadow,
&${componentCls}-cell-fix-end-shadow
`]: {
overflow: "visible",
[`${componentCls}-cell-content`]: {
...textEllipsis,
display: "block"
}
},
[`${componentCls}-column-title`]: {
...textEllipsis,
wordBreak: "keep-all"
}
} } };
};
var genEmptyStyle = (token) => {
const { componentCls } = token;
return { [`${componentCls}-wrapper`]: { [`${componentCls}-tbody > tr${componentCls}-placeholder`]: {
textAlign: "center",
color: token.colorTextDisabled,
"&:hover > th, &:hover > td": { background: token.colorBgContainer }
} } };
};
var genExpandStyle = (token) => {
const { componentCls, antCls, motionDurationSlow, lineWidth, paddingXS, lineType, tableBorderColor, tableExpandIconBg, tableExpandColumnWidth, borderRadius, tablePaddingVertical, tablePaddingHorizontal, tableExpandedRowBg, paddingXXS, expandIconMarginTop, expandIconSize, expandIconHalfInner, expandIconScale, calc } = token;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
const expandIconLineOffset = calc(paddingXXS).sub(lineWidth).equal();
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-expand-icon-col`]: { width: tableExpandColumnWidth },
[`${componentCls}-row-expand-icon-cell`]: {
textAlign: "center",
[`${componentCls}-row-expand-icon`]: {
display: "inline-flex",
float: "none",
verticalAlign: "sub"
}
},
[`${componentCls}-row-indent`]: {
height: 1,
float: "left"
},
[`${componentCls}-row-expand-icon`]: {
...operationUnit(token),
position: "relative",
float: "left",
width: expandIconSize,
height: expandIconSize,
color: "inherit",
lineHeight: unit$1(expandIconSize),
background: tableExpandIconBg,
border: tableBorder,
borderRadius,
transform: `scale(${expandIconScale})`,
"&:focus, &:hover, &:active": { borderColor: "currentcolor" },
"&::before, &::after": {
position: "absolute",
background: "currentcolor",
transition: `transform ${motionDurationSlow} ease-out`,
content: "\"\""
},
"&::before": {
top: expandIconHalfInner,
insetInlineEnd: expandIconLineOffset,
insetInlineStart: expandIconLineOffset,
height: lineWidth
},
"&::after": {
top: expandIconLineOffset,
bottom: expandIconLineOffset,
insetInlineStart: expandIconHalfInner,
width: lineWidth,
transform: "rotate(90deg)"
},
"&-collapsed::before": { transform: "rotate(-180deg)" },
"&-collapsed::after": { transform: "rotate(0deg)" },
"&-spaced": {
"&::before, &::after": {
display: "none",
content: "none"
},
background: "transparent",
border: 0,
visibility: "hidden"
}
},
[`${componentCls}-row-indent + ${componentCls}-row-expand-icon`]: {
marginTop: expandIconMarginTop,
marginInlineEnd: paddingXS
},
[`tr${componentCls}-expanded-row`]: {
"&, &:hover": { "> th, > td": { background: tableExpandedRowBg } },
[`${antCls}-descriptions-view`]: {
display: "flex",
table: {
flex: "auto",
width: "100%"
}
}
},
[`${componentCls}-expanded-row-fixed`]: {
position: "relative",
margin: `${unit$1(calc(tablePaddingVertical).mul(-1).equal())} ${unit$1(calc(tablePaddingHorizontal).mul(-1).equal())}`,
padding: `${unit$1(tablePaddingVertical)} ${unit$1(tablePaddingHorizontal)}`
}
} };
};
var genFilterStyle = (token) => {
const { componentCls, antCls, iconCls, tableFilterDropdownWidth, tableFilterDropdownSearchWidth, paddingXXS, paddingXS, colorText, lineWidth, lineType, tableBorderColor, headerIconColor, fontSizeSM, tablePaddingHorizontal, borderRadius, motionDurationSlow, colorIcon, colorPrimary, tableHeaderFilterActiveBg, colorTextDisabled, tableFilterDropdownBg, tableFilterDropdownHeight, controlItemBgHover, controlItemBgActive, boxShadowSecondary, filterDropdownMenuBg, calc } = token;
const dropdownPrefixCls = `${antCls}-dropdown`;
const tableFilterDropdownPrefixCls = `${componentCls}-filter-dropdown`;
const treePrefixCls = `${antCls}-tree`;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
return [
{ [`${componentCls}-wrapper`]: {
[`${componentCls}-filter-column`]: {
display: "flex",
justifyContent: "space-between"
},
[`${componentCls}-filter-trigger`]: {
position: "relative",
display: "flex",
alignItems: "center",
marginBlock: calc(paddingXXS).mul(-1).equal(),
marginInline: `${unit$1(paddingXXS)} ${unit$1(calc(tablePaddingHorizontal).div(2).mul(-1).equal())}`,
padding: `0 ${unit$1(paddingXXS)}`,
color: headerIconColor,
fontSize: fontSizeSM,
borderRadius,
cursor: "pointer",
transition: `all ${motionDurationSlow}`,
"&:hover": {
color: colorIcon,
background: tableHeaderFilterActiveBg
},
"&.active": { color: colorPrimary }
}
} },
{ [`${antCls}-dropdown`]: { [tableFilterDropdownPrefixCls]: {
...resetComponent(token),
minWidth: tableFilterDropdownWidth,
backgroundColor: tableFilterDropdownBg,
borderRadius,
boxShadow: boxShadowSecondary,
overflow: "hidden",
[`${dropdownPrefixCls}-menu`]: {
maxHeight: tableFilterDropdownHeight,
overflowX: "hidden",
border: 0,
boxShadow: "none",
borderRadius: "unset",
backgroundColor: filterDropdownMenuBg,
"&:empty::after": {
display: "block",
padding: `${unit$1(paddingXS)} 0`,
color: colorTextDisabled,
fontSize: fontSizeSM,
textAlign: "center",
content: "\"Not Found\""
}
},
[`${tableFilterDropdownPrefixCls}-tree`]: {
paddingBlock: `${unit$1(paddingXS)} 0`,
paddingInline: paddingXS,
[treePrefixCls]: { padding: 0 },
[`${treePrefixCls}-treenode ${treePrefixCls}-node-content-wrapper:hover`]: { backgroundColor: controlItemBgHover },
[`${treePrefixCls}-treenode-checkbox-checked ${treePrefixCls}-node-content-wrapper`]: { "&, &:hover": { backgroundColor: controlItemBgActive } }
},
[`${tableFilterDropdownPrefixCls}-search`]: {
padding: paddingXS,
borderBottom: tableBorder,
"&-input": {
input: { minWidth: tableFilterDropdownSearchWidth },
[iconCls]: { color: colorTextDisabled }
}
},
[`${tableFilterDropdownPrefixCls}-checkall`]: {
width: "100%",
marginBottom: paddingXXS,
marginInlineStart: paddingXXS
},
[`${tableFilterDropdownPrefixCls}-btns`]: {
display: "flex",
justifyContent: "space-between",
padding: `${unit$1(calc(paddingXS).sub(lineWidth).equal())} ${unit$1(paddingXS)}`,
overflow: "hidden",
borderTop: tableBorder
}
} } },
{ [`${antCls}-dropdown ${tableFilterDropdownPrefixCls}, ${tableFilterDropdownPrefixCls}-submenu`]: {
[`${antCls}-checkbox-wrapper + span`]: {
paddingInlineStart: paddingXS,
color: colorText
},
"> ul": {
maxHeight: "calc(100vh - 130px)",
overflowX: "hidden",
overflowY: "auto"
}
} }
];
};
function getShadowStyle({ colorSplit: shadowColor }) {
return [{ boxShadow: `inset 10px 0 8px -8px ${shadowColor}` }, { boxShadow: `inset -10px 0 8px -8px ${shadowColor}` }];
}
var genFixedStyle = (token) => {
const { componentCls, lineWidth, motionDurationSlow, zIndexTableFixed, tableBg, calc } = token;
const cellCls = `${componentCls}-cell`;
const fixCellCls = `${cellCls}-fix`;
const sharedShadowStyle = {
position: "absolute",
top: 0,
bottom: calc(lineWidth).mul(-1).equal(),
width: 30,
transition: `box-shadow ${motionDurationSlow}`,
content: "\"\"",
pointerEvents: "none"
};
const [leftShadowStyle, rightShadowStyle] = getShadowStyle(token);
return { [`${componentCls}-wrapper`]: {
[`${cellCls}${fixCellCls}`]: { position: "sticky" },
[fixCellCls]: {
zIndex: `calc(var(--z-offset-reverse) + ${zIndexTableFixed})`,
background: tableBg,
"&:after": sharedShadowStyle,
"&-start:after": { insetInlineStart: "100%" },
"&-end:after": { insetInlineEnd: "100%" },
"&-start-shadow-show:after": leftShadowStyle,
"&-end-shadow-show:after": rightShadowStyle
},
[`${componentCls}-container`]: {
position: "relative",
"&:before, &:after": {
...sharedShadowStyle,
zIndex: `calc(var(--columns-count) * 2 + ${zIndexTableFixed} + 1)`
},
"&:before": { insetInlineStart: 0 },
"&:after": { insetInlineEnd: 0 }
},
[`${componentCls}-has-fix-start ${componentCls}-container:before`]: { display: "none" },
[`${componentCls}-has-fix-end ${componentCls}-container:after`]: { display: "none" },
[`${componentCls}-fix-start-shadow-show ${componentCls}-container:before`]: leftShadowStyle,
[`${componentCls}-fix-end-shadow-show ${componentCls}-container:after`]: rightShadowStyle
} };
};
var genPaginationStyle = (token) => {
const { componentCls, antCls, margin } = token;
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-pagination${antCls}-pagination`]: { margin: `${unit$1(margin)} 0` },
[`${componentCls}-pagination`]: {
display: "flex",
flexWrap: "wrap",
rowGap: token.paddingXS,
"> *": { flex: "none" },
"&-start": { justifyContent: "flex-start" },
"&-center": { justifyContent: "center" },
"&-end": { justifyContent: "flex-end" }
}
} };
};
var genRadiusStyle = (token) => {
const { componentCls, tableRadius } = token;
return { [`${componentCls}-wrapper`]: { [componentCls]: {
[`${componentCls}-title, ${componentCls}-header`]: { borderRadius: `${unit$1(tableRadius)} ${unit$1(tableRadius)} 0 0` },
[`${componentCls}-title + ${componentCls}-container`]: {
borderStartStartRadius: 0,
borderStartEndRadius: 0,
[`${componentCls}-header, table`]: { borderRadius: 0 },
"table > thead > tr:first-child": { "th:first-child, th:last-child, td:first-child, td:last-child": { borderRadius: 0 } }
},
[`&${componentCls}-bordered${componentCls}-no-header`]: { [`> ${componentCls}-container`]: { [`> ${componentCls}-content, > ${componentCls}-body`]: { "> table > tbody > tr:first-child": {
"> *:first-child": { borderStartStartRadius: tableRadius },
"> *:last-child": { borderStartEndRadius: tableRadius }
} } } },
"&-container": {
borderStartStartRadius: tableRadius,
borderStartEndRadius: tableRadius,
"&::before": { borderStartStartRadius: tableRadius },
"&::after": { borderStartEndRadius: tableRadius },
[`> ${componentCls}-content`]: {
borderStartStartRadius: tableRadius,
borderStartEndRadius: tableRadius
},
"table > thead > tr:first-child": {
"> *:first-child": { borderStartStartRadius: tableRadius },
"> *:last-child": { borderStartEndRadius: tableRadius }
}
},
"&-footer": { borderRadius: `0 0 ${unit$1(tableRadius)} ${unit$1(tableRadius)}` }
} } };
};
var genStyle = (token) => {
const { componentCls } = token;
const [leftShadowStyle, rightShadowStyle] = getShadowStyle(token);
return { [`${componentCls}-wrapper-rtl`]: {
direction: "rtl",
table: { direction: "rtl" },
[`${componentCls}-row-expand-icon`]: {
float: "right",
"&::after": { transform: "rotate(-90deg)" },
"&-collapsed::before": { transform: "rotate(180deg)" },
"&-collapsed::after": { transform: "rotate(0deg)" }
},
[`${componentCls}-cell-fix`]: {
"&-start-shadow-show:after": rightShadowStyle,
"&-end-shadow-show:after": leftShadowStyle
},
[`${componentCls}-container`]: { [`${componentCls}-row-indent`]: { float: "right" } },
[`${componentCls}-fix-start-shadow-show ${componentCls}-container:before`]: rightShadowStyle,
[`${componentCls}-fix-end-shadow-show ${componentCls}-container:after`]: leftShadowStyle
} };
};
var genSelectionStyle = (token) => {
const { componentCls, antCls, iconCls, fontSizeIcon, padding, paddingXS, headerIconColor, headerIconHoverColor, tableSelectionColumnWidth, tableSelectedRowBg, tableSelectedRowHoverBg, tableRowHoverBg, tablePaddingHorizontal, calc } = token;
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-selection-col`]: {
width: tableSelectionColumnWidth,
[`&${componentCls}-selection-col-with-dropdown`]: { width: calc(tableSelectionColumnWidth).add(fontSizeIcon).add(calc(padding).div(4)).equal() }
},
[`${componentCls}-bordered ${componentCls}-selection-col`]: {
width: calc(tableSelectionColumnWidth).add(calc(paddingXS).mul(2)).equal(),
[`&${componentCls}-selection-col-with-dropdown`]: { width: calc(tableSelectionColumnWidth).add(fontSizeIcon).add(calc(padding).div(4)).add(calc(paddingXS).mul(2)).equal() }
},
[`
table tr th${componentCls}-selection-column,
table tr td${componentCls}-selection-column,
${componentCls}-selection-column
`]: {
paddingInlineEnd: token.paddingXS,
paddingInlineStart: token.paddingXS,
textAlign: "center",
[`${antCls}-radio-wrapper`]: { marginInlineEnd: 0 }
},
[`table tr th${componentCls}-selection-column${componentCls}-cell-fix-left`]: { zIndex: calc(token.zIndexTableFixed).add(1).equal({ unit: false }) },
[`table tr th${componentCls}-selection-column::after`]: { backgroundColor: "transparent !important" },
[`${componentCls}-selection`]: {
position: "relative",
display: "inline-flex",
flexDirection: "column"
},
[`${componentCls}-selection-extra`]: {
position: "absolute",
top: 0,
zIndex: 1,
cursor: "pointer",
transition: `all ${token.motionDurationSlow}`,
marginInlineStart: "100%",
paddingInlineStart: unit$1(calc(tablePaddingHorizontal).div(4).equal()),
[iconCls]: {
color: headerIconColor,
fontSize: fontSizeIcon,
verticalAlign: "baseline",
"&:hover": { color: headerIconHoverColor }
}
},
[`${componentCls}-tbody`]: { [`${componentCls}-row`]: {
[`&${componentCls}-row-selected`]: { [`> ${componentCls}-cell`]: {
background: tableSelectedRowBg,
"&-row-hover": { background: tableSelectedRowHoverBg }
} },
[`> ${componentCls}-cell-row-hover`]: { background: tableRowHoverBg }
} }
} };
};
var genSizeStyle = (token) => {
const { componentCls, tableExpandColumnWidth, calc } = token;
const getSizeStyle = (size, paddingVertical, paddingHorizontal, fontSize) => ({ [`${componentCls}${componentCls}-${size}`]: {
fontSize,
[`
${componentCls}-title,
${componentCls}-footer,
${componentCls}-cell,
${componentCls}-thead > tr > th,
${componentCls}-tbody > tr > th,
${componentCls}-tbody > tr > td,
tfoot > tr > th,
tfoot > tr > td
`]: { padding: `${unit$1(paddingVertical)} ${unit$1(paddingHorizontal)}` },
[`${componentCls}-filter-trigger`]: { marginInlineEnd: unit$1(calc(paddingHorizontal).div(2).mul(-1).equal()) },
[`${componentCls}-expanded-row-fixed`]: { margin: `${unit$1(calc(paddingVertical).mul(-1).equal())} ${unit$1(calc(paddingHorizontal).mul(-1).equal())}` },
[`${componentCls}-tbody`]: { [`${componentCls}-wrapper:only-child ${componentCls}`]: {
marginBlock: unit$1(calc(paddingVertical).mul(-1).equal()),
marginInline: `${unit$1(calc(tableExpandColumnWidth).sub(paddingHorizontal).equal())} ${unit$1(calc(paddingHorizontal).mul(-1).equal())}`
} },
[`${componentCls}-selection-extra`]: { paddingInlineStart: unit$1(calc(paddingHorizontal).div(4).equal()) }
} });
return { [`${componentCls}-wrapper`]: {
...getSizeStyle("medium", token.tablePaddingVerticalMiddle, token.tablePaddingHorizontalMiddle, token.tableFontSizeMiddle),
...getSizeStyle("small", token.tablePaddingVerticalSmall, token.tablePaddingHorizontalSmall, token.tableFontSizeSmall)
} };
};
var genSorterStyle = (token) => {
const { componentCls, marginXXS, fontSizeIcon, headerIconColor, headerIconHoverColor } = token;
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-thead th${componentCls}-column-has-sorters`]: {
outline: "none",
cursor: "pointer",
transition: `all ${token.motionDurationSlow}, left 0s`,
"&:hover": {
background: token.tableHeaderSortHoverBg,
"&::before": { backgroundColor: "transparent !important" }
},
"&:focus-visible": { color: token.colorPrimary },
[`
&${componentCls}-cell-fix-left:hover,
&${componentCls}-cell-fix-right:hover
`]: { background: token.tableFixedHeaderSortActiveBg }
},
[`${componentCls}-thead th${componentCls}-column-sort`]: {
background: token.tableHeaderSortBg,
"&::before": { backgroundColor: "transparent !important" }
},
[`td${componentCls}-column-sort`]: { background: token.tableBodySortBg },
[`${componentCls}-column-title`]: {
position: "relative",
zIndex: 1,
flex: 1,
minWidth: 0
},
[`${componentCls}-column-sorters`]: {
display: "flex",
flex: "auto",
alignItems: "center",
justifyContent: "space-between",
"&::after": {
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
content: "\"\""
}
},
[`${componentCls}-column-sorters-tooltip-target-sorter`]: { "&::after": { content: "none" } },
[`${componentCls}-column-sorter`]: {
marginInlineStart: marginXXS,
color: headerIconColor,
fontSize: 0,
transition: `color ${token.motionDurationSlow}`,
"&-inner": {
display: "inline-flex",
flexDirection: "column",
alignItems: "center"
},
"&-up, &-down": {
fontSize: fontSizeIcon,
"&.active": { color: token.colorPrimary }
},
[`${componentCls}-column-sorter-up + ${componentCls}-column-sorter-down`]: { marginTop: "-0.3em" }
},
[`${componentCls}-column-sorters:hover ${componentCls}-column-sorter`]: { color: headerIconHoverColor }
} };
};
var genStickyStyle = (token) => {
const { componentCls, opacityLoading, tableScrollThumbBg, tableScrollThumbBgHover, tableScrollThumbSize, tableScrollBg, stickyScrollBarBorderRadius, lineWidth, lineType, tableBorderColor, zIndexTableFixed } = token;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
return { [`${componentCls}-wrapper`]: { [`${componentCls}-sticky`]: {
"&-holder": {
position: "sticky",
zIndex: `calc(var(--columns-count) * 2 + ${zIndexTableFixed} + 1)`,
background: token.colorBgContainer
},
"&-scroll": {
position: "sticky",
bottom: 0,
height: `${unit$1(tableScrollThumbSize)} !important`,
zIndex: `calc(var(--columns-count) * 2 + ${zIndexTableFixed} + 1)`,
display: "flex",
alignItems: "center",
background: tableScrollBg,
borderTop: tableBorder,
opacity: opacityLoading,
"&:hover": { transformOrigin: "center bottom" },
"&-bar": {
height: tableScrollThumbSize,
backgroundColor: tableScrollThumbBg,
borderRadius: stickyScrollBarBorderRadius,
transition: `all ${token.motionDurationSlow}, transform 0s`,
position: "absolute",
bottom: 0,
"&:hover, &-active": { backgroundColor: tableScrollThumbBgHover }
}
}
} } };
};
var genSummaryStyle = (token) => {
const { componentCls, lineWidth, tableBorderColor, calc } = token;
const tableBorder = `${unit$1(lineWidth)} ${token.lineType} ${tableBorderColor}`;
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-summary`]: {
position: "relative",
zIndex: token.zIndexTableFixed,
background: token.tableBg,
"> tr": { "> th, > td": { borderBottom: tableBorder } }
},
[`div${componentCls}-summary`]: { boxShadow: `0 ${unit$1(calc(lineWidth).mul(-1).equal())} 0 ${tableBorderColor}` }
} };
};
var genVirtualStyle = (token) => {
const { componentCls, motionDurationMid, lineWidth, lineType, tableBorderColor, calc } = token;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
const rowCellCls = `${componentCls}-expanded-row-cell`;
return { [`${componentCls}-wrapper`]: {
[`${componentCls}-tbody-virtual`]: {
[`${componentCls}-tbody-virtual-scrollbar`]: {
cursor: "pointer",
"&:hover": { backgroundColor: token.colorFillQuaternary }
},
[`${componentCls}-tbody-virtual-holder-inner`]: { [`
& > ${componentCls}-row,
& > div:not(${componentCls}-row) > ${componentCls}-row
`]: {
display: "flex",
boxSizing: "border-box",
width: "100%",
[`> ${componentCls}-cell`]: { alignContent: "center" }
} },
[`${componentCls}-cell`]: {
borderBottom: tableBorder,
transition: `background-color ${motionDurationMid}`
},
[`${componentCls}-expanded-row`]: { [`${rowCellCls}${rowCellCls}-fixed`]: {
position: "sticky",
insetInlineStart: 0,
overflow: "hidden",
width: `calc(var(--virtual-width) - ${unit$1(lineWidth)})`,
borderInlineEnd: "none"
} }
},
[`${componentCls}-bordered`]: {
[`${componentCls}-tbody-virtual`]: {
"&:after": {
content: "\"\"",
insetInline: 0,
bottom: 0,
borderBottom: tableBorder,
position: "absolute"
},
[`${componentCls}-cell`]: {
borderInlineEnd: tableBorder,
[`&${componentCls}-cell-fix-right-first:before`]: {
content: "\"\"",
position: "absolute",
insetBlock: 0,
insetInlineStart: calc(lineWidth).mul(-1).equal(),
borderInlineStart: tableBorder
}
}
},
[`&${componentCls}-virtual`]: { [`${componentCls}-placeholder ${componentCls}-cell`]: {
borderInlineEnd: tableBorder,
borderBottom: tableBorder
} }
}
} };
};
var genTableStyle = (token) => {
const { componentCls, fontWeightStrong, tablePaddingVertical, tablePaddingHorizontal, tableExpandColumnWidth, lineWidth, lineType, tableBorderColor, tableFontSize, tableBg, tableRadius, tableHeaderTextColor, motionDurationMid, tableHeaderBg, tableHeaderCellSplitColor, tableFooterTextColor, tableFooterBg, calc } = token;
const tableBorder = `${unit$1(lineWidth)} ${lineType} ${tableBorderColor}`;
return { [`${componentCls}-wrapper`]: {
clear: "both",
maxWidth: "100%",
["--rc-virtual-list-scrollbar-bg"]: token.tableScrollBg,
...clearFix(),
[componentCls]: {
...resetComponent(token),
fontSize: tableFontSize,
background: tableBg,
borderRadius: `${unit$1(tableRadius)} ${unit$1(tableRadius)} 0 0`,
scrollbarColor: `${token.tableScrollThumbBg} ${token.tableScrollBg}`
},
table: {
width: "100%",
textAlign: "start",
borderRadius: `${unit$1(tableRadius)} ${unit$1(tableRadius)} 0 0`,
borderCollapse: "separate",
borderSpacing: 0
},
[`
${componentCls}-cell,
${componentCls}-thead > tr > th,
${componentCls}-tbody > tr > th,
${componentCls}-tbody > tr > td,
tfoot > tr > th,
tfoot > tr > td
`]: {
position: "relative",
padding: `${unit$1(tablePaddingVertical)} ${unit$1(tablePaddingHorizontal)}`,
overflowWrap: "break-word"
},
[`${componentCls}-title`]: { padding: `${unit$1(tablePaddingVertical)} ${unit$1(tablePaddingHorizontal)}` },
[`${componentCls}-thead`]: {
"> tr > th, > tr > td": {
position: "relative",
color: tableHeaderTextColor,
fontWeight: fontWeightStrong,
textAlign: "start",
background: tableHeaderBg,
borderBottom: tableBorder,
transition: `background-color ${motionDurationMid} ease`,
"&[colspan]:not([colspan='1'])": { textAlign: "center" },
[`&:not(:last-child):not(${componentCls}-selection-column):not(${componentCls}-row-expand-icon-cell):not([colspan])::before`]: {
position: "absolute",
top: "50%",
insetInlineEnd: 0,
width: 1,
height: "1.6em",
backgroundColor: tableHeaderCellSplitColor,
transform: "translateY(-50%)",
transition: `background-color ${motionDurationMid}`,
content: "\"\""
}
},
"> tr:not(:last-child) > th[colspan]": { borderBottom: 0 }
},
[`${componentCls}-tbody`]: { "> tr": {
"> th, > td": {
borderBottom: tableBorder,
transition: [`background-color`, `border-color`].map((prop) => `${prop} ${motionDurationMid}`).join(", "),
[`
> ${componentCls}-wrapper:only-child,
> ${componentCls}-expanded-row-fixed > ${componentCls}-wrapper:only-child
`]: { [componentCls]: {
marginBlock: unit$1(calc(tablePaddingVertical).mul(-1).equal()),
marginInline: `${unit$1(calc(tableExpandColumnWidth).sub(tablePaddingHorizontal).equal())}
${unit$1(calc(tablePaddingHorizontal).mul(-1).equal())}`,
[`${componentCls}-tbody > tr:last-child > td`]: {
borderBottomWidth: 0,
"&:first-child, &:last-child": { borderRadius: 0 }
}
} }
},
"> th": {
position: "relative",
color: tableHeaderTextColor,
fontWeight: fontWeightStrong,
textAlign: "start",
background: tableHeaderBg,
borderBottom: tableBorder,
transition: `background-color ${motionDurationMid} ease`
},
[`& > ${componentCls}-measure-cell`]: {
paddingBlock: `0 !important`,
borderBlock: `0 !important`,
[`${componentCls}-measure-cell-content`]: {
height: 0,
overflow: "hidden",
pointerEvents: "none"
}
}
} },
[`${componentCls}-footer`]: {
padding: `${unit$1(tablePaddingVertical)} ${unit$1(tablePaddingHorizontal)}`,
color: tableFooterTextColor,
background: tableFooterBg
}
} };
};
var prepareComponentToken = (token) => {
const { colorFillAlter, colorBgContainer, colorTextHeading, colorFillSecondary, colorFillContent, controlItemBgActive, controlItemBgActiveHover, padding, paddingSM, paddingXS, colorBorderSecondary, borderRadiusLG, controlHeight, colorTextPlaceholder, fontSize, fontSizeSM, lineHeight, lineWidth, colorIcon, colorIconHover, opacityLoading, controlInteractiveSize } = token;
const colorFillSecondarySolid = new FastColor(colorFillSecondary).onBackground(colorBgContainer).toHexString();
const colorFillContentSolid = new FastColor(colorFillContent).onBackground(colorBgContainer).toHexString();
const colorFillAlterSolid = new FastColor(colorFillAlter).onBackground(colorBgContainer).toHexString();
const baseColorAction = new FastColor(colorIcon);
const baseColorActionHover = new FastColor(colorIconHover);
const expandIconHalfInner = controlInteractiveSize / 2 - lineWidth;
const expandIconSize = expandIconHalfInner * 2 + lineWidth * 3;
return {
headerBg: colorFillAlterSolid,
headerColor: colorTextHeading,
headerSortActiveBg: colorFillSecondarySolid,
headerSortHoverBg: colorFillContentSolid,
bodySortBg: colorFillAlterSolid,
rowHoverBg: colorFillAlterSolid,
rowSelectedBg: controlItemBgActive,
rowSelectedHoverBg: controlItemBgActiveHover,
rowExpandedBg: colorFillAlter,
cellPaddingBlock: padding,
cellPaddingInline: padding,
cellPaddingBlockMD: paddingSM,
cellPaddingInlineMD: paddingXS,
cellPaddingBlockSM: paddingXS,
cellPaddingInlineSM: paddingXS,
borderColor: colorBorderSecondary,
headerBorderRadius: borderRadiusLG,
footerBg: colorFillAlterSolid,
footerColor: colorTextHeading,
cellFontSize: fontSize,
cellFontSizeMD: fontSize,
cellFontSizeSM: fontSize,
headerSplitColor: colorBorderSecondary,
fixedHeaderSortActiveBg: colorFillSecondarySolid,
headerFilterHoverBg: colorFillContent,
filterDropdownMenuBg: colorBgContainer,
filterDropdownBg: colorBgContainer,
expandIconBg: colorBgContainer,
selectionColumnWidth: controlHeight,
stickyScrollBarBg: colorTextPlaceholder,
stickyScrollBarBorderRadius: 100,
expandIconMarginTop: (fontSize * lineHeight - lineWidth * 3) / 2 - Math.ceil((fontSizeSM * 1.4 - lineWidth * 3) / 2),
headerIconColor: baseColorAction.clone().setA(baseColorAction.a * opacityLoading).toRgbString(),
headerIconHoverColor: baseColorActionHover.clone().setA(baseColorActionHover.a * opacityLoading).toRgbString(),
expandIconHalfInner,
expandIconSize,
expandIconScale: controlInteractiveSize / expandIconSize
};
};
var zIndexTableFixed = 2;
var style_default = genStyleHooks("Table", (token) => {
const { colorTextHeading, colorSplit, colorBgContainer, controlInteractiveSize: checkboxSize, headerBg, headerColor, headerSortActiveBg, headerSortHoverBg, bodySortBg, rowHoverBg, rowSelectedBg, rowSelectedHoverBg, rowExpandedBg, cellPaddingBlock, cellPaddingInline, cellPaddingBlockMD, cellPaddingInlineMD, cellPaddingBlockSM, cellPaddingInlineSM, borderColor, footerBg, footerColor, headerBorderRadius, cellFontSize, cellFontSizeMD, cellFontSizeSM, headerSplitColor, fixedHeaderSortActiveBg, headerFilterHoverBg, filterDropdownBg, expandIconBg, selectionColumnWidth, stickyScrollBarBg, calc } = token;
const tableToken = merge(token, {
tableFontSize: cellFontSize,
tableBg: colorBgContainer,
tableRadius: headerBorderRadius,
tablePaddingVertical: cellPaddingBlock,
tablePaddingHorizontal: cellPaddingInline,
tablePaddingVerticalMiddle: cellPaddingBlockMD,
tablePaddingHorizontalMiddle: cellPaddingInlineMD,
tablePaddingVerticalSmall: cellPaddingBlockSM,
tablePaddingHorizontalSmall: cellPaddingInlineSM,
tableBorderColor: borderColor,
tableHeaderTextColor: headerColor,
tableHeaderBg: headerBg,
tableFooterTextColor: footerColor,
tableFooterBg: footerBg,
tableHeaderCellSplitColor: headerSplitColor,
tableHeaderSortBg: headerSortActiveBg,
tableHeaderSortHoverBg: headerSortHoverBg,
tableBodySortBg: bodySortBg,
tableFixedHeaderSortActiveBg: fixedHeaderSortActiveBg,
tableHeaderFilterActiveBg: headerFilterHoverBg,
tableFilterDropdownBg: filterDropdownBg,
tableRowHoverBg: rowHoverBg,
tableSelectedRowBg: rowSelectedBg,
tableSelectedRowHoverBg: rowSelectedHoverBg,
zIndexTableFixed,
tableFontSizeMiddle: cellFontSizeMD,
tableFontSizeSmall: cellFontSizeSM,
tableSelectionColumnWidth: selectionColumnWidth,
tableExpandIconBg: expandIconBg,
tableExpandColumnWidth: calc(checkboxSize).add(calc(token.padding).mul(2)).equal(),
tableExpandedRowBg: rowExpandedBg,
tableFilterDropdownWidth: 120,
tableFilterDropdownHeight: 264,
tableFilterDropdownSearchWidth: 140,
tableScrollThumbSize: 8,
tableScrollThumbBg: stickyScrollBarBg,
tableScrollThumbBgHover: colorTextHeading,
tableScrollBg: colorSplit
});
return [
genTableStyle(tableToken),
genPaginationStyle(tableToken),
genSummaryStyle(tableToken),
genSorterStyle(tableToken),
genFilterStyle(tableToken),
genBorderedStyle(tableToken),
genRadiusStyle(tableToken),
genExpandStyle(tableToken),
genSummaryStyle(tableToken),
genEmptyStyle(tableToken),
genSelectionStyle(tableToken),
genFixedStyle(tableToken),
genStickyStyle(tableToken),
genEllipsisStyle(tableToken),
genSizeStyle(tableToken),
genStyle(tableToken),
genVirtualStyle(tableToken)
];
}, prepareComponentToken, {
resetFont: false,
unitless: { expandIconScale: true }
});
var EMPTY_LIST = [];
var HeaderTableContext = /*#__PURE__*/ import_react.createContext({});
var HeaderTable = (props) => {
const { ariaProps, component = "table" } = import_react.useContext(HeaderTableContext);
return /*#__PURE__*/ import_react.createElement(component, {
...ariaProps,
...props
});
};
var InternalTable = (props, ref) => {
const { prefixCls: customizePrefixCls, className, rootClassName, style, classNames, styles, size: customizeSize, bordered, dropdownPrefixCls: customizeDropdownPrefixCls, dataSource, pagination, rowSelection: customizeRowSelection, rowKey: customizeRowKey, rowClassName, column, columns, children, childrenColumnName: legacyChildrenColumnName, onChange, getPopupContainer, loading, expandIcon, expandable, expandedRowRender, expandIconColumnIndex, indentSize, scroll, sortDirections, locale, showSorterTooltip = { target: "full-header" }, virtual, title, showHeader } = props;
devUseWarning();
const baseColumns = useFilledColumns(import_react.useMemo(() => columns || convertChildrenToColumns(children), [columns, children]), column);
const needResponsive = import_react.useMemo(() => baseColumns.some((col) => col.responsive), [baseColumns]);
const screens = useBreakpoint(needResponsive);
const mergedColumns = import_react.useMemo(() => {
const matched = new Set(Object.keys(screens).filter((m) => screens[m]));
return baseColumns.filter((c) => !c.responsive || c.responsive.some((r) => matched.has(r)));
}, [baseColumns, screens]);
const tableProps = omit(props, [
"className",
"style",
"column",
"columns"
]);
const components = tableProps.components;
const ariaProps = pickAttrs(tableProps, { aria: true });
const hasAriaProps = Object.keys(ariaProps).length > 0;
const headerTableContext = import_react.useMemo(() => ({
ariaProps,
component: components?.header?.table
}), [ariaProps, components?.header?.table]);
const mergedComponents = import_react.useMemo(() => {
if (!hasAriaProps) return components;
return {
...components,
header: {
...components?.header,
table: HeaderTable
}
};
}, [components, hasAriaProps]);
const { locale: contextLocale = localeValues, table } = import_react.useContext(ConfigContext);
const { getPrefixCls, direction, renderEmpty, getPopupContainer: getContextPopupContainer, className: contextClassName, style: contextStyle, classNames: contextClassNames, styles: contextStyles } = useComponentConfig("table");
const mergedSize = useSize((ctx) => customizeSize === "middle" ? "medium" : customizeSize ?? ctx);
const mergedProps = {
...props,
size: mergedSize,
bordered
};
const contextStyleRoot = useSemanticRootStyle(contextStyle);
const styleRoot = useSemanticRootStyle(style);
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [
contextStyles,
contextStyleRoot,
styles,
styleRoot
], { props: mergedProps }, {
pagination: { _default: "root" },
header: { _default: "wrapper" },
body: { _default: "wrapper" }
});
const tableLocale = {
...contextLocale.Table,
...locale
};
const [globalLocale] = useLocale("global", localeValues.global);
const rawData = dataSource || EMPTY_LIST;
const prefixCls = getPrefixCls("table", customizePrefixCls);
const dropdownPrefixCls = getPrefixCls("dropdown", customizeDropdownPrefixCls);
const [, token] = useToken();
const mergedRowSelection = import_react.useMemo(() => {
return isPlainObject(customizeRowSelection) ? {
columnWidth: token.Table?.selectionColumnWidth,
...customizeRowSelection
} : customizeRowSelection;
}, [customizeRowSelection, token.Table?.selectionColumnWidth]);
const rootCls = useCSSVarCls(prefixCls);
const [hashId, cssVarCls] = style_default(prefixCls, rootCls);
const mergedExpandable = {
childrenColumnName: legacyChildrenColumnName,
expandIconColumnIndex,
...expandable,
expandIcon: expandable?.expandIcon ?? table?.expandable?.expandIcon
};
const { childrenColumnName = "children" } = mergedExpandable;
const expandType = import_react.useMemo(() => {
if (rawData.some((item) => item?.[childrenColumnName])) return "nest";
if (expandedRowRender || expandable?.expandedRowRender) return "row";
return null;
}, [childrenColumnName, rawData]);
const internalRef = { body: import_react.useRef(null) };
const getContainerWidth = useContainerWidth(prefixCls);
const rootRef = import_react.useRef(null);
const tblRef = import_react.useRef(null);
useProxyImperativeHandle(ref, () => ({
...tblRef.current,
nativeElement: rootRef.current
}));
const rowKey = customizeRowKey || table?.rowKey || "key";
const mergedScroll = scroll ?? table?.scroll;
const getRowKey = import_react.useMemo(() => {
if (isFunction(rowKey)) return rowKey;
return (record) => record?.[rowKey];
}, [rowKey]);
const [getRecordByKey] = useLazyKVMap(rawData, childrenColumnName, getRowKey);
const changeEventInfo = {};
const triggerOnChange = (info, action, reset = false) => {
const changeInfo = {
...changeEventInfo,
...info
};
if (reset) {
changeEventInfo.resetPagination?.();
if (changeInfo.pagination?.current) changeInfo.pagination.current = 1;
if (pagination) pagination.onChange?.(1, changeInfo.pagination?.pageSize);
}
if (scroll && scroll.scrollToFirstRowOnChange !== false && internalRef.body.current) scrollTo(0, { getContainer: () => internalRef.body.current });
onChange?.(changeInfo.pagination, changeInfo.filters, changeInfo.sorter, {
currentDataSource: getFilterData(getSortData(rawData, changeInfo.sorterStates, childrenColumnName), changeInfo.filterStates, childrenColumnName),
action
});
};
/**
* Controlled state in `columns` is not a good idea that makes too many code (1000+ line?) to read
* state out and then put it back to title render. Move these code into `hooks` but still too
* complex. We should provides Table props like `sorter` & `filter` to handle control in next big
* version.
*/
const onSorterChange = (sorter, sorterStates) => {
triggerOnChange({
sorter,
sorterStates
}, "sort", false);
};
const [transformSorterColumns, sortStates, sorterTitleProps, getSorters] = useFilterSorter({
prefixCls,
mergedColumns,
baseColumns,
onSorterChange,
sortDirections: sortDirections || ["ascend", "descend"],
tableLocale,
showSorterTooltip,
globalLocale
});
const sortedData = import_react.useMemo(() => getSortData(rawData, sortStates, childrenColumnName), [
childrenColumnName,
rawData,
sortStates
]);
changeEventInfo.sorter = getSorters();
changeEventInfo.sorterStates = sortStates;
const onFilterChange = (filters, filterStates) => {
triggerOnChange({
filters,
filterStates
}, "filter", true);
};
const [transformFilterColumns, filterStates, filters] = useFilter({
prefixCls,
locale: tableLocale,
dropdownPrefixCls,
mergedColumns,
baseColumns,
onFilterChange,
getPopupContainer: getPopupContainer || getContextPopupContainer,
rootClassName: clsx(rootClassName, rootCls)
});
const mergedData = getFilterData(sortedData, filterStates, childrenColumnName);
changeEventInfo.filters = filters;
changeEventInfo.filterStates = filterStates;
const [transformTitleColumns] = useTitleColumns(useColumnTitleProps(sorterTitleProps, filters));
const onPaginationChange = (current, pageSize) => {
triggerOnChange({ pagination: {
...changeEventInfo.pagination,
current,
pageSize
} }, "paginate");
};
const [mergedPagination, resetPagination] = usePagination(mergedData.length, onPaginationChange, pagination);
const paginationClassNames = (info) => mergeClassNames({}, mergedClassNames.pagination, resolveStyleOrClass(mergedPagination.classNames, info));
const paginationStyles = (info) => mergeStyles(resolveStyleOrClass(mergedPagination.styles, info), mergedStyles.pagination);
changeEventInfo.pagination = pagination === false ? {} : getPaginationParam(mergedPagination, pagination);
changeEventInfo.resetPagination = resetPagination;
const pageData = import_react.useMemo(() => {
if (pagination === false || !mergedPagination.pageSize) return mergedData;
const { current = 1, total, pageSize = 10 } = mergedPagination;
if (mergedData.length < total) {
if (mergedData.length > pageSize) return mergedData.slice((current - 1) * pageSize, current * pageSize);
return mergedData;
}
return mergedData.slice((current - 1) * pageSize, current * pageSize);
}, [
!!pagination,
mergedData,
mergedPagination?.current,
mergedPagination?.pageSize,
mergedPagination?.total
]);
const [transformSelectionColumns, selectedKeySet] = useSelection({
prefixCls,
data: mergedData,
pageData,
getRowKey,
getRecordByKey,
expandType,
childrenColumnName,
locale: tableLocale,
getPopupContainer: getPopupContainer || getContextPopupContainer
}, mergedRowSelection);
const internalRowClassName = (record, index, indent) => {
return clsx({ [`${prefixCls}-row-selected`]: selectedKeySet.has(getRowKey(record, index)) }, isFunction(rowClassName) ? rowClassName(record, index, indent) : rowClassName);
};
mergedExpandable.__PARENT_RENDER_ICON__ = mergedExpandable.expandIcon;
mergedExpandable.expandIcon = mergedExpandable.expandIcon || expandIcon || renderExpandIcon(tableLocale);
if (expandType === "nest" && mergedExpandable.expandIconColumnIndex === void 0) mergedExpandable.expandIconColumnIndex = mergedRowSelection ? 1 : 0;
else if (mergedExpandable.expandIconColumnIndex > 0 && mergedRowSelection) mergedExpandable.expandIconColumnIndex -= 1;
if (!isNumber(mergedExpandable.indentSize)) mergedExpandable.indentSize = isNumber(indentSize) ? indentSize : 15;
const transformColumns = import_react.useCallback((innerColumns) => transformTitleColumns(transformSelectionColumns(transformFilterColumns(transformSorterColumns(innerColumns)))), [
transformSorterColumns,
transformFilterColumns,
transformSelectionColumns
]);
let topPaginationNode;
let bottomPaginationNode;
if (pagination !== false && mergedPagination?.total) {
const paginationSize = getPaginationSize(mergedPagination.size, mergedSize);
const renderPagination = (placement = "end") => /*#__PURE__*/ import_react.createElement(pagination_default, {
...mergedPagination,
classNames: paginationClassNames,
styles: paginationStyles,
className: clsx(`${prefixCls}-pagination`, `${prefixCls}-pagination-${placement}`, mergedPagination.className),
size: paginationSize
});
const { placement, position } = mergedPagination;
const mergedPlacement = placement ?? position;
if (Array.isArray(mergedPlacement)) {
const [topPos, bottomPos] = ["top", "bottom"].map((dir) => mergedPlacement.find((p) => p.includes(dir)));
const isDisable = mergedPlacement.every((p) => `${p}` === "none");
if (!topPos && !bottomPos && !isDisable) bottomPaginationNode = renderPagination();
if (topPos) topPaginationNode = renderPagination(normalizePlacement(topPos));
if (bottomPos) bottomPaginationNode = renderPagination(normalizePlacement(bottomPos));
} else bottomPaginationNode = renderPagination();
}
const spinProps = useSpinProps(loading);
const wrappercls = clsx(cssVarCls, rootCls, `${prefixCls}-wrapper`, contextClassName, { [`${prefixCls}-wrapper-rtl`]: direction === "rtl" }, className, rootClassName, mergedClassNames.root, hashId);
const mergedEmptyNode = import_react.useMemo(() => {
if (spinProps?.spinning && rawData === EMPTY_LIST) return null;
if (typeof locale?.emptyText !== "undefined") return locale.emptyText;
return renderEmpty?.("Table") || /*#__PURE__*/ import_react.createElement(DefaultRenderEmpty, { componentName: "Table" });
}, [
spinProps?.spinning,
rawData,
locale?.emptyText,
renderEmpty
]);
const TableComponent = virtual ? RcVirtualTable : RcTable;
const virtualProps = {};
const listItemHeight = import_react.useMemo(() => {
const { fontSize, lineHeight, lineWidth, padding, paddingXS, paddingSM } = token;
const fontHeight = Math.floor(fontSize * lineHeight);
switch (mergedSize) {
case "medium": return paddingSM * 2 + fontHeight + lineWidth;
case "small": return paddingXS * 2 + fontHeight + lineWidth;
default: return padding * 2 + fontHeight + lineWidth;
}
}, [token, mergedSize]);
if (virtual) virtualProps.listItemHeight = listItemHeight;
return /*#__PURE__*/ import_react.createElement("div", {
ref: rootRef,
className: wrappercls,
style: mergedStyles.root
}, /*#__PURE__*/ import_react.createElement(Spin, {
spinning: false,
...spinProps
}, topPaginationNode, /*#__PURE__*/ import_react.createElement(HeaderTableContext.Provider, { value: headerTableContext }, /*#__PURE__*/ import_react.createElement(TableComponent, {
...virtualProps,
...tableProps,
components: mergedComponents,
scroll: mergedScroll,
classNames: mergedClassNames,
styles: mergedStyles,
ref: tblRef,
columns: mergedColumns,
direction,
expandable: mergedExpandable,
prefixCls,
className: clsx({
[`${prefixCls}-medium`]: mergedSize === "medium",
[`${prefixCls}-small`]: mergedSize === "small",
[`${prefixCls}-bordered`]: bordered,
[`${prefixCls}-empty`]: rawData.length === 0,
[`${prefixCls}-no-header`]: !title && showHeader === false
}, cssVarCls, rootCls, hashId),
data: pageData,
rowKey: getRowKey,
rowClassName: internalRowClassName,
emptyText: mergedEmptyNode,
internalHooks: INTERNAL_HOOKS,
internalRefs: internalRef,
transformColumns,
getContainerWidth,
measureRowRender: (measureRow) => /*#__PURE__*/ import_react.createElement(TableMeasureRowContext.Provider, { value: true }, /*#__PURE__*/ import_react.createElement(ConfigProvider, { getPopupContainer: (node) => node }, measureRow))
})), bottomPaginationNode));
};
var InternalTable_default = /*#__PURE__*/ import_react.forwardRef(InternalTable);
var Table = (props, ref) => {
const renderTimesRef = import_react.useRef(0);
renderTimesRef.current += 1;
return /*#__PURE__*/ import_react.createElement(InternalTable_default, {
...props,
ref,
_renderTimes: renderTimesRef.current
});
};
var ForwardTable = /*#__PURE__*/ import_react.forwardRef(Table);
ForwardTable.SELECTION_COLUMN = SELECTION_COLUMN;
ForwardTable.EXPAND_COLUMN = EXPAND_COLUMN;
ForwardTable.SELECTION_ALL = SELECTION_ALL;
ForwardTable.SELECTION_INVERT = SELECTION_INVERT;
ForwardTable.SELECTION_NONE = SELECTION_NONE;
ForwardTable.Column = Column;
ForwardTable.ColumnGroup = ColumnGroup;
ForwardTable.Summary = FooterComponents;
var table_default = exports("t",ForwardTable);
var sharedClassNames = exports("s",{
tabPane: "max-h-[max(362px,calc(100vh-119px))] min-h-362px overflow-y-auto pr-12px",
settingsGroup: "mb-10px",
settingsGroupTitle: "flex items-center text-2em",
settingsGroupSubTitle: "flex items-center text-1.25em",
settingsLine: "flex flex-row flex-wrap items-center gap-x-10px gap-y-1"
});
function SettingsGroup({ children, title, titleClassName, contentClassName, resetSettingPaths, ...rest }) {
const hasResetButton = !!resetSettingPaths?.length;
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsGroup,
"data-role": "settings-group",
...rest,
children: [/* @__PURE__ */ jsxs("div", {
"data-role": "settings-group-title",
className: r(sharedClassNames.settingsGroupTitle, hasResetButton && "justify-between", titleClassName),
children: [hasResetButton ? /* @__PURE__ */ jsx("div", {
className: "flex items-center",
children: title
}) : title, resetSettingPaths && /* @__PURE__ */ jsx(ResetPartialSettingsButton, { paths: resetSettingPaths })]
}), /* @__PURE__ */ jsx("div", {
"data-role": "settings-group-content",
className: r("flex flex-col gap-y-1", contentClassName),
children
})]
});
}
function resetPartialSettings(paths) {
const { pickedSettings } = pickSettings(initialSettings, paths);
updateSettings(pickedSettings);
}
function ResetPartialSettingsButton({ paths, className }) {
return /* @__PURE__ */ jsx(Popconfirm, {
title: "确定重置下面的设置项?",
onConfirm: () => resetPartialSettings(paths),
children: /* @__PURE__ */ jsxs(button_default, {
className: clsx("gap-x-4px", className),
children: [/* @__PURE__ */ jsx(ForwardRef$26, { className: "mt--1px size-12px" }), /* @__PURE__ */ jsx("span", { children: "重置" })]
})
});
}
/** Phase 1 锚点解析 (输入分流内建): id 直输反查名 / 文本解析 id */
async function resolveAnchor(raw, signal) {
const input = raw.trim();
const parsed = parseTagInput(input);
if (!parsed) return {
input,
isIdPath: false
};
return {
anchor: parsed.kind === "id" ? await fetchTagInfoById(parsed.id, signal) : await fetchTagInfoByName(parsed.name, signal),
isIdPath: parsed.kind === "id",
input
};
}
/**
* 共现别名跨页累加合并 (「继续挖共现」逐页并入用): 同词 (归一化) 计数相加, 新词追加;
* 首见写法保留 —— 与老版候选清单随深挖轮次增长的行为一致。
*/
function mergeTagRecCooccurAliases(prev, next) {
const map = new Map(prev.map((entry) => [normalizeTagName(entry.name), {
name: entry.name,
count: entry.count ?? 0
}]));
for (const alias of next) {
const key = normalizeTagName(alias.name);
const old = map.get(key);
map.set(key, old ? {
...old,
count: old.count + alias.count
} : {
name: alias.name,
count: alias.count
});
}
return [...map.values()];
}
/** 一次 Phase 2 结果集的四重产出之①②③原料 (纯函数, 可测)
* hitNorms: 交集组合词流的多键命中集 (任一成员命中即 exact); 缺省 = [inputNorm] */
function analyzeAllV2Videos(videos, inputNorm, hitNorms) {
const exact = [];
const related = [];
const hitSet = new Set(hitNorms?.length ? hitNorms : [inputNorm]);
const glyphSpellings = /* @__PURE__ */ new Set();
const aliasMap = /* @__PURE__ */ new Map();
const cliques = [];
for (const video of videos) {
const tags = parseAllV2TagField(video.tag);
const titleNorm = normalizeTagName(stripHighlightTags(video.title ?? ""));
const titleHit = [...hitSet].some((n) => n.length >= 2 && titleNorm.includes(n));
if (!tags.length) {
(titleHit ? exact : related).push(video);
continue;
}
cliques.push({
bvid: video.bvid,
tags
});
if (!(tags.some((t) => hitSet.has(normalizeTagName(t))) || titleHit)) {
related.push(video);
continue;
}
exact.push(video);
const seenNorm = /* @__PURE__ */ new Set();
for (const t of tags) {
const norm = normalizeTagName(t);
if (hitSet.has(norm)) glyphSpellings.add(t);
else if (!seenNorm.has(norm)) {
seenNorm.add(norm);
const entry = aliasMap.get(norm) ?? {
name: t,
norm,
count: 0
};
entry.count++;
aliasMap.set(norm, entry);
}
}
}
const cooccurAliases = [...aliasMap.values()].sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
return {
exact,
related,
glyphSpellings: [...glyphSpellings],
cooccurAliases,
cliques
};
}
var CandidateBadgeLabel = {
exact: "精确",
cooccur: "共现",
suggest: "联想"
};
/** 徽章语义强度 (合并去重时强徽章胜): 精确 > 共现 > 联想 */
var badgeRank = {
exact: 0,
cooccur: 1,
suggest: 2
};
function candidateRowKey(row) {
return row.tag_id !== void 0 ? `id:${row.tag_id}` : `n:${normalizeTagName(row.tag_name)}`;
}
/** 精确 > 共现 > 联想;同类先看共现频次,再以使用量和名字破平局。 */
function compareCandidateRows(a, b) {
const badgeDiff = badgeRank[a.badge] - badgeRank[b.badge];
if (badgeDiff) return badgeDiff;
const ca = a.cooccur ?? 0;
const cb = b.cooccur ?? 0;
if (ca !== cb) return cb - ca;
const ua = a.use ?? -1;
const ub = b.use ?? -1;
if (ua !== ub) return ub - ua;
return a.tag_name.localeCompare(b.tag_name);
}
function sortCandidateRows(rows) {
return [...rows].sort(compareCandidateRows);
}
/** 合并候选行: 按 key 去重, 强徽章胜, 保留已存在的 checked/expanded 用户态 */
function mergeCandidateRows(existing, incoming) {
const map = /* @__PURE__ */ new Map();
for (const row of existing) map.set(row.key, row);
for (const row of incoming) {
const prev = map.get(row.key);
if (!prev) {
map.set(row.key, row);
continue;
}
map.set(row.key, {
...prev,
...row,
badge: badgeRank[prev.badge] <= badgeRank[row.badge] ? prev.badge : row.badge,
checked: prev.checked,
expanded: prev.expanded,
resolving: prev.resolving || row.resolving,
resolveFailed: prev.resolveFailed || row.resolveFailed,
use: row.use ?? prev.use,
cooccur: row.cooccur ?? prev.cooccur
});
}
const merged = sortCandidateRows([...map.values()]);
let kept = 0;
return merged.filter((r) => {
if (r.checked || r.expanded || r.resolving || r.resolveFailed) return true;
return ++kept <= 200;
});
}
/**
* tag/info 换 id (并发经 anonGetJson pLimit + 会话缓存撞名合并; 失败静默丢, index 对齐返回)
* 票 18 决议③: 归一化名去重后共享一次取数 (会话缓存层已合并, 此处防重复入队)
*/
async function resolveTagInfos(names, signal) {
const seenNorm = /* @__PURE__ */ new Set();
const uniq = names.filter((n) => {
const norm = normalizeTagName(n);
return seenNorm.has(norm) ? false : (seenNorm.add(norm), true);
});
const infos = await Promise.all(uniq.map((n) => fetchTagInfoByName(n, signal).catch(() => void 0)));
const byNorm = new Map(uniq.map((n, i) => [normalizeTagName(n), infos[i]]));
return names.map((n) => byNorm.get(normalizeTagName(n)));
}
/**
* 候选行惰性解析 (票 18 决议①): 勾选的未解析行现场 tag/info 换 id
* - 成功: 回填 tag_id/use + 官方名, 清 resolveFailed
* - 失败: 标 resolveFailed (票 11 三态; 已解析行/未勾选行原样返回)
*/
async function resolveCandidateRows(rows, signal) {
const pendings = rows.filter((r) => r.tag_id === void 0);
if (!pendings.length) return [...rows];
const infos = await resolveTagInfos(pendings.map((r) => r.tag_name), signal);
const byNorm = /* @__PURE__ */ new Map();
pendings.forEach((r, i) => {
const info = infos[i];
if (info) byNorm.set(normalizeTagName(r.tag_name), info);
});
return rows.map((r) => {
if (r.tag_id !== void 0) return r;
const info = byNorm.get(normalizeTagName(r.tag_name));
if (!info) return {
...r,
resolveFailed: true
};
return {
...r,
tag_id: info.id,
tag_name: info.name,
use: info.use,
resolveFailed: false
};
});
}
async function runPhase2Round(args) {
const { keyword, inputNorm, anchor, fromPage, withSuggest = false, signal } = args;
const pageCount = args.pageCount ?? 3;
const pages = Array.from({ length: pageCount }, (_, i) => fromPage + i).filter((p) => p <= 50);
const settled = await Promise.allSettled(pages.map((p) => fetchAllV2Search(keyword, {
page: p,
signal
})));
const videos = settled.flatMap((r) => r.status === "fulfilled" ? r.value : []);
const pagesFetched = settled.filter((r) => r.status === "fulfilled").length;
const allPagesFailed = pages.length > 0 && pagesFetched === 0;
if (signal?.aborted) return emptyResult();
for (const video of videos) {
const tagNames = parseAllV2TagField(video.tag);
if (video.bvid && tagNames.length) persistVideoTags(video.bvid, tagNames.map((tag_name) => ({ tag_name })), { partial: true });
}
const analysis = analyzeAllV2Videos(videos, inputNorm, args.matchNorms);
const spellings = args.resolveVariants === false ? [] : analysis.glyphSpellings.slice(0, 20);
const variantEntries = dedupeById((spellings.length ? await resolveTagInfos(spellings, signal) : []).filter((i) => !!i));
const aliases = analysis.cooccurAliases.slice(0, 50);
const suggestNames = withSuggest && !tagRecBackoff.isEngaged && videos.length > 0 ? (await fetchSuggest(keyword, signal).catch(() => [])).slice(0, 10) : [];
if (signal?.aborted) return emptyResult();
return {
videos,
analysis,
pagesFetched,
allPagesFailed,
variantEntries,
newCandidates: buildCandidateRows({
anchor,
variantEntries,
aliases,
suggest: suggestNames,
inputNorm
})
};
}
function emptyResult() {
return {
videos: [],
analysis: {
exact: [],
related: [],
glyphSpellings: [],
cooccurAliases: [],
cliques: []
},
pagesFetched: 0,
allPagesFailed: false,
variantEntries: [],
newCandidates: []
};
}
function dedupeById(infos) {
const seen = /* @__PURE__ */ new Set();
return infos.filter((i) => seen.has(i.id) ? false : (seen.add(i.id), true));
}
function buildCandidateRows(args) {
const { anchor, variantEntries, aliases, suggest, inputNorm } = args;
const rows = [];
const push = (row) => {
rows.push({
...row,
key: candidateRowKey(row)
});
};
if (anchor) push({
tag_name: anchor.name,
tag_id: anchor.id,
use: anchor.use,
badge: "exact",
checked: true
});
for (const v of variantEntries) push({
tag_name: v.name,
tag_id: v.id,
use: v.use,
badge: "exact",
checked: true
});
for (const a of aliases) push({
tag_name: a.name,
cooccur: a.count,
badge: "cooccur",
checked: false
});
for (const s of suggest) {
if (normalizeTagName(s) === inputNorm) continue;
push({
tag_name: s,
badge: "suggest",
checked: false
});
}
const map = /* @__PURE__ */ new Map();
for (const row of rows) {
const prev = map.get(row.key);
if (!prev) {
map.set(row.key, row);
continue;
}
map.set(row.key, {
...row,
badge: badgeRank[prev.badge] <= badgeRank[row.badge] ? prev.badge : row.badge,
checked: prev.checked || row.checked,
cooccur: prev.cooccur ?? row.cooccur,
use: prev.use ?? row.use
});
}
return sortCandidateRows([...map.values()]);
}
/**
* [tag-rec] 牌组循环抽样 (spec §4.3 tab1 选组 / §3.6 deck 会话态; 票 17 升维三层)
* - 抽样内核 = 加权不放回牌堆 WeightedIdPile: {id,weight} 单位按 weight=张数入堆,
* 不放回抽 K 个不同 id (同单位重复卡抽到即废, weight 只影响中抽概率无配额);
* 残堆先续抽,抽干仍不足 K 时仅补尚未抽中的单位。
* - L1/L2 三层公平性 (票 17, TagRecUnitsDeck):
* 外层: 可抽 L1 (enabled&&weight>0 且麾下 ≥1 可抽 L2) 按 weight 不放回抽 K 个不同 L1
* 中层: 被抽中 L1 麾下可抽 L2 按各自 weight 入小堆;预取窗口可分配多个不同 L2。
* 内层: 抽中 L2 的 tag_ids ≤10 全量, >10 按同会话共享游标轮转取 10 切片 (pickTagIdsForRound)
* - 查看范围 (R7, 原票 26 主题筛升级): drawUnits 携 viewedL1Ids => 外层单位再筛范围内 L1;
* 零写入 (不改 L1 enabled), undefined = 不筛
* - 资格指纹: 外层覆盖 当前主题 id + L1 表 {id,enabled,weight,themeId} + 每组 l1Id 归属 + L2 既有资格项, 变即重建;
* name/note 等显示字段不入指纹 (改了不动牌堆); 组序不敏感 (排序后指纹)
* - 生命周期由调用方管理;收藏推荐与库存同会话共享,资格变化时重建,不落盘。
*/
/** 入抽资格 = 三态「正常」 (enabled && weight>0); 挂起(weight=0)/停用不入堆 */
function isDeckDrawable(g) {
return groupState(g) === "normal" && (!g.tag_ids || g.tag_ids.length > 0 || hasTagRecCombination(g.combination));
}
/** 二级组会话覆盖只产生副本,空组与 weight=0 仍由抽样资格拒绝。 */
function withTagRecGroupScope(groups, viewedGroupIds, temporary) {
const temp = new Set(temporary);
return groups.map((g) => {
const enabled = (g.enabled || temp.has(g.id)) && (!viewedGroupIds || viewedGroupIds.has(g.id));
return enabled === g.enabled ? g : {
...g,
enabled
};
});
}
/**
* L1 入抽资格 (票 17, 语义同 L2: enabled && weight>0);
* R7 查看范围: viewedL1Ids 非空时还要求 L1 在范围内 (undefined = 不筛; 范围展开见 model/view-scope);
* 麾下 ≥1 可抽 L2 才实际入外层堆 —— 见 TagRecUnitsDeck
*/
function isL1Drawable(l1, viewedL1Ids) {
if (!l1.enabled || l1.weight <= 0) return false;
if (viewedL1Ids && !viewedL1Ids.has(l1.id)) return false;
return true;
}
/**
* R7 临时打开合流: temp 集内的 L1 视同 enabled (返回映射副本, 不改 store)。
* 会话级 —— 刷新还原; 合流后 deck 判定/指纹/计数全链自然生效
*/
function withTagRecTempEnables(l1s, tempEnabledIds) {
if (!tempEnabledIds?.length) return [...l1s];
const set = new Set(tempEnabledIds);
return l1s.map((l) => set.has(l.id) && !l.enabled ? {
...l,
enabled: true
} : l);
}
/** L2 资格指纹项 (票 17: 既有项 + l1Id 归属 —— 归属变更视同资格变更) */
var l2FpEntry = (g) => `${g.id}=${g.enabled ? 1 : 0}:${g.weight}@${g.l1Id ?? ""}#${g.tag_ids.map((e) => e.id).join(",")}:${hasTagRecCombination(g.combination) ? 1 : 0}`;
/**
* 外层资格指纹 (票 17 + R7): 查看范围 (L1 id 集展开) + L1 表 {id,enabled,weight} +
* 每组 l1Id 归属 + L2 自身资格项; 组序不敏感; name/note 不入指纹 —— 任一变即重建整棵牌堆
* (查看范围变更视同资格变更, 「资格变即重建」语义保持)
* AUDIT-A1-2: 指纹收窄到「会影响抽取结果」的字段 —— 不可抽实体 (停用/挂起/范围外) 只留
* id+不可抽位: 挂起组改 tag_ids / 停用 L1 改 weight 不再炸掉整棵牌堆与共享态;
* 其资格字段在该实体转可抽时随「off→on」翻转自然入指纹, 重建时机不丢。
*/
function unitsFingerprint(groups, l1s, viewedL1Ids) {
const l1WithDrawableChild = new Set(groups.filter((g) => g.l1Id && isDeckDrawable(g)).map((g) => g.l1Id));
const activeParents = new Set(l1s.filter((l) => isL1Drawable(l, viewedL1Ids) && l1WithDrawableChild.has(l.id)).map((l) => l.id));
const l1Part = l1s.map((l) => isL1Drawable(l, viewedL1Ids) && l1WithDrawableChild.has(l.id) ? `L1#${l.id}=1:${l.weight}` : `L1#${l.id}=0`).sort().join("|");
const l2Part = groups.map((g) => g.l1Id && activeParents.has(g.l1Id) && isDeckDrawable(g) ? l2FpEntry(g) : `${g.id}=0`).sort().join("|");
return `view=${viewedL1Ids ? [...viewedL1Ids].sort().join(",") : ""}::${l1Part}::${l2Part}`;
}
/**
* 加权不放回牌堆内核 (spec §3.6 机制): units 由调用侧按可抽过滤;
* 资格指纹变 / 余量 整副重洗; 逐张抽, 同单位重复卡抽到即废, 返回 ≤K 个不同 id (抽出顺序)
*/
var WeightedIdPile = class {
shuffleFn;
pile = [];
fingerprint = "";
constructor(shuffleFn = shuffle) {
this.shuffleFn = shuffleFn;
}
/** 当前堆内剩余牌数 (诊断/测试观测用) */
get pileSize() {
return this.pile.length;
}
reshuffle(units) {
const pile = [];
for (const u of units) {
const w = Math.min(Math.max(Math.floor(u.weight) || 0, 0), 5);
for (let i = 0; i < w; i++) pile.push(u.id);
}
this.pile = this.shuffleFn(pile);
}
/**
* AUDIT-A1-1: 残堆不再按「剩余卡数 !picked.has(u.id));
if (!rest.length) break;
this.reshuffle(rest);
if (!this.pile.length) break;
}
const id = this.pile.pop();
if (picked.has(id)) continue;
picked.add(id);
pickedIds.push(id);
}
return pickedIds;
}
};
/**
* 三层公平抽样 deck (票 17): 外层 L1 加权不放回 => 中层被抽中 L1 麾下 L2 加权抽 1
* 返回解析后的 L2 组集 —— 下游 sources/roundNegsByGroup/groupNames 全链路不感知层级
*/
var TagRecUnitsDeck = exports("j",class {
shuffleFn;
outer;
/** 中层小堆: l1Id => 麾下 L2 牌堆 (懒建, 被外层抽中才抽) */
inners = /* @__PURE__ */ new Map();
/**
* @param shuffleFn 洗牌注入点 (默认 es-toolkit shuffle; 测试传确定性实现)
*/
constructor(shuffleFn = shuffle) {
this.shuffleFn = shuffleFn;
this.outer = new WeightedIdPile(shuffleFn);
}
/** 外层堆剩余牌数 (诊断/测试观测用) */
get pileSize() {
return this.outer.pileSize;
}
/**
* 供给抽样: 外层不放回抽 ≤K 个不同 L1;缺省每父组抽 1 个 L2,预取窗口可分配多个。
* R7 查看范围: viewedL1Ids 非空 => 外层单位只留范围内的 L1 (undefined = 不筛; 纯过滤, 零写入)
* 返回按抽出顺序的 L2 组 (不足 K 返回实际抽得数; 无可抽单位返回 [])
*/
drawUnits(groups, l1s, k, viewedL1Ids, fingerprint, maxGroups) {
const childrenByL1 = /* @__PURE__ */ new Map();
for (const g of groups) {
if (!g.l1Id || !isDeckDrawable(g)) continue;
const arr = childrenByL1.get(g.l1Id) ?? [];
arr.push(g);
childrenByL1.set(g.l1Id, arr);
}
const units = l1s.filter((l) => isL1Drawable(l, viewedL1Ids) && childrenByL1.has(l.id));
const groupLimit = maxGroups === void 0 ? void 0 : Math.max(0, Math.floor(maxGroups) || 0);
const pickedL1Ids = this.outer.draw(units.map((l) => ({
id: l.id,
weight: l.weight
})), fingerprint ?? unitsFingerprint(groups, l1s, viewedL1Ids), groupLimit === void 0 ? k : Math.min(k, groupLimit));
const aliveL1 = new Set(l1s.map((l) => l.id));
for (const key of this.inners.keys()) if (!aliveL1.has(key)) this.inners.delete(key);
const allocations = new Map(pickedL1Ids.map((id) => [id, 1]));
let remaining = (groupLimit ?? pickedL1Ids.length) - pickedL1Ids.length;
while (remaining > 0) {
let advanced = false;
for (const id of pickedL1Ids) {
if (remaining <= 0) break;
if (allocations.get(id) >= childrenByL1.get(id).length) continue;
allocations.set(id, allocations.get(id) + 1);
remaining--;
advanced = true;
}
if (!advanced) break;
}
const out = [];
for (const l1Id of pickedL1Ids) {
const children = childrenByL1.get(l1Id) ?? [];
if (!children.length) continue;
let inner = this.inners.get(l1Id);
if (!inner) this.inners.set(l1Id, inner = new WeightedIdPile(this.shuffleFn));
const gids = inner.draw(children.map((g) => ({
id: g.id,
weight: g.weight
})), children.map(l2FpEntry).sort().join("|"), allocations.get(l1Id));
for (const gid of gids) {
const g = children.find((x) => x.id === gid);
if (g) out.push(g);
}
}
return out;
}
});
/**
* 内层 tag 轮转 (票 17, 两 tab 共用纯函数):
* tag_ids ≤10 => 全量; >10 => 从 cursor 起取 10 切片并回绕推进, 逐轮覆盖全量
* 游标存取归调用侧 —— AUDIT-20 起落在梯队共享盒 (TagRecLadderSharedState.tagCursors/queryCursor,
* 跨 service 重建存活防大组首片饿死, 语境签名变随盒同清); 纯函数, 不改传入参数
*/
function pickTagIdsForRound(tagIds, cursor) {
if (tagIds.length <= 10) return {
entries: tagIds.slice(),
nextCursor: 0
};
const n = tagIds.length;
const start = ((Number.isFinite(cursor) ? Math.trunc(cursor) : 0) % n + n) % n;
return {
entries: Array.from({ length: 10 }, (_, i) => tagIds[(start + i) % n]),
nextCursor: (start + 10) % n
};
}
/**
* [tag-rec] 候选清单 (spec §4.2)
* 行: {tag_name, tag_id, use(读 count.use), 共现频次?, 来源徽章, checkbox}
* 排序: 精确 > 共现 > 联想,同类按共现频次再按使用量; 别名/联想行惰性入清单
* (无 use/tag_id 显示「待解析」,仍按已知共现排序; 「解析失败」可再勾重试)
* 徽章: 精确(锚点+归一化变体,默认勾) / 共现(别名,不勾) / 联想(不勾)
* 已在组内的行不进清单 (选定组正/负向 + 草稿组负向, 按 tag_id 判); 组内成员 chip 区默认收起,
* 展开开关在「存成项/并入当前组」右侧
* 两语义复用:
* - query (tab2): `TagRecCandidateList` —— 「扩进本次查询」并入查询单元 /「顺手存成项」存为新组
* - panel (面板): `TagRecCandidatePickList` —— 成组勾选器, 多选勾进目标 (组/黑名单),
* 确认动作由调用侧给 (「标签收藏」pane 用, 票 04)
*/
var badgeColor = {
exact: "green",
cooccur: "geekblue",
suggest: void 0
};
/** 票 12 预检标记: id 已在收藏表 => 极性徽标 (正向宿主灰「已在「X」」/ 负向宿主橙「已在「X」负向」; 多宿主 tooltip 列全) */
function HostGroupBadges({ list, tagId }) {
const pos = findGroupsByTagId(list, tagId);
const neg = findGroupsByNegativeTagId(list, tagId);
const badge = (hosts, suffix, color) => {
if (!hosts.length) return null;
const tag = /* @__PURE__ */ jsxs(Tag, {
color,
className: "mr-4px",
children: [
"已在「",
hosts[0].name,
"」",
suffix,
hosts.length > 1 ? `等${hosts.length}组` : ""
]
});
return hosts.length > 1 ? /* @__PURE__ */ jsx(AntdTooltip, {
title: hosts.map((g) => g.name).join("、"),
children: tag
}) : tag;
};
return /* @__PURE__ */ jsxs(Fragment, { children: [badge(pos, ""), badge(neg, "负向", "orange")] });
}
/** 行表本体: 两语义共享; 勾选态回写经 onToggleRow 由调用侧管 (session / 本地 state) */
function TagRecCandidateRowsTable({ rows, onToggleRow, emptyText = "暂无候选", expandedLabel = "已并入" }) {
const favList = useSnapshot(favoritesStore).list;
return /* @__PURE__ */ jsx(table_default, {
size: "small",
rowKey: "key",
columns: [
{
key: "check",
width: 32,
render: (_, row) => /* @__PURE__ */ jsx(Checkbox, {
checked: row.checked,
disabled: row.expanded,
onChange: (e) => onToggleRow(row.key, e.target.checked)
})
},
{
key: "name",
title: "标签",
render: (_, row) => /* @__PURE__ */ jsxs("span", { children: [row.tag_name, row.tag_id !== void 0 && /* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "ml-6px text-12px",
children: ["#", row.tag_id]
})] })
},
{
key: "use",
title: "use",
width: 70,
align: "right",
render: (_, row) => row.use === void 0 ? "-" : row.use.toLocaleString()
},
{
key: "cooccur",
title: "共现",
width: 56,
align: "right",
render: (_, row) => row.cooccur ?? "-"
},
{
key: "badge",
title: "来源",
width: 160,
render: (_, row) => /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(Tag, {
color: badgeColor[row.badge],
className: "mr-4px",
children: CandidateBadgeLabel[row.badge]
}),
row.expanded && /* @__PURE__ */ jsx(Tag, {
color: "purple",
children: expandedLabel
}),
row.resolving && /* @__PURE__ */ jsx(Tag, {
color: "blue",
children: "解析中…"
}),
!row.resolving && row.tag_id === void 0 && (row.resolveFailed ? /* @__PURE__ */ jsx(Tag, {
color: "red",
children: "解析失败"
}) : /* @__PURE__ */ jsx(Tag, { children: "待解析" })),
row.tag_id !== void 0 && /* @__PURE__ */ jsx(HostGroupBadges, {
list: favList,
tagId: row.tag_id
})
] })
}
],
dataSource: rows,
pagination: false,
scroll: { y: 220 },
locale: { emptyText }
});
}
/**
* panel 语义 (面板成组勾选器): 行由调用侧持有 (受控), 确认动作 = 勾选项 => onConfirm
* 「添加变体」(加入本组) / 「新建标签组」(存为一项) / 黑名单「添加」(入黑名单) 共用
*/
function TagRecCandidatePickList({ rows, onToggleRow, confirmLabel, onConfirm, statusText, busy = false, expandedLabel }) {
const checked = rows.filter((r) => r.checked && !r.expanded);
return /* @__PURE__ */ jsxs("div", {
className: "mt-6px",
children: [/* @__PURE__ */ jsx(TagRecCandidateRowsTable, {
rows,
onToggleRow,
expandedLabel
}), /* @__PURE__ */ jsxs("div", {
className: "mt-6px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "primary",
disabled: !checked.length || busy,
onClick: () => onConfirm(checked),
children: [confirmLabel, checked.length ? ` (${checked.length})` : ""]
}), statusText && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: statusText
})]
})]
});
}
/** 独立解析候选, 不读写主查询的草稿组、选定组或扩选暂存。 */
function CandidateResolvePicker({ confirmLabel, onConfirm, disabled = false, confirmIdInput = true, mineIdInput = false, existingTagIds = [], existingLabel }) {
const [draft, setDraft] = (0, import_react.useState)("");
const [rows, setRows] = (0, import_react.useState)([]);
const [phase, setPhase] = (0, import_react.useState)("idle");
const [confirmBusy, setConfirmBusy] = (0, import_react.useState)(false);
const runningRef = (0, import_react.useRef)(false);
const versionRef = (0, import_react.useRef)(0);
const controllerRef = (0, import_react.useRef)(void 0);
const disabledRef = (0, import_react.useRef)(disabled);
const onConfirmRef = (0, import_react.useRef)(onConfirm);
disabledRef.current = disabled;
onConfirmRef.current = onConfirm;
(0, import_react.useEffect)(() => {
if (!disabled) return;
versionRef.current++;
controllerRef.current?.abort();
runningRef.current = false;
setConfirmBusy(false);
setPhase((prev) => prev === "resolving" || prev === "mining" ? "idle" : prev);
}, [disabled]);
(0, import_react.useEffect)(() => () => {
versionRef.current++;
controllerRef.current?.abort();
}, []);
const run = async (value) => {
const input = value.trim();
if (!input || disabled || runningRef.current || confirmBusy) return;
runningRef.current = true;
controllerRef.current?.abort();
const controller = new AbortController();
controllerRef.current = controller;
const version = ++versionRef.current;
const current = () => version === versionRef.current && !controller.signal.aborted && !disabledRef.current;
setRows([]);
setPhase("resolving");
try {
const { anchor, isIdPath } = await resolveAnchor(input, controller.signal);
if (!current()) return;
if (!anchor) {
toast(`未能解析标签「${input}」`);
setPhase("error");
return;
}
if (isIdPath && confirmIdInput) {
onConfirmRef.current([{
id: anchor.id,
name: anchor.name
}]);
setPhase("idle");
return;
}
const keyword = isIdPath ? anchor.name : input;
const inputNorm = normalizeTagName(keyword);
setRows(buildCandidateRows({
anchor,
variantEntries: [],
aliases: [],
suggest: [],
inputNorm
}));
if (isIdPath && !mineIdInput) {
setPhase("done");
return;
}
setPhase("mining");
const res = await runPhase2Round({
keyword,
inputNorm,
anchor,
fromPage: 1,
withSuggest: true,
signal: controller.signal
});
if (!current()) return;
setRows((prev) => mergeCandidateRows(prev, res.newCandidates));
setPhase(res.allPagesFailed ? "error" : "done");
sediment.merge(inputNorm, res.analysis.cliques);
} catch {
if (current()) {
setPhase("error");
toast("标签解析失败, 可重试");
}
} finally {
if (version === versionRef.current) runningRef.current = false;
}
};
const busy = phase === "resolving" || phase === "mining";
const existing = new Set(existingTagIds);
const displayRows = rows.map((row) => row.tag_id !== void 0 && existing.has(row.tag_id) ? {
...row,
expanded: true
} : row);
const statusText = phase === "mining" ? "正在查询视频共现标签…" : phase === "done" ? "已就绪, 勾选后确认" : phase === "error" ? "解析未完成, 已有标签仍可确认或重新解析" : void 0;
return /* @__PURE__ */ jsxs("div", {
className: "mb-6px min-w-0 w-full",
children: [
/* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "max-w-full w-240px",
placeholder: "标签名, 或纯数字 tag_id",
enterButton: "解析",
allowClear: true,
value: draft,
loading: busy,
disabled: disabled || busy || confirmBusy,
onChange: (e) => setDraft(e.target.value),
onSearch: (value) => void run(value)
}),
!!rows.length && /* @__PURE__ */ jsx(TagRecCandidatePickList, {
rows: displayRows,
expandedLabel: existingLabel,
onToggleRow: (key, checked) => {
if (disabled || confirmBusy) return;
setRows((prev) => prev.map((r) => r.key === key ? {
...r,
checked
} : r));
const target = rows.find((r) => r.key === key);
if (!checked || !target || target.tag_id !== void 0 || target.resolving) return;
const version = versionRef.current;
setRows((prev) => prev.map((r) => r.key === key ? {
...r,
resolving: true
} : r));
resolveCandidateRows([{
...target,
checked
}], controllerRef.current?.signal).catch(() => void 0).then((resolved) => {
if (version !== versionRef.current || disabledRef.current) return;
const result = resolved?.[0];
setRows((prev) => prev.map((r) => r.key === key ? {
...r,
resolving: false,
tag_id: result?.tag_id,
tag_name: result?.tag_name ?? r.tag_name,
use: result?.use,
resolveFailed: result?.resolveFailed ?? true
} : r));
});
},
confirmLabel,
busy: busy || confirmBusy || disabled,
statusText,
onConfirm: (picked) => {
if (disabled || runningRef.current || confirmBusy) return;
const version = versionRef.current;
setConfirmBusy(true);
resolveCandidateRows(picked, controllerRef.current?.signal).then((resolved) => {
if (version !== versionRef.current || disabledRef.current) return;
setRows((prev) => prev.map((r) => resolved.find((x) => x.key === r.key) ?? r));
const ok = [...new Map(resolved.filter((r) => r.tag_id !== void 0).map((r) => [r.tag_id, r])).values()];
const failed = resolved.filter((r) => r.tag_id === void 0).length;
if (failed) toast(`${failed} 个标签解析失败, 可重试`);
if (!ok.length) return;
for (const r of ok) sediment.confirm(normalizeTagName(r.tag_name));
onConfirmRef.current(ok.map((r) => ({
id: r.tag_id,
name: r.tag_name
})));
}).catch(() => {
if (version === versionRef.current && !disabledRef.current) toast("标签解析失败, 可重试");
}).finally(() => {
if (version === versionRef.current) setConfirmBusy(false);
});
}
}),
!rows.length && statusText && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-4px block text-12px",
children: statusText
})
]
});
}
/** 本地收藏层级索引: 每条正向成员最多进入 L2/L1/主题三张表, 不读负向或配置。 */
function createTagRecFavoriteTagIndex(groups, l1s, themes) {
const l1ById = new Map(l1s.map((l1) => [l1.id, l1]));
const themeById = new Map(themes.map((theme) => [theme.id, theme]));
const entries = /* @__PURE__ */ new Map();
const add = (key, tags) => {
let index = entries.get(key);
if (!index) entries.set(key, index = /* @__PURE__ */ new Map());
for (const tag of tags) if (!index.has(tag.id)) index.set(tag.id, tag);
};
for (const group of groups) {
add(`l2:${group.id}`, group.tag_ids);
const l1 = group.l1Id ? l1ById.get(group.l1Id) : void 0;
if (!l1) continue;
add(`l1:${l1.id}`, group.tag_ids);
if (l1.themeId && themeById.has(l1.themeId)) add(`theme:${l1.themeId}`, group.tag_ids);
}
const option = (value, level, names) => {
const count = entries.get(value)?.size ?? 0;
const path = names.filter(Boolean).join(" / ");
return {
value,
label: `${level} · ${path} (${count} 标签)`,
search: normalizeTagName(`${level} ${path}`),
disabled: !count
};
};
return {
options: [
...themes.map((theme) => option(`theme:${theme.id}`, "主题", [theme.name])),
...l1s.map((l1) => option(`l1:${l1.id}`, "一级组", [themeById.get(l1.themeId ?? "")?.name ?? "", l1.name])),
...groups.map((group) => {
const l1 = l1ById.get(group.l1Id ?? "");
return option(`l2:${group.id}`, "二级组", [
themeById.get(l1?.themeId ?? "")?.name ?? "",
l1?.name ?? "",
group.name
]);
})
],
/** 确认时复制快照, 同时选主题与其子组也只返回一份成员。 */
select(values) {
const selected = /* @__PURE__ */ new Map();
for (const value of values) for (const [id, entry] of entries.get(value) ?? []) if (!selected.has(id)) selected.set(id, { ...entry });
return [...selected.values()];
}
};
}
var CHIP_PAGE_SIZE = 24;
/** 只在展开时订阅收藏并构建层级索引, Select 使用虚拟列表。 */
function FavoriteTagPicker({ disabled, onConfirm, getPopupContainer }) {
const groups = useSnapshot(favoritesStore).list;
const l1s = useSnapshot(favoritesL1Store).list;
const themes = useSnapshot(themesStore).list;
const index = (0, import_react.useMemo)(() => createTagRecFavoriteTagIndex(groups, l1s, themes), [
groups,
l1s,
themes
]);
const [selected, setSelected] = (0, import_react.useState)([]);
const entries = (0, import_react.useMemo)(() => index.select(selected), [index, selected]);
return /* @__PURE__ */ jsxs("div", {
className: "mt-4px flex flex-col gap-y-4px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "复制所选范围内的正向标签快照, 不解析、不带入源组配置; 后续收藏变化不会联动此名单。"
}),
/* @__PURE__ */ jsx(Select, {
mode: "multiple",
showSearch: true,
allowClear: true,
virtual: true,
className: "w-full",
placeholder: "搜索主题 / 一级组 / 二级组",
value: selected,
options: index.options,
maxTagCount: "responsive",
disabled,
getPopupContainer,
filterOption: (input, option) => !!option?.search.includes(normalizeTagName(input)),
onChange: setSelected
}),
/* @__PURE__ */ jsxs(button_default, {
size: "small",
className: "self-start",
disabled: disabled || !entries.length,
onClick: () => {
if (disabled) return;
onConfirm(entries);
setSelected([]);
},
children: [
"加入名单 (",
entries.length,
")"
]
})
]
});
}
function TagFilterEntryPicker({ snapList, onChange, disabled, confirmLabel, getPopupContainer }) {
const [open, setOpen] = (0, import_react.useState)();
const [query, setQuery] = (0, import_react.useState)("");
const [page, setPage] = (0, import_react.useState)(1);
const filtered = (0, import_react.useMemo)(() => {
const norm = normalizeTagName(query);
return norm ? snapList.filter((tag) => normalizeTagName(tag.name).includes(norm) || String(tag.id).includes(query.trim())) : snapList;
}, [snapList, query]);
const currentPage = Math.min(page, Math.max(1, Math.ceil(filtered.length / CHIP_PAGE_SIZE)));
const add = (entries) => {
if (disabled) return;
const have = new Set(snapList.map((tag) => tag.id));
const adds = entries.filter((tag) => !have.has(tag.id) && have.add(tag.id));
if (adds.length) onChange([...snapList, ...adds.map((entry) => ({ ...entry }))]);
toast(adds.length ? `已加入 ${adds.length} 个标签` : "标签已在名单中");
};
return /* @__PURE__ */ jsxs("div", {
className: "mt-3px min-w-0 w-full",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px gap-y-4px",
children: [
/* @__PURE__ */ jsxs("span", {
className: "text-12px",
children: [
"已加入 ",
snapList.length,
" 个标签"
]
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-12px",
disabled,
onClick: () => setOpen(open === "resolve" ? void 0 : "resolve"),
children: open === "resolve" ? "收起解析" : "解析添加"
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-12px",
disabled,
onClick: () => setOpen(open === "favorites" ? void 0 : "favorites"),
children: open === "favorites" ? "收起收藏选择" : "从收藏选择"
}),
!!snapList.length && /* @__PURE__ */ jsx(button_default, {
size: "small",
type: "link",
className: "h-auto px-0 text-12px",
disabled,
onClick: () => {
if (disabled) return;
onChange([]);
setQuery("");
setPage(1);
},
children: "清空名单"
})
]
}),
(snapList.length > CHIP_PAGE_SIZE || !!query) && /* @__PURE__ */ jsx(Input, {
size: "small",
allowClear: true,
className: "my-4px",
placeholder: "筛选已加入标签名 / ID",
value: query,
disabled,
onChange: (e) => {
setQuery(e.target.value);
setPage(1);
}
}),
/* @__PURE__ */ jsx("div", {
className: "mt-4px flex flex-wrap items-center gap-4px",
children: filtered.slice((currentPage - 1) * CHIP_PAGE_SIZE, currentPage * CHIP_PAGE_SIZE).map((entry) => /* @__PURE__ */ jsx(Tag, {
closable: !disabled,
onClose: (e) => {
e.preventDefault();
if (!disabled) onChange(snapList.filter((tag) => tag.id !== entry.id).map((tag) => ({ ...tag })));
},
children: entry.name || `#${entry.id}`
}, entry.id))
}),
filtered.length > CHIP_PAGE_SIZE && /* @__PURE__ */ jsx(pagination_default, {
size: "small",
current: currentPage,
pageSize: CHIP_PAGE_SIZE,
total: filtered.length,
showSizeChanger: false,
onChange: setPage
}),
open === "favorites" && /* @__PURE__ */ jsx(FavoriteTagPicker, {
disabled,
onConfirm: add,
getPopupContainer
}),
open === "resolve" && /* @__PURE__ */ jsxs("div", {
className: "mt-4px",
children: [/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mb-4px block text-12px",
children: "任意标签 → 视频共现候选 → 勾选加入本名单; 不改变当前查询组"
}), /* @__PURE__ */ jsx(CandidateResolvePicker, {
confirmLabel,
confirmIdInput: false,
disabled,
onConfirm: add
})]
})
]
});
}
var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min(), 1);
/**
* [tag-rec] FilterForm (spec §5.1/§5.2) —— 「标签过滤」pane 与 tab2 过滤 Popover 双处复用
* - 绑定对象 = `core` (TagRecFilterCore 形状的 valtio proxy):
* pane 传 `settings.tagRec.filter` (configPath 段, 持久化/迁移白拿);
* tab2 传 `tagRecCustomSession.filterOverride` (会话副本, 刷新即丢不持久)
* - 结构 = 预设选择器 + 总开关 + 维度开关列表 (13 维三族) + 高级区(权重/锚点/小池阈值/严格模式, 全标「待校准」)
* + K 旋钮 (低频不进 tabbar, 仅 global) + 「恢复出厂」(仅 global) + 自定义预设增删改名 (仅 global)
* - scope='session' 时预设选择器退化为「应用预设整套覆盖副本」, 管理动作/恢复出厂/K 不渲染
* - readOnly (票 15): tab2 无覆盖时只读预览全局 —— 全控件禁用, 预设行退化为静态文本
* - getPopupContainer: 内嵌 Select 弹层的就近容器 —— Popover 语境必须挂进 Popover 内容节点,
* 否则点击下拉项被外层 Popover 判为「外部点击」而误关
*/
function TagRecFilterForm({ core, scope, readOnly, getPopupContainer }) {
const snap = useSnapshot(core);
const disabled = readOnly || !snap.enabled;
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
/* @__PURE__ */ jsx(TagRecPresetRow, {
core,
scope,
readOnly,
getPopupContainer
}),
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "总开关"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: snap.enabled,
disabled: readOnly,
onChange: (v) => core.enabled = v
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "关闭内容过滤,池筛选仍生效;应用预设会覆盖整套全局配置"
})
]
}),
/* @__PURE__ */ jsx(PoolsEnabledRow, {
value: snap.poolsEnabled,
disabled: readOnly,
onToggle: (p, on) => {
core.poolsEnabled ??= {};
if (on) delete core.poolsEnabled[p];
else core.poolsEnabled[p] = false;
if (!Object.keys(core.poolsEnabled).length) delete core.poolsEnabled;
}
}),
/* @__PURE__ */ jsx(DimsSection, {
core,
snap,
disabled,
getPopupContainer
}),
/* @__PURE__ */ jsx(AdvancedSection, {
core,
snap,
disabled
}),
/* @__PURE__ */ jsx(TagRecPoolSlotsBlock, {
core,
disabled,
getPopupContainer
}),
scope === "global" && /* @__PURE__ */ jsx(KRow, {})
]
});
}
var C = {
block: "b-1px b-gate-border rounded-6px b-solid px-8px py-6px",
dimLabel: "min-w-110px",
familyTitle: "mt-2px text-12px text-gate-text font-bold opacity-60"
};
var PRESET_CUSTOM_VALUE = "__custom__";
function TagRecPresetRow({ core, scope, readOnly, getPopupContainer }) {
const { presets, activePreset } = useSettingsSnapshot().tagRec.filter;
const filterSettings = settings.tagRec.filter;
const [saving, setSaving] = (0, import_react.useState)(false);
const [renaming, setRenaming] = (0, import_react.useState)(false);
const [applied, setApplied] = (0, import_react.useState)(() => settings.tagRec.filter.activePreset || void 0);
/** 会话副本归属名当前是否还对应真实预设档 (同名伪值/已删档 => 回落「自定义」显示) */
const appliedIsPreset = !!applied && applied !== PRESET_CUSTOM_VALUE && presets.some((p) => p.name === applied);
/**
* [tag-rec] UIUX-U1-9: 会话副本任一维度编辑 => 预设归属回落「自定义」伪档。
* 订阅 core 深变更; 「应用预设」的整套覆写经「写后仍与所选档同形」豁免 ——
* 否则 apply 的 mutation 回响会立刻把刚选中的档打回自定义 (valtio notify 微批异步, 届时 applied 已更新)
*/
const appliedRef = (0, import_react.useRef)(applied);
appliedRef.current = applied;
(0, import_react.useEffect)(() => {
if (scope !== "session" || readOnly) return;
return subscribe(core, () => {
const name = appliedRef.current;
if (!name || name === PRESET_CUSTOM_VALUE) return;
const p = filterSettings.presets.find((x) => x.name === name);
if (!(!!p && isEqual$1(JSON.parse(JSON.stringify(snapshot(core))), JSON.parse(JSON.stringify(p.config))))) setApplied(PRESET_CUSTOM_VALUE);
});
}, [
core,
scope,
readOnly
]);
const presetOptions = presets.map((p) => ({
value: p.name,
label: p.builtin ? p.name : `${p.name} (自定义)`
}));
if (readOnly) return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "预设"
}), /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: presets.some((p) => p.name === activePreset) ? activePreset : "自定义"
})]
});
if (scope === "session") return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "预设"
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-170px",
placeholder: "选择预设整套覆盖",
options: [...presetOptions, {
value: PRESET_CUSTOM_VALUE,
label: "自定义"
}],
value: appliedIsPreset ? applied : PRESET_CUSTOM_VALUE,
getPopupContainer,
onChange: (name) => {
if (name === PRESET_CUSTOM_VALUE) {
setApplied(PRESET_CUSTOM_VALUE);
return;
}
if (applyTagRecPresetToCore(filterSettings.presets, core, name)) {
setApplied(name);
toast(`已按预设「${name}」覆盖会话副本`);
}
}
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "只覆盖本会话副本, 不动全局配置"
})
]
});
const activeIsPreset = presets.some((p) => p.name === activePreset);
const activeBuiltin = !!presets.find((p) => p.name === activePreset)?.builtin;
const saveAsPreset = (raw) => {
const name = raw.trim();
if (!name) return;
const existing = filterSettings.presets.find((p) => p.name === name);
upsertTagRecPreset(filterSettings, {
name,
builtin: existing?.builtin,
config: cloneTagRecFilterCore(filterSettings)
});
filterSettings.activePreset = name;
toast(`已存为预设「${name}」`);
setSaving(false);
};
const restoreFactory = () => {
const name = filterSettings.activePreset;
if (restoreTagRecBuiltinPreset(filterSettings, name)) {
applyTagRecPreset(filterSettings, name);
toast(`已恢复出厂「${name}」`);
}
};
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-4px",
children: [
/* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "预设"
}),
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-170px",
options: [...presetOptions, {
value: PRESET_CUSTOM_VALUE,
label: "自定义"
}],
value: activeIsPreset ? activePreset : PRESET_CUSTOM_VALUE,
getPopupContainer,
onChange: (v) => {
if (v === PRESET_CUSTOM_VALUE) filterSettings.activePreset = "";
else applyTagRecPreset(filterSettings, v);
}
}),
/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setSaving((v) => !v),
children: saving ? "收起" : "存为预设"
}),
activeIsPreset && !activeBuiltin && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(button_default, {
size: "small",
onClick: () => setRenaming((v) => !v),
children: "重命名"
}), /* @__PURE__ */ jsx(Popconfirm, {
title: "删除预设",
description: `删除自定义预设「${activePreset}」?`,
onConfirm: () => removeTagRecPreset(filterSettings, activePreset),
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
children: "删除"
})
})] }),
activeIsPreset && activeBuiltin && /* @__PURE__ */ jsx(Popconfirm, {
title: "恢复出厂",
description: `将内置预设「${activePreset}」恢复为出厂配置并立即应用?`,
onConfirm: restoreFactory,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
children: "恢复出厂"
})
})
]
}),
saving && /* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "w-260px",
placeholder: "预设名 (同名覆盖)",
enterButton: "保存",
onSearch: saveAsPreset
}),
renaming && activeIsPreset && !activeBuiltin && /* @__PURE__ */ jsx(Input.Search, {
size: "small",
className: "w-260px",
defaultValue: activePreset,
enterButton: "改名",
onSearch: (v) => {
if (renameTagRecPreset(filterSettings, activePreset, v)) {
toast(`已改名为「${v.trim()}」`);
setRenaming(false);
} else toast("改名失败: 空名 / 与既有预设重名");
}
})
]
});
}
function DimRow({ label, tooltip, checked, disabled, onToggle, children }) {
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-6px gap-y-3px",
children: [
/* @__PURE__ */ jsx(Checkbox, {
className: C.dimLabel,
checked,
disabled,
onChange: (e) => onToggle(e.target.checked),
children: label
}),
!!tooltip && /* @__PURE__ */ jsx(HelpInfo, {
className: "ml--2px",
children: tooltip
}),
children
]
});
}
function NumInput({ value, onChange, disabled, min = 0, max, step, width = "w-90px", placeholder }) {
return /* @__PURE__ */ jsx(TypedInputNumber, {
size: "small",
className: width,
min,
max,
step,
value,
placeholder,
disabled,
onChange: (v) => onChange(v ?? 0)
});
}
/** 时长维预设 (秒级端点, 0=不限) —— ≤5分短 / 5~30中 / 30~60长 / ≥60剧场版 */
var DURATION_PRESETS = [
{
key: "any",
label: "不限",
min: 0,
max: 0
},
{
key: "short",
label: "短视频 ≤5分",
min: 0,
max: 300
},
{
key: "mid",
label: "中视频 5~30分",
min: 300,
max: 1800
},
{
key: "long",
label: "长视频 30~60分",
min: 1800,
max: 3600
},
{
key: "movie",
label: "剧场版 ≥60分",
min: 3600,
max: 0
}
];
/** 时:分 输入对 -> 秒 (时长维自定义档); 用户手改会丢掉秒级零头 */
function DurationHmInput({ value, onChange, disabled }) {
const hours = Math.floor(value / 3600);
const minutes = Math.floor(value % 3600 / 60);
return /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-2px",
children: [
/* @__PURE__ */ jsx(NumInput, {
width: "w-48px",
min: 0,
max: 99,
value: hours,
onChange: (h) => onChange(h * 3600 + minutes * 60),
disabled
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "时"
}),
/* @__PURE__ */ jsx(NumInput, {
width: "w-48px",
min: 0,
max: 59,
value: minutes,
onChange: (m) => onChange(hours * 3600 + m * 60),
disabled
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "分"
})
]
});
}
/** 内联标签列表编辑: 已有项 closable Tag + 尾部输入框 (Enter/失焦提交) */
function ChipsEditor({ items, render, onRemove, onAdd, placeholder, disabled }) {
const [draft, setDraft] = (0, import_react.useState)("");
const commit = () => {
const t = draft.trim();
if (t) {
onAdd(t);
setDraft("");
}
};
return /* @__PURE__ */ jsxs("span", {
className: "max-w-full inline-flex flex-wrap items-center gap-4px",
children: [items.map((t, i) => /* @__PURE__ */ jsx(Tag, {
closable: !disabled,
onClose: (e) => {
e.preventDefault();
onRemove(i);
},
children: render(t)
}, i)), /* @__PURE__ */ jsx(Input, {
size: "small",
className: "w-150px",
placeholder,
value: draft,
disabled,
onChange: (e) => setDraft(e.target.value),
onPressEnter: commit,
onBlur: commit
})]
});
}
/** pubdate from/to 日期输入: 'YYYY-MM-DD' 文本态 <-> unix 秒 (0/undefined=未设); 不用 DatePicker (Popover 内嵌无弹层冲突) */
function SinceDateInput({ value, onChange, disabled }) {
const shown = value > 0 ? (0, import_dayjs_min.default)(value * 1e3).format("YYYY-MM-DD") : "";
const [text, setText] = (0, import_react.useState)(shown);
(0, import_react.useEffect)(() => setText(shown), [shown]);
const commit = () => {
const t = text.trim();
if (!t) {
if (value) onChange(0);
return;
}
const d = (0, import_dayjs_min.default)(t);
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(t) || !d.isValid()) {
toast("日期格式 YYYY-MM-DD");
setText(shown);
return;
}
onChange(d.startOf("day").unix());
};
return /* @__PURE__ */ jsx(Input, {
size: "small",
className: "w-110px",
placeholder: "YYYY-MM-DD",
value: text,
disabled,
onChange: (e) => setText(e.target.value),
onPressEnter: commit,
onBlur: commit
});
}
/** pubdate.tod 一天内时段输入: 'HH:mm-HH:mm' <-> {from,to} 分钟 (空=未设; from>to=跨零点, 票04) */
function TodInput({ value, onChange, disabled }) {
const fmt = (m) => m === void 0 ? "" : `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
const shown = value && (value.from !== void 0 || value.to !== void 0) ? `${fmt(value.from)}-${fmt(value.to)}` : "";
const [text, setText] = (0, import_react.useState)(shown);
(0, import_react.useEffect)(() => setText(shown), [shown]);
const parseHm = (s) => {
const m = /^(\d{1,2}):(\d{1,2})$/.exec(s.trim());
if (!m) return void 0;
const h = +m[1];
const mi = +m[2];
return h <= 23 && mi <= 59 ? h * 60 + mi : void 0;
};
const commit = () => {
const t = text.trim();
if (!t) {
if (shown) onChange(void 0);
return;
}
const sep = t.indexOf("-");
const from = sep > 0 ? parseHm(t.slice(0, sep)) : void 0;
const to = sep > 0 ? parseHm(t.slice(sep + 1)) : void 0;
if (from === void 0 || to === void 0) {
toast("时段格式 HH:mm-HH:mm");
setText(shown);
return;
}
onChange({
from,
to
});
};
return /* @__PURE__ */ jsx(Input, {
size: "small",
className: "w-110px",
placeholder: "HH:mm-HH:mm",
value: text,
disabled,
onChange: (e) => setText(e.target.value),
onPressEnter: commit,
onBlur: commit
});
}
/** stat 七项绝对值上下限 (票04): 折叠区, 每行 = [min]~[max] 一对 dim (勾选同启停; 0=未设端) */
var STAT_BOUND_ROWS = [
{
key: "like",
label: "点赞数"
},
{
key: "fav",
label: "收藏数"
},
{
key: "coin",
label: "投币数"
},
{
key: "danmaku",
label: "弹幕数"
},
{
key: "reply",
label: "评论数"
},
{
key: "share",
label: "分享数"
}
];
function StatBoundsRows({ d, cd, disabled }) {
const [open, setOpen] = (0, import_react.useState)(false);
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
className: "flex items-center gap-x-6px",
children: /* @__PURE__ */ jsxs(button_default, {
size: "small",
type: "text",
className: "px-0 text-12px",
onClick: () => setOpen((v) => !v),
children: [open ? "收起" : "展开", "数值上下限 (点赞/收藏/投币/弹幕/评论/分享)"]
})
}), open && STAT_BOUND_ROWS.map(({ key, label }) => {
const kMin = `${key}Min`;
const kMax = `${key}Max`;
const checked = d[kMin].enabled || d[kMax].enabled;
const rowDisabled = disabled || !checked;
return /* @__PURE__ */ jsxs(DimRow, {
label,
tooltip: "绝对值上下限; 0=该端未设; 勾选后按区间过滤, 缺字段维度豁免放行",
checked,
disabled,
onToggle: (v) => (cd[kMin].enabled = v, cd[kMax].enabled = v),
children: [
/* @__PURE__ */ jsx(NumInput, {
value: d[kMin].value,
onChange: (v) => cd[kMin].value = v,
disabled: rowDisabled,
placeholder: "≥",
width: "w-90px"
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "~"
}),
/* @__PURE__ */ jsx(NumInput, {
value: d[kMax].value,
onChange: (v) => cd[kMax].value = v,
disabled: rowDisabled,
placeholder: "≤",
width: "w-90px"
})
]
}, key);
})] });
}
/** 分区条目录入 (票03/12): 名搜索下拉选一级分区 (双体系 id 一并落条); 叶级 v2 条目走视频卡片右键补 */
function PartitionEntryPicker({ list, snapList, disabled, getPopupContainer }) {
const options = Object.entries(PARTITION_NAME_TO_PARENT).map(([name, ids]) => ({
value: name,
label: `${name}${ids.v1 !== void 0 ? ` (v1:${ids.v1})` : ""}${ids.v2 !== void 0 ? ` (v2:${ids.v2})` : ""}`
}));
return /* @__PURE__ */ jsxs("span", {
className: "max-w-full inline-flex flex-wrap items-center gap-4px",
children: [snapList.map((e, i) => /* @__PURE__ */ jsxs(Tag, {
closable: !disabled,
onClose: (ev) => {
ev.preventDefault();
list.splice(i, 1);
},
children: [e.name, e.v1 !== void 0 || e.v2 !== void 0 ? "" : " (仅名)"]
}, i)), /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-170px",
showSearch: true,
placeholder: "搜分区名添加",
options,
value: null,
disabled,
getPopupContainer,
filterOption: (input, opt) => !!opt?.label?.toString().includes(input.trim()),
onChange: (name) => {
const ids = PARTITION_NAME_TO_PARENT[name];
if (snapList.some((e) => e.name === name)) {
toast("已存在");
return;
}
list.push({
name,
...ids?.v2 !== void 0 ? { v2: ids.v2 } : { v1: ids?.v1 },
level: "parent"
});
}
})]
});
}
function DimsSection({ core, snap, disabled, getPopupContainer, hideListDims, only }) {
const d = snap.dims;
const cd = core.dims;
const dimProps = (key) => ({
checked: d[key].enabled,
disabled,
onToggle: (v) => cd[key].enabled = v
});
const editorOff = (key) => disabled || !d[key].enabled;
const [customDur, setCustomDur] = (0, import_react.useState)(false);
const durationKey = customDur || !DURATION_PRESETS.some((p) => p.min === d.duration.min && p.max === d.duration.max) ? "custom" : DURATION_PRESETS.find((p) => p.min === d.duration.min && p.max === d.duration.max).key;
const rateEditor = (key) => /* @__PURE__ */ jsx(NumInput, {
value: d[key].value,
onChange: (v) => cd[key].value = v,
disabled: editorOff(key),
max: 1,
step: .01
});
return /* @__PURE__ */ jsxs("div", {
className: C.block,
children: [
only !== "list" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
className: "mb-4px text-12px font-bold",
children: "维度开关"
}), /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-3px",
children: [
/* @__PURE__ */ jsx("div", {
className: C.familyTitle,
children: "绝对值"
}),
/* @__PURE__ */ jsx(DimRow, {
label: "播放量 ≥",
tooltip: "低于该值滤除; 0=未设",
...dimProps("viewMin"),
children: /* @__PURE__ */ jsx(NumInput, {
value: d.viewMin.value,
onChange: (v) => cd.viewMin.value = v,
disabled: editorOff("viewMin"),
step: 1e3
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "播放量 ≤",
tooltip: "高于该值滤除; 0=未设",
...dimProps("viewMax"),
children: /* @__PURE__ */ jsx(NumInput, {
value: d.viewMax.value,
onChange: (v) => cd.viewMax.value = v,
disabled: editorOff("viewMax"),
step: 1e3
})
}),
/* @__PURE__ */ jsx(StatBoundsRows, {
d,
cd,
disabled
}),
/* @__PURE__ */ jsx("div", {
className: C.familyTitle,
children: "比率 (num/view ≥)"
}),
/* @__PURE__ */ jsx(DimRow, {
label: "点赞率",
tooltip: "like/view ≥;默认仅参与质量排序,开启后按阈值筛选",
...dimProps("likeRate"),
children: rateEditor("likeRate")
}),
/* @__PURE__ */ jsx(DimRow, {
label: "收藏率",
tooltip: "favorite/view ≥",
...dimProps("favRate"),
children: rateEditor("favRate")
}),
/* @__PURE__ */ jsx(DimRow, {
label: "投币率",
tooltip: "coin/view ≥",
...dimProps("coinRate"),
children: rateEditor("coinRate")
}),
/* @__PURE__ */ jsx(DimRow, {
label: "互动率",
tooltip: "(danmaku+reply)/view ≥",
...dimProps("interactRate"),
children: rateEditor("interactRate")
}),
/* @__PURE__ */ jsx(DimRow, {
label: "分享率",
tooltip: "share/view ≥",
...dimProps("shareRate"),
children: rateEditor("shareRate")
}),
/* @__PURE__ */ jsx(DimRow, {
label: "综合分 ≥",
tooltip: "综合分门槛 (0~100); 与比率族同属小池降级失效集",
...dimProps("minScore"),
children: /* @__PURE__ */ jsx(NumInput, {
value: d.minScore.value,
onChange: (v) => cd.minScore.value = v,
disabled: editorOff("minScore"),
max: 100,
step: 5
})
}),
/* @__PURE__ */ jsx("div", {
className: C.familyTitle,
children: "元信息"
}),
/* @__PURE__ */ jsxs(DimRow, {
label: "时长",
tooltip: "预设档快捷选; 自定义按时:分; 端点留 0=不限",
...dimProps("duration"),
children: [/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-130px",
value: durationKey,
disabled: editorOff("duration"),
getPopupContainer,
options: [...DURATION_PRESETS.map((p) => ({
value: p.key,
label: p.label
})), {
value: "custom",
label: "自定义"
}],
onChange: (key) => {
const preset = DURATION_PRESETS.find((p) => p.key === key);
setCustomDur(!preset);
if (preset) {
cd.duration.min = preset.min;
cd.duration.max = preset.max;
}
}
}), durationKey === "custom" && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(DurationHmInput, {
value: d.duration.min,
onChange: (v) => cd.duration.min = v,
disabled: editorOff("duration")
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "~"
}),
/* @__PURE__ */ jsx(DurationHmInput, {
value: d.duration.max,
onChange: (v) => cd.duration.max = v,
disabled: editorOff("duration")
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "0=不限"
})
] })]
}),
/* @__PURE__ */ jsxs(DimRow, {
label: "发布日期",
tooltip: "快捷窗 token / 日期区间 / 一天内时段; 空=未设 (票04)",
...dimProps("pubdate"),
children: [
/* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-100px",
options: [
{
value: "",
label: "不限"
},
{
value: "today",
label: "今天"
},
{
value: "d7",
label: "近 7 天"
},
{
value: "d30",
label: "近 30 天"
},
{
value: "d180",
label: "近半年"
},
{
value: "days",
label: "最近 N 天"
},
{
value: "range",
label: "日期区间"
}
],
value: d.pubdate.win ?? "",
disabled: editorOff("pubdate"),
getPopupContainer,
onChange: (v) => {
cd.pubdate.win = v || void 0;
if (v !== "days") cd.pubdate.days = void 0;
if (v !== "range") cd.pubdate.from = void 0, cd.pubdate.to = void 0;
}
}),
d.pubdate.win === "days" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(NumInput, {
value: d.pubdate.days ?? 0,
onChange: (v) => cd.pubdate.days = v,
disabled: editorOff("pubdate"),
min: 1,
width: "w-70px"
}), /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "天内"
})] }),
d.pubdate.win === "range" && /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(SinceDateInput, {
value: d.pubdate.from ?? 0,
onChange: (v) => cd.pubdate.from = v || void 0,
disabled: editorOff("pubdate")
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "~"
}),
/* @__PURE__ */ jsx(SinceDateInput, {
value: d.pubdate.to ?? 0,
onChange: (v) => cd.pubdate.to = v || void 0,
disabled: editorOff("pubdate")
})
] }),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "时段"
}),
/* @__PURE__ */ jsx(TodInput, {
value: d.pubdate.tod,
onChange: (v) => cd.pubdate.tod = v,
disabled: editorOff("pubdate")
})
]
})
]
})] }),
only !== "numeric" && !hideListDims && /* @__PURE__ */ jsxs("div", {
className: only === "list" ? "flex flex-col gap-y-3px" : "mt-8px flex flex-col gap-y-3px",
children: [
/* @__PURE__ */ jsx("div", {
className: C.familyTitle,
children: "名单 (屏蔽 / 仅看)"
}),
/* @__PURE__ */ jsx(DimRow, {
label: "标题·仅看词",
tooltip: "开 = 只剩标题命中词 (域收窄, 子串 ≥2 字符); 缺字段豁免 (严格模式反转)",
...dimProps("titleAllow"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.titleAllow.list,
render: (k) => k,
disabled: editorOff("titleAllow"),
placeholder: "关键词",
onAdd: (raw) => {
if (cd.titleAllow.list.includes(raw)) {
toast("已存在");
return;
}
cd.titleAllow.list.push(raw);
},
onRemove: (i) => cd.titleAllow.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "标题·屏蔽词",
tooltip: "标题含任一关键词即滤 (不区分大小写)",
...dimProps("titleKeywords"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.titleKeywords.list,
render: (k) => k,
disabled: editorOff("titleKeywords"),
placeholder: "关键词",
onAdd: (raw) => {
if (cd.titleKeywords.list.includes(raw)) {
toast("已存在");
return;
}
cd.titleKeywords.list.push(raw);
},
onRemove: (i) => cd.titleKeywords.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "UP 主·屏蔽",
tooltip: "UP 主名或 mid 命中即滤",
...dimProps("upBlacklist"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.upBlacklist.list,
render: (e) => e.mid !== void 0 ? `${e.name || e.mid} (mid:${e.mid})` : e.name,
disabled: editorOff("upBlacklist"),
placeholder: "UP 名 或 mid",
onAdd: (raw) => {
const mid = /^\d+$/.test(raw) ? Number(raw) : void 0;
const name = mid === void 0 ? raw : "";
if (cd.upBlacklist.list.some((e) => mid !== void 0 ? e.mid === mid : e.name === name)) {
toast("已存在");
return;
}
cd.upBlacklist.list.push({
name,
mid
});
},
onRemove: (i) => cd.upBlacklist.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "UP 主·仅看",
tooltip: "开启过滤后,名单各项都带 UID(可从视频右键加入)时,将在这些 UP 的投稿中按标签名搜索,可能漏掉只写在视频标签里的内容。含纯名字条目仍在原结果中筛选;屏蔽规则优先。",
...dimProps("upAllow"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.upAllow.list,
render: (e) => e.mid !== void 0 ? `${e.name || e.mid} (mid:${e.mid})` : e.name,
disabled: editorOff("upAllow"),
placeholder: "UID(推荐)或 UP 名",
onAdd: (raw) => {
const mid = /^\d+$/.test(raw) ? Number(raw) : void 0;
const name = mid === void 0 ? raw : "";
if (cd.upAllow.list.some((e) => mid !== void 0 ? e.mid === mid : e.name === name)) {
toast("已存在");
return;
}
cd.upAllow.list.push({
name,
mid
});
},
onRemove: (i) => cd.upAllow.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "简介·屏蔽词",
tooltip: "简介含任一关键词即滤 (归一化子串); 仅搜索池/相关池判定, tag/top 池不适用 (票02)",
...dimProps("descKeywords"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.descKeywords.list,
render: (k) => k,
disabled: editorOff("descKeywords"),
placeholder: "关键词",
onAdd: (raw) => {
if (cd.descKeywords.list.includes(raw)) {
toast("已存在");
return;
}
cd.descKeywords.list.push(raw);
},
onRemove: (i) => cd.descKeywords.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "简介·仅看词",
tooltip: "开 = 只剩简介命中词 (域收窄); tag/top 条目无简介 => 豁免 (严格模式反转)",
...dimProps("descAllow"),
children: /* @__PURE__ */ jsx(ChipsEditor, {
items: d.descAllow.list,
render: (k) => k,
disabled: editorOff("descAllow"),
placeholder: "关键词",
onAdd: (raw) => {
if (cd.descAllow.list.includes(raw)) {
toast("已存在");
return;
}
cd.descAllow.list.push(raw);
},
onRemove: (i) => cd.descAllow.list.splice(i, 1)
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "分区·屏蔽",
tooltip: "命中即滤,与仅看池独立。新版一级分区按父子关系判断;搜索、相关池缺字段时补查,未确认的内容保留待检。",
...dimProps("partitionBlock"),
children: /* @__PURE__ */ jsx(PartitionEntryPicker, {
list: cd.partitionBlock.list,
snapList: d.partitionBlock.list,
disabled: editorOff("partitionBlock"),
getPopupContainer
})
}),
/* @__PURE__ */ jsx(DimRow, {
label: "分区·仅看",
tooltip: "只展示确认命中分区的内容;缺字段会分批核验,查不清的内容保留待检。同分区同时屏蔽时,屏蔽优先。",
...dimProps("partitionAllow"),
children: /* @__PURE__ */ jsx(PartitionEntryPicker, {
list: cd.partitionAllow.list,
snapList: d.partitionAllow.list,
disabled: editorOff("partitionAllow"),
getPopupContainer
})
}),
["tagBlock", "tagAllow"].map((key) => /* @__PURE__ */ jsxs("div", {
className: "min-w-0",
children: [/* @__PURE__ */ jsx(DimRow, {
label: key === "tagBlock" ? "标签·屏蔽" : "标签·仅看",
tooltip: key === "tagBlock" ? "命中任一名单标签即屏蔽; 与组内负向叠加。严格校验会对缺标签的视频补查完整标签。" : "在当前查询范围内筛选, 命中任一名单标签即可保留; 不会扩成该标签的独立视频池。屏蔽优先; 严格校验会补查缺失标签。",
...dimProps(key),
children: /* @__PURE__ */ jsx(Checkbox, {
checked: !!d[key].strict,
disabled: editorOff(key),
onChange: (e) => cd[key].strict = e.target.checked,
children: "严格校验"
})
}), /* @__PURE__ */ jsx(TagFilterEntryPicker, {
snapList: d[key].list,
onChange: (entries) => cd[key].list = entries,
disabled: editorOff(key),
confirmLabel: key === "tagBlock" ? "加入屏蔽标签" : "加入仅看标签",
getPopupContainer
})]
}, key)),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mb-4px text-12px",
children: "未开严格时复用已有标签/浅池, 缺标签放行, 不按名单追加 tag/top; 开严格才对待显示的缺标签视频补查。"
})
]
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "mt-4px block text-12px",
children: "组内负向恒为一维 (由各组展开行的负向成员驱动, 无独立开关); 缺字段维度默认豁免放行"
})
]
});
}
function AdvancedSection({ core, snap, disabled, hideStrict, sectionTitle = "高级参数" }) {
const w = snap.score.weights;
const a = snap.score.anchors;
const cw = core.score.weights;
const ca = core.score.anchors;
const wsum = Object.values(w).reduce((s, x) => s + x, 0);
const field = (label, value, onChange, opts) => /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-4px",
children: [/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: label
}), /* @__PURE__ */ jsx(NumInput, {
value,
onChange,
disabled,
width: opts?.width ?? "w-76px",
min: opts?.min ?? 0,
max: opts?.max,
step: opts?.step
})]
});
return /* @__PURE__ */ jsxs("div", {
className: C.block,
children: [/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex items-center text-12px font-bold",
children: [
sectionTitle,
/* @__PURE__ */ jsx(Tag, {
color: "orange",
className: "ml-6px mr-0",
children: "待校准"
}),
/* @__PURE__ */ jsx(HelpInfo, { children: "综合分权重/锚点、小池降级阈值、严格模式均为出厂估值, 尚未实测校准 (spec §3.3); 权重和应为 1" })
]
}), /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-3px",
children: [
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-3px",
children: [field("小池降级阈值", snap.smallPoolThreshold, (v) => core.smallPoolThreshold = v, { step: 5 }), /* @__PURE__ */ jsx(HelpInfo, { children: "轮内池有效条数 < 阈值时, 比率类维度与综合分门槛自动失效 (硬条件仍生效)" })]
}),
!hideStrict && /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-3px",
children: [
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: "严格模式"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: snap.strictMode,
disabled,
onChange: (v) => core.strictMode = v
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "开 = 源缺字段即不过; 关 = 缺字段维度不评估放行 (默认)"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-3px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-120px text-12px",
children: "综合分权重 (Σ≈1)"
}),
field("播放", w.view, (v) => cw.view = v, {
max: 1,
step: .05
}),
field("赞率", w.likeRate, (v) => cw.likeRate = v, {
max: 1,
step: .05
}),
field("收藏率", w.favRate, (v) => cw.favRate = v, {
max: 1,
step: .05
}),
field("投币率", w.coinRate, (v) => cw.coinRate = v, {
max: 1,
step: .05
}),
field("互动率", w.interactRate, (v) => cw.interactRate = v, {
max: 1,
step: .05
}),
/* @__PURE__ */ jsxs(Typography.Text, {
type: Math.abs(wsum - 1) > .001 ? "warning" : "secondary",
className: "text-12px",
children: ["Σ=", wsum.toFixed(2)]
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-10px gap-y-3px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-120px text-12px",
children: "锚点 (分量=1 的参照值)"
}),
field("log10播放", a.viewLog10, (v) => ca.viewLog10 = v, { step: 1 }),
field("赞率", a.likeRate, (v) => ca.likeRate = v, { step: .01 }),
field("收藏率", a.favRate, (v) => ca.favRate = v, { step: .01 }),
field("投币率", a.coinRate, (v) => ca.coinRate = v, { step: .01 }),
field("互动率", a.interactRate, (v) => ca.interactRate = v, { step: .01 })
]
})
]
})]
});
}
function KRow() {
const { k } = useSettingsSnapshot().tagRec;
return /* @__PURE__ */ jsxs("div", {
className: sharedClassNames.settingsLine,
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-60px font-bold",
children: "K"
}),
/* @__PURE__ */ jsx(NumInput, {
value: k,
onChange: (v) => settings.tagRec.k = clampTagRecK(v),
min: 1,
max: 10,
step: 1
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "「主题推荐」每次预取至多 K 个一级组 (1~10),二级组按权重交错展示"
})
]
});
}
/**
* [tag-rec] R8 池筛选行 (只看这些池): 4 复选; false=不取 (稀疏存储, 勾=摘键);
* 纯产出层 (后台照取照算, 不影响算分/深挖/共现); 全关/仅剩一池 => ⚠ 防呆
* - value 可传 core.poolsEnabled (全局/会话) 或 draft 全量记录 (组配置弹层)
* - disabledOf: 上层已关的池在本层不可重开 (覆盖只写 false, 防呆)
*/
function PoolsEnabledRow({ value, onToggle, disabled, disabledOf }) {
const onCount = TAG_REC_ALL_POOL_KEYS.filter((p) => value?.[p] !== false).length;
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-4px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "text-12px font-bold",
children: "池筛选"
}), /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-11px",
children: "选择使用的推荐来源;关闭后不再为本组拉取。共享搜索、其他组和负向校验仍可能请求"
})]
}),
/* @__PURE__ */ jsx("div", {
className: "flex flex-wrap items-center gap-x-12px gap-y-4px",
children: TAG_REC_ALL_POOL_KEYS.map((p) => /* @__PURE__ */ jsx(Checkbox, {
checked: value?.[p] !== false,
disabled: disabled || disabledOf?.(p),
onChange: (e) => onToggle(p, e.target.checked),
children: /* @__PURE__ */ jsx("span", {
className: "text-12px",
children: TAG_REC_POOL_LABELS[p]
})
}, p))
}),
onCount <= 1 && /* @__PURE__ */ jsxs(Typography.Text, {
type: "warning",
className: "mt-4px block text-11px",
children: ["⚠ ", onCount === 0 ? "全部池已关闭 —— 将刷不出视频" : "只剩一个池 —— 可能刷不出视频"]
})
] });
}
/** 差分槽位截形: 写盘/脏检只取 dims/score/smallPoolThreshold 三键 (名单走 negative 容器另判) */
var pickTagRecScopePatch = exports("p",(o) => {
if (!o) return void 0;
const out = {};
if (o.dims !== void 0) out.dims = o.dims;
if (o.score !== void 0) out.score = o.score;
if (o.smallPoolThreshold !== void 0) out.smallPoolThreshold = o.smallPoolThreshold;
return Object.keys(out).length ? out : void 0;
});
/** 池槽覆盖摘要文 (dims 键名走滤除维度中文名; score/阈值各一短词) */
function tagRecPoolSlotSummary(slot) {
const parts = [];
const dims = slot.dims ? Object.keys(slot.dims) : [];
if (dims.length) parts.push(`阈值 ${dims.map((k) => TAG_REC_DROP_DIM_LABELS[k] ?? k).join("·")}`);
if (slot.score) parts.push("算分权重/锚点");
if (slot.smallPoolThreshold !== void 0) parts.push(`小池阈值 ${slot.smallPoolThreshold}`);
return parts.join(" · ") || "(空槽)";
}
function TagRecScopeConfigEditor({ title, hint, core, presets, slots, resolve, resolveBase, storedOverrideOf, negativeOf, resolvePoolsEnabled, basePoolsEnabled, storedPoolsEnabledOf, onSave, onClose, getPopupContainer, statusContent }) {
const [scopeKey, setScopeKey] = (0, import_react.useState)(slots[0]?.key ?? "");
const slot = slots.find((s) => s.key === scopeKey) ?? slots[0];
const isCell = !!slot?.cell;
/** per-scope 草稿 (迟建; 切范围不丢未存编辑) */
const draftsRef = (0, import_react.useRef)(/* @__PURE__ */ new Map());
const draftOf = (key) => {
let d = draftsRef.current.get(key);
if (!d) {
const pools = storedPoolsEnabledOf?.(key) ?? {};
d = proxy({
cfg: cloneTagRecFilterCore(resolve(key)),
neg: cloneDeep(negativeOf?.(key) ?? {}),
pools: { ...pools }
});
draftsRef.current.set(key, d);
}
return d;
};
const draft = slot ? draftOf(slot.key) : void 0;
const emptyDraftRef = (0, import_react.useRef)(void 0);
emptyDraftRef.current ??= {
cfg: core,
neg: {},
pools: {}
};
const snap = useSnapshot(draft ?? emptyDraftRef.current);
const baseCfg = slot ? resolveBase(slot.key) : core;
/** 存盘: 逐已建草稿槽差分 => entries 交调用方组装落位 (调用方定槽位语义) */
const onSaveClick = () => {
const entries = [];
for (const [key, d] of draftsRef.current) {
const s = slots.find((x) => x.key === key);
if (!s) continue;
const raw = diffTagRecScopeOverride(d.cfg, resolveBase(key));
const sparsePools = resolvePoolsEnabled ? (() => {
const rec = {};
for (const p of TAG_REC_POOL_KEYS) if (typeof d.pools[p] === "boolean") rec[p] = d.pools[p];
return Object.keys(rec).length ? rec : void 0;
})() : void 0;
entries.push({
key,
patch: raw && hasTagRecOverride(raw) ? raw : void 0,
negative: s.cell ? void 0 : normalizeTagRecNegativeDraft(d.neg),
...resolvePoolsEnabled ? { poolsEnabled: sparsePools } : {}
});
}
onSave(entries);
onClose();
};
/** 未保存草稿判 —— 与存盘同一差分通道, 逐已建草稿槽比对「将写盘形 vs 现存量」 */
const hasUnsavedDrafts = () => {
for (const [key, d] of draftsRef.current) {
if (!isEqual$1(diffTagRecScopeOverride(d.cfg, resolveBase(key)), storedOverrideOf?.(key))) return true;
if (resolvePoolsEnabled && !isEqual$1(d.pools, storedPoolsEnabledOf?.(key) ?? {})) return true;
if (!slots.find((x) => x.key === key)?.cell && !isEqual$1(normalizeTagRecNegativeDraft(d.neg), normalizeTagRecNegativeDraft(negativeOf?.(key)))) return true;
}
return false;
};
const requestClose = async () => {
if (hasUnsavedDrafts()) {
if (!await antModal.confirm({
centered: true,
title: "放弃未保存的修改?",
content: "各槽草稿改动将整批丢弃",
okText: "放弃",
cancelText: "继续编辑"
})) return;
}
onClose();
};
/** 本槽全回继承: 草稿抄基准 + 名单清空 */
const assignDraftToBase = () => {
if (!draft) return;
const b = cloneTagRecFilterCore(baseCfg);
draft.cfg.dims = b.dims;
draft.cfg.score = b.score;
draft.cfg.smallPoolThreshold = b.smallPoolThreshold;
draft.neg.up = void 0;
draft.neg.upAllow = void 0;
draft.neg.title = void 0;
draft.neg.titleAllow = void 0;
draft.neg.desc = void 0;
draft.neg.descAllow = void 0;
draft.neg.partitionBlock = void 0;
draft.neg.partitionAllow = void 0;
draft.neg.partition = void 0;
draft.neg.strict = void 0;
draft.neg.tagBlock = void 0;
draft.neg.tagAllow = void 0;
if (resolvePoolsEnabled) draft.pools = {};
};
const overriddenDims = TAG_REC_PATCHABLE_DIM_KEYS.filter((k) => !isEqual$1(snap.cfg.dims[k], baseCfg.dims[k]));
const overriddenScore = [...Object.keys(snap.cfg.score.weights).filter((k) => snap.cfg.score.weights[k] !== baseCfg.score.weights[k]).map((k) => ({
sec: "weights",
k
})), ...Object.keys(snap.cfg.score.anchors).filter((k) => snap.cfg.score.anchors[k] !== baseCfg.score.anchors[k]).map((k) => ({
sec: "anchors",
k
}))];
const thresholdOverridden = snap.cfg.smallPoolThreshold !== baseCfg.smallPoolThreshold;
const overrideCount = overriddenDims.length + overriddenScore.length + (thresholdOverridden ? 1 : 0);
const resetDims = (k) => {
if (!draft) return;
draft.cfg.dims[k] = cloneDeep(baseCfg.dims[k]);
};
const overrideChips = /* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-4px text-12px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "本层覆盖:"
}),
overriddenDims.map((k) => /* @__PURE__ */ jsx(Tag, {
closable: true,
onClose: () => resetDims(k),
className: "mr-0",
children: TAG_REC_DROP_DIM_LABELS[k]
}, k)),
overriddenScore.map(({ sec, k }) => /* @__PURE__ */ jsxs(Tag, {
closable: true,
onClose: () => {
if (!draft) return;
draft.cfg.score[sec][k] = baseCfg.score[sec][k];
},
className: "mr-0",
children: [
sec === "weights" ? "权重" : "锚点",
"·",
k
]
}, `${sec}.${k}`)),
thresholdOverridden && draft && /* @__PURE__ */ jsx(Tag, {
closable: true,
onClose: () => draft.cfg.smallPoolThreshold = baseCfg.smallPoolThreshold,
className: "mr-0",
children: "小池阈值"
}),
!overrideCount && /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "无 (全部继承)"
})
]
});
if (!slot || !draft) return null;
const negDraft = draft.neg;
const negSnap = snap.neg;
const namesTab = {
key: "negative",
label: "名单",
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "屏蔽名单与上下层叠加, 屏蔽优先。标签仅看按「会话 > 二级组 > 一级组 > 全局」取最近的开启非空名单, 命中其中任一标签即可保留。UP 主/标题/简介/分区仅看同为域收窄 (就近覆盖), 与同实体屏蔽池同存时屏蔽先判赢。"
}),
["tagBlock", "tagAllow"].map((key) => {
const rule = negSnap[key];
const update = () => negDraft[key] ??= {
enabled: false,
strict: false,
list: []
};
return /* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: key === "tagBlock" ? "标签·屏蔽" : "标签·仅看"
}), /* @__PURE__ */ jsxs("div", {
className: "min-w-0 flex-1",
children: [/* @__PURE__ */ jsxs("div", {
className: "flex flex-wrap items-center gap-x-8px",
children: [/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!rule?.enabled,
onChange: (value) => update().enabled = value
}), /* @__PURE__ */ jsx(Checkbox, {
checked: !!rule?.strict,
disabled: !rule?.enabled,
onChange: (e) => update().strict = e.target.checked,
children: "严格校验"
})]
}), /* @__PURE__ */ jsx(TagFilterEntryPicker, {
snapList: rule?.list ?? [],
onChange: (entries) => update().list = entries,
disabled: !rule?.enabled,
confirmLabel: key === "tagBlock" ? "加入屏蔽标签" : "加入仅看标签",
getPopupContainer
})]
})]
}, key);
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "未开严格: 用已有标签/浅池命中, 缺标签放行, 不额外取 tag/top。开严格: 仅对待显示的缺标签视频补查, 两侧与右键浮窗共用缓存; 失败/超时/退避放行并记未校验。"
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "组内负向·严格校验"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!negSnap.strict,
onChange: (v) => negDraft.strict = v || void 0
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "开 = 补查原组内负向成员的命中 (默认关)"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "UP 主·屏蔽"
}), /* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.up ?? [],
render: (e) => e.mid !== void 0 ? `${e.name || e.mid} (mid:${e.mid})` : e.name,
placeholder: "UP 名 或 mid",
onAdd: (raw) => {
const mid = /^\d+$/.test(raw) ? Number(raw) : void 0;
const name = mid === void 0 ? raw : "";
const list = negDraft.up ??= [];
if (list.some((e) => mid !== void 0 ? e.mid === mid : e.name === name)) return toast("已存在");
list.push({
name,
mid
});
},
onRemove: (i) => negDraft.up?.splice(i, 1)
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "UP 主·仅看"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!negSnap.upAllow?.enabled,
onChange: (v) => {
negDraft.upAllow ??= {
enabled: false,
list: []
};
negDraft.upAllow.enabled = v;
}
}),
/* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.upAllow?.list ?? [],
render: (e) => e.mid !== void 0 ? `${e.name || e.mid} (mid:${e.mid})` : e.name,
placeholder: "UID(推荐)或 UP 名",
onAdd: (raw) => {
const mid = /^\d+$/.test(raw) ? Number(raw) : void 0;
const name = mid === void 0 ? raw : "";
const rule = negDraft.upAllow ??= {
enabled: false,
list: []
};
if (rule.list.some((e) => mid !== void 0 ? e.mid === mid : e.name === name)) return toast("已存在");
rule.list.push({
name,
mid
});
},
onRemove: (i) => negDraft.upAllow?.list.splice(i, 1)
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "开启过滤后,名单各项都带 UID 时从 UP 投稿中按标签名搜索,可能漏掉仅视频标签命中的内容;含纯名字条目仍筛选原结果。屏蔽规则优先。"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "标题·屏蔽词"
}), /* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.title ?? [],
render: (k) => k,
placeholder: "关键词 (归一化子串, ≥2 字符)",
onAdd: (raw) => {
const list = negDraft.title ??= [];
if (list.includes(raw)) return toast("已存在");
list.push(raw);
},
onRemove: (i) => negDraft.title?.splice(i, 1)
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "标题·仅看词"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!negSnap.titleAllow?.enabled,
onChange: (v) => {
negDraft.titleAllow ??= {
enabled: false,
list: []
};
negDraft.titleAllow.enabled = v;
}
}),
/* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.titleAllow?.list ?? [],
render: (k) => k,
placeholder: "关键词",
onAdd: (raw) => {
const rule = negDraft.titleAllow ??= {
enabled: false,
list: []
};
if (rule.list.includes(raw)) return toast("已存在");
rule.list.push(raw);
},
onRemove: (i) => negDraft.titleAllow?.list.splice(i, 1)
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "开 = 本组流量只剩标题命中词 (域收窄); 缺字段豁免 (严格模式反转)"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "简介·屏蔽词"
}), /* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.desc ?? [],
render: (k) => k,
placeholder: "关键词 (仅搜索池/相关池判定)",
onAdd: (raw) => {
const list = negDraft.desc ??= [];
if (list.includes(raw)) return toast("已存在");
list.push(raw);
},
onRemove: (i) => negDraft.desc?.splice(i, 1)
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "简介·仅看词"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!negSnap.descAllow?.enabled,
onChange: (v) => {
negDraft.descAllow ??= {
enabled: false,
list: []
};
negDraft.descAllow.enabled = v;
}
}),
/* @__PURE__ */ jsx(ChipsEditor, {
items: negSnap.descAllow?.list ?? [],
render: (k) => k,
placeholder: "关键词",
onAdd: (raw) => {
const rule = negDraft.descAllow ??= {
enabled: false,
list: []
};
if (rule.list.includes(raw)) return toast("已存在");
rule.list.push(raw);
},
onRemove: (i) => negDraft.descAllow?.list.splice(i, 1)
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "开 = 本组流量只剩简介命中词 (域收窄); tag/top 条目无简介 => 豁免 (严格模式反转)"
})
]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "分区·屏蔽"
}), /* @__PURE__ */ jsx(PartitionEntryPicker, {
list: negDraft.partitionBlock ??= [],
snapList: negSnap.partitionBlock ?? [],
getPopupContainer
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-start gap-x-8px",
children: [
/* @__PURE__ */ jsx("span", {
className: "min-w-110px text-12px font-bold",
children: "分区·仅看"
}),
/* @__PURE__ */ jsx(Switch, {
size: "small",
checked: !!negSnap.partitionAllow?.enabled,
onChange: (v) => {
negDraft.partitionAllow ??= {
enabled: false,
list: []
};
negDraft.partitionAllow.enabled = v;
}
}),
/* @__PURE__ */ jsx(PartitionEntryPicker, {
list: (negDraft.partitionAllow ??= {
enabled: false,
list: []
}).list,
snapList: negSnap.partitionAllow?.list ?? [],
getPopupContainer
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "默认关防误杀; 开 = 流量只剩命中分区 (与屏蔽池独立, 同分区屏蔽先判赢)"
})
]
})
]
})
};
const dimsTab = {
key: "dims",
label: /* @__PURE__ */ jsx(Badge, {
count: overriddenDims.length,
size: "small",
offset: [6, 0],
children: "阈值"
}),
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [
overrideChips,
!isCell && resolvePoolsEnabled && /* @__PURE__ */ jsx("div", {
className: "flex flex-wrap gap-8px",
children: TAG_REC_POOL_KEYS.map((p) => /* @__PURE__ */ jsxs("label", { children: [
TAG_REC_POOL_LABELS[p],
" ",
/* @__PURE__ */ jsx(Select, {
size: "small",
value: snap.pools[p] === void 0 ? "inherit" : snap.pools[p] ? "on" : "off",
options: [
{
value: "inherit",
label: `继承(${basePoolsEnabled?.(scopeKey)?.[p] === false ? "不使用" : "使用"})`
},
{
value: "on",
label: "使用"
},
{
value: "off",
label: "不使用"
}
],
onChange: (value) => {
if (value === "inherit") delete draft.pools[p];
else draft.pools[p] = value === "on";
}
})
] }, p))
}),
/* @__PURE__ */ jsx(DimsSection, {
core: draft.cfg,
snap: snap.cfg,
disabled: false,
getPopupContainer,
hideListDims: true
})
]
})
};
const scoreTab = {
key: "score",
label: /* @__PURE__ */ jsx(Badge, {
count: overriddenScore.length + (thresholdOverridden ? 1 : 0),
size: "small",
offset: [6, 0],
children: "算分"
}),
children: /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-8px",
children: [overrideChips, /* @__PURE__ */ jsx(AdvancedSection, {
core: draft.cfg,
snap: snap.cfg,
disabled: false,
hideStrict: true,
sectionTitle: "算分与小池降级"
})]
})
};
const tabs = isCell ? [dimsTab, scoreTab] : [
namesTab,
dimsTab,
scoreTab
];
return /* @__PURE__ */ jsxs(Modal, {
open: true,
title: /* @__PURE__ */ jsxs("span", {
className: "inline-flex items-center gap-x-10px",
children: [title, slots.length > 1 && /* @__PURE__ */ jsx(Select, {
size: "small",
className: "w-190px",
value: scopeKey,
getPopupContainer,
options: slots.map((s) => ({
value: s.key,
label: s.label
})),
onChange: setScopeKey
})]
}),
width: 720,
maskClosable: false,
onCancel: () => void requestClose(),
onOk: onSaveClick,
okText: "保存",
cancelText: "取消",
children: [
/* @__PURE__ */ jsxs("div", {
className: "mb-6px flex items-center gap-x-8px",
children: [/* @__PURE__ */ jsxs(Typography.Text, {
type: "secondary",
className: "text-12px",
children: ["生效链: 会话 > 格(组×池) > 组 > L1 > 池 > 全局;", isCell ? " 本槽只装阈值/算分 (名单不进此层)" : " 应用预设可把整套参数填进当前槽"]
}), !isCell && !!presets?.length && /* @__PURE__ */ jsx(Select, {
size: "small",
className: "ml-auto w-130px",
placeholder: "应用预设…",
options: presets.map((p) => ({
value: p.name,
label: p.name
})),
getPopupContainer,
onChange: (name) => {
if (applyTagRecPresetToCore(presets, draft.cfg, name)) toast(`已把预设「${name}」填入当前槽 (保存生效)`);
}
})]
}),
hint && /* @__PURE__ */ jsx("div", {
className: "mb-6px",
children: /* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: hint
})
}),
/* @__PURE__ */ jsxs("div", {
className: "max-h-60vh overflow-y-auto pr-4px",
children: [
statusContent,
/* @__PURE__ */ jsx(Tabs, {
size: "small",
items: tabs
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "「本层覆盖」表示与上层不同,包含内置配置和手动修改。关闭徽标或保存与上层相同的值可恢复继承。改动保存后生效。"
}),
/* @__PURE__ */ jsx(Popconfirm, {
title: "将清空本槽自定义与本槽名单(含已保存项),确定?",
okText: "恢复继承",
cancelText: "取消",
getPopupContainer,
onConfirm: assignDraftToBase,
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
className: "mt-6px",
children: "恢复本槽全部继承"
})
})
]
})
]
});
}
/**
* 池层槽位块 (R6): 四池各一行「覆盖摘要 + 深编⚙ + 清空」—— 写 core.pools[p] (TagRecScopeOverride 槽)
* - 复用点: FilterForm (全局+会话两处), ShareFilterEditor 池级覆盖 tab, 预设内嵌 CoreEditor
* - 深编弹同款 ScopeConfigEditor (单槽无 Select): 生效解 = 全局链+本池槽; 基准 = 摘槽后全局链
* (allV2Related 摘槽后 score 回落 allV2 槽 —— resolveTagRecEffective 内置口径)
* - 会话 scope 同样可调: 引擎对 sessionCore.pools 照常生效 (格层遮蔽不影响池层)
*/
function TagRecPoolSlotsBlock({ core, disabled, getPopupContainer }) {
const snap = useSnapshot(core);
const [editing, setEditing] = (0, import_react.useState)();
return /* @__PURE__ */ jsxs("div", {
className: "flex flex-col gap-y-6px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "池层槽位 = 按来源池覆盖阈值/算分/小池阈值 (在 组/L1 之下、全局之上; 名单不进池层)。 缺省槽 = 全继承全局;「搜索相关池」算分缺槽时回落「搜索池」档 (两源字段形同构)。"
}),
/* @__PURE__ */ jsx(Typography.Text, {
type: "secondary",
className: "text-12px",
children: "搜索池的内置配置不计投币权重,以适配缺少投币数的结果,通常无需手动调整。"
}),
TAG_REC_POOL_KEYS.map((pk) => {
const slot = snap.pools?.[pk];
return /* @__PURE__ */ jsx("div", {
className: "b-1px b-gate-border rounded-6px b-dashed px-8px py-6px",
children: /* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-x-8px",
children: [
/* @__PURE__ */ jsx(Typography.Text, {
strong: true,
className: "text-12px",
children: TAG_REC_POOL_LABELS[pk]
}),
/* @__PURE__ */ jsx("span", {
className: "text-12px",
children: slot ? `已覆盖: ${tagRecPoolSlotSummary(slot)}` : "全继承 (无槽)"
}),
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
/* @__PURE__ */ jsx(button_default, {
size: "small",
disabled,
onClick: () => setEditing(pk),
children: "⚙ 深编"
}),
slot && /* @__PURE__ */ jsx(Popconfirm, {
title: `清空 ${TAG_REC_POOL_LABELS[pk]} 的覆盖?`,
description: "槽整摘 = 该池回上层继承",
onConfirm: () => {
if (core.pools) {
delete core.pools[pk];
if (!Object.keys(core.pools).length) delete core.pools;
}
},
children: /* @__PURE__ */ jsx(button_default, {
size: "small",
danger: true,
disabled,
children: "清空"
})
})
]
})
}, pk);
}),
editing && /* @__PURE__ */ jsx(TagRecScopeConfigEditor, {
title: `${TAG_REC_POOL_LABELS[editing]} · 池层槽`,
core,
slots: [{
key: `pool:${editing}`,
label: TAG_REC_POOL_LABELS[editing],
cell: true
}],
resolve: () => resolveTagRecEffective(core, editing).cfg,
resolveBase: () => resolveTagRecEffective({
...core,
pools: {
...core.pools,
[editing]: void 0
}
}, editing).cfg,
storedOverrideOf: () => pickTagRecScopePatch(core.pools?.[editing]),
onSave: (entries) => {
const e = entries.find((x) => x.key === `pool:${editing}`);
if (!e) return;
if (e.patch) (core.pools ??= {})[editing] = e.patch;
else if (core.pools) {
delete core.pools[editing];
if (!Object.keys(core.pools).length) delete core.pools;
}
toast("已保存池层槽位");
},
onClose: () => setEditing(void 0),
getPopupContainer
})
]
});
}
/** 恢复浏览过的内容,从头召回但保留标签关系;两 tab 的内存状态在下次刷新时重新建立。 */
function clearTagRecSeenRecords() {
seen.clear();
sediment.resetDigProgress();
notifyTagRecSeenCleared();
tagRecSharedSession.recoveryRevision += 1;
}
/** 观察语料与进度可重新采集;刷新时同时丢弃服务内的旧挖掘进度。 */
function clearTagRecSedimentRecords() {
sediment.clear();
tagRecSharedSession.recoveryRevision += 1;
}
var TabPaneKey = exports("T",/* @__PURE__ */ function(TabPaneKey) {
TabPaneKey["Basic"] = "basic";
TabPaneKey["VideoCard"] = "video-card";
TabPaneKey["Filter"] = "filter";
TabPaneKey["ThemeSelect"] = "theme-select";
TabPaneKey["VideoSourceTabConfig"] = "video-source-tab-config";
TabPaneKey["OtherPages"] = "other-pages";
TabPaneKey["Advance"] = "advance";
TabPaneKey["TagRecFavorites"] = "tag-rec-favorites";
TabPaneKey["TagRecFilter"] = "tag-rec-filter";
return TabPaneKey;
}({}));
var modalSettingsStore = exports("m",proxy({ tab: "tag-rec-favorites" }));
function tagRecDiscoveryPolicy(settings = {}) {
const number = (value, fallback, min, max) => value === void 0 || !Number.isFinite(value) ? fallback : Math.min(max, Math.max(min, Math.round(value)));
return {
mode: settings.mode === "fixed" ? "fixed" : "adaptive",
autoDigMax: number(settings.autoDigMax, 3, 1, 20),
lowYield: number(settings.lowYield, 3, 1, 50),
lowYieldStreakLimit: number(settings.lowYieldStreakLimit, 2, 1, 20),
emptyTtlMinutes: number(settings.emptyTtlMinutes, 60, 5, 1440)
};
}})}}));
System.register("./risk-log-ZLVUftoN-DnHKear8.js", ['axios'],(function(exports,module){'use strict';var isAxiosError;return{setters:[function(module){isAxiosError=module.isAxiosError;}],execute:(function(){exports({C:pLimit,J:ensureTagRecFingerprint,K:gmAnonGet,L:fingerprintCookieHeader,M:isRiskControlResponse,h:clear,i:isRiskControlError,j:dump,k:recordAnonRequest,l:recordUpstreamResponse,m:createReactRoot,p:pmap,s:summary,v:retryAfterDelay,w:waitForAbortable});var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esmMin = exports("S",(fn, res, err) => () => {
if (err) throw err[0];
try {
return fn && (res = fn(fn = 0)), res;
} catch (e) {
throw err = [e], e;
}
});
var __commonJSMin = exports("y",(cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports));
var __exportAll = exports("U",(all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
__defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
});
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = exports("_",(mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod)));
var __toCommonJS = exports("P",(mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod));
/**
* @license React
* react.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var require_react_production = /* @__PURE__ */ __commonJSMin(((exports) => {
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_CONSUMER_TYPE = Symbol.for("react.consumer");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_ACTIVITY_TYPE = Symbol.for("react.activity");
var REACT_VIEW_TRANSITION_TYPE = Symbol.for("react.view_transition");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
function getIteratorFn(maybeIterable) {
if (null === maybeIterable || "object" !== typeof maybeIterable) return null;
maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable["@@iterator"];
return "function" === typeof maybeIterable ? maybeIterable : null;
}
var ReactNoopUpdateQueue = {
isMounted: function() {
return false;
},
enqueueForceUpdate: function() {},
enqueueReplaceState: function() {},
enqueueSetState: function() {}
};
var assign = Object.assign;
var emptyObject = {};
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
if ("object" !== typeof partialState && "function" !== typeof partialState && null != partialState) throw Error("takes an object of state variables to update or a function which returns an object of state variables.");
this.updater.enqueueSetState(this, partialState, callback, "setState");
};
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
var pureComponentPrototype = PureComponent.prototype = new ComponentDummy();
pureComponentPrototype.constructor = PureComponent;
assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
var isArrayImpl = Array.isArray;
function noop() {}
var ReactSharedInternals = {
H: null,
A: null,
T: null,
S: null
};
var hasOwnProperty = Object.prototype.hasOwnProperty;
function ReactElement(type, key, props) {
var refProp = props.ref;
return {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref: void 0 !== refProp ? refProp : null,
props
};
}
function cloneAndReplaceKey(oldElement, newKey) {
return ReactElement(oldElement.type, newKey, oldElement.props);
}
function isValidElement(object) {
return "object" === typeof object && null !== object && object.$$typeof === REACT_ELEMENT_TYPE;
}
function escape(key) {
var escaperLookup = {
"=": "=0",
":": "=2"
};
return "$" + key.replace(/[=:]/g, function(match) {
return escaperLookup[match];
});
}
var userProvidedKeyEscapeRegex = /\/+/g;
function getElementKey(element, index) {
return "object" === typeof element && null !== element && null != element.key ? escape("" + element.key) : index.toString(36);
}
function resolveThenable(thenable) {
switch (thenable.status) {
case "fulfilled": return thenable.value;
case "rejected": throw thenable.reason;
default: switch ("string" === typeof thenable.status ? thenable.then(noop, noop) : (thenable.status = "pending", thenable.then(function(fulfilledValue) {
"pending" === thenable.status && (thenable.status = "fulfilled", thenable.value = fulfilledValue);
}, function(error) {
"pending" === thenable.status && (thenable.status = "rejected", thenable.reason = error);
})), thenable.status) {
case "fulfilled": return thenable.value;
case "rejected": throw thenable.reason;
}
}
throw thenable;
}
function mapIntoArray(children, array, escapedPrefix, nameSoFar, callback) {
var type = typeof children;
if ("undefined" === type || "boolean" === type) children = null;
var invokeCallback = false;
if (null === children) invokeCallback = true;
else switch (type) {
case "bigint":
case "string":
case "number":
invokeCallback = true;
break;
case "object": switch (children.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
invokeCallback = true;
break;
case REACT_LAZY_TYPE: return invokeCallback = children._init, mapIntoArray(invokeCallback(children._payload), array, escapedPrefix, nameSoFar, callback);
}
}
if (invokeCallback) return callback = callback(children), invokeCallback = "" === nameSoFar ? "." + getElementKey(children, 0) : nameSoFar, isArrayImpl(callback) ? (escapedPrefix = "", null != invokeCallback && (escapedPrefix = invokeCallback.replace(userProvidedKeyEscapeRegex, "$&/") + "/"), mapIntoArray(callback, array, escapedPrefix, "", function(c) {
return c;
})) : null != callback && (isValidElement(callback) && (callback = cloneAndReplaceKey(callback, escapedPrefix + (null == callback.key || children && children.key === callback.key ? "" : ("" + callback.key).replace(userProvidedKeyEscapeRegex, "$&/") + "/") + invokeCallback)), array.push(callback)), 1;
invokeCallback = 0;
var nextNamePrefix = "" === nameSoFar ? "." : nameSoFar + ":";
if (isArrayImpl(children)) for (var i = 0; i < children.length; i++) nameSoFar = children[i], type = nextNamePrefix + getElementKey(nameSoFar, i), invokeCallback += mapIntoArray(nameSoFar, array, escapedPrefix, type, callback);
else if (i = getIteratorFn(children), "function" === typeof i) for (children = i.call(children), i = 0; !(nameSoFar = children.next()).done;) nameSoFar = nameSoFar.value, type = nextNamePrefix + getElementKey(nameSoFar, i++), invokeCallback += mapIntoArray(nameSoFar, array, escapedPrefix, type, callback);
else if ("object" === type) {
if ("function" === typeof children.then) return mapIntoArray(resolveThenable(children), array, escapedPrefix, nameSoFar, callback);
array = String(children);
throw Error("Objects are not valid as a React child (found: " + ("[object Object]" === array ? "object with keys {" + Object.keys(children).join(", ") + "}" : array) + "). If you meant to render a collection of children, use an array instead.");
}
return invokeCallback;
}
function mapChildren(children, func, context) {
if (null == children) return children;
var result = [], count = 0;
mapIntoArray(children, result, "", "", function(child) {
return func.call(context, child, count++);
});
return result;
}
function lazyInitializer(payload) {
if (-1 === payload._status) {
var ctor = payload._result, thenable = ctor();
thenable.then(function(moduleObject) {
if (0 === payload._status || -1 === payload._status) payload._status = 1, payload._result = moduleObject, void 0 === thenable.status && (thenable.status = "fulfilled", thenable.value = moduleObject);
}, function(error) {
if (0 === payload._status || -1 === payload._status) payload._status = 2, payload._result = error, void 0 === thenable.status && (thenable.status = "rejected", thenable.reason = error);
});
-1 === payload._status && (payload._status = 0, payload._result = thenable);
}
if (1 === payload._status) return payload._result.default;
throw payload._result;
}
var reportGlobalError = "function" === typeof reportError ? reportError : function(error) {
if ("object" === typeof window && "function" === typeof window.ErrorEvent) {
var event = new window.ErrorEvent("error", {
bubbles: true,
cancelable: true,
message: "object" === typeof error && null !== error && "string" === typeof error.message ? String(error.message) : String(error),
error
});
if (!window.dispatchEvent(event)) return;
} else if ("object" === typeof process && "function" === typeof process.emit) {
process.emit("uncaughtException", error);
return;
}
console.error(error);
};
function startTransition(scope) {
var prevTransition = ReactSharedInternals.T, currentTransition = {};
currentTransition.types = null !== prevTransition ? prevTransition.types : null;
ReactSharedInternals.T = currentTransition;
try {
var returnValue = scope(), onStartTransitionFinish = ReactSharedInternals.S;
null !== onStartTransitionFinish && onStartTransitionFinish(currentTransition, returnValue);
"object" === typeof returnValue && null !== returnValue && "function" === typeof returnValue.then && returnValue.then(noop, reportGlobalError);
} catch (error) {
reportGlobalError(error);
} finally {
null !== prevTransition && null !== currentTransition.types && (prevTransition.types = currentTransition.types), ReactSharedInternals.T = prevTransition;
}
}
function addTransitionType(type) {
var transition = ReactSharedInternals.T;
if (null !== transition) {
var transitionTypes = transition.types;
null === transitionTypes ? transition.types = [type] : -1 === transitionTypes.indexOf(type) && transitionTypes.push(type);
} else startTransition(addTransitionType.bind(null, type));
}
var Children = {
map: mapChildren,
forEach: function(children, forEachFunc, forEachContext) {
mapChildren(children, function() {
forEachFunc.apply(this, arguments);
}, forEachContext);
},
count: function(children) {
var n = 0;
mapChildren(children, function() {
n++;
});
return n;
},
toArray: function(children) {
return mapChildren(children, function(child) {
return child;
}) || [];
},
only: function(children) {
if (!isValidElement(children)) throw Error("React.Children.only expected to receive a single React element child.");
return children;
}
};
exports.Activity = REACT_ACTIVITY_TYPE;
exports.Children = Children;
exports.Component = Component;
exports.Fragment = REACT_FRAGMENT_TYPE;
exports.Profiler = REACT_PROFILER_TYPE;
exports.PureComponent = PureComponent;
exports.StrictMode = REACT_STRICT_MODE_TYPE;
exports.Suspense = REACT_SUSPENSE_TYPE;
exports.ViewTransition = REACT_VIEW_TRANSITION_TYPE;
exports.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = ReactSharedInternals;
exports.__COMPILER_RUNTIME = {
__proto__: null,
c: function(size) {
return ReactSharedInternals.H.useMemoCache(size);
}
};
exports.addTransitionType = addTransitionType;
exports.cache = function(fn) {
return function() {
return fn.apply(null, arguments);
};
};
exports.cacheSignal = function() {
return null;
};
exports.cloneElement = function(element, config, children) {
if (null === element || void 0 === element) throw Error("The argument must be a React element, but you passed " + element + ".");
var props = assign({}, element.props), key = element.key;
if (null != config) for (propName in void 0 !== config.key && (key = "" + config.key), config) !hasOwnProperty.call(config, propName) || "key" === propName || "__self" === propName || "__source" === propName || "ref" === propName && void 0 === config.ref || (props[propName] = config[propName]);
var propName = arguments.length - 2;
if (1 === propName) props.children = children;
else if (1 < propName) {
for (var childArray = Array(propName), i = 0; i < propName; i++) childArray[i] = arguments[i + 2];
props.children = childArray;
}
return ReactElement(element.type, key, props);
};
exports.createContext = function(defaultValue) {
defaultValue = {
$$typeof: REACT_CONTEXT_TYPE,
_currentValue: defaultValue,
_currentValue2: defaultValue,
_threadCount: 0,
Provider: null,
Consumer: null
};
defaultValue.Provider = defaultValue;
defaultValue.Consumer = {
$$typeof: REACT_CONSUMER_TYPE,
_context: defaultValue
};
return defaultValue;
};
exports.createElement = function(type, config, children) {
var propName, props = {}, key = null;
if (null != config) for (propName in void 0 !== config.key && (key = "" + config.key), config) hasOwnProperty.call(config, propName) && "key" !== propName && "__self" !== propName && "__source" !== propName && (props[propName] = config[propName]);
var childrenLength = arguments.length - 2;
if (1 === childrenLength) props.children = children;
else if (1 < childrenLength) {
for (var childArray = Array(childrenLength), i = 0; i < childrenLength; i++) childArray[i] = arguments[i + 2];
props.children = childArray;
}
if (type && type.defaultProps) for (propName in childrenLength = type.defaultProps, childrenLength) void 0 === props[propName] && (props[propName] = childrenLength[propName]);
return ReactElement(type, key, props);
};
exports.createRef = function() {
return { current: null };
};
exports.forwardRef = function(render) {
return {
$$typeof: REACT_FORWARD_REF_TYPE,
render
};
};
exports.isValidElement = isValidElement;
exports.lazy = function(ctor) {
return {
$$typeof: REACT_LAZY_TYPE,
_payload: {
_status: -1,
_result: ctor
},
_init: lazyInitializer
};
};
exports.memo = function(type, compare) {
return {
$$typeof: REACT_MEMO_TYPE,
type,
compare: void 0 === compare ? null : compare
};
};
exports.startTransition = startTransition;
exports.unstable_useCacheRefresh = function() {
return ReactSharedInternals.H.useCacheRefresh();
};
exports.use = function(usable) {
return ReactSharedInternals.H.use(usable);
};
exports.useActionState = function(action, initialState, permalink) {
return ReactSharedInternals.H.useActionState(action, initialState, permalink);
};
exports.useCallback = function(callback, deps) {
return ReactSharedInternals.H.useCallback(callback, deps);
};
exports.useContext = function(Context) {
return ReactSharedInternals.H.useContext(Context);
};
exports.useDebugValue = function() {};
exports.useDeferredValue = function(value, initialValue) {
return ReactSharedInternals.H.useDeferredValue(value, initialValue);
};
exports.useEffect = function(create, deps) {
return ReactSharedInternals.H.useEffect(create, deps);
};
exports.useEffectEvent = function(callback) {
return ReactSharedInternals.H.useEffectEvent(callback);
};
exports.useId = function() {
return ReactSharedInternals.H.useId();
};
exports.useImperativeHandle = function(ref, create, deps) {
return ReactSharedInternals.H.useImperativeHandle(ref, create, deps);
};
exports.useInsertionEffect = function(create, deps) {
return ReactSharedInternals.H.useInsertionEffect(create, deps);
};
exports.useLayoutEffect = function(create, deps) {
return ReactSharedInternals.H.useLayoutEffect(create, deps);
};
exports.useMemo = function(create, deps) {
return ReactSharedInternals.H.useMemo(create, deps);
};
exports.useOptimistic = function(passthrough, reducer) {
return ReactSharedInternals.H.useOptimistic(passthrough, reducer);
};
exports.useReducer = function(reducer, initialArg, init) {
return ReactSharedInternals.H.useReducer(reducer, initialArg, init);
};
exports.useRef = function(initialValue) {
return ReactSharedInternals.H.useRef(initialValue);
};
exports.useState = function(initialState) {
return ReactSharedInternals.H.useState(initialState);
};
exports.useSyncExternalStore = function(subscribe, getSnapshot, getServerSnapshot) {
return ReactSharedInternals.H.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
};
exports.useTransition = function() {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.3.0";
}));
var require_react = exports("r",/* @__PURE__ */ __commonJSMin(((exports, module) => {
module.exports = require_react_production();
})));
/**
* Coerce `value`.
*/
function coerce(value) {
if (value instanceof Error) return value.stack || value.message;
return value;
}
/**
* Selects a color for a debug namespace
* @return An ANSI color code for the given namespace
*/
function selectColor(colors, namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = (hash << 5) - hash + namespace.charCodeAt(i);
hash |= 0;
}
return colors[Math.abs(hash) % colors.length];
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*/
function matchesTemplate(search, template) {
let searchIndex = 0;
let templateIndex = 0;
let starIndex = -1;
let matchIndex = 0;
while (searchIndex < search.length) if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === "*")) {
if (template[templateIndex] === "*") {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++;
} else {
searchIndex++;
templateIndex++;
}
} else if (starIndex !== -1) {
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else return false;
while (templateIndex < template.length && template[templateIndex] === "*") templateIndex++;
return templateIndex === template.length;
}
function humanize(value) {
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}s`;
return `${value}ms`;
}
var globalNamespaces = "";
function createDebug$2(namespace, options) {
let prevTime;
let enableOverride;
let namespacesCache;
let enabledCache;
const debug = ((...args) => {
if (!debug.enabled) return;
const curr = Date.now();
const diff = curr - (prevTime || curr);
prevTime = curr;
args[0] = coerce(args[0]);
if (typeof args[0] !== "string") args.unshift("%O");
let index = 0;
args[0] = args[0].replace(/%([a-z%])/gi, (match, format) => {
if (match === "%%") return "%";
index++;
const formatter = options.formatters[format];
if (typeof formatter === "function") {
const value = args[index];
match = formatter.call(debug, value);
args.splice(index, 1);
index--;
}
return match;
});
options.formatArgs.call(debug, diff, args);
debug.log(...args);
});
debug.extend = function(namespace, delimiter = ":") {
return createDebug$2(this.namespace + delimiter + namespace, {
useColors: this.useColors,
color: this.color,
formatArgs: this.formatArgs,
formatters: this.formatters,
inspectOpts: this.inspectOpts,
log: this.log,
humanize: this.humanize
});
};
Object.assign(debug, options);
debug.namespace = namespace;
Object.defineProperty(debug, "enabled", {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride != null) return enableOverride;
if (namespacesCache !== globalNamespaces) {
namespacesCache = globalNamespaces;
enabledCache = enabled(namespace);
}
return enabledCache;
},
set: (v) => {
enableOverride = v;
}
});
return debug;
}
var names = [];
var skips = [];
function enable(namespaces) {
globalNamespaces = namespaces;
names = [];
skips = [];
const split = globalNamespaces.trim().replace(/\s+/g, ",").split(",").filter(Boolean);
for (const ns of split) if (ns[0] === "-") skips.push(ns.slice(1));
else names.push(ns);
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*/
function enabled(name) {
for (const skip of skips) if (matchesTemplate(name, skip)) return false;
for (const ns of names) if (matchesTemplate(name, ns)) return true;
return false;
}
function formatArgs$1(diff, args) {
args[0] = `${this.namespace} ${args[0]} +${this.humanize(diff)}`;
}
var defaultOptions = {
useColors: false,
color: "",
formatArgs: formatArgs$1,
formatters: {
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
j(v) {
try {
return JSON.stringify(v);
} catch (error) {
return `[UnexpectedJSONParseError]: ${error.message}`;
}
} },
inspectOpts: {},
humanize,
log: console.debug || console.log || (() => {})
};
function createDebug$1(namespace, options) {
return createDebug$2(namespace, Object.assign({}, defaultOptions, options));
}
function load() {
let r;
try {
r = localStorage.getItem("debug") || localStorage.getItem("DEBUG");
} catch (_unused) {}
if (!r && typeof process !== "undefined" && "env" in process) r = {}.DEBUG;
return r || "";
}
enable(load());
var colors = [
"#0000CC",
"#0000FF",
"#0033CC",
"#0033FF",
"#0066CC",
"#0066FF",
"#0099CC",
"#0099FF",
"#00CC00",
"#00CC33",
"#00CC66",
"#00CC99",
"#00CCCC",
"#00CCFF",
"#3300CC",
"#3300FF",
"#3333CC",
"#3333FF",
"#3366CC",
"#3366FF",
"#3399CC",
"#3399FF",
"#33CC00",
"#33CC33",
"#33CC66",
"#33CC99",
"#33CCCC",
"#33CCFF",
"#6600CC",
"#6600FF",
"#6633CC",
"#6633FF",
"#66CC00",
"#66CC33",
"#9900CC",
"#9900FF",
"#9933CC",
"#9933FF",
"#99CC00",
"#99CC33",
"#CC0000",
"#CC0033",
"#CC0066",
"#CC0099",
"#CC00CC",
"#CC00FF",
"#CC3300",
"#CC3333",
"#CC3366",
"#CC3399",
"#CC33CC",
"#CC33FF",
"#CC6600",
"#CC6633",
"#CC9900",
"#CC9933",
"#CCCC00",
"#CCCC33",
"#FF0000",
"#FF0033",
"#FF0066",
"#FF0099",
"#FF00CC",
"#FF00FF",
"#FF3300",
"#FF3333",
"#FF3366",
"#FF3399",
"#FF33CC",
"#FF33FF",
"#FF6600",
"#FF6633",
"#FF9900",
"#FF9933",
"#FFCC00",
"#FFCC33"
];
/**
* Colorize log arguments if enabled.
*/
function formatArgs(diff, args) {
const { useColors } = this;
args[0] = `${(useColors ? "%c" : "") + this.namespace + (useColors ? " %c" : " ") + args[0] + (useColors ? "%c " : " ")}+${this.humanize(diff)}`;
if (!useColors) return;
const c = `color: ${this.color}`;
args.splice(1, 0, c, "color: inherit");
let index = 0;
let lastC = 0;
args[0].replace(/%[a-z%]/gi, (match) => {
if (match === "%%") return;
index++;
if (match === "%c") lastC = index;
});
args.splice(lastC, 0, c);
}
function createDebug(namespace, options) {
var _ref;
const color = (_ref = options) !== null && _ref !== void 0 ? _ref : selectColor(colors, namespace);
return createDebug$1(namespace, Object.assign({
useColors: true,
color,
formatArgs
}, options));
}
/**
* @license React
* scheduler.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var require_scheduler_production = /* @__PURE__ */ __commonJSMin(((exports) => {
function push(heap, node) {
var index = heap.length;
heap.push(node);
a: for (; 0 < index;) {
var parentIndex = index - 1 >>> 1, parent = heap[parentIndex];
if (0 < compare(parent, node)) heap[parentIndex] = node, heap[index] = parent, index = parentIndex;
else break a;
}
}
function peek(heap) {
return 0 === heap.length ? null : heap[0];
}
function pop(heap) {
if (0 === heap.length) return null;
var first = heap[0], last = heap.pop();
if (last !== first) {
heap[0] = last;
a: for (var index = 0, length = heap.length, halfLength = length >>> 1; index < halfLength;) {
var leftIndex = 2 * (index + 1) - 1, left = heap[leftIndex], rightIndex = leftIndex + 1, right = heap[rightIndex];
if (0 > compare(left, last)) rightIndex < length && 0 > compare(right, left) ? (heap[index] = right, heap[rightIndex] = last, index = rightIndex) : (heap[index] = left, heap[leftIndex] = last, index = leftIndex);
else if (rightIndex < length && 0 > compare(right, last)) heap[index] = right, heap[rightIndex] = last, index = rightIndex;
else break a;
}
}
return first;
}
function compare(a, b) {
var diff = a.sortIndex - b.sortIndex;
return 0 !== diff ? diff : a.id - b.id;
}
exports.unstable_now = void 0;
if ("object" === typeof performance && "function" === typeof performance.now) {
var localPerformance = performance;
exports.unstable_now = function() {
return localPerformance.now();
};
} else {
var localDate = Date, initialTime = localDate.now();
exports.unstable_now = function() {
return localDate.now() - initialTime;
};
}
var taskQueue = [];
var timerQueue = [];
var taskIdCounter = 1;
var currentTask = null;
var currentPriorityLevel = 3;
var isPerformingWork = false;
var isHostCallbackScheduled = false;
var isHostTimeoutScheduled = false;
var needsPaint = false;
var localSetTimeout = "function" === typeof setTimeout ? setTimeout : null;
var localClearTimeout = "function" === typeof clearTimeout ? clearTimeout : null;
var localSetImmediate = "undefined" !== typeof setImmediate ? setImmediate : null;
function advanceTimers(currentTime) {
for (var timer = peek(timerQueue); null !== timer;) {
if (null === timer.callback) pop(timerQueue);
else if (timer.startTime <= currentTime) pop(timerQueue), timer.sortIndex = timer.expirationTime, push(taskQueue, timer);
else break;
timer = peek(timerQueue);
}
}
function handleTimeout(currentTime) {
isHostTimeoutScheduled = false;
advanceTimers(currentTime);
if (!isHostCallbackScheduled) if (null !== peek(taskQueue)) isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline());
else {
var firstTimer = peek(timerQueue);
null !== firstTimer && requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
}
var isMessageLoopRunning = false;
var taskTimeoutID = -1;
var frameInterval = 5;
var startTime = -1;
function shouldYieldToHost() {
return needsPaint ? true : exports.unstable_now() - startTime < frameInterval ? false : true;
}
function performWorkUntilDeadline() {
needsPaint = false;
if (isMessageLoopRunning) {
var currentTime = exports.unstable_now();
startTime = currentTime;
var hasMoreWork = true;
try {
a: {
isHostCallbackScheduled = !1;
isHostTimeoutScheduled && (isHostTimeoutScheduled = !1, localClearTimeout(taskTimeoutID), taskTimeoutID = -1);
isPerformingWork = !0;
var previousPriorityLevel = currentPriorityLevel;
try {
b: {
advanceTimers(currentTime);
for (currentTask = peek(taskQueue); null !== currentTask && !(currentTask.expirationTime > currentTime && shouldYieldToHost());) {
var callback = currentTask.callback;
if ("function" === typeof callback) {
currentTask.callback = null;
currentPriorityLevel = currentTask.priorityLevel;
var continuationCallback = callback(currentTask.expirationTime <= currentTime);
currentTime = exports.unstable_now();
if ("function" === typeof continuationCallback) {
currentTask.callback = continuationCallback;
advanceTimers(currentTime);
hasMoreWork = !0;
break b;
}
currentTask === peek(taskQueue) && pop(taskQueue);
advanceTimers(currentTime);
} else pop(taskQueue);
currentTask = peek(taskQueue);
}
if (null !== currentTask) hasMoreWork = !0;
else {
var firstTimer = peek(timerQueue);
null !== firstTimer && requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
hasMoreWork = !1;
}
}
break a;
} finally {
currentTask = null, currentPriorityLevel = previousPriorityLevel, isPerformingWork = !1;
}
hasMoreWork = void 0;
}
} finally {
hasMoreWork ? schedulePerformWorkUntilDeadline() : isMessageLoopRunning = false;
}
}
}
var schedulePerformWorkUntilDeadline;
if ("function" === typeof localSetImmediate) schedulePerformWorkUntilDeadline = function() {
localSetImmediate(performWorkUntilDeadline);
};
else if ("undefined" !== typeof MessageChannel) {
var channel = new MessageChannel(), port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = function() {
port.postMessage(null);
};
} else schedulePerformWorkUntilDeadline = function() {
localSetTimeout(performWorkUntilDeadline, 0);
};
function requestHostTimeout(callback, ms) {
taskTimeoutID = localSetTimeout(function() {
callback(exports.unstable_now());
}, ms);
}
exports.unstable_IdlePriority = 5;
exports.unstable_ImmediatePriority = 1;
exports.unstable_LowPriority = 4;
exports.unstable_NormalPriority = 3;
exports.unstable_Profiling = null;
exports.unstable_UserBlockingPriority = 2;
exports.unstable_cancelCallback = function(task) {
task.callback = null;
};
exports.unstable_forceFrameRate = function(fps) {
0 > fps || 125 < fps ? console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported") : frameInterval = 0 < fps ? Math.floor(1e3 / fps) : 5;
};
exports.unstable_getCurrentPriorityLevel = function() {
return currentPriorityLevel;
};
exports.unstable_next = function(eventHandler) {
switch (currentPriorityLevel) {
case 1:
case 2:
case 3:
var priorityLevel = 3;
break;
default: priorityLevel = currentPriorityLevel;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
exports.unstable_requestPaint = function() {
needsPaint = true;
};
exports.unstable_runWithPriority = function(priorityLevel, eventHandler) {
switch (priorityLevel) {
case 1:
case 2:
case 3:
case 4:
case 5: break;
default: priorityLevel = 3;
}
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = priorityLevel;
try {
return eventHandler();
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
exports.unstable_scheduleCallback = function(priorityLevel, callback, options) {
var currentTime = exports.unstable_now();
"object" === typeof options && null !== options ? (options = options.delay, options = "number" === typeof options && 0 < options ? currentTime + options : currentTime) : options = currentTime;
switch (priorityLevel) {
case 1:
var timeout = -1;
break;
case 2:
timeout = 250;
break;
case 5:
timeout = 1073741823;
break;
case 4:
timeout = 1e4;
break;
default: timeout = 5e3;
}
timeout = options + timeout;
priorityLevel = {
id: taskIdCounter++,
callback,
priorityLevel,
startTime: options,
expirationTime: timeout,
sortIndex: -1
};
options > currentTime ? (priorityLevel.sortIndex = options, push(timerQueue, priorityLevel), null === peek(taskQueue) && priorityLevel === peek(timerQueue) && (isHostTimeoutScheduled ? (localClearTimeout(taskTimeoutID), taskTimeoutID = -1) : isHostTimeoutScheduled = true, requestHostTimeout(handleTimeout, options - currentTime))) : (priorityLevel.sortIndex = timeout, push(taskQueue, priorityLevel), isHostCallbackScheduled || isPerformingWork || (isHostCallbackScheduled = true, isMessageLoopRunning || (isMessageLoopRunning = true, schedulePerformWorkUntilDeadline())));
return priorityLevel;
};
exports.unstable_shouldYield = shouldYieldToHost;
exports.unstable_wrapCallback = function(callback) {
var parentPriorityLevel = currentPriorityLevel;
return function() {
var previousPriorityLevel = currentPriorityLevel;
currentPriorityLevel = parentPriorityLevel;
try {
return callback.apply(this, arguments);
} finally {
currentPriorityLevel = previousPriorityLevel;
}
};
};
}));
var require_scheduler = /* @__PURE__ */ __commonJSMin(((exports, module) => {
module.exports = require_scheduler_production();
}));
/**
* @license React
* react-dom.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var require_react_dom_production = /* @__PURE__ */ __commonJSMin(((exports$1) => {
var React = require_react();
function formatProdErrorMessage(code) {
var url = "https://react.dev/errors/" + code;
if (1 < arguments.length) {
url += "?args[]=" + encodeURIComponent(arguments[1]);
for (var i = 2; i < arguments.length; i++) url += "&args[]=" + encodeURIComponent(arguments[i]);
}
return "Minified React error #" + code + "; visit " + url + " for the full message or use the non-minified dev environment for full errors and additional helpful warnings.";
}
function noop() {}
var Internals = {
d: {
f: noop,
r: function() {
throw Error(formatProdErrorMessage(522));
},
D: noop,
C: noop,
L: noop,
m: noop,
X: noop,
S: noop,
M: noop
},
p: 0,
findDOMNode: null
};
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_RECOVERABLE_TYPE = Symbol.for("react.recoverable");
var REACT_OPTIMISTIC_KEY = Symbol.for("react.optimistic_key");
function createPortal$1(children, containerInfo, implementation) {
var key = 3 < arguments.length && void 0 !== arguments[3] ? arguments[3] : null;
return {
$$typeof: REACT_PORTAL_TYPE,
key: null == key ? null : key === REACT_OPTIMISTIC_KEY ? REACT_OPTIMISTIC_KEY : "" + key,
children,
containerInfo,
implementation
};
}
var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
function getCrossOriginStringAs(as, input) {
if ("font" === as) return "";
if ("string" === typeof input) return "use-credentials" === input ? input : "";
}
exports$1.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE = Internals;
exports$1.browser = function(reason) {
return {
$$typeof: REACT_RECOVERABLE_TYPE,
_reason: reason
};
};
exports$1.createPortal = function(children, container) {
var key = 2 < arguments.length && void 0 !== arguments[2] ? arguments[2] : null;
if (!container || 1 !== container.nodeType && 9 !== container.nodeType && 11 !== container.nodeType) throw Error(formatProdErrorMessage(299));
return createPortal$1(children, container, null, key);
};
exports$1.flushSync = function(fn) {
var previousTransition = ReactSharedInternals.T, previousUpdatePriority = Internals.p;
try {
if (ReactSharedInternals.T = null, Internals.p = 2, fn) return fn();
} finally {
ReactSharedInternals.T = previousTransition, Internals.p = previousUpdatePriority, Internals.d.f();
}
};
exports$1.preconnect = function(href, options) {
"string" === typeof href && (options ? (options = options.crossOrigin, options = "string" === typeof options ? "use-credentials" === options ? options : "" : void 0) : options = null, Internals.d.C(href, options));
};
exports$1.prefetchDNS = function(href) {
"string" === typeof href && Internals.d.D(href);
};
exports$1.preinit = function(href, options) {
if ("string" === typeof href && options && "string" === typeof options.as) {
var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin), integrity = "string" === typeof options.integrity ? options.integrity : void 0, fetchPriority = "string" === typeof options.fetchPriority ? options.fetchPriority : void 0;
"style" === as ? Internals.d.S(href, "string" === typeof options.precedence ? options.precedence : void 0, {
crossOrigin,
integrity,
fetchPriority
}) : "script" === as && Internals.d.X(href, {
crossOrigin,
integrity,
fetchPriority,
nonce: "string" === typeof options.nonce ? options.nonce : void 0
});
}
};
exports$1.preinitModule = function(href, options) {
if ("string" === typeof href) if ("object" === typeof options && null !== options) {
if (null == options.as || "script" === options.as) {
var crossOrigin = getCrossOriginStringAs(options.as, options.crossOrigin);
Internals.d.M(href, {
crossOrigin,
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
nonce: "string" === typeof options.nonce ? options.nonce : void 0,
fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
});
}
} else options ?? Internals.d.M(href);
};
exports$1.preload = function(href, options) {
if ("string" === typeof href && "object" === typeof options && null !== options && "string" === typeof options.as) {
var as = options.as, crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
Internals.d.L(href, as, {
crossOrigin,
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
nonce: "string" === typeof options.nonce ? options.nonce : void 0,
type: "string" === typeof options.type ? options.type : void 0,
fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0,
referrerPolicy: "string" === typeof options.referrerPolicy ? options.referrerPolicy : void 0,
imageSrcSet: "string" === typeof options.imageSrcSet ? options.imageSrcSet : void 0,
imageSizes: "string" === typeof options.imageSizes ? options.imageSizes : void 0,
media: "string" === typeof options.media ? options.media : void 0
});
}
};
exports$1.preloadModule = function(href, options) {
if ("string" === typeof href) if (options) {
var crossOrigin = getCrossOriginStringAs(options.as, options.crossOrigin);
Internals.d.m(href, {
as: "string" === typeof options.as && "script" !== options.as ? options.as : void 0,
crossOrigin,
integrity: "string" === typeof options.integrity ? options.integrity : void 0,
nonce: "string" === typeof options.nonce ? options.nonce : void 0,
fetchPriority: "string" === typeof options.fetchPriority ? options.fetchPriority : void 0
});
} else Internals.d.m(href);
};
exports$1.requestFormReset = function(form) {
Internals.d.r(form);
};
exports$1.unstable_batchedUpdates = function(fn, a) {
return fn(a);
};
exports$1.useFormState = function(action, initialState, permalink) {
return ReactSharedInternals.H.useFormState(action, initialState, permalink);
};
exports$1.useFormStatus = function() {
return ReactSharedInternals.H.useHostTransitionStatus();
};
exports$1.version = "19.3.0";
}));
var require_react_dom = exports("b",/* @__PURE__ */ __commonJSMin(((exports, module) => {
function checkDCE() {
if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === "undefined" || typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== "function") return;
try {
__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE);
} catch (err) {
console.error(err);
}
}
checkDCE();
module.exports = require_react_dom_production();
})));
/**
* @license React
* react-dom-client.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var require_react_dom_client_production = /* @__PURE__ */ __commonJSMin(((exports$1) => {
var Scheduler = require_scheduler();
var React = require_react();
var ReactDOM = require_react_dom();
function formatProdErrorMessage(code) {
var url = "https://react.dev/errors/" + code;
if (1 < arguments.length) {
url += "?args[]=" + encodeURIComponent(arguments[1]);
for (var i = 2; i < arguments.length; i++) url += "&args[]=" + encodeURIComponent(arguments[i]);
}
return "Minified React error #" + code + "; visit " + url + " for the full message or use the non-minified dev environment for full errors and additional helpful warnings.";
}
function isValidContainer(node) {
return !(!node || 1 !== node.nodeType && 9 !== node.nodeType && 11 !== node.nodeType);
}
function getNearestMountedFiber(fiber) {
for (var node = fiber, nextNode = node; nextNode && !nextNode.alternate;) node = nextNode, 0 !== (node.flags & 4098) && (fiber = node.return), nextNode = node.return;
for (; node.return;) node = node.return;
return 3 === node.tag ? fiber : null;
}
function getSuspenseInstanceFromFiber(fiber) {
if (13 === fiber.tag) {
var suspenseState = fiber.memoizedState;
null === suspenseState && (fiber = fiber.alternate, null !== fiber && (suspenseState = fiber.memoizedState));
if (null !== suspenseState) return suspenseState.dehydrated;
}
return null;
}
function getActivityInstanceFromFiber(fiber) {
if (31 === fiber.tag) {
var activityState = fiber.memoizedState;
null === activityState && (fiber = fiber.alternate, null !== fiber && (activityState = fiber.memoizedState));
if (null !== activityState) return activityState.dehydrated;
}
return null;
}
function assertIsMounted(fiber) {
if (getNearestMountedFiber(fiber) !== fiber) throw Error(formatProdErrorMessage(188));
}
function findCurrentFiberUsingSlowPath(fiber) {
var alternate = fiber.alternate;
if (!alternate) {
alternate = getNearestMountedFiber(fiber);
if (null === alternate) throw Error(formatProdErrorMessage(188));
return alternate !== fiber ? null : fiber;
}
for (var a = fiber, b = alternate;;) {
var parentA = a.return;
if (null === parentA) break;
var parentB = parentA.alternate;
if (null === parentB) {
b = parentA.return;
if (null !== b) {
a = b;
continue;
}
break;
}
if (parentA.child === parentB.child) {
for (parentB = parentA.child; parentB;) {
if (parentB === a) return assertIsMounted(parentA), fiber;
if (parentB === b) return assertIsMounted(parentA), alternate;
parentB = parentB.sibling;
}
throw Error(formatProdErrorMessage(188));
}
if (a.return !== b.return) a = parentA, b = parentB;
else {
for (var didFindChild = false, child$0 = parentA.child; child$0;) {
if (child$0 === a) {
didFindChild = true;
a = parentA;
b = parentB;
break;
}
if (child$0 === b) {
didFindChild = true;
b = parentA;
a = parentB;
break;
}
child$0 = child$0.sibling;
}
if (!didFindChild) {
for (child$0 = parentB.child; child$0;) {
if (child$0 === a) {
didFindChild = true;
a = parentB;
b = parentA;
break;
}
if (child$0 === b) {
didFindChild = true;
b = parentB;
a = parentA;
break;
}
child$0 = child$0.sibling;
}
if (!didFindChild) throw Error(formatProdErrorMessage(189));
}
}
if (a.alternate !== b) throw Error(formatProdErrorMessage(190));
}
if (3 !== a.tag) throw Error(formatProdErrorMessage(188));
return a.stateNode.current === a ? fiber : alternate;
}
function findCurrentHostFiberImpl(node) {
var tag = node.tag;
if (5 === tag || 26 === tag || 27 === tag || 6 === tag) return node;
for (node = node.child; null !== node;) {
tag = findCurrentHostFiberImpl(node);
if (null !== tag) return tag;
node = node.sibling;
}
return null;
}
function traverseVisibleInstancesAndTextInstances(child, searchWithinHosts, fn, a, b, c) {
for (; null !== child;) {
if ((5 === child.tag || 27 === child.tag || 6 === child.tag) && fn(child, a, b, c) || (22 !== child.tag || null === child.memoizedState) && (searchWithinHosts || 5 !== child.tag && 27 !== child.tag) && traverseVisibleInstancesAndTextInstances(child.child, searchWithinHosts, fn, a, b, c)) return true;
child = child.sibling;
}
return false;
}
function getFragmentParentInstanceOrContainerFiber(fiber) {
for (fiber = fiber.return; null !== fiber;) {
if (3 === fiber.tag || 5 === fiber.tag || 27 === fiber.tag) return fiber;
fiber = fiber.return;
}
return null;
}
function fiberIsPortaledIntoHost(fiber) {
var foundPortalParent = false;
for (fiber = fiber.return; null !== fiber;) {
4 === fiber.tag && (foundPortalParent = true);
if (3 === fiber.tag || 5 === fiber.tag || 27 === fiber.tag) break;
fiber = fiber.return;
}
return foundPortalParent;
}
function getFragmentInstanceOrTextInstanceSiblings(fiber) {
var result = [null, null], parentHostFiber = getFragmentParentInstanceOrContainerFiber(fiber);
if (null === parentHostFiber) return result;
findFragmentInstanceOrTextInstanceSiblings(result, fiber, parentHostFiber.child, { foundSelf: false });
return result;
}
function findFragmentInstanceOrTextInstanceSiblings(result, self, child, state) {
for (; null !== child;) {
if (child === self) state.foundSelf = true;
else if (5 === child.tag || 27 === child.tag || 6 === child.tag) {
if (state.foundSelf) return result[1] = child, true;
result[0] = child;
} else if ((22 !== child.tag || null === child.memoizedState) && findFragmentInstanceOrTextInstanceSiblings(result, self, child.child, state)) return true;
child = child.sibling;
}
return false;
}
function getInstanceFromHostFiber(fiber) {
switch (fiber.tag) {
case 5:
case 27:
case 6: return fiber.stateNode;
case 3: return fiber.stateNode.containerInfo;
default: throw Error(formatProdErrorMessage(559));
}
}
var searchTarget = null;
var searchBoundary = null;
function isFiberPrecedingCheck(child, target, boundary) {
return child === boundary ? true : child === target ? (searchTarget = child, true) : false;
}
function isFiberFollowingCheck(child, target, boundary) {
return child === boundary ? (searchBoundary = child, false) : child === target ? (null !== searchBoundary && (searchTarget = child), true) : false;
}
function getParentForFragmentAncestors(inst) {
if (null === inst) return null;
do
inst = null === inst ? null : inst.return;
while (inst && 5 !== inst.tag && 27 !== inst.tag && 3 !== inst.tag);
return inst ? inst : null;
}
function getLowestCommonAncestor(instA, instB, getParent) {
for (var depthA = 0, tempA = instA; tempA; tempA = getParent(tempA)) depthA++;
tempA = 0;
for (var tempB = instB; tempB; tempB = getParent(tempB)) tempA++;
for (; 0 < depthA - tempA;) instA = getParent(instA), depthA--;
for (; 0 < tempA - depthA;) instB = getParent(instB), tempA--;
for (; depthA--;) {
if (instA === instB || null !== instB && instA === instB.alternate) return instA;
instA = getParent(instA);
instB = getParent(instB);
}
return null;
}
var assign = Object.assign;
var REACT_LEGACY_ELEMENT_TYPE = Symbol.for("react.element");
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
var REACT_PORTAL_TYPE = Symbol.for("react.portal");
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
var REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode");
var REACT_PROFILER_TYPE = Symbol.for("react.profiler");
var REACT_CONSUMER_TYPE = Symbol.for("react.consumer");
var REACT_CONTEXT_TYPE = Symbol.for("react.context");
var REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref");
var REACT_SUSPENSE_TYPE = Symbol.for("react.suspense");
var REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list");
var REACT_MEMO_TYPE = Symbol.for("react.memo");
var REACT_LAZY_TYPE = Symbol.for("react.lazy");
var REACT_ACTIVITY_TYPE = Symbol.for("react.activity");
var REACT_LEGACY_HIDDEN_TYPE = Symbol.for("react.legacy_hidden");
var REACT_MEMO_CACHE_SENTINEL = Symbol.for("react.memo_cache_sentinel");
var REACT_VIEW_TRANSITION_TYPE = Symbol.for("react.view_transition");
var REACT_RECOVERABLE_TYPE = Symbol.for("react.recoverable");
var MAYBE_ITERATOR_SYMBOL = Symbol.iterator;
function getIteratorFn(maybeIterable) {
if (null === maybeIterable || "object" !== typeof maybeIterable) return null;
maybeIterable = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable["@@iterator"];
return "function" === typeof maybeIterable ? maybeIterable : null;
}
var REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference");
function getComponentNameFromType(type) {
if (null == type) return null;
if ("function" === typeof type) return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null;
if ("string" === typeof type) return type;
switch (type) {
case REACT_FRAGMENT_TYPE: return "Fragment";
case REACT_PROFILER_TYPE: return "Profiler";
case REACT_STRICT_MODE_TYPE: return "StrictMode";
case REACT_SUSPENSE_TYPE: return "Suspense";
case REACT_SUSPENSE_LIST_TYPE: return "SuspenseList";
case REACT_ACTIVITY_TYPE: return "Activity";
case REACT_VIEW_TRANSITION_TYPE: return "ViewTransition";
}
if ("object" === typeof type) switch (type.$$typeof) {
case REACT_PORTAL_TYPE: return "Portal";
case REACT_CONTEXT_TYPE: return type.displayName || "Context";
case REACT_CONSUMER_TYPE: return (type._context.displayName || "Context") + ".Consumer";
case REACT_FORWARD_REF_TYPE:
var innerType = type.render;
type = type.displayName;
type || (type = innerType.displayName || innerType.name || "", type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef");
return type;
case REACT_MEMO_TYPE: return innerType = type.displayName || null, null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo";
case REACT_LAZY_TYPE:
innerType = type._payload;
type = type._init;
try {
return getComponentNameFromType(type(innerType));
} catch (x) {}
}
return null;
}
var isArrayImpl = Array.isArray;
var ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
var ReactDOMSharedInternals = ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
var sharedNotPendingObject = {
pending: false,
data: null,
method: null,
action: null
};
var valueStack = [];
var index = -1;
function createCursor(defaultValue) {
return { current: defaultValue };
}
function pop(cursor) {
0 > index || (cursor.current = valueStack[index], valueStack[index] = null, index--);
}
function push(cursor, value) {
index++;
valueStack[index] = cursor.current;
cursor.current = value;
}
var contextStackCursor = createCursor(null);
var contextFiberStackCursor = createCursor(null);
var rootInstanceStackCursor = createCursor(null);
var hostTransitionProviderCursor = createCursor(null);
function pushHostContainer(fiber, nextRootInstance) {
push(rootInstanceStackCursor, nextRootInstance);
push(contextFiberStackCursor, fiber);
push(contextStackCursor, null);
switch (nextRootInstance.nodeType) {
case 9:
case 11:
fiber = (fiber = nextRootInstance.documentElement) ? (fiber = fiber.namespaceURI) ? getOwnHostContext(fiber) : 0 : 0;
break;
default: if (fiber = nextRootInstance.tagName, nextRootInstance = nextRootInstance.namespaceURI) nextRootInstance = getOwnHostContext(nextRootInstance), fiber = getChildHostContextProd(nextRootInstance, fiber);
else switch (fiber) {
case "svg":
fiber = 1;
break;
case "math":
fiber = 2;
break;
default: fiber = 0;
}
}
pop(contextStackCursor);
push(contextStackCursor, fiber);
}
function popHostContainer() {
pop(contextStackCursor);
pop(contextFiberStackCursor);
pop(rootInstanceStackCursor);
}
function pushHostContext(fiber) {
var stateHook = fiber.memoizedState;
null !== stateHook && (HostTransitionContext._currentValue = stateHook.memoizedState, push(hostTransitionProviderCursor, fiber));
stateHook = contextStackCursor.current;
var JSCompiler_inline_result = getChildHostContextProd(stateHook, fiber.type);
stateHook !== JSCompiler_inline_result && (push(contextFiberStackCursor, fiber), push(contextStackCursor, JSCompiler_inline_result));
}
function popHostContext(fiber) {
contextFiberStackCursor.current === fiber && (pop(contextStackCursor), pop(contextFiberStackCursor));
hostTransitionProviderCursor.current === fiber && (pop(hostTransitionProviderCursor), HostTransitionContext._currentValue = sharedNotPendingObject);
}
var prefix;
var suffix;
function describeBuiltInComponentFrame(name) {
if (void 0 === prefix) try {
throw Error();
} catch (x) {
var match = x.stack.trim().match(/\n( *(at )?)/);
prefix = match && match[1] || "";
suffix = -1 < x.stack.indexOf("\n at") ? " ()" : -1 < x.stack.indexOf("@") ? "@unknown:0:0" : "";
}
return "\n" + prefix + name + suffix;
}
var reentry = false;
function describeNativeComponentFrame(fn, construct) {
if (!fn || reentry) return "";
reentry = true;
var previousPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = void 0;
try {
var RunInRootFrame = { DetermineComponentFrameRoot: function() {
try {
if (construct) {
var Fake = function() {
throw Error();
};
Object.defineProperty(Fake.prototype, "props", { set: function() {
throw Error();
} });
if ("object" === typeof Reflect && Reflect.construct) {
try {
Reflect.construct(Fake, []);
} catch (x) {
var control = x;
}
Reflect.construct(fn, [], Fake);
} else {
try {
Fake.call();
} catch (x$1) {
control = x$1;
}
Fake = !1;
try {
var prevProps = Object.getOwnPropertyDescriptor(fn.prototype, "props");
Object.defineProperty(fn.prototype, "props", {
configurable: !0,
set: function() {
throw Error();
}
});
Fake = !0;
new fn();
} finally {
Fake && (void 0 !== prevProps ? Object.defineProperty(fn.prototype, "props", prevProps) : delete fn.prototype.props);
}
}
} else {
try {
throw Error();
} catch (x$2) {
control = x$2;
}
(Fake = fn()) && "function" === typeof Fake.catch && Fake.catch(function() {});
}
} catch (sample) {
if (sample && control && "string" === typeof sample.stack) return [sample.stack, control.stack];
}
return [null, null];
} };
RunInRootFrame.DetermineComponentFrameRoot.displayName = "DetermineComponentFrameRoot";
var namePropDescriptor = Object.getOwnPropertyDescriptor(RunInRootFrame.DetermineComponentFrameRoot, "name");
namePropDescriptor && namePropDescriptor.configurable && Object.defineProperty(RunInRootFrame.DetermineComponentFrameRoot, "name", { value: "DetermineComponentFrameRoot" });
var _RunInRootFrame$Deter = RunInRootFrame.DetermineComponentFrameRoot(), sampleStack = _RunInRootFrame$Deter[0], controlStack = _RunInRootFrame$Deter[1];
if (sampleStack && controlStack) {
var sampleLines = sampleStack.split("\n"), controlLines = controlStack.split("\n");
for (namePropDescriptor = RunInRootFrame = 0; RunInRootFrame < sampleLines.length && !sampleLines[RunInRootFrame].includes("DetermineComponentFrameRoot");) RunInRootFrame++;
for (; namePropDescriptor < controlLines.length && !controlLines[namePropDescriptor].includes("DetermineComponentFrameRoot");) namePropDescriptor++;
if (RunInRootFrame === sampleLines.length || namePropDescriptor === controlLines.length) for (RunInRootFrame = sampleLines.length - 1, namePropDescriptor = controlLines.length - 1; 1 <= RunInRootFrame && 0 <= namePropDescriptor && sampleLines[RunInRootFrame] !== controlLines[namePropDescriptor];) namePropDescriptor--;
for (; 1 <= RunInRootFrame && 0 <= namePropDescriptor; RunInRootFrame--, namePropDescriptor--) if (sampleLines[RunInRootFrame] !== controlLines[namePropDescriptor]) {
if (1 !== RunInRootFrame || 1 !== namePropDescriptor) do
if (RunInRootFrame--, namePropDescriptor--, 0 > namePropDescriptor || sampleLines[RunInRootFrame] !== controlLines[namePropDescriptor]) {
var frame = "\n" + sampleLines[RunInRootFrame].replace(" at new ", " at ");
fn.displayName && frame.includes("") && (frame = frame.replace("", fn.displayName));
return frame;
}
while (1 <= RunInRootFrame && 0 <= namePropDescriptor);
break;
}
}
} finally {
reentry = false, Error.prepareStackTrace = previousPrepareStackTrace;
}
return (previousPrepareStackTrace = fn ? fn.displayName || fn.name : "") ? describeBuiltInComponentFrame(previousPrepareStackTrace) : "";
}
function describeFiber(fiber, childFiber) {
switch (fiber.tag) {
case 26:
case 27:
case 5: return describeBuiltInComponentFrame(fiber.type);
case 16: return describeBuiltInComponentFrame("Lazy");
case 13: return fiber.child !== childFiber && null !== childFiber ? describeBuiltInComponentFrame("Suspense Fallback") : describeBuiltInComponentFrame("Suspense");
case 19: return describeBuiltInComponentFrame("SuspenseList");
case 0:
case 15: return describeNativeComponentFrame(fiber.type, false);
case 11: return describeNativeComponentFrame(fiber.type.render, false);
case 1: return describeNativeComponentFrame(fiber.type, true);
case 31: return describeBuiltInComponentFrame("Activity");
case 30: return describeBuiltInComponentFrame("ViewTransition");
default: return "";
}
}
function getStackByFiberInDevAndProd(workInProgress) {
try {
var info = "", previous = null;
do
info += describeFiber(workInProgress, previous), previous = workInProgress, workInProgress = workInProgress.return;
while (workInProgress);
return info;
} catch (x) {
return "\nError generating stack: " + x.message + "\n" + x.stack;
}
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
var scheduleCallback$3 = Scheduler.unstable_scheduleCallback;
var cancelCallback$1 = Scheduler.unstable_cancelCallback;
var shouldYield = Scheduler.unstable_shouldYield;
var requestPaint = Scheduler.unstable_requestPaint;
var now = Scheduler.unstable_now;
var getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
var ImmediatePriority = Scheduler.unstable_ImmediatePriority;
var UserBlockingPriority = Scheduler.unstable_UserBlockingPriority;
var NormalPriority$1 = Scheduler.unstable_NormalPriority;
var LowPriority = Scheduler.unstable_LowPriority;
var IdlePriority = Scheduler.unstable_IdlePriority;
var log$1 = Scheduler.log;
var unstable_setDisableYieldValue = Scheduler.unstable_setDisableYieldValue;
var rendererID = null;
var injectedHook = null;
function setIsStrictModeForDevtools(newIsStrictMode) {
"function" === typeof log$1 && unstable_setDisableYieldValue(newIsStrictMode);
if (injectedHook && "function" === typeof injectedHook.setStrictMode) try {
injectedHook.setStrictMode(rendererID, newIsStrictMode);
} catch (err) {}
}
var clz32 = Math.clz32 ? Math.clz32 : clz32Fallback;
var log = Math.log;
var LN2 = Math.LN2;
function clz32Fallback(x) {
x >>>= 0;
return 0 === x ? 32 : 31 - (log(x) / LN2 | 0) | 0;
}
var nextTransitionUpdateLane = 256;
var nextTransitionDeferredLane = 262144;
var nextRetryLane = 4194304;
function getHighestPriorityLanes(lanes) {
var pendingSyncLanes = lanes & 42;
if (0 !== pendingSyncLanes) return pendingSyncLanes;
switch (lanes & -lanes) {
case 1: return 1;
case 2: return 2;
case 4: return 4;
case 8: return 8;
case 16: return 16;
case 32: return 32;
case 64: return 64;
case 128: return 128;
case 256:
case 512:
case 1024:
case 2048:
case 4096:
case 8192:
case 16384:
case 32768:
case 65536:
case 131072: return lanes & -lanes;
case 262144:
case 524288:
case 1048576:
case 2097152: return lanes & 3932160;
case 4194304:
case 8388608:
case 16777216:
case 33554432: return lanes & 62914560;
case 67108864: return 67108864;
case 134217728: return 134217728;
case 268435456: return 268435456;
case 536870912: return 536870912;
case 1073741824: return 0;
default: return lanes;
}
}
function getNextLanes(root, wipLanes, rootHasPendingCommit) {
var pendingLanes = root.pendingLanes;
if (0 === pendingLanes) return 0;
var nextLanes = 0, suspendedLanes = root.suspendedLanes, pingedLanes = root.pingedLanes;
root = root.warmLanes;
var nonIdlePendingLanes = pendingLanes & 134217727;
0 !== nonIdlePendingLanes ? (pendingLanes = nonIdlePendingLanes & ~suspendedLanes, 0 !== pendingLanes ? nextLanes = getHighestPriorityLanes(pendingLanes) : (pingedLanes &= nonIdlePendingLanes, 0 !== pingedLanes ? nextLanes = getHighestPriorityLanes(pingedLanes) : rootHasPendingCommit || (rootHasPendingCommit = nonIdlePendingLanes & ~root, 0 !== rootHasPendingCommit && (nextLanes = getHighestPriorityLanes(rootHasPendingCommit))))) : (nonIdlePendingLanes = pendingLanes & ~suspendedLanes, 0 !== nonIdlePendingLanes ? nextLanes = getHighestPriorityLanes(nonIdlePendingLanes) : 0 !== pingedLanes ? nextLanes = getHighestPriorityLanes(pingedLanes) : rootHasPendingCommit || (rootHasPendingCommit = pendingLanes & ~root, 0 !== rootHasPendingCommit && (nextLanes = getHighestPriorityLanes(rootHasPendingCommit))));
return 0 === nextLanes ? 0 : 0 !== wipLanes && wipLanes !== nextLanes && 0 === (wipLanes & suspendedLanes) && (suspendedLanes = nextLanes & -nextLanes, rootHasPendingCommit = wipLanes & -wipLanes, suspendedLanes >= rootHasPendingCommit || 32 === suspendedLanes && 0 !== (rootHasPendingCommit & 4194048)) ? wipLanes : nextLanes;
}
function checkIfRootIsPrerendering(root, renderLanes) {
return 0 === (root.pendingLanes & ~(root.suspendedLanes & ~root.pingedLanes) & renderLanes);
}
function getEntangledLanes(root, renderLanes) {
0 !== (renderLanes & 8) && (renderLanes |= renderLanes & 32);
var allEntangledLanes = root.entangledLanes;
if (0 !== allEntangledLanes) for (root = root.entanglements, allEntangledLanes &= renderLanes; 0 < allEntangledLanes;) {
var index$4 = 31 - clz32(allEntangledLanes), lane = 1 << index$4;
renderLanes |= root[index$4];
allEntangledLanes &= ~lane;
}
return renderLanes;
}
function computeExpirationTime(lane, currentTime) {
switch (lane) {
case 1:
case 2:
case 4:
case 8:
case 64: return currentTime + 250;
case 16:
case 32:
case 128:
case 256:
case 512:
case 1024:
case 2048:
case 4096:
case 8192:
case 16384:
case 32768:
case 65536:
case 131072:
case 262144:
case 524288:
case 1048576:
case 2097152: return currentTime + 5e3;
case 4194304:
case 8388608:
case 16777216:
case 33554432: return -1;
case 67108864:
case 134217728:
case 268435456:
case 536870912:
case 1073741824: return -1;
default: return -1;
}
}
function claimNextRetryLane() {
var lane = nextRetryLane;
nextRetryLane <<= 1;
0 === (nextRetryLane & 62914560) && (nextRetryLane = 4194304);
return lane;
}
function createLaneMap(initial) {
for (var laneMap = [], i = 0; 31 > i; i++) laneMap.push(initial);
return laneMap;
}
function markRootUpdated$1(root, updateLane) {
root.pendingLanes |= updateLane;
268435456 !== updateLane && (root.suspendedLanes = 0, root.pingedLanes = 0, root.warmLanes = 0);
}
function markRootFinished(root, finishedLanes, remainingLanes, spawnedLane, updatedLanes, suspendedRetryLanes) {
var previouslyPendingLanes = root.pendingLanes;
root.pendingLanes = remainingLanes;
root.suspendedLanes = 0;
root.pingedLanes = 0;
root.warmLanes = 0;
root.expiredLanes &= remainingLanes;
root.entangledLanes &= remainingLanes;
root.errorRecoveryDisabledLanes &= remainingLanes;
root.shellSuspendCounter = 0;
var entanglements = root.entanglements, expirationTimes = root.expirationTimes, hiddenUpdates = root.hiddenUpdates;
for (remainingLanes = previouslyPendingLanes & ~remainingLanes; 0 < remainingLanes;) {
var index$7 = 31 - clz32(remainingLanes), lane = 1 << index$7;
entanglements[index$7] = 0;
expirationTimes[index$7] = -1;
var hiddenUpdatesForLane = hiddenUpdates[index$7];
if (null !== hiddenUpdatesForLane) for (hiddenUpdates[index$7] = null, index$7 = 0; index$7 < hiddenUpdatesForLane.length; index$7++) {
var update = hiddenUpdatesForLane[index$7];
null !== update && (update.lane &= -536870913);
}
remainingLanes &= ~lane;
}
0 !== spawnedLane && markSpawnedDeferredLane(root, spawnedLane, 0);
0 !== suspendedRetryLanes && 0 === updatedLanes && 0 !== root.tag && (root.suspendedLanes |= suspendedRetryLanes & ~(previouslyPendingLanes & ~finishedLanes));
}
function markSpawnedDeferredLane(root, spawnedLane, entangledLanes) {
root.pendingLanes |= spawnedLane;
root.suspendedLanes &= ~spawnedLane;
var spawnedLaneIndex = 31 - clz32(spawnedLane);
root.entangledLanes |= spawnedLane;
root.entanglements[spawnedLaneIndex] = root.entanglements[spawnedLaneIndex] | 1073741824 | entangledLanes & 261930;
}
function markRootEntangled(root, entangledLanes) {
var rootEntangledLanes = root.entangledLanes |= entangledLanes;
for (root = root.entanglements; rootEntangledLanes;) {
var index$8 = 31 - clz32(rootEntangledLanes), lane = 1 << index$8;
lane & entangledLanes | root[index$8] & entangledLanes && (root[index$8] |= entangledLanes);
rootEntangledLanes &= ~lane;
}
}
function getBumpedLaneForHydration(root, renderLanes) {
var renderLane = renderLanes & -renderLanes;
renderLane = 0 !== (renderLane & 42) ? 1 : getBumpedLaneForHydrationByLane(renderLane);
return 0 !== (renderLane & (root.suspendedLanes | renderLanes)) ? 0 : renderLane;
}
function getBumpedLaneForHydrationByLane(lane) {
switch (lane) {
case 2:
lane = 1;
break;
case 8:
lane = 4;
break;
case 32:
lane = 16;
break;
case 256:
case 512:
case 1024:
case 2048:
case 4096:
case 8192:
case 16384:
case 32768:
case 65536:
case 131072:
case 262144:
case 524288:
case 1048576:
case 2097152:
case 4194304:
case 8388608:
case 16777216:
case 33554432:
lane = 128;
break;
case 268435456:
lane = 134217728;
break;
default: lane = 0;
}
return lane;
}
function lanesToEventPriority(lanes) {
lanes &= -lanes;
return 2 < lanes ? 8 < lanes ? 0 !== (lanes & 134217727) ? 32 : 268435456 : 8 : 2;
}
function resolveUpdatePriority() {
var updatePriority = ReactDOMSharedInternals.p;
if (0 !== updatePriority) return updatePriority;
updatePriority = window.event;
return void 0 === updatePriority ? 32 : getEventPriority(updatePriority.type);
}
function runWithPriority(priority, fn) {
var previousPriority = ReactDOMSharedInternals.p;
try {
return ReactDOMSharedInternals.p = priority, fn();
} finally {
ReactDOMSharedInternals.p = previousPriority;
}
}
var randomKey = Math.random().toString(36).slice(2);
var internalInstanceKey = "__reactFiber$" + randomKey;
var internalPropsKey = "__reactProps$" + randomKey;
var internalContainerInstanceKey = "__reactContainer$" + randomKey;
var internalEventHandlersKey = "__reactEvents$" + randomKey;
var internalEventHandlerListenersKey = "__reactListeners$" + randomKey;
var internalEventHandlesSetKey = "__reactHandles$" + randomKey;
var internalRootNodeResourcesKey = "__reactResources$" + randomKey;
var internalHoistableMarker = "__reactMarker$" + randomKey;
var internalLoadPendingKey = "__reactLoad$" + randomKey;
function detachDeletedInstance(node) {
delete node[internalInstanceKey];
delete node[internalPropsKey];
delete node[internalEventHandlerListenersKey];
delete node[internalEventHandlesSetKey];
}
function getClosestInstanceFromNode(targetNode) {
var targetInst;
if (targetInst = targetNode[internalInstanceKey]) return targetInst;
for (var parentNode = targetNode.parentNode; parentNode;) {
if (targetInst = parentNode[internalContainerInstanceKey] || parentNode[internalInstanceKey]) {
parentNode = targetInst.alternate;
if (null !== targetInst.child || null !== parentNode && null !== parentNode.child) for (targetNode = getParentHydrationBoundary(targetNode); null !== targetNode;) {
if (parentNode = targetNode[internalInstanceKey]) return parentNode;
targetNode = getParentHydrationBoundary(targetNode);
}
return targetInst;
}
targetNode = parentNode;
parentNode = targetNode.parentNode;
}
return null;
}
function getInstanceFromNode(node) {
if (node = node[internalInstanceKey] || node[internalContainerInstanceKey]) {
var tag = node.tag;
if (5 === tag || 6 === tag || 13 === tag || 31 === tag || 26 === tag || 27 === tag || 3 === tag) return node;
}
return null;
}
function getNodeFromInstance(inst) {
var tag = inst.tag;
if (5 === tag || 26 === tag || 27 === tag || 6 === tag) return inst.stateNode;
throw Error(formatProdErrorMessage(33));
}
function getResourcesFromRoot(root) {
var resources = root[internalRootNodeResourcesKey];
resources || (resources = root[internalRootNodeResourcesKey] = {
hoistableStyles: /* @__PURE__ */ new Map(),
hoistableScripts: /* @__PURE__ */ new Map()
});
return resources;
}
function markNodeAsHoistable(node) {
node[internalHoistableMarker] = true;
}
function clearPendingLoadOnNode(node) {
node[internalLoadPendingKey] = void 0;
}
var allNativeEvents = /* @__PURE__ */ new Set();
var registrationNameDependencies = {};
function registerTwoPhaseEvent(registrationName, dependencies) {
registerDirectEvent(registrationName, dependencies);
registerDirectEvent(registrationName + "Capture", dependencies);
}
function registerDirectEvent(registrationName, dependencies) {
registrationNameDependencies[registrationName] = dependencies;
for (registrationName = 0; registrationName < dependencies.length; registrationName++) allNativeEvents.add(dependencies[registrationName]);
}
var VALID_ATTRIBUTE_NAME_REGEX = RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD][:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*$");
var illegalAttributeNameCache = {};
var validatedAttributeNameCache = {};
function isAttributeNameSafe(attributeName) {
if (hasOwnProperty.call(validatedAttributeNameCache, attributeName)) return true;
if (hasOwnProperty.call(illegalAttributeNameCache, attributeName)) return false;
if (VALID_ATTRIBUTE_NAME_REGEX.test(attributeName)) return validatedAttributeNameCache[attributeName] = true;
illegalAttributeNameCache[attributeName] = true;
return false;
}
var viewTransitionMutationContext = false;
function pushMutationContext() {
var prev = viewTransitionMutationContext;
viewTransitionMutationContext = false;
return prev;
}
function setValueForAttribute(node, name, value) {
if (isAttributeNameSafe(name)) if (null === value) node.removeAttribute(name);
else {
switch (typeof value) {
case "undefined":
case "function":
case "symbol":
node.removeAttribute(name);
return;
case "boolean":
var prefix$10 = name.toLowerCase().slice(0, 5);
if ("data-" !== prefix$10 && "aria-" !== prefix$10) {
node.removeAttribute(name);
return;
}
}
node.setAttribute(name, value);
}
}
function setValueForKnownAttribute(node, name, value) {
if (null === value) node.removeAttribute(name);
else {
switch (typeof value) {
case "undefined":
case "function":
case "symbol":
case "boolean":
node.removeAttribute(name);
return;
}
node.setAttribute(name, value);
}
}
function setValueForNamespacedAttribute(node, namespace, name, value) {
if (null === value) node.removeAttribute(name);
else {
switch (typeof value) {
case "undefined":
case "function":
case "symbol":
case "boolean":
node.removeAttribute(name);
return;
}
node.setAttributeNS(namespace, name, value);
}
}
function getToStringValue(value) {
switch (typeof value) {
case "bigint":
case "boolean":
case "number":
case "string":
case "undefined": return value;
case "object": return value;
default: return "";
}
}
function isCheckable(elem) {
var type = elem.type;
return (elem = elem.nodeName) && "input" === elem.toLowerCase() && ("checkbox" === type || "radio" === type);
}
function trackValueOnNode(node, valueField, currentValue) {
var descriptor = Object.getOwnPropertyDescriptor(node.constructor.prototype, valueField);
if (!node.hasOwnProperty(valueField) && "undefined" !== typeof descriptor && "function" === typeof descriptor.get && "function" === typeof descriptor.set) {
var get = descriptor.get, set = descriptor.set;
Object.defineProperty(node, valueField, {
configurable: true,
get: function() {
return get.call(this);
},
set: function(value) {
currentValue = "" + value;
set.call(this, value);
}
});
Object.defineProperty(node, valueField, { enumerable: descriptor.enumerable });
return {
getValue: function() {
return currentValue;
},
setValue: function(value) {
currentValue = "" + value;
},
stopTracking: function() {
node._valueTracker = null;
delete node[valueField];
}
};
}
}
function track(node) {
if (!node._valueTracker) {
var valueField = isCheckable(node) ? "checked" : "value";
node._valueTracker = trackValueOnNode(node, valueField, "" + node[valueField]);
}
}
function updateValueIfChanged(node) {
if (!node) return false;
var tracker = node._valueTracker;
if (!tracker) return true;
var lastValue = tracker.getValue();
var value = "";
node && (value = isCheckable(node) ? node.checked ? "true" : "false" : node.value);
node = value;
return node !== lastValue ? (tracker.setValue(node), true) : false;
}
var escapeSelectorAttributeValueInsideDoubleQuotesRegex = /[\n"\\]/g;
function escapeSelectorAttributeValueInsideDoubleQuotes(value) {
return value.replace(escapeSelectorAttributeValueInsideDoubleQuotesRegex, function(ch) {
return "\\" + ch.charCodeAt(0).toString(16) + " ";
});
}
function updateInput(element, value, defaultValue, lastDefaultValue, checked, defaultChecked, type, name) {
element.name = "";
null != type && "function" !== typeof type && "symbol" !== typeof type && "boolean" !== typeof type ? element.type = type : element.removeAttribute("type");
if (null != value) if ("number" === type) {
if (0 === value && "" === element.value || element.value != value) element.value = "" + getToStringValue(value);
} else element.value !== "" + getToStringValue(value) && (element.value = "" + getToStringValue(value));
else "submit" !== type && "reset" !== type || element.removeAttribute("value");
null != value ? "number" === type && element.value == value ? setDefaultValue(element, getToStringValue(element.value)) : setDefaultValue(element, getToStringValue(value)) : null != defaultValue ? setDefaultValue(element, getToStringValue(defaultValue)) : null != lastDefaultValue && element.removeAttribute("value");
null == checked && null != defaultChecked && (element.defaultChecked = !!defaultChecked);
null != checked && (element.checked = checked && "function" !== typeof checked && "symbol" !== typeof checked);
null != name && "function" !== typeof name && "symbol" !== typeof name && "boolean" !== typeof name ? element.name = "" + getToStringValue(name) : element.removeAttribute("name");
}
function initInput(element, value, defaultValue, checked, defaultChecked, type, name, isHydrating) {
null != type && "function" !== typeof type && "symbol" !== typeof type && "boolean" !== typeof type && (element.type = type);
if (null != value || null != defaultValue) {
if (!("submit" !== type && "reset" !== type || void 0 !== value && null !== value)) {
track(element);
return;
}
defaultValue = null != defaultValue ? "" + getToStringValue(defaultValue) : "";
value = null != value ? "" + getToStringValue(value) : defaultValue;
isHydrating || value === element.value || (element.value = value);
element.defaultValue = value;
}
checked = null != checked ? checked : defaultChecked;
checked = "function" !== typeof checked && "symbol" !== typeof checked && !!checked;
element.checked = isHydrating ? element.checked : !!checked;
element.defaultChecked = !!checked;
null != name && "function" !== typeof name && "symbol" !== typeof name && "boolean" !== typeof name && (element.name = name);
track(element);
}
function setDefaultValue(node, value) {
node.defaultValue !== "" + value && (node.defaultValue = "" + value);
}
function updateOptions(node, multiple, propValue, setDefaultSelected) {
node = node.options;
if (multiple) {
multiple = {};
for (var i = 0; i < propValue.length; i++) multiple["$" + propValue[i]] = true;
for (propValue = 0; propValue < node.length; propValue++) i = multiple.hasOwnProperty("$" + node[propValue].value), node[propValue].selected !== i && (node[propValue].selected = i), i && setDefaultSelected && (node[propValue].defaultSelected = true);
} else {
propValue = "" + getToStringValue(propValue);
multiple = null;
for (i = 0; i < node.length; i++) {
if (node[i].value === propValue) {
node[i].selected = true;
setDefaultSelected && (node[i].defaultSelected = true);
return;
}
null !== multiple || node[i].disabled || (multiple = node[i]);
}
null !== multiple && (multiple.selected = true);
}
}
function updateTextarea(element, value, defaultValue) {
if (null != value && (value = "" + getToStringValue(value), value !== element.value && (element.value = value), null == defaultValue)) {
element.defaultValue !== value && (element.defaultValue = value);
return;
}
element.defaultValue = null != defaultValue ? "" + getToStringValue(defaultValue) : "";
}
function initTextarea(element, value, defaultValue, children) {
if (null == value) {
if (null != children) {
if (null != defaultValue) throw Error(formatProdErrorMessage(92));
if (isArrayImpl(children)) {
if (1 < children.length) throw Error(formatProdErrorMessage(93));
children = children[0];
}
defaultValue = children;
}
defaultValue ??= "";
value = defaultValue;
}
defaultValue = getToStringValue(value);
element.defaultValue = defaultValue;
children = element.textContent;
children === defaultValue && "" !== children && null !== children && (element.value = children);
track(element);
}
function setTextContent(node, text) {
if (text) {
var firstChild = node.firstChild;
if (firstChild && firstChild === node.lastChild && 3 === firstChild.nodeType) {
firstChild.nodeValue = text;
return;
}
}
node.textContent = text;
}
var unitlessNumbers = new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth MozAnimationIterationCount MozBoxFlex MozBoxFlexGroup MozLineClamp msAnimationIterationCount msFlex msZoom msFlexGrow msFlexNegative msFlexOrder msFlexPositive msFlexShrink msGridColumn msGridColumnSpan msGridRow msGridRowSpan WebkitAnimationIterationCount WebkitBoxFlex WebKitBoxFlexGroup WebkitBoxOrdinalGroup WebkitColumnCount WebkitColumns WebkitFlex WebkitFlexGrow WebkitFlexPositive WebkitFlexShrink WebkitLineClamp".split(" "));
function setValueForStyle(style, styleName, value) {
var isCustomProperty = 0 === styleName.indexOf("--");
null == value || "boolean" === typeof value || "" === value ? isCustomProperty ? style.setProperty(styleName, "") : "float" === styleName ? style.cssFloat = "" : style[styleName] = "" : isCustomProperty ? style.setProperty(styleName, value) : "number" !== typeof value || 0 === value || unitlessNumbers.has(styleName) ? "float" === styleName ? style.cssFloat = value : style[styleName] = ("" + value).trim() : style[styleName] = value + "px";
}
function setValueForStyles(node, styles, prevStyles) {
if (null != styles && "object" !== typeof styles) throw Error(formatProdErrorMessage(62));
node = node.style;
if (null != prevStyles) {
for (var styleName in prevStyles) !prevStyles.hasOwnProperty(styleName) || null != styles && styles.hasOwnProperty(styleName) || (0 === styleName.indexOf("--") ? node.setProperty(styleName, "") : "float" === styleName ? node.cssFloat = "" : node[styleName] = "", viewTransitionMutationContext = true);
for (var styleName$16 in styles) styleName = styles[styleName$16], styles.hasOwnProperty(styleName$16) && prevStyles[styleName$16] !== styleName && (setValueForStyle(node, styleName$16, styleName), viewTransitionMutationContext = true);
} else for (var styleName$17 in styles) styles.hasOwnProperty(styleName$17) && setValueForStyle(node, styleName$17, styles[styleName$17]);
}
function isCustomElement(tagName) {
if (-1 === tagName.indexOf("-")) return false;
switch (tagName) {
case "annotation-xml":
case "color-profile":
case "font-face":
case "font-face-src":
case "font-face-uri":
case "font-face-format":
case "font-face-name":
case "missing-glyph": return false;
default: return true;
}
}
var aliases = /* @__PURE__ */ new Map([
["acceptCharset", "accept-charset"],
["htmlFor", "for"],
["httpEquiv", "http-equiv"],
["crossOrigin", "crossorigin"],
["accentHeight", "accent-height"],
["alignmentBaseline", "alignment-baseline"],
["arabicForm", "arabic-form"],
["baselineShift", "baseline-shift"],
["capHeight", "cap-height"],
["clipPath", "clip-path"],
["clipRule", "clip-rule"],
["colorInterpolation", "color-interpolation"],
["colorInterpolationFilters", "color-interpolation-filters"],
["colorProfile", "color-profile"],
["colorRendering", "color-rendering"],
["dominantBaseline", "dominant-baseline"],
["enableBackground", "enable-background"],
["fillOpacity", "fill-opacity"],
["fillRule", "fill-rule"],
["floodColor", "flood-color"],
["floodOpacity", "flood-opacity"],
["fontFamily", "font-family"],
["fontSize", "font-size"],
["fontSizeAdjust", "font-size-adjust"],
["fontStretch", "font-stretch"],
["fontStyle", "font-style"],
["fontVariant", "font-variant"],
["fontWeight", "font-weight"],
["glyphName", "glyph-name"],
["glyphOrientationHorizontal", "glyph-orientation-horizontal"],
["glyphOrientationVertical", "glyph-orientation-vertical"],
["horizAdvX", "horiz-adv-x"],
["horizOriginX", "horiz-origin-x"],
["imageRendering", "image-rendering"],
["letterSpacing", "letter-spacing"],
["lightingColor", "lighting-color"],
["markerEnd", "marker-end"],
["markerMid", "marker-mid"],
["markerStart", "marker-start"],
["maskType", "mask-type"],
["overlinePosition", "overline-position"],
["overlineThickness", "overline-thickness"],
["paintOrder", "paint-order"],
["panose-1", "panose-1"],
["pointerEvents", "pointer-events"],
["renderingIntent", "rendering-intent"],
["shapeRendering", "shape-rendering"],
["stopColor", "stop-color"],
["stopOpacity", "stop-opacity"],
["strikethroughPosition", "strikethrough-position"],
["strikethroughThickness", "strikethrough-thickness"],
["strokeDasharray", "stroke-dasharray"],
["strokeDashoffset", "stroke-dashoffset"],
["strokeLinecap", "stroke-linecap"],
["strokeLinejoin", "stroke-linejoin"],
["strokeMiterlimit", "stroke-miterlimit"],
["strokeOpacity", "stroke-opacity"],
["strokeWidth", "stroke-width"],
["textAnchor", "text-anchor"],
["textDecoration", "text-decoration"],
["textRendering", "text-rendering"],
["transformOrigin", "transform-origin"],
["underlinePosition", "underline-position"],
["underlineThickness", "underline-thickness"],
["unicodeBidi", "unicode-bidi"],
["unicodeRange", "unicode-range"],
["unitsPerEm", "units-per-em"],
["vAlphabetic", "v-alphabetic"],
["vHanging", "v-hanging"],
["vIdeographic", "v-ideographic"],
["vMathematical", "v-mathematical"],
["vectorEffect", "vector-effect"],
["vertAdvY", "vert-adv-y"],
["vertOriginX", "vert-origin-x"],
["vertOriginY", "vert-origin-y"],
["wordSpacing", "word-spacing"],
["writingMode", "writing-mode"],
["xmlnsXlink", "xmlns:xlink"],
["xHeight", "x-height"]
]);
var isJavaScriptProtocol = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
function sanitizeURL(url) {
return isJavaScriptProtocol.test("" + url) ? "javascript:throw new Error('React has blocked a javascript: URL as a security precaution.')" : url;
}
function noop$1() {}
var currentReplayingEvent = null;
function getEventTarget(nativeEvent) {
nativeEvent = nativeEvent.target || nativeEvent.srcElement || window;
nativeEvent.correspondingUseElement && (nativeEvent = nativeEvent.correspondingUseElement);
return 3 === nativeEvent.nodeType ? nativeEvent.parentNode : nativeEvent;
}
var restoreTarget = null;
var restoreQueue = null;
function restoreStateOfTarget(target) {
var internalInstance = getInstanceFromNode(target);
if (internalInstance && (target = internalInstance.stateNode)) {
var props = target[internalPropsKey] || null;
a: switch (target = internalInstance.stateNode, internalInstance.type) {
case "input":
updateInput(target, props.value, props.defaultValue, props.defaultValue, props.checked, props.defaultChecked, props.type, props.name);
internalInstance = props.name;
if ("radio" === props.type && null != internalInstance) {
for (props = target; props.parentNode;) props = props.parentNode;
props = props.querySelectorAll("input[name=\"" + escapeSelectorAttributeValueInsideDoubleQuotes("" + internalInstance) + "\"][type=\"radio\"]");
for (internalInstance = 0; internalInstance < props.length; internalInstance++) {
var otherNode = props[internalInstance];
if (otherNode !== target && otherNode.form === target.form) {
var otherProps = otherNode[internalPropsKey] || null;
if (!otherProps) throw Error(formatProdErrorMessage(90));
updateInput(otherNode, otherProps.value, otherProps.defaultValue, otherProps.defaultValue, otherProps.checked, otherProps.defaultChecked, otherProps.type, otherProps.name);
}
}
for (internalInstance = 0; internalInstance < props.length; internalInstance++) otherNode = props[internalInstance], otherNode.form === target.form && updateValueIfChanged(otherNode);
}
break a;
case "textarea":
updateTextarea(target, props.value, props.defaultValue);
break a;
case "select": internalInstance = props.value, null != internalInstance && updateOptions(target, !!props.multiple, internalInstance, false);
}
}
}
var isInsideEventHandler = false;
function batchedUpdates$1(fn, a, b) {
if (isInsideEventHandler) return fn(a, b);
isInsideEventHandler = true;
try {
return fn(a);
} finally {
if (isInsideEventHandler = false, null !== restoreTarget || null !== restoreQueue) {
if (flushSyncWork$1(), restoreTarget && (a = restoreTarget, fn = restoreQueue, restoreQueue = restoreTarget = null, restoreStateOfTarget(a), fn)) for (a = 0; a < fn.length; a++) restoreStateOfTarget(fn[a]);
}
}
}
function getListener(inst, registrationName) {
var stateNode = inst.stateNode;
if (null === stateNode) return null;
var props = stateNode[internalPropsKey] || null;
if (null === props) return null;
stateNode = props[registrationName];
a: switch (registrationName) {
case "onClick":
case "onClickCapture":
case "onDoubleClick":
case "onDoubleClickCapture":
case "onMouseDown":
case "onMouseDownCapture":
case "onMouseMove":
case "onMouseMoveCapture":
case "onMouseUp":
case "onMouseUpCapture":
case "onMouseEnter":
(props = !props.disabled) || (inst = inst.type, props = !("button" === inst || "input" === inst || "select" === inst || "textarea" === inst));
inst = !props;
break a;
default: inst = false;
}
if (inst) return null;
if (stateNode && "function" !== typeof stateNode) throw Error(formatProdErrorMessage(231, registrationName, typeof stateNode));
return stateNode;
}
var canUseDOM = !("undefined" === typeof window || "undefined" === typeof window.document || "undefined" === typeof window.document.createElement);
var passiveBrowserEventsSupported = false;
if (canUseDOM) try {
var options = {};
Object.defineProperty(options, "passive", { get: function() {
passiveBrowserEventsSupported = !0;
} });
window.addEventListener("test", options, options);
window.removeEventListener("test", options, options);
} catch (e) {
passiveBrowserEventsSupported = false;
}
var root = null;
var startText = null;
var fallbackText = null;
function getData() {
if (fallbackText) return fallbackText;
var start, startValue = startText, startLength = startValue.length, end, endValue = "value" in root ? root.value : root.textContent, endLength = endValue.length;
for (start = 0; start < startLength && startValue[start] === endValue[start]; start++);
var minEnd = startLength - start;
for (end = 1; end <= minEnd && startValue[startLength - end] === endValue[endLength - end]; end++);
return fallbackText = endValue.slice(start, 1 < end ? 1 - end : void 0);
}
function getEventCharCode(nativeEvent) {
var keyCode = nativeEvent.keyCode;
"charCode" in nativeEvent ? (nativeEvent = nativeEvent.charCode, 0 === nativeEvent && 13 === keyCode && (nativeEvent = 13)) : nativeEvent = keyCode;
10 === nativeEvent && (nativeEvent = 13);
return 32 <= nativeEvent || 13 === nativeEvent ? nativeEvent : 0;
}
function functionThatReturnsTrue() {
return true;
}
function functionThatReturnsFalse() {
return false;
}
function createSyntheticEvent(Interface) {
function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
for (var propName in Interface) Interface.hasOwnProperty(propName) && (reactName = Interface[propName], this[propName] = reactName ? reactName(nativeEvent) : nativeEvent[propName]);
this.isDefaultPrevented = (null != nativeEvent.defaultPrevented ? nativeEvent.defaultPrevented : false === nativeEvent.returnValue) ? functionThatReturnsTrue : functionThatReturnsFalse;
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
assign(SyntheticBaseEvent.prototype, {
preventDefault: function() {
this.defaultPrevented = true;
var event = this.nativeEvent;
event && (event.preventDefault ? event.preventDefault() : "unknown" !== typeof event.returnValue && (event.returnValue = false), this.isDefaultPrevented = functionThatReturnsTrue);
},
stopPropagation: function() {
var event = this.nativeEvent;
event && (event.stopPropagation ? event.stopPropagation() : "unknown" !== typeof event.cancelBubble && (event.cancelBubble = true), this.isPropagationStopped = functionThatReturnsTrue);
},
persist: function() {},
isPersistent: functionThatReturnsTrue
});
return SyntheticBaseEvent;
}
var EventInterface = {
eventPhase: 0,
bubbles: 0,
cancelable: 0,
timeStamp: function(event) {
return event.timeStamp || Date.now();
},
defaultPrevented: 0,
isTrusted: 0
};
var SyntheticEvent = createSyntheticEvent(EventInterface);
var UIEventInterface = assign({}, EventInterface, {
view: 0,
detail: 0
});
var SyntheticUIEvent = createSyntheticEvent(UIEventInterface);
var lastMovementX;
var lastMovementY;
var lastMouseEvent;
var MouseEventInterface = assign({}, UIEventInterface, {
screenX: 0,
screenY: 0,
clientX: 0,
clientY: 0,
pageX: 0,
pageY: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
getModifierState: getEventModifierState,
button: 0,
buttons: 0,
relatedTarget: function(event) {
return void 0 === event.relatedTarget ? event.fromElement === event.srcElement ? event.toElement : event.fromElement : event.relatedTarget;
},
movementX: function(event) {
if ("movementX" in event) return event.movementX;
event !== lastMouseEvent && (lastMouseEvent && "mousemove" === event.type ? (lastMovementX = event.screenX - lastMouseEvent.screenX, lastMovementY = event.screenY - lastMouseEvent.screenY) : lastMovementY = lastMovementX = 0, lastMouseEvent = event);
return lastMovementX;
},
movementY: function(event) {
return "movementY" in event ? event.movementY : lastMovementY;
}
});
var SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface);
var SyntheticDragEvent = createSyntheticEvent(assign({}, MouseEventInterface, { dataTransfer: 0 }));
var SyntheticFocusEvent = createSyntheticEvent(assign({}, UIEventInterface, { relatedTarget: 0 }));
var SyntheticAnimationEvent = createSyntheticEvent(assign({}, EventInterface, {
animationName: 0,
elapsedTime: 0,
pseudoElement: 0
}));
var SyntheticClipboardEvent = createSyntheticEvent(assign({}, EventInterface, { clipboardData: function(event) {
return "clipboardData" in event ? event.clipboardData : window.clipboardData;
} }));
var SyntheticCompositionEvent = createSyntheticEvent(assign({}, EventInterface, { data: 0 }));
var normalizeKey = {
Esc: "Escape",
Spacebar: " ",
Left: "ArrowLeft",
Up: "ArrowUp",
Right: "ArrowRight",
Down: "ArrowDown",
Del: "Delete",
Win: "OS",
Menu: "ContextMenu",
Apps: "ContextMenu",
Scroll: "ScrollLock",
MozPrintableKey: "Unidentified"
};
var translateToKey = {
8: "Backspace",
9: "Tab",
12: "Clear",
13: "Enter",
16: "Shift",
17: "Control",
18: "Alt",
19: "Pause",
20: "CapsLock",
27: "Escape",
32: " ",
33: "PageUp",
34: "PageDown",
35: "End",
36: "Home",
37: "ArrowLeft",
38: "ArrowUp",
39: "ArrowRight",
40: "ArrowDown",
45: "Insert",
46: "Delete",
112: "F1",
113: "F2",
114: "F3",
115: "F4",
116: "F5",
117: "F6",
118: "F7",
119: "F8",
120: "F9",
121: "F10",
122: "F11",
123: "F12",
144: "NumLock",
145: "ScrollLock",
224: "Meta"
};
var modifierKeyToProp = {
Alt: "altKey",
Control: "ctrlKey",
Meta: "metaKey",
Shift: "shiftKey"
};
function modifierStateGetter(keyArg) {
var nativeEvent = this.nativeEvent;
return nativeEvent.getModifierState ? nativeEvent.getModifierState(keyArg) : (keyArg = modifierKeyToProp[keyArg]) ? !!nativeEvent[keyArg] : false;
}
function getEventModifierState() {
return modifierStateGetter;
}
var SyntheticKeyboardEvent = createSyntheticEvent(assign({}, UIEventInterface, {
key: function(nativeEvent) {
if (nativeEvent.key) {
var key = normalizeKey[nativeEvent.key] || nativeEvent.key;
if ("Unidentified" !== key) return key;
}
return "keypress" === nativeEvent.type ? (nativeEvent = getEventCharCode(nativeEvent), 13 === nativeEvent ? "Enter" : String.fromCharCode(nativeEvent)) : "keydown" === nativeEvent.type || "keyup" === nativeEvent.type ? translateToKey[nativeEvent.keyCode] || "Unidentified" : "";
},
code: 0,
location: 0,
ctrlKey: 0,
shiftKey: 0,
altKey: 0,
metaKey: 0,
repeat: 0,
locale: 0,
getModifierState: getEventModifierState,
charCode: function(event) {
return "keypress" === event.type ? getEventCharCode(event) : 0;
},
keyCode: function(event) {
return "keydown" === event.type || "keyup" === event.type ? event.keyCode : 0;
},
which: function(event) {
return "keypress" === event.type ? getEventCharCode(event) : "keydown" === event.type || "keyup" === event.type ? event.keyCode : 0;
}
}));
var SyntheticPointerEvent = createSyntheticEvent(assign({}, MouseEventInterface, {
pointerId: 0,
width: 0,
height: 0,
pressure: 0,
tangentialPressure: 0,
tiltX: 0,
tiltY: 0,
twist: 0,
pointerType: 0,
isPrimary: 0
}));
var SyntheticSubmitEvent = createSyntheticEvent(assign({}, EventInterface, { submitter: 0 }));
var SyntheticTouchEvent = createSyntheticEvent(assign({}, UIEventInterface, {
touches: 0,
targetTouches: 0,
changedTouches: 0,
altKey: 0,
metaKey: 0,
ctrlKey: 0,
shiftKey: 0,
getModifierState: getEventModifierState
}));
var SyntheticTransitionEvent = createSyntheticEvent(assign({}, EventInterface, {
propertyName: 0,
elapsedTime: 0,
pseudoElement: 0
}));
var SyntheticWheelEvent = createSyntheticEvent(assign({}, MouseEventInterface, {
deltaX: function(event) {
return "deltaX" in event ? event.deltaX : "wheelDeltaX" in event ? -event.wheelDeltaX : 0;
},
deltaY: function(event) {
return "deltaY" in event ? event.deltaY : "wheelDeltaY" in event ? -event.wheelDeltaY : "wheelDelta" in event ? -event.wheelDelta : 0;
},
deltaZ: 0,
deltaMode: 0
}));
var SyntheticToggleEvent = createSyntheticEvent(assign({}, EventInterface, {
newState: 0,
oldState: 0,
source: 0
}));
var END_KEYCODES = [
9,
13,
27,
32
];
var canUseCompositionEvent = canUseDOM && "CompositionEvent" in window;
var documentMode = null;
canUseDOM && "documentMode" in document && (documentMode = document.documentMode);
var canUseTextInputEvent = canUseDOM && "TextEvent" in window && !documentMode;
var useFallbackCompositionData = canUseDOM && (!canUseCompositionEvent || documentMode && 8 < documentMode && 11 >= documentMode);
var SPACEBAR_CHAR = String.fromCharCode(32);
var hasSpaceKeypress = false;
function isFallbackCompositionEnd(domEventName, nativeEvent) {
switch (domEventName) {
case "keyup": return -1 !== END_KEYCODES.indexOf(nativeEvent.keyCode);
case "keydown": return 229 !== nativeEvent.keyCode;
case "keypress":
case "mousedown":
case "focusout": return true;
default: return false;
}
}
function getDataFromCustomEvent(nativeEvent) {
nativeEvent = nativeEvent.detail;
return "object" === typeof nativeEvent && "data" in nativeEvent ? nativeEvent.data : null;
}
var isComposing = false;
function getNativeBeforeInputChars(domEventName, nativeEvent) {
switch (domEventName) {
case "compositionend": return getDataFromCustomEvent(nativeEvent);
case "keypress":
if (32 !== nativeEvent.which) return null;
hasSpaceKeypress = true;
return SPACEBAR_CHAR;
case "textInput": return domEventName = nativeEvent.data, domEventName === SPACEBAR_CHAR && hasSpaceKeypress ? null : domEventName;
default: return null;
}
}
function getFallbackBeforeInputChars(domEventName, nativeEvent) {
if (isComposing) return "compositionend" === domEventName || !canUseCompositionEvent && isFallbackCompositionEnd(domEventName, nativeEvent) ? (domEventName = getData(), fallbackText = startText = root = null, isComposing = false, domEventName) : null;
switch (domEventName) {
case "paste": return null;
case "keypress":
if (!(nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) || nativeEvent.ctrlKey && nativeEvent.altKey) {
if (nativeEvent.char && 1 < nativeEvent.char.length) return nativeEvent.char;
if (nativeEvent.which) return String.fromCharCode(nativeEvent.which);
}
return null;
case "compositionend": return useFallbackCompositionData && "ko" !== nativeEvent.locale ? null : nativeEvent.data;
default: return null;
}
}
var supportedInputTypes = {
color: true,
date: true,
datetime: true,
"datetime-local": true,
email: true,
month: true,
number: true,
password: true,
range: true,
search: true,
tel: true,
text: true,
time: true,
url: true,
week: true
};
function isTextInputElement(elem) {
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return "input" === nodeName ? !!supportedInputTypes[elem.type] : "textarea" === nodeName ? true : false;
}
function createAndAccumulateChangeEvent(dispatchQueue, inst, nativeEvent, target) {
restoreTarget ? restoreQueue ? restoreQueue.push(target) : restoreQueue = [target] : restoreTarget = target;
inst = accumulateTwoPhaseListeners(inst, "onChange");
0 < inst.length && (nativeEvent = new SyntheticEvent("onChange", "change", null, nativeEvent, target), dispatchQueue.push({
event: nativeEvent,
listeners: inst
}));
}
var activeElement$1 = null;
var activeElementInst$1 = null;
function runEventInBatch(dispatchQueue) {
processDispatchQueue(dispatchQueue, 0);
}
function getInstIfValueChanged(targetInst) {
if (updateValueIfChanged(getNodeFromInstance(targetInst))) return targetInst;
}
function getTargetInstForChangeEvent(domEventName, targetInst) {
if ("change" === domEventName) return targetInst;
}
var isInputEventSupported = false;
if (canUseDOM) {
var JSCompiler_inline_result$jscomp$318;
if (canUseDOM) {
var isSupported$jscomp$inline_474 = "oninput" in document;
if (!isSupported$jscomp$inline_474) {
var element$jscomp$inline_475 = document.createElement("div");
element$jscomp$inline_475.setAttribute("oninput", "return;");
isSupported$jscomp$inline_474 = "function" === typeof element$jscomp$inline_475.oninput;
}
JSCompiler_inline_result$jscomp$318 = isSupported$jscomp$inline_474;
} else JSCompiler_inline_result$jscomp$318 = false;
isInputEventSupported = JSCompiler_inline_result$jscomp$318 && (!document.documentMode || 9 < document.documentMode);
}
function stopWatchingForValueChange() {
activeElement$1 && (activeElement$1.detachEvent("onpropertychange", handlePropertyChange), activeElementInst$1 = activeElement$1 = null);
}
function handlePropertyChange(nativeEvent) {
if ("value" === nativeEvent.propertyName && getInstIfValueChanged(activeElementInst$1)) {
var dispatchQueue = [];
createAndAccumulateChangeEvent(dispatchQueue, activeElementInst$1, nativeEvent, getEventTarget(nativeEvent));
batchedUpdates$1(runEventInBatch, dispatchQueue);
}
}
function handleEventsForInputEventPolyfill(domEventName, target, targetInst) {
"focusin" === domEventName ? (stopWatchingForValueChange(), activeElement$1 = target, activeElementInst$1 = targetInst, activeElement$1.attachEvent("onpropertychange", handlePropertyChange)) : "focusout" === domEventName && stopWatchingForValueChange();
}
function getTargetInstForInputEventPolyfill(domEventName) {
if ("selectionchange" === domEventName || "keyup" === domEventName || "keydown" === domEventName) return getInstIfValueChanged(activeElementInst$1);
}
function getTargetInstForClickEvent(domEventName, targetInst) {
if ("click" === domEventName) return getInstIfValueChanged(targetInst);
}
function getTargetInstForInputOrChangeEvent(domEventName, targetInst) {
if ("input" === domEventName || "change" === domEventName) return getInstIfValueChanged(targetInst);
}
function is(x, y) {
return x === y && (0 !== x || 1 / x === 1 / y) || x !== x && y !== y;
}
var objectIs = "function" === typeof Object.is ? Object.is : is;
function shallowEqual(objA, objB) {
if (objectIs(objA, objB)) return true;
if ("object" !== typeof objA || null === objA || "object" !== typeof objB || null === objB) return false;
var keysA = Object.keys(objA), keysB = Object.keys(objB);
if (keysA.length !== keysB.length) return false;
for (keysB = 0; keysB < keysA.length; keysB++) {
var currentKey = keysA[keysB];
if (!hasOwnProperty.call(objB, currentKey) || !objectIs(objA[currentKey], objB[currentKey])) return false;
}
return true;
}
function getActiveElement(doc) {
doc = doc || ("undefined" !== typeof document ? document : void 0);
if ("undefined" === typeof doc) return null;
try {
return doc.activeElement || doc.body;
} catch (e$20) {
return doc.body;
}
}
function getLeafNode(node) {
for (; node && node.firstChild;) node = node.firstChild;
return node;
}
function getNodeForCharacterOffset(root, offset) {
var node = getLeafNode(root);
root = 0;
for (var nodeEnd; node;) {
if (3 === node.nodeType) {
nodeEnd = root + node.textContent.length;
if (root <= offset && nodeEnd >= offset) return {
node,
offset: offset - root
};
root = nodeEnd;
}
a: {
for (; node;) {
if (node.nextSibling) {
node = node.nextSibling;
break a;
}
node = node.parentNode;
}
node = void 0;
}
node = getLeafNode(node);
}
}
function containsNode(outerNode, innerNode) {
return outerNode && innerNode ? outerNode === innerNode ? true : outerNode && 3 === outerNode.nodeType ? false : innerNode && 3 === innerNode.nodeType ? containsNode(outerNode, innerNode.parentNode) : "contains" in outerNode ? outerNode.contains(innerNode) : outerNode.compareDocumentPosition ? !!(outerNode.compareDocumentPosition(innerNode) & 16) : false : false;
}
function getActiveElementDeep(containerInfo) {
containerInfo = null != containerInfo && null != containerInfo.ownerDocument && null != containerInfo.ownerDocument.defaultView ? containerInfo.ownerDocument.defaultView : window;
for (var element = getActiveElement(containerInfo.document); element instanceof containerInfo.HTMLIFrameElement;) {
try {
var JSCompiler_inline_result = "string" === typeof element.contentWindow.location.href;
} catch (err) {
JSCompiler_inline_result = false;
}
if (JSCompiler_inline_result) containerInfo = element.contentWindow;
else break;
element = getActiveElement(containerInfo.document);
}
return element;
}
function hasSelectionCapabilities(elem) {
var nodeName = elem && elem.nodeName && elem.nodeName.toLowerCase();
return nodeName && ("input" === nodeName && ("text" === elem.type || "search" === elem.type || "tel" === elem.type || "url" === elem.type || "password" === elem.type) || "textarea" === nodeName || "true" === elem.contentEditable);
}
var skipSelectionChangeEvent = canUseDOM && "documentMode" in document && 11 >= document.documentMode;
var activeElement = null;
var activeElementInst = null;
var lastSelection = null;
var mouseDown = false;
function constructSelectEvent(dispatchQueue, nativeEvent, nativeEventTarget) {
var doc = nativeEventTarget.window === nativeEventTarget ? nativeEventTarget.document : 9 === nativeEventTarget.nodeType ? nativeEventTarget : nativeEventTarget.ownerDocument;
mouseDown || null == activeElement || activeElement !== getActiveElement(doc) || (doc = activeElement, "selectionStart" in doc && hasSelectionCapabilities(doc) ? doc = {
start: doc.selectionStart,
end: doc.selectionEnd
} : (doc = (doc.ownerDocument && doc.ownerDocument.defaultView || window).getSelection(), doc = {
anchorNode: doc.anchorNode,
anchorOffset: doc.anchorOffset,
focusNode: doc.focusNode,
focusOffset: doc.focusOffset
}), lastSelection && shallowEqual(lastSelection, doc) || (lastSelection = doc, doc = accumulateTwoPhaseListeners(activeElementInst, "onSelect"), 0 < doc.length && (nativeEvent = new SyntheticEvent("onSelect", "select", null, nativeEvent, nativeEventTarget), dispatchQueue.push({
event: nativeEvent,
listeners: doc
}), nativeEvent.target = activeElement)));
}
function makePrefixMap(styleProp, eventName) {
var prefixes = {};
prefixes[styleProp.toLowerCase()] = eventName.toLowerCase();
prefixes["Webkit" + styleProp] = "webkit" + eventName;
prefixes["Moz" + styleProp] = "moz" + eventName;
return prefixes;
}
var vendorPrefixes = {
animationend: makePrefixMap("Animation", "AnimationEnd"),
animationiteration: makePrefixMap("Animation", "AnimationIteration"),
animationstart: makePrefixMap("Animation", "AnimationStart"),
transitionrun: makePrefixMap("Transition", "TransitionRun"),
transitionstart: makePrefixMap("Transition", "TransitionStart"),
transitioncancel: makePrefixMap("Transition", "TransitionCancel"),
transitionend: makePrefixMap("Transition", "TransitionEnd")
};
var prefixedEventNames = {};
var style = {};
canUseDOM && (style = document.createElement("div").style, "AnimationEvent" in window || (delete vendorPrefixes.animationend.animation, delete vendorPrefixes.animationiteration.animation, delete vendorPrefixes.animationstart.animation), "TransitionEvent" in window || delete vendorPrefixes.transitionend.transition);
function getVendorPrefixedEventName(eventName) {
if (prefixedEventNames[eventName]) return prefixedEventNames[eventName];
if (!vendorPrefixes[eventName]) return eventName;
var prefixMap = vendorPrefixes[eventName], styleProp;
for (styleProp in prefixMap) if (prefixMap.hasOwnProperty(styleProp) && styleProp in style) return prefixedEventNames[eventName] = prefixMap[styleProp];
return eventName;
}
var ANIMATION_END = getVendorPrefixedEventName("animationend");
var ANIMATION_ITERATION = getVendorPrefixedEventName("animationiteration");
var ANIMATION_START = getVendorPrefixedEventName("animationstart");
var TRANSITION_RUN = getVendorPrefixedEventName("transitionrun");
var TRANSITION_START = getVendorPrefixedEventName("transitionstart");
var TRANSITION_CANCEL = getVendorPrefixedEventName("transitioncancel");
var TRANSITION_END = getVendorPrefixedEventName("transitionend");
var topLevelEventsToReactNames = /* @__PURE__ */ new Map();
var simpleEventPluginEvents = "abort auxClick beforeToggle cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error fullscreenChange fullscreenError gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");
simpleEventPluginEvents.push("scrollEnd");
function registerSimpleEvent(domEventName, reactName) {
topLevelEventsToReactNames.set(domEventName, reactName);
registerTwoPhaseEvent(reactName, [domEventName]);
}
var globalClientIdCounter$1 = 0;
function getViewTransitionName(props, instance) {
if (null != props.name && "auto" !== props.name) return props.name;
if (null !== instance.autoName) return instance.autoName;
props = pendingEffectsRoot.identifierPrefix;
var globalClientId = globalClientIdCounter$1++;
props = "_" + props + "t_" + globalClientId.toString(32) + "_";
return instance.autoName = props;
}
function getClassNameByType(classByType) {
if (null == classByType || "string" === typeof classByType) return classByType;
var className = null, activeTypes = pendingTransitionTypes;
if (null !== activeTypes) for (var i = 0; i < activeTypes.length; i++) {
var match = classByType[activeTypes[i]];
if (null != match) {
if ("none" === match) return "none";
className = null == className ? match : className + (" " + match);
}
}
return null == className ? classByType.default : className;
}
function getViewTransitionClassName(defaultClass, eventClass) {
defaultClass = getClassNameByType(defaultClass);
eventClass = getClassNameByType(eventClass);
return null == eventClass ? "auto" === defaultClass ? null : defaultClass : "auto" === eventClass ? null : eventClass;
}
var reportGlobalError = "function" === typeof reportError ? reportError : function(error) {
if ("object" === typeof window && "function" === typeof window.ErrorEvent) {
var event = new window.ErrorEvent("error", {
bubbles: true,
cancelable: true,
message: "object" === typeof error && null !== error && "string" === typeof error.message ? String(error.message) : String(error),
error
});
if (!window.dispatchEvent(event)) return;
} else if ("object" === typeof process && "function" === typeof process.emit) {
process.emit("uncaughtException", error);
return;
}
console.error(error);
};
var concurrentQueues = [];
var concurrentQueuesIndex = 0;
var concurrentlyUpdatedLanes = 0;
function finishQueueingConcurrentUpdates() {
for (var endIndex = concurrentQueuesIndex, i = concurrentlyUpdatedLanes = concurrentQueuesIndex = 0; i < endIndex;) {
var fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
var queue = concurrentQueues[i];
concurrentQueues[i++] = null;
var update = concurrentQueues[i];
concurrentQueues[i++] = null;
var lane = concurrentQueues[i];
concurrentQueues[i++] = null;
if (null !== queue && null !== update) {
var pending = queue.pending;
null === pending ? update.next = update : (update.next = pending.next, pending.next = update);
queue.pending = update;
}
0 !== lane && markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
function enqueueUpdate$1(fiber, queue, update, lane) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes |= lane;
fiber.lanes |= lane;
fiber = fiber.alternate;
null !== fiber && (fiber.lanes |= lane);
}
function enqueueConcurrentHookUpdate(fiber, queue, update, lane) {
enqueueUpdate$1(fiber, queue, update, lane);
return getRootForUpdatedFiber(fiber);
}
function enqueueConcurrentRenderForLane(fiber, lane) {
enqueueUpdate$1(fiber, null, null, lane);
return getRootForUpdatedFiber(fiber);
}
function markUpdateLaneFromFiberToRoot(sourceFiber, update, lane) {
sourceFiber.lanes |= lane;
var alternate = sourceFiber.alternate;
null !== alternate && (alternate.lanes |= lane);
for (var isHidden = false, parent = sourceFiber.return; null !== parent;) parent.childLanes |= lane, alternate = parent.alternate, null !== alternate && (alternate.childLanes |= lane), 22 === parent.tag && (sourceFiber = parent.stateNode, null === sourceFiber || sourceFiber._visibility & 1 || (isHidden = true)), sourceFiber = parent, parent = parent.return;
return 3 === sourceFiber.tag ? (parent = sourceFiber.stateNode, isHidden && null !== update && (isHidden = 31 - clz32(lane), sourceFiber = parent.hiddenUpdates, alternate = sourceFiber[isHidden], null === alternate ? sourceFiber[isHidden] = [update] : alternate.push(update), update.lane = lane | 536870912), parent) : null;
}
function getRootForUpdatedFiber(sourceFiber) {
if (50 < nestedUpdateCount) throw nestedUpdateCount = 0, rootWithNestedUpdates = null, Error(formatProdErrorMessage(185));
for (var parent = sourceFiber.return; null !== parent;) sourceFiber = parent, parent = sourceFiber.return;
return 3 === sourceFiber.tag ? sourceFiber.stateNode : null;
}
var emptyContextObject = {};
function FiberNode(tag, pendingProps, key, mode) {
this.tag = tag;
this.key = key;
this.sibling = this.child = this.return = this.stateNode = this.type = this.elementType = null;
this.index = 0;
this.refCleanup = this.ref = null;
this.pendingProps = pendingProps;
this.dependencies = this.memoizedState = this.updateQueue = this.memoizedProps = null;
this.mode = mode;
this.subtreeFlags = this.flags = 0;
this.deletions = null;
this.childLanes = this.lanes = 0;
this.alternate = null;
}
function createFiberImplClass(tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
}
function shouldConstruct(Component) {
Component = Component.prototype;
return !(!Component || !Component.isReactComponent);
}
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
null === workInProgress ? (workInProgress = createFiberImplClass(current.tag, pendingProps, current.key, current.mode), workInProgress.elementType = current.elementType, workInProgress.type = current.type, workInProgress.stateNode = current.stateNode, workInProgress.alternate = current, current.alternate = workInProgress) : (workInProgress.pendingProps = pendingProps, workInProgress.type = current.type, workInProgress.flags = 0, workInProgress.subtreeFlags = 0, workInProgress.deletions = null);
workInProgress.flags = current.flags & 1206910976;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
pendingProps = current.dependencies;
workInProgress.dependencies = null === pendingProps ? null : {
lanes: pendingProps.lanes,
firstContext: pendingProps.firstContext
};
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
workInProgress.refCleanup = current.refCleanup;
return workInProgress;
}
function resetWorkInProgress(workInProgress, renderLanes) {
workInProgress.flags &= 1206910978;
var current = workInProgress.alternate;
null === current ? (workInProgress.childLanes = 0, workInProgress.lanes = renderLanes, workInProgress.child = null, workInProgress.subtreeFlags = 0, workInProgress.memoizedProps = null, workInProgress.memoizedState = null, workInProgress.updateQueue = null, workInProgress.dependencies = null, workInProgress.stateNode = null) : (workInProgress.childLanes = current.childLanes, workInProgress.lanes = current.lanes, workInProgress.child = current.child, workInProgress.subtreeFlags = 0, workInProgress.deletions = null, workInProgress.memoizedProps = current.memoizedProps, workInProgress.memoizedState = current.memoizedState, workInProgress.updateQueue = current.updateQueue, workInProgress.type = current.type, renderLanes = current.dependencies, workInProgress.dependencies = null === renderLanes ? null : {
lanes: renderLanes.lanes,
firstContext: renderLanes.firstContext
});
return workInProgress;
}
function createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes) {
var fiberTag = 0;
owner = type;
if ("function" === typeof owner) shouldConstruct(owner) && (fiberTag = 1);
else if ("string" === typeof owner) fiberTag = isHostHoistableType(type, pendingProps, contextStackCursor.current) ? 26 : "html" === type || "head" === type || "body" === type ? 27 : 5;
else a: switch (owner) {
case REACT_ACTIVITY_TYPE: return type = createFiberImplClass(31, pendingProps, key, mode), type.elementType = REACT_ACTIVITY_TYPE, type.lanes = lanes, type;
case REACT_FRAGMENT_TYPE: return createFiberFromFragment(pendingProps.children, mode, lanes, key);
case REACT_STRICT_MODE_TYPE:
fiberTag = 8;
mode |= 24;
break;
case REACT_PROFILER_TYPE: return type = createFiberImplClass(12, pendingProps, key, mode | 2), type.elementType = REACT_PROFILER_TYPE, type.lanes = lanes, type;
case REACT_SUSPENSE_TYPE: return type = createFiberImplClass(13, pendingProps, key, mode), type.elementType = REACT_SUSPENSE_TYPE, type.lanes = lanes, type;
case REACT_SUSPENSE_LIST_TYPE: return type = createFiberImplClass(19, pendingProps, key, mode), type.elementType = REACT_SUSPENSE_LIST_TYPE, type.lanes = lanes, type;
case REACT_LEGACY_HIDDEN_TYPE:
case REACT_VIEW_TRANSITION_TYPE: return type = mode | 32, type = createFiberImplClass(30, pendingProps, key, type), type.elementType = REACT_VIEW_TRANSITION_TYPE, type.lanes = lanes, type.stateNode = {
autoName: null,
paired: null,
clones: null,
ref: null
}, type;
default:
if ("object" === typeof owner && null !== owner) switch (owner.$$typeof) {
case REACT_CONTEXT_TYPE:
fiberTag = 10;
break a;
case REACT_CONSUMER_TYPE:
fiberTag = 9;
break a;
case REACT_FORWARD_REF_TYPE:
fiberTag = 11;
break a;
case REACT_MEMO_TYPE:
fiberTag = 14;
break a;
case REACT_LAZY_TYPE:
fiberTag = 16;
owner = null;
break a;
}
fiberTag = 29;
pendingProps = Error(formatProdErrorMessage(130, null === type ? "null" : typeof type, ""));
owner = null;
}
key = createFiberImplClass(fiberTag, pendingProps, key, mode);
key.elementType = type;
key.type = owner;
key.lanes = lanes;
return key;
}
function createFiberFromFragment(elements, mode, lanes, key) {
elements = createFiberImplClass(7, elements, key, mode);
elements.lanes = lanes;
return elements;
}
function createFiberFromText(content, mode, lanes) {
content = createFiberImplClass(6, content, null, mode);
content.lanes = lanes;
return content;
}
function createFiberFromDehydratedFragment(dehydratedNode) {
var fiber = createFiberImplClass(18, null, null, 0);
fiber.stateNode = dehydratedNode;
return fiber;
}
function createFiberFromPortal(portal, mode, lanes) {
mode = createFiberImplClass(4, null !== portal.children ? portal.children : [], portal.key, mode);
mode.lanes = lanes;
mode.stateNode = {
containerInfo: portal.containerInfo,
pendingChildren: null,
implementation: portal.implementation
};
return mode;
}
var CapturedStacks = /* @__PURE__ */ new WeakMap();
function createCapturedValueAtFiber(value, source) {
if ("object" === typeof value && null !== value) {
var existing = CapturedStacks.get(value);
if (void 0 !== existing) return existing;
source = {
value,
source,
stack: getStackByFiberInDevAndProd(source)
};
CapturedStacks.set(value, source);
return source;
}
return {
value,
source,
stack: getStackByFiberInDevAndProd(source)
};
}
var forkStack = [];
var forkStackIndex = 0;
var treeForkProvider = null;
var treeForkCount = 0;
var idStack = [];
var idStackIndex = 0;
var treeContextProvider = null;
var treeContextId = 1;
var treeContextOverflow = "";
function pushTreeFork(workInProgress, totalChildren) {
forkStack[forkStackIndex++] = treeForkCount;
forkStack[forkStackIndex++] = treeForkProvider;
treeForkProvider = workInProgress;
treeForkCount = totalChildren;
}
function pushTreeId(workInProgress, totalChildren, index) {
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextProvider = workInProgress;
var baseIdWithLeadingBit = treeContextId;
workInProgress = treeContextOverflow;
var baseLength = 32 - clz32(baseIdWithLeadingBit) - 1;
baseIdWithLeadingBit &= ~(1 << baseLength);
index += 1;
var length = 32 - clz32(totalChildren) + baseLength;
if (30 < length) {
var numberOfOverflowBits = baseLength - baseLength % 5;
length = (baseIdWithLeadingBit & (1 << numberOfOverflowBits) - 1).toString(32);
baseIdWithLeadingBit >>= numberOfOverflowBits;
baseLength -= numberOfOverflowBits;
treeContextId = 1 << 32 - clz32(totalChildren) + baseLength | index << baseLength | baseIdWithLeadingBit;
treeContextOverflow = length + workInProgress;
} else treeContextId = 1 << length | index << baseLength | baseIdWithLeadingBit, treeContextOverflow = workInProgress;
}
function pushMaterializedTreeId(workInProgress) {
null !== workInProgress.return && (pushTreeFork(workInProgress, 1), pushTreeId(workInProgress, 1, 0));
}
function popTreeContext(workInProgress) {
for (; workInProgress === treeForkProvider;) treeForkProvider = forkStack[--forkStackIndex], forkStack[forkStackIndex] = null, treeForkCount = forkStack[--forkStackIndex], forkStack[forkStackIndex] = null;
for (; workInProgress === treeContextProvider;) treeContextProvider = idStack[--idStackIndex], idStack[idStackIndex] = null, treeContextOverflow = idStack[--idStackIndex], idStack[idStackIndex] = null, treeContextId = idStack[--idStackIndex], idStack[idStackIndex] = null;
}
function restoreSuspendedTreeContext(workInProgress, suspendedContext) {
idStack[idStackIndex++] = treeContextId;
idStack[idStackIndex++] = treeContextOverflow;
idStack[idStackIndex++] = treeContextProvider;
treeContextId = suspendedContext.id;
treeContextOverflow = suspendedContext.overflow;
treeContextProvider = workInProgress;
}
var hydrationParentFiber = null;
var nextHydratableInstance = null;
var isHydrating = false;
var hydrationErrors = null;
var rootOrSingletonContext = false;
var HydrationMismatchException = Error(formatProdErrorMessage(519));
function throwOnHydrationMismatch(fiber) {
queueHydrationError(createCapturedValueAtFiber(Error(formatProdErrorMessage(418, 1 < arguments.length && void 0 !== arguments[1] && arguments[1] ? "text" : "HTML", "")), fiber));
throw HydrationMismatchException;
}
function prepareToHydrateHostInstance(fiber) {
var instance = fiber.stateNode, type = fiber.type, props = fiber.memoizedProps;
instance[internalInstanceKey] = fiber;
instance[internalPropsKey] = props;
switch (type) {
case "dialog":
listenToNonDelegatedEvent("cancel", instance);
listenToNonDelegatedEvent("close", instance);
break;
case "iframe":
case "object":
case "embed":
listenToNonDelegatedEvent("load", instance);
break;
case "video":
case "audio":
for (type = 0; type < mediaEventTypes.length; type++) listenToNonDelegatedEvent(mediaEventTypes[type], instance);
break;
case "source":
listenToNonDelegatedEvent("error", instance);
break;
case "img":
case "image":
case "link":
listenToNonDelegatedEvent("error", instance);
listenToNonDelegatedEvent("load", instance);
break;
case "details":
listenToNonDelegatedEvent("toggle", instance);
break;
case "input":
listenToNonDelegatedEvent("invalid", instance);
initInput(instance, props.value, props.defaultValue, props.checked, props.defaultChecked, props.type, props.name, true);
break;
case "select":
listenToNonDelegatedEvent("invalid", instance);
break;
case "textarea": listenToNonDelegatedEvent("invalid", instance), initTextarea(instance, props.value, props.defaultValue, props.children);
}
type = props.children;
"string" !== typeof type && "number" !== typeof type && "bigint" !== typeof type || instance.textContent === "" + type || true === props.suppressHydrationWarning || checkForUnmatchedText(instance.textContent, type) ? (null != props.popover && (listenToNonDelegatedEvent("beforetoggle", instance), listenToNonDelegatedEvent("toggle", instance)), null != props.onScroll && listenToNonDelegatedEvent("scroll", instance), null != props.onScrollEnd && listenToNonDelegatedEvent("scrollend", instance), null != props.onClick && (instance.onclick = noop$1), instance = true) : instance = false;
instance || throwOnHydrationMismatch(fiber, true);
}
function popToNextHostParent(fiber) {
for (hydrationParentFiber = fiber.return; hydrationParentFiber;) switch (hydrationParentFiber.tag) {
case 5:
case 31:
case 13:
rootOrSingletonContext = false;
return;
case 27:
case 3:
rootOrSingletonContext = true;
return;
default: hydrationParentFiber = hydrationParentFiber.return;
}
}
function popHydrationState(fiber) {
if (fiber !== hydrationParentFiber) return false;
if (!isHydrating) return popToNextHostParent(fiber), isHydrating = true, false;
var tag = fiber.tag, JSCompiler_temp;
if (JSCompiler_temp = 3 !== tag && 27 !== tag) {
if (JSCompiler_temp = 5 === tag) JSCompiler_temp = fiber.type, JSCompiler_temp = !("form" !== JSCompiler_temp && "button" !== JSCompiler_temp) || shouldSetTextContent(fiber.type, fiber.memoizedProps);
JSCompiler_temp = !JSCompiler_temp;
}
JSCompiler_temp && nextHydratableInstance && throwOnHydrationMismatch(fiber);
popToNextHostParent(fiber);
if (13 === tag) {
fiber = fiber.memoizedState;
fiber = null !== fiber ? fiber.dehydrated : null;
if (!fiber) throw Error(formatProdErrorMessage(317));
nextHydratableInstance = getNextHydratableInstanceAfterHydrationBoundary(fiber);
} else if (31 === tag) {
fiber = fiber.memoizedState;
fiber = null !== fiber ? fiber.dehydrated : null;
if (!fiber) throw Error(formatProdErrorMessage(317));
nextHydratableInstance = getNextHydratableInstanceAfterHydrationBoundary(fiber);
} else 27 === tag ? (tag = nextHydratableInstance, isSingletonScope(fiber.type) ? (fiber = previousHydratableOnEnteringScopedSingleton, previousHydratableOnEnteringScopedSingleton = null, nextHydratableInstance = fiber) : nextHydratableInstance = tag) : nextHydratableInstance = hydrationParentFiber ? getNextHydratable(fiber.stateNode.nextSibling) : null;
return true;
}
function resetHydrationState() {
nextHydratableInstance = hydrationParentFiber = null;
isHydrating = false;
}
function upgradeHydrationErrorsToRecoverable() {
var queuedErrors = hydrationErrors;
null !== queuedErrors && (null === workInProgressRootRecoverableErrors ? workInProgressRootRecoverableErrors = queuedErrors : workInProgressRootRecoverableErrors.push.apply(workInProgressRootRecoverableErrors, queuedErrors), hydrationErrors = null);
return queuedErrors;
}
function queueHydrationError(error) {
null === hydrationErrors ? hydrationErrors = [error] : hydrationErrors.push(error);
}
var valueCursor = createCursor(null);
var currentlyRenderingFiber$1 = null;
var lastContextDependency = null;
function pushProvider(providerFiber, context, nextValue) {
push(valueCursor, context._currentValue);
context._currentValue = nextValue;
}
function popProvider(context) {
context._currentValue = valueCursor.current;
pop(valueCursor);
}
function scheduleContextWorkOnParentPath(parent, renderLanes, propagationRoot) {
for (; null !== parent;) {
var alternate = parent.alternate;
(parent.childLanes & renderLanes) !== renderLanes ? (parent.childLanes |= renderLanes, null !== alternate && (alternate.childLanes |= renderLanes)) : null !== alternate && (alternate.childLanes & renderLanes) !== renderLanes && (alternate.childLanes |= renderLanes);
if (parent === propagationRoot) break;
parent = parent.return;
}
}
function propagateContextChanges(workInProgress, contexts, renderLanes, forcePropagateEntireTree) {
var fiber = workInProgress.child;
null !== fiber && (fiber.return = workInProgress);
for (; null !== fiber;) {
var list = fiber.dependencies;
if (null !== list) {
var nextFiber = fiber.child;
list = list.firstContext;
a: for (; null !== list;) {
var dependency = list;
list = fiber;
for (var i = 0; i < contexts.length; i++) if (dependency.context === contexts[i]) {
list.lanes |= renderLanes;
dependency = list.alternate;
null !== dependency && (dependency.lanes |= renderLanes);
scheduleContextWorkOnParentPath(list.return, renderLanes, workInProgress);
forcePropagateEntireTree || (nextFiber = null);
break a;
}
list = dependency.next;
}
} else if (18 === fiber.tag) {
nextFiber = fiber.return;
if (null === nextFiber) throw Error(formatProdErrorMessage(341));
nextFiber.lanes |= renderLanes;
list = nextFiber.alternate;
null !== list && (list.lanes |= renderLanes);
scheduleContextWorkOnParentPath(nextFiber, renderLanes, workInProgress);
nextFiber = null;
} else 13 === fiber.tag && null !== fiber.memoizedState && null === fiber.memoizedState.dehydrated ? (fiber.lanes |= renderLanes, nextFiber = fiber.alternate, null !== nextFiber && (nextFiber.lanes |= renderLanes), scheduleContextWorkOnParentPath(fiber.return, renderLanes, workInProgress), nextFiber = fiber.child, nextFiber = null !== nextFiber ? nextFiber.sibling : null) : nextFiber = fiber.child;
if (null !== nextFiber) nextFiber.return = fiber;
else for (nextFiber = fiber; null !== nextFiber;) {
if (nextFiber === workInProgress) {
nextFiber = null;
break;
}
fiber = nextFiber.sibling;
if (null !== fiber) {
fiber.return = nextFiber.return;
nextFiber = fiber;
break;
}
nextFiber = nextFiber.return;
}
fiber = nextFiber;
}
}
function propagateParentContextChanges(current, workInProgress, renderLanes, forcePropagateEntireTree) {
current = null;
for (var parent = workInProgress, isInsidePropagationBailout = false; null !== parent;) {
if (!isInsidePropagationBailout) {
if (0 !== (parent.flags & 524288)) isInsidePropagationBailout = true;
else if (0 !== (parent.flags & 262144)) break;
}
if (10 === parent.tag) {
var currentParent = parent.alternate;
if (null === currentParent) throw Error(formatProdErrorMessage(387));
currentParent = currentParent.memoizedProps;
if (null !== currentParent) {
var context = parent.type;
objectIs(parent.pendingProps.value, currentParent.value) || (null !== current ? current.push(context) : current = [context]);
}
} else if (parent === hostTransitionProviderCursor.current) {
currentParent = parent.alternate;
if (null === currentParent) throw Error(formatProdErrorMessage(387));
currentParent.memoizedState.memoizedState !== parent.memoizedState.memoizedState && (null !== current ? current.push(HostTransitionContext) : current = [HostTransitionContext]);
}
parent = parent.return;
}
null !== current && propagateContextChanges(workInProgress, current, renderLanes, forcePropagateEntireTree);
workInProgress.flags |= 262144;
return null !== current;
}
function checkIfContextChanged(currentDependencies) {
for (currentDependencies = currentDependencies.firstContext; null !== currentDependencies;) {
if (!objectIs(currentDependencies.context._currentValue, currentDependencies.memoizedValue)) return true;
currentDependencies = currentDependencies.next;
}
return false;
}
function prepareToReadContext(workInProgress) {
currentlyRenderingFiber$1 = workInProgress;
lastContextDependency = null;
workInProgress = workInProgress.dependencies;
null !== workInProgress && (workInProgress.firstContext = null);
}
function readContext(context) {
return readContextForConsumer(currentlyRenderingFiber$1, context);
}
function readContextDuringReconciliation(consumer, context) {
null === currentlyRenderingFiber$1 && prepareToReadContext(consumer);
return readContextForConsumer(consumer, context);
}
function readContextForConsumer(consumer, context) {
var value = context._currentValue;
context = {
context,
memoizedValue: value,
next: null
};
if (null === lastContextDependency) {
if (null === consumer) throw Error(formatProdErrorMessage(308));
lastContextDependency = context;
consumer.dependencies = {
lanes: 0,
firstContext: context
};
consumer.flags |= 524288;
} else lastContextDependency = lastContextDependency.next = context;
return value;
}
var AbortControllerLocal = "undefined" !== typeof AbortController ? AbortController : function() {
var listeners = [], signal = this.signal = {
aborted: false,
addEventListener: function(type, listener) {
listeners.push(listener);
}
};
this.abort = function() {
signal.aborted = true;
listeners.forEach(function(listener) {
return listener();
});
};
};
var scheduleCallback$2 = Scheduler.unstable_scheduleCallback;
var NormalPriority = Scheduler.unstable_NormalPriority;
var CacheContext = {
$$typeof: REACT_CONTEXT_TYPE,
Consumer: null,
Provider: null,
_currentValue: null,
_currentValue2: null,
_threadCount: 0
};
function createCache() {
return {
controller: new AbortControllerLocal(),
data: /* @__PURE__ */ new Map(),
refCount: 0
};
}
function releaseCache(cache) {
cache.refCount--;
0 === cache.refCount && scheduleCallback$2(NormalPriority, function() {
cache.controller.abort();
});
}
function queueTransitionTypes(root, transitionTypes) {
if (0 !== (root.pendingLanes & 4194048)) {
var queued = root.transitionTypes;
null === queued && (queued = root.transitionTypes = []);
for (root = 0; root < transitionTypes.length; root++) {
var transitionType = transitionTypes[root];
-1 === queued.indexOf(transitionType) && queued.push(transitionType);
}
}
}
var entangledTransitionTypes = null;
function claimQueuedTransitionTypes(root) {
var claimed = root.transitionTypes;
root.transitionTypes = null;
return claimed;
}
var currentEntangledListeners = null;
var currentEntangledPendingCount = 0;
var currentEntangledLane = 0;
var currentEntangledActionThenable = null;
function entangleAsyncAction(transition, thenable) {
if (null === currentEntangledListeners) {
var entangledListeners = currentEntangledListeners = [];
currentEntangledPendingCount = 0;
currentEntangledLane = requestTransitionLane();
currentEntangledActionThenable = {
status: "pending",
value: void 0,
then: function(resolve) {
entangledListeners.push(resolve);
}
};
}
currentEntangledPendingCount++;
thenable.then(pingEngtangledActionScope, pingEngtangledActionScope);
return thenable;
}
function pingEngtangledActionScope() {
if (0 === --currentEntangledPendingCount && (entangledTransitionTypes = null, null !== currentEntangledListeners)) {
null !== currentEntangledActionThenable && (currentEntangledActionThenable.status = "fulfilled");
var listeners = currentEntangledListeners;
currentEntangledListeners = null;
currentEntangledLane = 0;
currentEntangledActionThenable = null;
for (var i = 0; i < listeners.length; i++) (0, listeners[i])();
}
}
function chainThenableValue(thenable, result) {
var listeners = [], thenableWithOverride = {
status: "pending",
value: null,
reason: null,
then: function(resolve) {
listeners.push(resolve);
}
};
thenable.then(function() {
thenableWithOverride.status = "fulfilled";
thenableWithOverride.value = result;
for (var i = 0; i < listeners.length; i++) (0, listeners[i])(result);
}, function(error) {
thenableWithOverride.status = "rejected";
thenableWithOverride.reason = error;
for (error = 0; error < listeners.length; error++) (0, listeners[error])(void 0);
});
return thenableWithOverride;
}
var prevOnStartTransitionFinish = ReactSharedInternals.S;
ReactSharedInternals.S = function(transition, returnValue) {
globalMostRecentTransitionTime = now();
"object" === typeof returnValue && null !== returnValue && "function" === typeof returnValue.then && entangleAsyncAction(transition, returnValue);
if (null !== entangledTransitionTypes) for (var root$28 = firstScheduledRoot; null !== root$28;) queueTransitionTypes(root$28, entangledTransitionTypes), root$28 = root$28.next;
root$28 = transition.types;
if (null !== root$28) {
for (var root$29 = firstScheduledRoot; null !== root$29;) queueTransitionTypes(root$29, root$28), root$29 = root$29.next;
if (0 !== currentEntangledLane) {
root$29 = entangledTransitionTypes;
null === root$29 && (root$29 = entangledTransitionTypes = []);
for (var i = 0; i < root$28.length; i++) {
var transitionType = root$28[i];
-1 === root$29.indexOf(transitionType) && root$29.push(transitionType);
}
}
}
null !== prevOnStartTransitionFinish && prevOnStartTransitionFinish(transition, returnValue);
};
var resumedCache = createCursor(null);
function peekCacheFromPool() {
var cacheResumedFromPreviousRender = resumedCache.current;
return null !== cacheResumedFromPreviousRender ? cacheResumedFromPreviousRender : workInProgressRoot.pooledCache;
}
function pushTransition(offscreenWorkInProgress, prevCachePool) {
null === prevCachePool ? push(resumedCache, resumedCache.current) : push(resumedCache, prevCachePool.pool);
}
function getSuspendedCache() {
var cacheFromPool = peekCacheFromPool();
return null === cacheFromPool ? null : {
parent: CacheContext._currentValue,
pool: cacheFromPool
};
}
var SuspenseException = Error(formatProdErrorMessage(460));
var SuspenseyCommitException = Error(formatProdErrorMessage(474));
var SuspenseActionException = Error(formatProdErrorMessage(542));
var noopSuspenseyCommitThenable = { then: function() {} };
function isThenableResolved(thenable) {
thenable = thenable.status;
return "fulfilled" === thenable || "rejected" === thenable;
}
function trackUsedThenable(thenableState, thenable, index) {
index = thenableState[index];
void 0 === index ? thenableState.push(thenable) : index !== thenable && (thenable.then(noop$1, noop$1), thenable = index);
switch (thenable.status) {
case "fulfilled": return thenable.value;
case "rejected":
thenableState = thenable.reason;
checkIfUseWrappedInAsyncCatch(thenableState);
if (void 0 === thenableState && !("reason" in thenable)) throw Error(formatProdErrorMessage(600));
throw thenableState;
default:
if ("string" === typeof thenable.status) thenable.then(noop$1, noop$1);
else {
thenableState = workInProgressRoot;
if (null !== thenableState && 100 < thenableState.shellSuspendCounter) throw Error(formatProdErrorMessage(482));
thenableState = thenable;
thenableState.status = "pending";
thenableState.then(function(fulfilledValue) {
if ("pending" === thenable.status) {
var fulfilledThenable = thenable;
fulfilledThenable.status = "fulfilled";
fulfilledThenable.value = fulfilledValue;
}
}, function(error) {
if ("pending" === thenable.status) {
var rejectedThenable = thenable;
rejectedThenable.status = "rejected";
rejectedThenable.reason = error;
}
});
}
switch (thenable.status) {
case "fulfilled": return thenable.value;
case "rejected": throw thenableState = thenable.reason, checkIfUseWrappedInAsyncCatch(thenableState), thenableState;
}
suspendedThenable = thenable;
throw SuspenseException;
}
}
function resolveLazy(lazyType) {
try {
var init = lazyType._init;
return init(lazyType._payload);
} catch (x) {
if (null !== x && "object" === typeof x && "function" === typeof x.then) throw suspendedThenable = x, SuspenseException;
throw x;
}
}
var suspendedThenable = null;
function getSuspendedThenable() {
if (null === suspendedThenable) throw Error(formatProdErrorMessage(459));
var thenable = suspendedThenable;
suspendedThenable = null;
return thenable;
}
function checkIfUseWrappedInAsyncCatch(rejectedReason) {
if (rejectedReason === SuspenseException || rejectedReason === SuspenseActionException) throw Error(formatProdErrorMessage(483));
}
var thenableState$1 = null;
var thenableIndexCounter$1 = 0;
function unwrapThenable(thenable) {
var index = thenableIndexCounter$1;
thenableIndexCounter$1 += 1;
null === thenableState$1 && (thenableState$1 = []);
return trackUsedThenable(thenableState$1, thenable, index);
}
function coerceRef(workInProgress, element) {
element = element.props.ref;
workInProgress.ref = void 0 !== element ? element : null;
}
function throwOnInvalidObjectTypeImpl(returnFiber, newChild) {
if (newChild.$$typeof === REACT_LEGACY_ELEMENT_TYPE) throw Error(formatProdErrorMessage(525));
returnFiber = Object.prototype.toString.call(newChild);
throw Error(formatProdErrorMessage(31, "[object Object]" === returnFiber ? "object with keys {" + Object.keys(newChild).join(", ") + "}" : returnFiber));
}
function createChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber, childToDelete) {
if (shouldTrackSideEffects) {
var deletions = returnFiber.deletions;
null === deletions ? (returnFiber.deletions = [childToDelete], returnFiber.flags |= 16) : deletions.push(childToDelete);
}
}
function deleteRemainingChildren(returnFiber, currentFirstChild) {
if (!shouldTrackSideEffects) return null;
for (; null !== currentFirstChild;) deleteChild(returnFiber, currentFirstChild), currentFirstChild = currentFirstChild.sibling;
return null;
}
function mapRemainingChildren(currentFirstChild) {
for (var existingChildren = /* @__PURE__ */ new Map(); null !== currentFirstChild;) null === currentFirstChild.key ? existingChildren.set(currentFirstChild.index, currentFirstChild) : existingChildren.set(currentFirstChild.key, currentFirstChild), currentFirstChild = currentFirstChild.sibling;
return existingChildren;
}
function useFiber(fiber, pendingProps) {
fiber = createWorkInProgress(fiber, pendingProps);
fiber.index = 0;
fiber.sibling = null;
return fiber;
}
function placeChild(newFiber, lastPlacedIndex, newIndex) {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) return newFiber.flags |= 1048576, lastPlacedIndex;
newIndex = newFiber.alternate;
if (null !== newIndex) return newIndex = newIndex.index, newIndex < lastPlacedIndex ? (newFiber.flags |= 2, lastPlacedIndex) : newIndex;
newFiber.flags |= 134217730;
return lastPlacedIndex;
}
function placeSingleChild(newFiber) {
shouldTrackSideEffects && null === newFiber.alternate && (newFiber.flags |= 134217730);
return newFiber;
}
function updateTextNode(returnFiber, current, textContent, lanes) {
if (null === current || 6 !== current.tag) return current = createFiberFromText(textContent, returnFiber.mode, lanes), current.return = returnFiber, current;
current = useFiber(current, textContent);
current.return = returnFiber;
return current;
}
function updateElement(returnFiber, current, element, lanes) {
var elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) return returnFiber = updateFragment(returnFiber, current, element.props.children, lanes, element.key), coerceRef(returnFiber, element), returnFiber;
if (null !== current && (current.elementType === elementType || "object" === typeof elementType && null !== elementType && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === current.type)) return current = useFiber(current, element.props), coerceRef(current, element), current.return = returnFiber, current;
current = createFiberFromTypeAndProps(element.type, element.key, element.props, null, returnFiber.mode, lanes);
coerceRef(current, element);
current.return = returnFiber;
return current;
}
function updatePortal(returnFiber, current, portal, lanes) {
if (null === current || 4 !== current.tag || current.stateNode.containerInfo !== portal.containerInfo || current.stateNode.implementation !== portal.implementation) return current = createFiberFromPortal(portal, returnFiber.mode, lanes), current.return = returnFiber, current;
current = useFiber(current, portal.children || []);
current.return = returnFiber;
return current;
}
function updateFragment(returnFiber, current, fragment, lanes, key) {
if (null === current || 7 !== current.tag) return current = createFiberFromFragment(fragment, returnFiber.mode, lanes, key), current.return = returnFiber, current;
current = useFiber(current, fragment);
current.return = returnFiber;
return current;
}
function createChild(returnFiber, newChild, lanes) {
if ("string" === typeof newChild && "" !== newChild || "number" === typeof newChild || "bigint" === typeof newChild) return newChild = createFiberFromText("" + newChild, returnFiber.mode, lanes), newChild.return = returnFiber, newChild;
if ("object" === typeof newChild && null !== newChild) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: return lanes = createFiberFromTypeAndProps(newChild.type, newChild.key, newChild.props, null, returnFiber.mode, lanes), coerceRef(lanes, newChild), lanes.return = returnFiber, lanes;
case REACT_PORTAL_TYPE: return newChild = createFiberFromPortal(newChild, returnFiber.mode, lanes), newChild.return = returnFiber, newChild;
case REACT_LAZY_TYPE: return newChild = resolveLazy(newChild), createChild(returnFiber, newChild, lanes);
}
if (isArrayImpl(newChild) || getIteratorFn(newChild)) return newChild = createFiberFromFragment(newChild, returnFiber.mode, lanes, null), newChild.return = returnFiber, newChild;
if ("function" === typeof newChild.then) return createChild(returnFiber, unwrapThenable(newChild), lanes);
if (newChild.$$typeof === REACT_CONTEXT_TYPE) return createChild(returnFiber, readContextDuringReconciliation(returnFiber, newChild), lanes);
throwOnInvalidObjectTypeImpl(returnFiber, newChild);
}
return null;
}
function updateSlot(returnFiber, oldFiber, newChild, lanes) {
var key = null !== oldFiber ? oldFiber.key : null;
if ("string" === typeof newChild && "" !== newChild || "number" === typeof newChild || "bigint" === typeof newChild) return null !== key ? null : updateTextNode(returnFiber, oldFiber, "" + newChild, lanes);
if ("object" === typeof newChild && null !== newChild) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: return newChild.key === key ? updateElement(returnFiber, oldFiber, newChild, lanes) : null;
case REACT_PORTAL_TYPE: return newChild.key === key ? updatePortal(returnFiber, oldFiber, newChild, lanes) : null;
case REACT_LAZY_TYPE: return newChild = resolveLazy(newChild), updateSlot(returnFiber, oldFiber, newChild, lanes);
}
if (isArrayImpl(newChild) || getIteratorFn(newChild)) return null !== key ? null : updateFragment(returnFiber, oldFiber, newChild, lanes, null);
if ("function" === typeof newChild.then) return updateSlot(returnFiber, oldFiber, unwrapThenable(newChild), lanes);
if (newChild.$$typeof === REACT_CONTEXT_TYPE) return updateSlot(returnFiber, oldFiber, readContextDuringReconciliation(returnFiber, newChild), lanes);
throwOnInvalidObjectTypeImpl(returnFiber, newChild);
}
return null;
}
function updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes) {
if ("string" === typeof newChild && "" !== newChild || "number" === typeof newChild || "bigint" === typeof newChild) return existingChildren = existingChildren.get(newIdx) || null, updateTextNode(returnFiber, existingChildren, "" + newChild, lanes);
if ("object" === typeof newChild && null !== newChild) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: return existingChildren = existingChildren.get(null === newChild.key ? newIdx : newChild.key) || null, updateElement(returnFiber, existingChildren, newChild, lanes);
case REACT_PORTAL_TYPE: return existingChildren = existingChildren.get(null === newChild.key ? newIdx : newChild.key) || null, updatePortal(returnFiber, existingChildren, newChild, lanes);
case REACT_LAZY_TYPE: return newChild = resolveLazy(newChild), updateFromMap(existingChildren, returnFiber, newIdx, newChild, lanes);
}
if (isArrayImpl(newChild) || getIteratorFn(newChild)) return existingChildren = existingChildren.get(newIdx) || null, updateFragment(returnFiber, existingChildren, newChild, lanes, null);
if ("function" === typeof newChild.then) return updateFromMap(existingChildren, returnFiber, newIdx, unwrapThenable(newChild), lanes);
if (newChild.$$typeof === REACT_CONTEXT_TYPE) return updateFromMap(existingChildren, returnFiber, newIdx, readContextDuringReconciliation(returnFiber, newChild), lanes);
throwOnInvalidObjectTypeImpl(returnFiber, newChild);
}
return null;
}
function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
for (var resultingFirstChild = null, previousNewFiber = null, oldFiber = currentFirstChild, newIdx = currentFirstChild = 0, nextOldFiber = null; null !== oldFiber && newIdx < newChildren.length; newIdx++) {
oldFiber.index > newIdx ? (nextOldFiber = oldFiber, oldFiber = null) : nextOldFiber = oldFiber.sibling;
var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
if (null === newFiber) {
null === oldFiber && (oldFiber = nextOldFiber);
break;
}
shouldTrackSideEffects && oldFiber && null === newFiber.alternate && deleteChild(returnFiber, oldFiber);
currentFirstChild = placeChild(newFiber, currentFirstChild, newIdx);
null === previousNewFiber ? resultingFirstChild = newFiber : previousNewFiber.sibling = newFiber;
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) return deleteRemainingChildren(returnFiber, oldFiber), isHydrating && pushTreeFork(returnFiber, newIdx), resultingFirstChild;
if (null === oldFiber) {
for (; newIdx < newChildren.length; newIdx++) oldFiber = createChild(returnFiber, newChildren[newIdx], lanes), null !== oldFiber && (currentFirstChild = placeChild(oldFiber, currentFirstChild, newIdx), null === previousNewFiber ? resultingFirstChild = oldFiber : previousNewFiber.sibling = oldFiber, previousNewFiber = oldFiber);
isHydrating && pushTreeFork(returnFiber, newIdx);
return resultingFirstChild;
}
for (oldFiber = mapRemainingChildren(oldFiber); newIdx < newChildren.length; newIdx++) nextOldFiber = updateFromMap(oldFiber, returnFiber, newIdx, newChildren[newIdx], lanes), null !== nextOldFiber && (shouldTrackSideEffects && (newFiber = nextOldFiber.alternate, null !== newFiber && oldFiber.delete(null === newFiber.key ? newIdx : newFiber.key)), currentFirstChild = placeChild(nextOldFiber, currentFirstChild, newIdx), null === previousNewFiber ? resultingFirstChild = nextOldFiber : previousNewFiber.sibling = nextOldFiber, previousNewFiber = nextOldFiber);
shouldTrackSideEffects && oldFiber.forEach(function(child) {
return deleteChild(returnFiber, child);
});
isHydrating && pushTreeFork(returnFiber, newIdx);
return resultingFirstChild;
}
function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildren, lanes) {
if (null == newChildren) throw Error(formatProdErrorMessage(151));
for (var resultingFirstChild = null, previousNewFiber = null, oldFiber = currentFirstChild, newIdx = currentFirstChild = 0, nextOldFiber = null, step = newChildren.next(); null !== oldFiber && !step.done; newIdx++, step = newChildren.next()) {
oldFiber.index > newIdx ? (nextOldFiber = oldFiber, oldFiber = null) : nextOldFiber = oldFiber.sibling;
var newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes);
if (null === newFiber) {
null === oldFiber && (oldFiber = nextOldFiber);
break;
}
shouldTrackSideEffects && oldFiber && null === newFiber.alternate && deleteChild(returnFiber, oldFiber);
currentFirstChild = placeChild(newFiber, currentFirstChild, newIdx);
null === previousNewFiber ? resultingFirstChild = newFiber : previousNewFiber.sibling = newFiber;
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (step.done) return deleteRemainingChildren(returnFiber, oldFiber), isHydrating && pushTreeFork(returnFiber, newIdx), resultingFirstChild;
if (null === oldFiber) {
for (; !step.done; newIdx++, step = newChildren.next()) step = createChild(returnFiber, step.value, lanes), null !== step && (currentFirstChild = placeChild(step, currentFirstChild, newIdx), null === previousNewFiber ? resultingFirstChild = step : previousNewFiber.sibling = step, previousNewFiber = step);
isHydrating && pushTreeFork(returnFiber, newIdx);
return resultingFirstChild;
}
for (oldFiber = mapRemainingChildren(oldFiber); !step.done; newIdx++, step = newChildren.next()) step = updateFromMap(oldFiber, returnFiber, newIdx, step.value, lanes), null !== step && (shouldTrackSideEffects && (nextOldFiber = step.alternate, null !== nextOldFiber && oldFiber.delete(null === nextOldFiber.key ? newIdx : nextOldFiber.key)), currentFirstChild = placeChild(step, currentFirstChild, newIdx), null === previousNewFiber ? resultingFirstChild = step : previousNewFiber.sibling = step, previousNewFiber = step);
shouldTrackSideEffects && oldFiber.forEach(function(child) {
return deleteChild(returnFiber, child);
});
isHydrating && pushTreeFork(returnFiber, newIdx);
return resultingFirstChild;
}
function reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes) {
"object" === typeof newChild && null !== newChild && newChild.type === REACT_FRAGMENT_TYPE && null === newChild.key && void 0 === newChild.props.ref && (newChild = newChild.props.children);
if ("object" === typeof newChild && null !== newChild) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
a: {
for (var key = newChild.key; null !== currentFirstChild;) {
if (currentFirstChild.key === key) {
key = newChild.type;
if (key === REACT_FRAGMENT_TYPE) {
if (7 === currentFirstChild.tag) {
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
lanes = useFiber(currentFirstChild, newChild.props.children);
coerceRef(lanes, newChild);
lanes.return = returnFiber;
returnFiber = lanes;
break a;
}
} else if (currentFirstChild.elementType === key || "object" === typeof key && null !== key && key.$$typeof === REACT_LAZY_TYPE && resolveLazy(key) === currentFirstChild.type) {
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
lanes = useFiber(currentFirstChild, newChild.props);
coerceRef(lanes, newChild);
lanes.return = returnFiber;
returnFiber = lanes;
break a;
}
deleteRemainingChildren(returnFiber, currentFirstChild);
break;
} else deleteChild(returnFiber, currentFirstChild);
currentFirstChild = currentFirstChild.sibling;
}
newChild.type === REACT_FRAGMENT_TYPE ? (lanes = createFiberFromFragment(newChild.props.children, returnFiber.mode, lanes, newChild.key), coerceRef(lanes, newChild), lanes.return = returnFiber, returnFiber = lanes) : (lanes = createFiberFromTypeAndProps(newChild.type, newChild.key, newChild.props, null, returnFiber.mode, lanes), coerceRef(lanes, newChild), lanes.return = returnFiber, returnFiber = lanes);
}
return placeSingleChild(returnFiber);
case REACT_PORTAL_TYPE:
a: {
for (key = newChild.key; null !== currentFirstChild;) {
if (currentFirstChild.key === key) if (4 === currentFirstChild.tag && currentFirstChild.stateNode.containerInfo === newChild.containerInfo && currentFirstChild.stateNode.implementation === newChild.implementation) {
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
lanes = useFiber(currentFirstChild, newChild.children || []);
lanes.return = returnFiber;
returnFiber = lanes;
break a;
} else {
deleteRemainingChildren(returnFiber, currentFirstChild);
break;
}
else deleteChild(returnFiber, currentFirstChild);
currentFirstChild = currentFirstChild.sibling;
}
lanes = createFiberFromPortal(newChild, returnFiber.mode, lanes);
lanes.return = returnFiber;
returnFiber = lanes;
}
return placeSingleChild(returnFiber);
case REACT_LAZY_TYPE: return newChild = resolveLazy(newChild), reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes);
}
if (isArrayImpl(newChild)) return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
if (getIteratorFn(newChild)) {
key = getIteratorFn(newChild);
if ("function" !== typeof key) throw Error(formatProdErrorMessage(150));
newChild = key.call(newChild);
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes);
}
if ("function" === typeof newChild.then) return reconcileChildFibersImpl(returnFiber, currentFirstChild, unwrapThenable(newChild), lanes);
if (newChild.$$typeof === REACT_CONTEXT_TYPE) return reconcileChildFibersImpl(returnFiber, currentFirstChild, readContextDuringReconciliation(returnFiber, newChild), lanes);
throwOnInvalidObjectTypeImpl(returnFiber, newChild);
}
return "string" === typeof newChild && "" !== newChild || "number" === typeof newChild || "bigint" === typeof newChild ? (newChild = "" + newChild, null !== currentFirstChild && 6 === currentFirstChild.tag ? (deleteRemainingChildren(returnFiber, currentFirstChild.sibling), lanes = useFiber(currentFirstChild, newChild), lanes.return = returnFiber, returnFiber = lanes) : (deleteRemainingChildren(returnFiber, currentFirstChild), lanes = createFiberFromText(newChild, returnFiber.mode, lanes), lanes.return = returnFiber, returnFiber = lanes), placeSingleChild(returnFiber)) : deleteRemainingChildren(returnFiber, currentFirstChild);
}
return function(returnFiber, currentFirstChild, newChild, lanes) {
try {
thenableIndexCounter$1 = 0;
var firstChildFiber = reconcileChildFibersImpl(returnFiber, currentFirstChild, newChild, lanes);
thenableState$1 = null;
return firstChildFiber;
} catch (x) {
if (x === SuspenseException || x === SuspenseActionException) throw x;
var fiber = createFiberImplClass(29, x, null, returnFiber.mode);
fiber.lanes = lanes;
fiber.return = returnFiber;
return fiber;
}
};
}
var reconcileChildFibers = createChildReconciler(true);
var mountChildFibers = createChildReconciler(false);
var hasForceUpdate = false;
function initializeUpdateQueue(fiber) {
fiber.updateQueue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
lanes: 0,
hiddenCallbacks: null
},
callbacks: null
};
}
function cloneUpdateQueue(current, workInProgress) {
current = current.updateQueue;
workInProgress.updateQueue === current && (workInProgress.updateQueue = {
baseState: current.baseState,
firstBaseUpdate: current.firstBaseUpdate,
lastBaseUpdate: current.lastBaseUpdate,
shared: current.shared,
callbacks: null
});
}
function createUpdate(lane) {
return {
lane,
tag: 0,
payload: null,
callback: null,
next: null
};
}
function enqueueUpdate(fiber, update, lane) {
var updateQueue = fiber.updateQueue;
if (null === updateQueue) return null;
updateQueue = updateQueue.shared;
if (0 !== (executionContext & 2)) {
var pending = updateQueue.pending;
null === pending ? update.next = update : (update.next = pending.next, pending.next = update);
updateQueue.pending = update;
update = getRootForUpdatedFiber(fiber);
markUpdateLaneFromFiberToRoot(fiber, null, lane);
return update;
}
enqueueUpdate$1(fiber, updateQueue, update, lane);
return getRootForUpdatedFiber(fiber);
}
function entangleTransitions(root, fiber, lane) {
fiber = fiber.updateQueue;
if (null !== fiber && (fiber = fiber.shared, 0 !== (lane & 4194048))) {
var queueLanes = fiber.lanes;
queueLanes &= root.pendingLanes;
lane |= queueLanes;
fiber.lanes = lane;
markRootEntangled(root, lane);
}
}
function enqueueCapturedUpdate(workInProgress, capturedUpdate) {
var queue = workInProgress.updateQueue, current = workInProgress.alternate;
if (null !== current && (current = current.updateQueue, queue === current)) {
var newFirst = null, newLast = null;
queue = queue.firstBaseUpdate;
if (null !== queue) {
do {
var clone = {
lane: queue.lane,
tag: queue.tag,
payload: queue.payload,
callback: null,
next: null
};
null === newLast ? newFirst = newLast = clone : newLast = newLast.next = clone;
queue = queue.next;
} while (null !== queue);
null === newLast ? newFirst = newLast = capturedUpdate : newLast = newLast.next = capturedUpdate;
} else newFirst = newLast = capturedUpdate;
queue = {
baseState: current.baseState,
firstBaseUpdate: newFirst,
lastBaseUpdate: newLast,
shared: current.shared,
callbacks: current.callbacks
};
workInProgress.updateQueue = queue;
return;
}
workInProgress = queue.lastBaseUpdate;
null === workInProgress ? queue.firstBaseUpdate = capturedUpdate : workInProgress.next = capturedUpdate;
queue.lastBaseUpdate = capturedUpdate;
}
var didReadFromEntangledAsyncAction = false;
function suspendIfUpdateReadFromEntangledAsyncAction() {
if (didReadFromEntangledAsyncAction) {
var entangledActionThenable = currentEntangledActionThenable;
if (null !== entangledActionThenable) throw entangledActionThenable;
}
}
function processUpdateQueue(workInProgress$jscomp$0, props, instance$jscomp$0, renderLanes) {
didReadFromEntangledAsyncAction = false;
var queue = workInProgress$jscomp$0.updateQueue;
hasForceUpdate = false;
var firstBaseUpdate = queue.firstBaseUpdate, lastBaseUpdate = queue.lastBaseUpdate, pendingQueue = queue.shared.pending;
if (null !== pendingQueue) {
queue.shared.pending = null;
var lastPendingUpdate = pendingQueue, firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
null === lastBaseUpdate ? firstBaseUpdate = firstPendingUpdate : lastBaseUpdate.next = firstPendingUpdate;
lastBaseUpdate = lastPendingUpdate;
var current = workInProgress$jscomp$0.alternate;
null !== current && (current = current.updateQueue, pendingQueue = current.lastBaseUpdate, pendingQueue !== lastBaseUpdate && (null === pendingQueue ? current.firstBaseUpdate = firstPendingUpdate : pendingQueue.next = firstPendingUpdate, current.lastBaseUpdate = lastPendingUpdate));
}
if (null !== firstBaseUpdate) {
var newState = queue.baseState;
lastBaseUpdate = 0;
current = firstPendingUpdate = lastPendingUpdate = null;
pendingQueue = firstBaseUpdate;
do {
var updateLane = pendingQueue.lane & -536870913, isHiddenUpdate = updateLane !== pendingQueue.lane;
if (isHiddenUpdate ? (workInProgressRootRenderLanes & updateLane) === updateLane : (renderLanes & updateLane) === updateLane) {
0 !== updateLane && updateLane === currentEntangledLane && (didReadFromEntangledAsyncAction = true);
null !== current && (current = current.next = {
lane: 0,
tag: pendingQueue.tag,
payload: pendingQueue.payload,
callback: null,
next: null
});
a: {
var workInProgress = workInProgress$jscomp$0, update = pendingQueue;
updateLane = props;
var instance = instance$jscomp$0;
switch (update.tag) {
case 1:
workInProgress = update.payload;
if ("function" === typeof workInProgress) {
newState = workInProgress.call(instance, newState, updateLane);
break a;
}
newState = workInProgress;
break a;
case 3: workInProgress.flags = workInProgress.flags & -65537 | 128;
case 0:
workInProgress = update.payload;
updateLane = "function" === typeof workInProgress ? workInProgress.call(instance, newState, updateLane) : workInProgress;
if (null === updateLane || void 0 === updateLane) break a;
newState = assign({}, newState, updateLane);
break a;
case 2: hasForceUpdate = true;
}
}
updateLane = pendingQueue.callback;
null !== updateLane && (workInProgress$jscomp$0.flags |= 64, isHiddenUpdate && (workInProgress$jscomp$0.flags |= 8192), isHiddenUpdate = queue.callbacks, null === isHiddenUpdate ? queue.callbacks = [updateLane] : isHiddenUpdate.push(updateLane));
} else isHiddenUpdate = {
lane: updateLane,
tag: pendingQueue.tag,
payload: pendingQueue.payload,
callback: pendingQueue.callback,
next: null
}, null === current ? (firstPendingUpdate = current = isHiddenUpdate, lastPendingUpdate = newState) : current = current.next = isHiddenUpdate, lastBaseUpdate |= updateLane;
pendingQueue = pendingQueue.next;
if (null === pendingQueue) if (pendingQueue = queue.shared.pending, null === pendingQueue) break;
else isHiddenUpdate = pendingQueue, pendingQueue = isHiddenUpdate.next, isHiddenUpdate.next = null, queue.lastBaseUpdate = isHiddenUpdate, queue.shared.pending = null;
} while (1);
null === current && (lastPendingUpdate = newState);
queue.baseState = lastPendingUpdate;
queue.firstBaseUpdate = firstPendingUpdate;
queue.lastBaseUpdate = current;
null === firstBaseUpdate && (queue.shared.lanes = 0);
workInProgressRootSkippedLanes |= lastBaseUpdate;
workInProgress$jscomp$0.lanes = lastBaseUpdate;
workInProgress$jscomp$0.memoizedState = newState;
}
}
function callCallback(callback, context) {
if ("function" !== typeof callback) throw Error(formatProdErrorMessage(191, callback));
callback.call(context);
}
function commitCallbacks(updateQueue, context) {
var callbacks = updateQueue.callbacks;
if (null !== callbacks) for (updateQueue.callbacks = null, updateQueue = 0; updateQueue < callbacks.length; updateQueue++) callCallback(callbacks[updateQueue], context);
}
var currentTreeHiddenStackCursor = createCursor(null);
var prevEntangledRenderLanesCursor = createCursor(0);
function pushHiddenContext(fiber, context) {
fiber = entangledRenderLanes;
push(prevEntangledRenderLanesCursor, fiber);
push(currentTreeHiddenStackCursor, context);
entangledRenderLanes = fiber | context.baseLanes;
}
function reuseHiddenContextOnStack() {
push(prevEntangledRenderLanesCursor, entangledRenderLanes);
push(currentTreeHiddenStackCursor, currentTreeHiddenStackCursor.current);
}
function popHiddenContext() {
entangledRenderLanes = prevEntangledRenderLanesCursor.current;
pop(currentTreeHiddenStackCursor);
pop(prevEntangledRenderLanesCursor);
}
var suspenseHandlerStackCursor = createCursor(null);
var shellBoundary = null;
function pushPrimaryTreeSuspenseHandler(handler) {
var current = handler.alternate;
push(suspenseStackCursor, suspenseStackCursor.current & 1);
push(suspenseHandlerStackCursor, handler);
null === shellBoundary && (null === current || null !== currentTreeHiddenStackCursor.current ? shellBoundary = handler : null !== current.memoizedState && (shellBoundary = handler));
}
function pushDehydratedActivitySuspenseHandler(fiber) {
push(suspenseStackCursor, suspenseStackCursor.current);
push(suspenseHandlerStackCursor, fiber);
null === shellBoundary && (shellBoundary = fiber);
}
function pushOffscreenSuspenseHandler(fiber) {
22 === fiber.tag ? (push(suspenseStackCursor, suspenseStackCursor.current), push(suspenseHandlerStackCursor, fiber), null === shellBoundary && (shellBoundary = fiber)) : reuseSuspenseHandlerOnStack();
}
function reuseSuspenseHandlerOnStack() {
push(suspenseStackCursor, suspenseStackCursor.current);
push(suspenseHandlerStackCursor, suspenseHandlerStackCursor.current);
}
function popSuspenseHandler(fiber) {
pop(suspenseHandlerStackCursor);
shellBoundary === fiber && (shellBoundary = null);
pop(suspenseStackCursor);
}
var suspenseStackCursor = createCursor(0);
function pushSuspenseListContext(fiber, newContext) {
push(suspenseHandlerStackCursor, suspenseHandlerStackCursor.current);
push(suspenseStackCursor, newContext);
}
function popSuspenseListContext(fiber) {
pop(suspenseStackCursor);
pop(suspenseHandlerStackCursor);
shellBoundary === fiber && (shellBoundary = null);
}
function findFirstSuspended(row) {
for (var node = row; null !== node;) {
if (13 === node.tag) {
var state = node.memoizedState;
if (null !== state && (state = state.dehydrated, null === state || isSuspenseInstancePending(state) || isSuspenseInstanceFallback(state))) return node;
} else if (19 === node.tag && "independent" !== node.memoizedProps.revealOrder) {
if (0 !== (node.flags & 128)) return node;
} else if (null !== node.child) {
node.child.return = node;
node = node.child;
continue;
}
if (node === row) break;
for (; null === node.sibling;) {
if (null === node.return || node.return === row) return null;
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
return null;
}
var renderLanes = 0;
var currentlyRenderingFiber = null;
var currentHook = null;
var workInProgressHook = null;
var didScheduleRenderPhaseUpdate = false;
var didScheduleRenderPhaseUpdateDuringThisPass = false;
var shouldDoubleInvokeUserFnsInHooksDEV = false;
var localIdCounter = 0;
var thenableIndexCounter = 0;
var thenableState = null;
var globalClientIdCounter = 0;
function throwInvalidHookError() {
throw Error(formatProdErrorMessage(321));
}
function areHookInputsEqual(nextDeps, prevDeps) {
if (null === prevDeps) return false;
for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) if (!objectIs(nextDeps[i], prevDeps[i])) return false;
return true;
}
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = 0;
ReactSharedInternals.H = null === current || null === current.memoizedState ? HooksDispatcherOnMount : HooksDispatcherOnUpdate;
shouldDoubleInvokeUserFnsInHooksDEV = false;
nextRenderLanes = Component(props, secondArg);
shouldDoubleInvokeUserFnsInHooksDEV = false;
didScheduleRenderPhaseUpdateDuringThisPass && (nextRenderLanes = renderWithHooksAgain(workInProgress, Component, props, secondArg));
finishRenderingHooks(current);
return nextRenderLanes;
}
function finishRenderingHooks(current) {
ReactSharedInternals.H = ContextOnlyDispatcher;
var didRenderTooFewHooks = null !== currentHook && null !== currentHook.next;
renderLanes = 0;
workInProgressHook = currentHook = currentlyRenderingFiber = null;
didScheduleRenderPhaseUpdate = false;
thenableIndexCounter = 0;
thenableState = null;
if (didRenderTooFewHooks) throw Error(formatProdErrorMessage(300));
null === current || didReceiveUpdate || (current = current.dependencies, null !== current && checkIfContextChanged(current) && (didReceiveUpdate = true));
}
function renderWithHooksAgain(workInProgress, Component, props, secondArg) {
currentlyRenderingFiber = workInProgress;
var numberOfReRenders = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass && (thenableState = null);
thenableIndexCounter = 0;
didScheduleRenderPhaseUpdateDuringThisPass = false;
if (25 <= numberOfReRenders) throw Error(formatProdErrorMessage(301));
numberOfReRenders += 1;
workInProgressHook = currentHook = null;
if (null != workInProgress.updateQueue) {
var children = workInProgress.updateQueue;
children.lastEffect = null;
children.events = null;
children.stores = null;
null != children.memoCache && (children.memoCache.index = 0);
}
ReactSharedInternals.H = HooksDispatcherOnRerender;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
return children;
}
function TransitionAwareHostComponent() {
var dispatcher = ReactSharedInternals.H, maybeThenable = dispatcher.useState()[0];
maybeThenable = "function" === typeof maybeThenable.then ? useThenable(maybeThenable) : maybeThenable;
dispatcher = dispatcher.useState()[0];
(null !== currentHook ? currentHook.memoizedState : null) !== dispatcher && (currentlyRenderingFiber.flags |= 1024);
return maybeThenable;
}
function checkDidRenderIdHook() {
var didRenderIdHook = 0 !== localIdCounter;
localIdCounter = 0;
return didRenderIdHook;
}
function bailoutHooks(current, workInProgress, lanes) {
workInProgress.updateQueue = current.updateQueue;
workInProgress.flags &= -2053;
current.lanes &= ~lanes;
}
function resetHooksOnUnwind(workInProgress) {
if (didScheduleRenderPhaseUpdate) {
for (workInProgress = workInProgress.memoizedState; null !== workInProgress;) {
var queue = workInProgress.queue;
null !== queue && (queue.pending = null);
workInProgress = workInProgress.next;
}
didScheduleRenderPhaseUpdate = false;
}
renderLanes = 0;
workInProgressHook = currentHook = currentlyRenderingFiber = null;
didScheduleRenderPhaseUpdateDuringThisPass = false;
thenableIndexCounter = localIdCounter = 0;
thenableState = null;
}
function mountWorkInProgressHook() {
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
null === workInProgressHook ? currentlyRenderingFiber.memoizedState = workInProgressHook = hook : workInProgressHook = workInProgressHook.next = hook;
return workInProgressHook;
}
function updateWorkInProgressHook() {
if (null === currentHook) {
var nextCurrentHook = currentlyRenderingFiber.alternate;
nextCurrentHook = null !== nextCurrentHook ? nextCurrentHook.memoizedState : null;
} else nextCurrentHook = currentHook.next;
var nextWorkInProgressHook = null === workInProgressHook ? currentlyRenderingFiber.memoizedState : workInProgressHook.next;
if (null !== nextWorkInProgressHook) workInProgressHook = nextWorkInProgressHook, currentHook = nextCurrentHook;
else {
if (null === nextCurrentHook) {
if (null === currentlyRenderingFiber.alternate) throw Error(formatProdErrorMessage(467));
throw Error(formatProdErrorMessage(310));
}
currentHook = nextCurrentHook;
nextCurrentHook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null
};
null === workInProgressHook ? currentlyRenderingFiber.memoizedState = workInProgressHook = nextCurrentHook : workInProgressHook = workInProgressHook.next = nextCurrentHook;
}
return workInProgressHook;
}
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
events: null,
stores: null,
memoCache: null
};
}
function useThenable(thenable) {
var index = thenableIndexCounter;
thenableIndexCounter += 1;
null === thenableState && (thenableState = []);
thenable = trackUsedThenable(thenableState, thenable, index);
index = currentlyRenderingFiber;
null === (null === workInProgressHook ? index.memoizedState : workInProgressHook.next) && (index = index.alternate, ReactSharedInternals.H = null === index || null === index.memoizedState ? HooksDispatcherOnMount : HooksDispatcherOnUpdate);
return thenable;
}
function use(usable) {
if (null !== usable && "object" === typeof usable) {
if ("function" === typeof usable.then) return useThenable(usable);
if (usable.$$typeof === REACT_RECOVERABLE_TYPE) return;
if (usable.$$typeof === REACT_CONTEXT_TYPE) return readContext(usable);
}
throw Error(formatProdErrorMessage(438, String(usable)));
}
function useMemoCache(size) {
var memoCache = null, updateQueue = currentlyRenderingFiber.updateQueue;
null !== updateQueue && (memoCache = updateQueue.memoCache);
if (null == memoCache) {
var current = currentlyRenderingFiber.alternate;
null !== current && (current = current.updateQueue, null !== current && (current = current.memoCache, null != current && (memoCache = {
data: current.data.map(function(array) {
return array.slice();
}),
index: 0
})));
}
memoCache ??= {
data: [],
index: 0
};
null === updateQueue && (updateQueue = createFunctionComponentUpdateQueue(), currentlyRenderingFiber.updateQueue = updateQueue);
updateQueue.memoCache = memoCache;
updateQueue = memoCache.data[memoCache.index];
if (void 0 === updateQueue) for (updateQueue = memoCache.data[memoCache.index] = Array(size), current = 0; current < size; current++) updateQueue[current] = REACT_MEMO_CACHE_SENTINEL;
memoCache.index++;
return updateQueue;
}
function basicStateReducer(state, action) {
return "function" === typeof action ? action(state) : action;
}
function updateReducer(reducer) {
return updateReducerImpl(updateWorkInProgressHook(), currentHook, reducer);
}
function updateReducerImpl(hook, current, reducer) {
var queue = hook.queue;
if (null === queue) throw Error(formatProdErrorMessage(311));
queue.lastRenderedReducer = reducer;
var baseQueue = hook.baseQueue, pendingQueue = queue.pending;
if (null !== pendingQueue) {
if (null !== baseQueue) {
var baseFirst = baseQueue.next;
baseQueue.next = pendingQueue.next;
pendingQueue.next = baseFirst;
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
pendingQueue = hook.baseState;
if (null === baseQueue) hook.memoizedState = pendingQueue;
else {
current = baseQueue.next;
var newBaseQueueFirst = baseFirst = null, newBaseQueueLast = null, update = current, didReadFromEntangledAsyncAction$64 = false;
do {
var updateLane = update.lane & -536870913;
if (updateLane !== update.lane ? (workInProgressRootRenderLanes & updateLane) === updateLane : (renderLanes & updateLane) === updateLane) {
var revertLane = update.revertLane;
if (0 === revertLane) null !== newBaseQueueLast && (newBaseQueueLast = newBaseQueueLast.next = {
lane: 0,
revertLane: 0,
gesture: null,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null
}), updateLane === currentEntangledLane && (didReadFromEntangledAsyncAction$64 = true);
else if ((renderLanes & revertLane) === revertLane) {
update = update.next;
revertLane === currentEntangledLane && (didReadFromEntangledAsyncAction$64 = true);
continue;
} else updateLane = {
lane: 0,
revertLane: update.revertLane,
gesture: null,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null
}, null === newBaseQueueLast ? (newBaseQueueFirst = newBaseQueueLast = updateLane, baseFirst = pendingQueue) : newBaseQueueLast = newBaseQueueLast.next = updateLane, currentlyRenderingFiber.lanes |= revertLane, workInProgressRootSkippedLanes |= revertLane;
updateLane = update.action;
shouldDoubleInvokeUserFnsInHooksDEV && reducer(pendingQueue, updateLane);
pendingQueue = update.hasEagerState ? update.eagerState : reducer(pendingQueue, updateLane);
} else revertLane = {
lane: updateLane,
revertLane: update.revertLane,
gesture: update.gesture,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null
}, null === newBaseQueueLast ? (newBaseQueueFirst = newBaseQueueLast = revertLane, baseFirst = pendingQueue) : newBaseQueueLast = newBaseQueueLast.next = revertLane, currentlyRenderingFiber.lanes |= updateLane, workInProgressRootSkippedLanes |= updateLane;
update = update.next;
} while (null !== update && update !== current);
null === newBaseQueueLast ? baseFirst = pendingQueue : newBaseQueueLast.next = newBaseQueueFirst;
if (!objectIs(pendingQueue, hook.memoizedState) && (didReceiveUpdate = true, didReadFromEntangledAsyncAction$64 && (reducer = currentEntangledActionThenable, null !== reducer))) throw reducer;
hook.memoizedState = pendingQueue;
hook.baseState = baseFirst;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = pendingQueue;
}
null === baseQueue && (queue.lanes = 0);
return [hook.memoizedState, queue.dispatch];
}
function rerenderReducer(reducer) {
var hook = updateWorkInProgressHook(), queue = hook.queue;
if (null === queue) throw Error(formatProdErrorMessage(311));
queue.lastRenderedReducer = reducer;
var dispatch = queue.dispatch, lastRenderPhaseUpdate = queue.pending, newState = hook.memoizedState;
if (null !== lastRenderPhaseUpdate) {
queue.pending = null;
var update = lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
do
newState = reducer(newState, update.action), update = update.next;
while (update !== lastRenderPhaseUpdate);
objectIs(newState, hook.memoizedState) || (didReceiveUpdate = true);
hook.memoizedState = newState;
null === hook.baseQueue && (hook.baseState = newState);
queue.lastRenderedState = newState;
}
return [newState, dispatch];
}
function updateSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
var fiber = currentlyRenderingFiber, hook = updateWorkInProgressHook(), isHydrating$jscomp$0 = isHydrating;
if (isHydrating$jscomp$0) {
if (void 0 === getServerSnapshot) throw Error(formatProdErrorMessage(407));
getServerSnapshot = getServerSnapshot();
} else getServerSnapshot = getSnapshot();
var snapshotChanged = !objectIs((currentHook || hook).memoizedState, getServerSnapshot);
snapshotChanged && (hook.memoizedState = getServerSnapshot, didReceiveUpdate = true);
hook = hook.queue;
updateEffect(subscribeToStore.bind(null, fiber, hook, subscribe), [subscribe]);
subscribe = hook.getSnapshot !== getSnapshot || snapshotChanged || null !== workInProgressHook && 0 !== (workInProgressHook.memoizedState.tag & 1);
pushSimpleEffect(subscribe ? 9 : 8, { destroy: void 0 }, updateStoreInstance.bind(null, fiber, hook, getServerSnapshot, getSnapshot), null);
if (subscribe) {
fiber.flags |= 2048;
if (null === workInProgressRoot) throw Error(formatProdErrorMessage(349));
isHydrating$jscomp$0 || 0 !== (renderLanes & 127) || pushStoreConsistencyCheck(fiber, getSnapshot, getServerSnapshot);
}
return getServerSnapshot;
}
function pushStoreConsistencyCheck(fiber, getSnapshot, renderedSnapshot) {
fiber.flags |= 16384;
fiber = {
getSnapshot,
value: renderedSnapshot
};
getSnapshot = currentlyRenderingFiber.updateQueue;
null === getSnapshot ? (getSnapshot = createFunctionComponentUpdateQueue(), currentlyRenderingFiber.updateQueue = getSnapshot, getSnapshot.stores = [fiber]) : (renderedSnapshot = getSnapshot.stores, null === renderedSnapshot ? getSnapshot.stores = [fiber] : renderedSnapshot.push(fiber));
}
function updateStoreInstance(fiber, inst, nextSnapshot, getSnapshot) {
inst.value = nextSnapshot;
inst.getSnapshot = getSnapshot;
checkIfSnapshotChanged(inst) && forceStoreRerender(fiber);
}
function subscribeToStore(fiber, inst, subscribe) {
return subscribe(function() {
checkIfSnapshotChanged(inst) && forceStoreRerender(fiber);
});
}
function checkIfSnapshotChanged(inst) {
var latestGetSnapshot = inst.getSnapshot;
inst = inst.value;
try {
var nextValue = latestGetSnapshot();
return !objectIs(inst, nextValue);
} catch (error) {
return true;
}
}
function forceStoreRerender(fiber) {
var root = enqueueConcurrentRenderForLane(fiber, 2);
null !== root && scheduleUpdateOnFiber(root, fiber, 2);
}
function mountStateImpl(initialState) {
var hook = mountWorkInProgressHook();
if ("function" === typeof initialState) {
var initialStateInitializer = initialState;
initialState = initialStateInitializer();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
initialStateInitializer();
} finally {
setIsStrictModeForDevtools(false);
}
}
}
hook.memoizedState = hook.baseState = initialState;
hook.queue = {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
return hook;
}
function updateOptimisticImpl(hook, current, passthrough, reducer) {
hook.baseState = passthrough;
return updateReducerImpl(hook, currentHook, "function" === typeof reducer ? reducer : basicStateReducer);
}
function dispatchActionState(fiber, actionQueue, setPendingState, setState, payload) {
if (isRenderPhaseUpdate(fiber)) throw Error(formatProdErrorMessage(485));
fiber = actionQueue.action;
if (null !== fiber) {
var actionNode = {
payload,
action: fiber,
next: null,
isTransition: true,
status: "pending",
value: null,
reason: null,
listeners: [],
then: function(listener) {
actionNode.listeners.push(listener);
}
};
null !== ReactSharedInternals.T ? setPendingState(true) : actionNode.isTransition = false;
setState(actionNode);
setPendingState = actionQueue.pending;
null === setPendingState ? (actionNode.next = actionQueue.pending = actionNode, runActionStateAction(actionQueue, actionNode)) : (actionNode.next = setPendingState.next, actionQueue.pending = setPendingState.next = actionNode);
}
}
function runActionStateAction(actionQueue, node) {
var action = node.action, payload = node.payload, prevState = actionQueue.state;
if (node.isTransition) {
var prevTransition = ReactSharedInternals.T, currentTransition = {};
currentTransition.types = null !== prevTransition ? prevTransition.types : null;
ReactSharedInternals.T = currentTransition;
try {
var returnValue = action(prevState, payload), onStartTransitionFinish = ReactSharedInternals.S;
null !== onStartTransitionFinish && onStartTransitionFinish(currentTransition, returnValue);
handleActionReturnValue(actionQueue, node, returnValue);
} catch (error) {
onActionError(actionQueue, node, error);
} finally {
null !== prevTransition && null !== currentTransition.types && (prevTransition.types = currentTransition.types), ReactSharedInternals.T = prevTransition;
}
} else try {
prevTransition = action(prevState, payload), handleActionReturnValue(actionQueue, node, prevTransition);
} catch (error$70) {
onActionError(actionQueue, node, error$70);
}
}
function handleActionReturnValue(actionQueue, node, returnValue) {
null !== returnValue && "object" === typeof returnValue && "function" === typeof returnValue.then ? returnValue.then(function(nextState) {
onActionSuccess(actionQueue, node, nextState);
}, function(error) {
return onActionError(actionQueue, node, error);
}) : onActionSuccess(actionQueue, node, returnValue);
}
function onActionSuccess(actionQueue, actionNode, nextState) {
actionNode.status = "fulfilled";
actionNode.value = nextState;
notifyActionListeners(actionNode);
actionQueue.state = nextState;
actionNode = actionQueue.pending;
null !== actionNode && (nextState = actionNode.next, nextState === actionNode ? actionQueue.pending = null : (nextState = nextState.next, actionNode.next = nextState, runActionStateAction(actionQueue, nextState)));
}
function onActionError(actionQueue, actionNode, error) {
var last = actionQueue.pending;
actionQueue.pending = null;
if (null !== last) {
last = last.next;
do
actionNode.status = "rejected", actionNode.reason = error, notifyActionListeners(actionNode), actionNode = actionNode.next;
while (actionNode !== last);
}
actionQueue.action = null;
}
function notifyActionListeners(actionNode) {
actionNode = actionNode.listeners;
for (var i = 0; i < actionNode.length; i++) (0, actionNode[i])();
}
function actionStateReducer(oldState, newState) {
return newState;
}
function mountActionState(action, initialStateProp) {
if (isHydrating) {
var ssrFormState = workInProgressRoot.formState;
if (null !== ssrFormState) {
a: {
var JSCompiler_inline_result = currentlyRenderingFiber;
if (isHydrating) {
if (nextHydratableInstance) {
b: {
var JSCompiler_inline_result$jscomp$0 = nextHydratableInstance;
for (var inRootOrSingleton = rootOrSingletonContext; 8 !== JSCompiler_inline_result$jscomp$0.nodeType;) {
if (!inRootOrSingleton) {
JSCompiler_inline_result$jscomp$0 = null;
break b;
}
JSCompiler_inline_result$jscomp$0 = getNextHydratable(JSCompiler_inline_result$jscomp$0.nextSibling);
if (null === JSCompiler_inline_result$jscomp$0) {
JSCompiler_inline_result$jscomp$0 = null;
break b;
}
}
inRootOrSingleton = JSCompiler_inline_result$jscomp$0.data;
JSCompiler_inline_result$jscomp$0 = "F!" === inRootOrSingleton || "F" === inRootOrSingleton ? JSCompiler_inline_result$jscomp$0 : null;
}
if (JSCompiler_inline_result$jscomp$0) {
nextHydratableInstance = getNextHydratable(JSCompiler_inline_result$jscomp$0.nextSibling);
JSCompiler_inline_result = "F!" === JSCompiler_inline_result$jscomp$0.data;
break a;
}
}
throwOnHydrationMismatch(JSCompiler_inline_result);
}
JSCompiler_inline_result = false;
}
JSCompiler_inline_result && (initialStateProp = ssrFormState[0]);
}
}
ssrFormState = mountWorkInProgressHook();
ssrFormState.memoizedState = ssrFormState.baseState = initialStateProp;
JSCompiler_inline_result = {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: actionStateReducer,
lastRenderedState: initialStateProp
};
ssrFormState.queue = JSCompiler_inline_result;
ssrFormState = dispatchSetState.bind(null, currentlyRenderingFiber, JSCompiler_inline_result);
JSCompiler_inline_result.dispatch = ssrFormState;
JSCompiler_inline_result = mountStateImpl(false);
inRootOrSingleton = dispatchOptimisticSetState.bind(null, currentlyRenderingFiber, false, JSCompiler_inline_result.queue);
JSCompiler_inline_result = mountWorkInProgressHook();
JSCompiler_inline_result$jscomp$0 = {
state: initialStateProp,
dispatch: null,
action,
pending: null
};
JSCompiler_inline_result.queue = JSCompiler_inline_result$jscomp$0;
ssrFormState = dispatchActionState.bind(null, currentlyRenderingFiber, JSCompiler_inline_result$jscomp$0, inRootOrSingleton, ssrFormState);
JSCompiler_inline_result$jscomp$0.dispatch = ssrFormState;
JSCompiler_inline_result.memoizedState = action;
return [
initialStateProp,
ssrFormState,
false
];
}
function updateActionState(action) {
return updateActionStateImpl(updateWorkInProgressHook(), currentHook, action);
}
function updateActionStateImpl(stateHook, currentStateHook, action) {
currentStateHook = updateReducerImpl(stateHook, currentStateHook, actionStateReducer)[0];
stateHook = updateReducer(basicStateReducer)[0];
if ("object" === typeof currentStateHook && null !== currentStateHook && "function" === typeof currentStateHook.then) try {
var state = useThenable(currentStateHook);
} catch (x) {
if (x === SuspenseException) throw SuspenseActionException;
throw x;
}
else state = currentStateHook;
currentStateHook = updateWorkInProgressHook();
var actionQueue = currentStateHook.queue, dispatch = actionQueue.dispatch;
action !== currentStateHook.memoizedState && (currentlyRenderingFiber.flags |= 2048, pushSimpleEffect(9, { destroy: void 0 }, actionStateActionEffect.bind(null, actionQueue, action), null));
return [
state,
dispatch,
stateHook
];
}
function actionStateActionEffect(actionQueue, action) {
actionQueue.action = action;
}
function rerenderActionState(action) {
var stateHook = updateWorkInProgressHook(), currentStateHook = currentHook;
if (null !== currentStateHook) return updateActionStateImpl(stateHook, currentStateHook, action);
updateWorkInProgressHook();
stateHook = stateHook.memoizedState;
currentStateHook = updateWorkInProgressHook();
var dispatch = currentStateHook.queue.dispatch;
currentStateHook.memoizedState = action;
return [
stateHook,
dispatch,
false
];
}
function pushSimpleEffect(tag, inst, create, deps) {
tag = {
tag,
create,
deps,
inst,
next: null
};
inst = currentlyRenderingFiber.updateQueue;
null === inst && (inst = createFunctionComponentUpdateQueue(), currentlyRenderingFiber.updateQueue = inst);
create = inst.lastEffect;
null === create ? inst.lastEffect = tag.next = tag : (deps = create.next, create.next = tag, tag.next = deps, inst.lastEffect = tag);
return tag;
}
function updateRef() {
return updateWorkInProgressHook().memoizedState;
}
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
var hook = mountWorkInProgressHook();
currentlyRenderingFiber.flags |= fiberFlags;
hook.memoizedState = pushSimpleEffect(1 | hookFlags, { destroy: void 0 }, create, void 0 === deps ? null : deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
var hook = updateWorkInProgressHook();
deps = void 0 === deps ? null : deps;
var inst = hook.memoizedState.inst;
null !== currentHook && null !== deps && areHookInputsEqual(deps, currentHook.memoizedState.deps) ? hook.memoizedState = pushSimpleEffect(hookFlags, inst, create, deps) : (currentlyRenderingFiber.flags |= fiberFlags, hook.memoizedState = pushSimpleEffect(1 | hookFlags, inst, create, deps));
}
function mountEffect(create, deps) {
mountEffectImpl(8390656, 8, create, deps);
}
function updateEffect(create, deps) {
updateEffectImpl(2048, 8, create, deps);
}
function useEffectEventImpl(payload) {
currentlyRenderingFiber.flags |= 4;
var componentUpdateQueue = currentlyRenderingFiber.updateQueue;
if (null === componentUpdateQueue) componentUpdateQueue = createFunctionComponentUpdateQueue(), currentlyRenderingFiber.updateQueue = componentUpdateQueue, componentUpdateQueue.events = [payload];
else {
var events = componentUpdateQueue.events;
null === events ? componentUpdateQueue.events = [payload] : events.push(payload);
}
}
function updateEvent(callback) {
var ref = updateWorkInProgressHook().memoizedState;
useEffectEventImpl({
ref,
nextImpl: callback
});
return function() {
if (0 !== (executionContext & 2)) throw Error(formatProdErrorMessage(440));
return ref.impl.apply(void 0, arguments);
};
}
function updateInsertionEffect(create, deps) {
return updateEffectImpl(4, 2, create, deps);
}
function updateLayoutEffect(create, deps) {
return updateEffectImpl(4, 4, create, deps);
}
function imperativeHandleEffect(create, ref) {
if ("function" === typeof ref) {
create = create();
var refCleanup = ref(create);
return function() {
"function" === typeof refCleanup ? refCleanup() : ref(null);
};
}
if (null !== ref && void 0 !== ref) return create = create(), ref.current = create, function() {
ref.current = null;
};
}
function updateImperativeHandle(ref, create, deps) {
deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null;
updateEffectImpl(4, 4, imperativeHandleEffect.bind(null, create, ref), deps);
}
function mountDebugValue() {}
function updateCallback(callback, deps) {
var hook = updateWorkInProgressHook();
deps = void 0 === deps ? null : deps;
var prevState = hook.memoizedState;
if (null !== deps && areHookInputsEqual(deps, prevState[1])) return prevState[0];
hook.memoizedState = [callback, deps];
return callback;
}
function updateMemo(nextCreate, deps) {
var hook = updateWorkInProgressHook();
deps = void 0 === deps ? null : deps;
var prevState = hook.memoizedState;
if (null !== deps && areHookInputsEqual(deps, prevState[1])) return prevState[0];
prevState = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
nextCreate();
} finally {
setIsStrictModeForDevtools(false);
}
}
hook.memoizedState = [prevState, deps];
return prevState;
}
function mountDeferredValueImpl(hook, value, initialValue) {
if (void 0 === initialValue || 0 !== (renderLanes & 1073741824) && 0 === (workInProgressRootRenderLanes & 261930)) return hook.memoizedState = value;
hook.memoizedState = initialValue;
hook = requestDeferredLane();
currentlyRenderingFiber.lanes |= hook;
workInProgressRootSkippedLanes |= hook;
return initialValue;
}
function updateDeferredValueImpl(hook, prevValue, value, initialValue) {
if (objectIs(value, prevValue)) return value;
if (null !== currentTreeHiddenStackCursor.current) return hook = mountDeferredValueImpl(hook, value, initialValue), objectIs(hook, prevValue) || (didReceiveUpdate = true), hook;
if (0 === (renderLanes & 106) || 0 !== (renderLanes & 1073741824) && 0 === (workInProgressRootRenderLanes & 261930)) return didReceiveUpdate = true, hook.memoizedState = value;
hook = requestDeferredLane();
currentlyRenderingFiber.lanes |= hook;
workInProgressRootSkippedLanes |= hook;
return prevValue;
}
function startTransition(fiber, queue, pendingState, finishedState, callback) {
var previousPriority = ReactDOMSharedInternals.p;
ReactDOMSharedInternals.p = 0 !== previousPriority && 8 > previousPriority ? previousPriority : 8;
var prevTransition = ReactSharedInternals.T, currentTransition = {};
currentTransition.types = null !== prevTransition ? prevTransition.types : null;
ReactSharedInternals.T = currentTransition;
dispatchOptimisticSetState(fiber, false, queue, pendingState);
try {
var returnValue = callback(), onStartTransitionFinish = ReactSharedInternals.S;
null !== onStartTransitionFinish && onStartTransitionFinish(currentTransition, returnValue);
if (null !== returnValue && "object" === typeof returnValue && "function" === typeof returnValue.then) dispatchSetStateInternal(fiber, queue, chainThenableValue(returnValue, finishedState), requestUpdateLane(fiber));
else dispatchSetStateInternal(fiber, queue, finishedState, requestUpdateLane(fiber));
} catch (error) {
dispatchSetStateInternal(fiber, queue, {
then: function() {},
status: "rejected",
reason: error
}, requestUpdateLane());
} finally {
ReactDOMSharedInternals.p = previousPriority, null !== prevTransition && null !== currentTransition.types && (prevTransition.types = currentTransition.types), ReactSharedInternals.T = prevTransition;
}
}
function noop() {}
function startHostTransition(formFiber, pendingState, action, formData) {
if (5 !== formFiber.tag) throw Error(formatProdErrorMessage(476));
var queue = ensureFormComponentIsStateful(formFiber).queue;
startTransition(formFiber, queue, pendingState, sharedNotPendingObject, null === action ? noop : function() {
requestFormReset$1(formFiber);
return action(formData);
});
}
function ensureFormComponentIsStateful(formFiber) {
var existingStateHook = formFiber.memoizedState;
if (null !== existingStateHook) return existingStateHook;
existingStateHook = {
memoizedState: sharedNotPendingObject,
baseState: sharedNotPendingObject,
baseQueue: null,
queue: {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: sharedNotPendingObject
},
next: null
};
var initialResetState = {};
existingStateHook.next = {
memoizedState: initialResetState,
baseState: initialResetState,
baseQueue: null,
queue: {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialResetState
},
next: null
};
formFiber.memoizedState = existingStateHook;
formFiber = formFiber.alternate;
null !== formFiber && (formFiber.memoizedState = existingStateHook);
return existingStateHook;
}
function requestFormReset$1(formFiber) {
var stateHook = ensureFormComponentIsStateful(formFiber);
null === stateHook.next && (stateHook = formFiber.alternate.memoizedState);
dispatchSetStateInternal(formFiber, stateHook.next.queue, {}, requestUpdateLane());
}
function useHostTransitionStatus() {
return readContext(HostTransitionContext);
}
function updateId() {
return updateWorkInProgressHook().memoizedState;
}
function updateRefresh() {
return updateWorkInProgressHook().memoizedState;
}
function refreshCache(fiber) {
for (var provider = fiber.return; null !== provider;) {
switch (provider.tag) {
case 24:
case 3:
var lane = requestUpdateLane();
fiber = createUpdate(lane);
var root$73 = enqueueUpdate(provider, fiber, lane);
null !== root$73 && (scheduleUpdateOnFiber(root$73, provider, lane), entangleTransitions(root$73, provider, lane));
provider = { cache: createCache() };
fiber.payload = provider;
return;
}
provider = provider.return;
}
}
function dispatchReducerAction(fiber, queue, action) {
var lane = requestUpdateLane();
action = {
lane,
revertLane: 0,
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null
};
isRenderPhaseUpdate(fiber) ? enqueueRenderPhaseUpdate(queue, action) : (action = enqueueConcurrentHookUpdate(fiber, queue, action, lane), null !== action && (scheduleUpdateOnFiber(action, fiber, lane), entangleTransitionUpdate(action, queue, lane)));
}
function dispatchSetState(fiber, queue, action) {
dispatchSetStateInternal(fiber, queue, action, requestUpdateLane());
}
function dispatchSetStateInternal(fiber, queue, action, lane) {
var update = {
lane,
revertLane: 0,
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) enqueueRenderPhaseUpdate(queue, update);
else {
var alternate = fiber.alternate;
if (0 === fiber.lanes && (null === alternate || 0 === alternate.lanes) && (alternate = queue.lastRenderedReducer, null !== alternate)) try {
var currentState = queue.lastRenderedState, eagerState = alternate(currentState, action);
update.hasEagerState = !0;
update.eagerState = eagerState;
if (objectIs(eagerState, currentState)) return enqueueUpdate$1(fiber, queue, update, 0), null === workInProgressRoot && finishQueueingConcurrentUpdates(), !1;
} catch (error) {}
action = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (null !== action) return scheduleUpdateOnFiber(action, fiber, lane), entangleTransitionUpdate(action, queue, lane), true;
}
return false;
}
function dispatchOptimisticSetState(fiber, throwIfDuringRender, queue, action) {
action = {
lane: 2,
revertLane: requestTransitionLane(),
gesture: null,
action,
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) {
if (throwIfDuringRender) throw Error(formatProdErrorMessage(479));
} else throwIfDuringRender = enqueueConcurrentHookUpdate(fiber, queue, action, 2), null !== throwIfDuringRender && scheduleUpdateOnFiber(throwIfDuringRender, fiber, 2);
}
function isRenderPhaseUpdate(fiber) {
var alternate = fiber.alternate;
return fiber === currentlyRenderingFiber || null !== alternate && alternate === currentlyRenderingFiber;
}
function enqueueRenderPhaseUpdate(queue, update) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
var pending = queue.pending;
null === pending ? update.next = update : (update.next = pending.next, pending.next = update);
queue.pending = update;
}
function entangleTransitionUpdate(root, queue, lane) {
if (0 !== (lane & 4194048)) {
var queueLanes = queue.lanes;
queueLanes &= root.pendingLanes;
lane |= queueLanes;
queue.lanes = lane;
markRootEntangled(root, lane);
}
}
var ContextOnlyDispatcher = {
readContext,
use,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useInsertionEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useSyncExternalStore: throwInvalidHookError,
useId: throwInvalidHookError,
useHostTransitionStatus: throwInvalidHookError,
useFormState: throwInvalidHookError,
useActionState: throwInvalidHookError,
useOptimistic: throwInvalidHookError,
useMemoCache: throwInvalidHookError,
useCacheRefresh: throwInvalidHookError,
useEffectEvent: throwInvalidHookError
};
var HooksDispatcherOnMount = {
readContext,
use,
useCallback: function(callback, deps) {
mountWorkInProgressHook().memoizedState = [callback, void 0 === deps ? null : deps];
return callback;
},
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: function(ref, create, deps) {
deps = null !== deps && void 0 !== deps ? deps.concat([ref]) : null;
mountEffectImpl(4194308, 4, imperativeHandleEffect.bind(null, create, ref), deps);
},
useLayoutEffect: function(create, deps) {
return mountEffectImpl(4194308, 4, create, deps);
},
useInsertionEffect: function(create, deps) {
mountEffectImpl(4, 2, create, deps);
},
useMemo: function(nextCreate, deps) {
var hook = mountWorkInProgressHook();
deps = void 0 === deps ? null : deps;
var nextValue = nextCreate();
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
nextCreate();
} finally {
setIsStrictModeForDevtools(false);
}
}
hook.memoizedState = [nextValue, deps];
return nextValue;
},
useReducer: function(reducer, initialArg, init) {
var hook = mountWorkInProgressHook();
if (void 0 !== init) {
var initialState = init(initialArg);
if (shouldDoubleInvokeUserFnsInHooksDEV) {
setIsStrictModeForDevtools(true);
try {
init(initialArg);
} finally {
setIsStrictModeForDevtools(false);
}
}
} else initialState = initialArg;
hook.memoizedState = hook.baseState = initialState;
reducer = {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState
};
hook.queue = reducer;
reducer = reducer.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber, reducer);
return [hook.memoizedState, reducer];
},
useRef: function(initialValue) {
var hook = mountWorkInProgressHook();
initialValue = { current: initialValue };
return hook.memoizedState = initialValue;
},
useState: function(initialState) {
initialState = mountStateImpl(initialState);
var queue = initialState.queue, dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue);
queue.dispatch = dispatch;
return [initialState.memoizedState, dispatch];
},
useDebugValue: mountDebugValue,
useDeferredValue: function(value, initialValue) {
return mountDeferredValueImpl(mountWorkInProgressHook(), value, initialValue);
},
useTransition: function() {
var stateHook = mountStateImpl(false);
stateHook = startTransition.bind(null, currentlyRenderingFiber, stateHook.queue, true, false);
mountWorkInProgressHook().memoizedState = stateHook;
return [false, stateHook];
},
useSyncExternalStore: function(subscribe, getSnapshot, getServerSnapshot) {
var fiber = currentlyRenderingFiber, hook = mountWorkInProgressHook();
if (isHydrating) {
if (void 0 === getServerSnapshot) throw Error(formatProdErrorMessage(407));
getServerSnapshot = getServerSnapshot();
} else {
getServerSnapshot = getSnapshot();
if (null === workInProgressRoot) throw Error(formatProdErrorMessage(349));
0 !== (workInProgressRootRenderLanes & 127) || pushStoreConsistencyCheck(fiber, getSnapshot, getServerSnapshot);
}
hook.memoizedState = getServerSnapshot;
var inst = {
value: getServerSnapshot,
getSnapshot
};
hook.queue = inst;
mountEffect(subscribeToStore.bind(null, fiber, inst, subscribe), [subscribe]);
fiber.flags |= 2048;
pushSimpleEffect(9, { destroy: void 0 }, updateStoreInstance.bind(null, fiber, inst, getServerSnapshot, getSnapshot), null);
return getServerSnapshot;
},
useId: function() {
var hook = mountWorkInProgressHook(), identifierPrefix = workInProgressRoot.identifierPrefix;
if (isHydrating) {
var JSCompiler_inline_result = treeContextOverflow;
var idWithLeadingBit = treeContextId;
JSCompiler_inline_result = (idWithLeadingBit & ~(1 << 32 - clz32(idWithLeadingBit) - 1)).toString(32) + JSCompiler_inline_result;
identifierPrefix = "_" + identifierPrefix + "R_" + JSCompiler_inline_result;
JSCompiler_inline_result = localIdCounter++;
0 < JSCompiler_inline_result && (identifierPrefix += "H" + JSCompiler_inline_result.toString(32));
identifierPrefix += "_";
} else JSCompiler_inline_result = globalClientIdCounter++, identifierPrefix = "_" + identifierPrefix + "r_" + JSCompiler_inline_result.toString(32) + "_";
return hook.memoizedState = identifierPrefix;
},
useHostTransitionStatus,
useFormState: mountActionState,
useActionState: mountActionState,
useOptimistic: function(passthrough) {
var hook = mountWorkInProgressHook();
hook.memoizedState = hook.baseState = passthrough;
var queue = {
pending: null,
lanes: 0,
dispatch: null,
lastRenderedReducer: null,
lastRenderedState: null
};
hook.queue = queue;
hook = dispatchOptimisticSetState.bind(null, currentlyRenderingFiber, true, queue);
queue.dispatch = hook;
return [passthrough, hook];
},
useMemoCache,
useCacheRefresh: function() {
return mountWorkInProgressHook().memoizedState = refreshCache.bind(null, currentlyRenderingFiber);
},
useEffectEvent: function(callback) {
var hook = mountWorkInProgressHook(), ref = { impl: callback };
hook.memoizedState = ref;
return function() {
if (0 !== (executionContext & 2)) throw Error(formatProdErrorMessage(440));
return ref.impl.apply(void 0, arguments);
};
}
};
var HooksDispatcherOnUpdate = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: function() {
return updateReducer(basicStateReducer);
},
useDebugValue: mountDebugValue,
useDeferredValue: function(value, initialValue) {
return updateDeferredValueImpl(updateWorkInProgressHook(), currentHook.memoizedState, value, initialValue);
},
useTransition: function() {
var booleanOrThenable = updateReducer(basicStateReducer)[0], start = updateWorkInProgressHook().memoizedState;
return ["boolean" === typeof booleanOrThenable ? booleanOrThenable : useThenable(booleanOrThenable), start];
},
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
useHostTransitionStatus,
useFormState: updateActionState,
useActionState: updateActionState,
useOptimistic: function(passthrough, reducer) {
return updateOptimisticImpl(updateWorkInProgressHook(), currentHook, passthrough, reducer);
},
useMemoCache,
useCacheRefresh: updateRefresh,
useEffectEvent: updateEvent
};
var HooksDispatcherOnRerender = {
readContext,
use,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: rerenderReducer,
useRef: updateRef,
useState: function() {
return rerenderReducer(basicStateReducer);
},
useDebugValue: mountDebugValue,
useDeferredValue: function(value, initialValue) {
var hook = updateWorkInProgressHook();
return null === currentHook ? mountDeferredValueImpl(hook, value, initialValue) : updateDeferredValueImpl(hook, currentHook.memoizedState, value, initialValue);
},
useTransition: function() {
var booleanOrThenable = rerenderReducer(basicStateReducer)[0], start = updateWorkInProgressHook().memoizedState;
return ["boolean" === typeof booleanOrThenable ? booleanOrThenable : useThenable(booleanOrThenable), start];
},
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
useHostTransitionStatus,
useFormState: rerenderActionState,
useActionState: rerenderActionState,
useOptimistic: function(passthrough, reducer) {
var hook = updateWorkInProgressHook();
if (null !== currentHook) return updateOptimisticImpl(hook, currentHook, passthrough, reducer);
hook.baseState = passthrough;
return [passthrough, hook.queue.dispatch];
},
useMemoCache,
useCacheRefresh: updateRefresh,
useEffectEvent: updateEvent
};
function applyDerivedStateFromProps(workInProgress, ctor, getDerivedStateFromProps, nextProps) {
ctor = workInProgress.memoizedState;
getDerivedStateFromProps = getDerivedStateFromProps(nextProps, ctor);
getDerivedStateFromProps = null === getDerivedStateFromProps || void 0 === getDerivedStateFromProps ? ctor : assign({}, ctor, getDerivedStateFromProps);
workInProgress.memoizedState = getDerivedStateFromProps;
0 === workInProgress.lanes && (workInProgress.updateQueue.baseState = getDerivedStateFromProps);
}
var classComponentUpdater = {
enqueueSetState: function(inst, payload, callback) {
inst = inst._reactInternals;
var lane = requestUpdateLane(), update = createUpdate(lane);
update.payload = payload;
void 0 !== callback && null !== callback && (update.callback = callback);
payload = enqueueUpdate(inst, update, lane);
null !== payload && (scheduleUpdateOnFiber(payload, inst, lane), entangleTransitions(payload, inst, lane));
},
enqueueReplaceState: function(inst, payload, callback) {
inst = inst._reactInternals;
var lane = requestUpdateLane(), update = createUpdate(lane);
update.tag = 1;
update.payload = payload;
void 0 !== callback && null !== callback && (update.callback = callback);
payload = enqueueUpdate(inst, update, lane);
null !== payload && (scheduleUpdateOnFiber(payload, inst, lane), entangleTransitions(payload, inst, lane));
},
enqueueForceUpdate: function(inst, callback) {
inst = inst._reactInternals;
var lane = requestUpdateLane(), update = createUpdate(lane);
update.tag = 2;
void 0 !== callback && null !== callback && (update.callback = callback);
callback = enqueueUpdate(inst, update, lane);
null !== callback && (scheduleUpdateOnFiber(callback, inst, lane), entangleTransitions(callback, inst, lane));
}
};
function checkShouldComponentUpdate(workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext) {
workInProgress = workInProgress.stateNode;
return "function" === typeof workInProgress.shouldComponentUpdate ? workInProgress.shouldComponentUpdate(newProps, newState, nextContext) : ctor.prototype && ctor.prototype.isPureReactComponent ? !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) : true;
}
function callComponentWillReceiveProps(workInProgress, instance, newProps, nextContext) {
workInProgress = instance.state;
"function" === typeof instance.componentWillReceiveProps && instance.componentWillReceiveProps(newProps, nextContext);
"function" === typeof instance.UNSAFE_componentWillReceiveProps && instance.UNSAFE_componentWillReceiveProps(newProps, nextContext);
instance.state !== workInProgress && classComponentUpdater.enqueueReplaceState(instance, instance.state, null);
}
function resolveClassComponentProps(Component, baseProps) {
var newProps = baseProps;
if ("ref" in baseProps) {
newProps = {};
for (var propName in baseProps) "ref" !== propName && (newProps[propName] = baseProps[propName]);
}
if (Component = Component.defaultProps) {
newProps === baseProps && (newProps = assign({}, newProps));
for (var propName$77 in Component) void 0 === newProps[propName$77] && (newProps[propName$77] = Component[propName$77]);
}
return newProps;
}
function defaultOnUncaughtError(error) {
reportGlobalError(error);
}
function defaultOnCaughtError(error) {
console.error(error);
}
function defaultOnRecoverableError(error) {
reportGlobalError(error);
}
function logUncaughtError(root, errorInfo) {
try {
var onUncaughtError = root.onUncaughtError;
onUncaughtError(errorInfo.value, { componentStack: errorInfo.stack });
} catch (e$78) {
setTimeout(function() {
throw e$78;
});
}
}
function logCaughtError(root, boundary, errorInfo) {
try {
var onCaughtError = root.onCaughtError;
onCaughtError(errorInfo.value, {
componentStack: errorInfo.stack,
errorBoundary: 1 === boundary.tag ? boundary.stateNode : null
});
} catch (e$79) {
setTimeout(function() {
throw e$79;
});
}
}
function createRootErrorUpdate(root, errorInfo, lane) {
lane = createUpdate(lane);
lane.tag = 3;
lane.payload = { element: null };
lane.callback = function() {
logUncaughtError(root, errorInfo);
};
return lane;
}
function createClassErrorUpdate(lane) {
lane = createUpdate(lane);
lane.tag = 3;
return lane;
}
function initializeClassErrorUpdate(update, root, fiber, errorInfo) {
var getDerivedStateFromError = fiber.type.getDerivedStateFromError;
if ("function" === typeof getDerivedStateFromError) {
var error = errorInfo.value;
update.payload = function() {
return getDerivedStateFromError(error);
};
update.callback = function() {
logCaughtError(root, fiber, errorInfo);
};
}
var inst = fiber.stateNode;
null !== inst && "function" === typeof inst.componentDidCatch && (update.callback = function() {
logCaughtError(root, fiber, errorInfo);
"function" !== typeof getDerivedStateFromError && (null === legacyErrorBoundariesThatAlreadyFailed ? legacyErrorBoundariesThatAlreadyFailed = /* @__PURE__ */ new Set([this]) : legacyErrorBoundariesThatAlreadyFailed.add(this));
var stack = errorInfo.stack;
this.componentDidCatch(errorInfo.value, { componentStack: null !== stack ? stack : "" });
});
}
function throwException(root, returnFiber, sourceFiber, value, rootRenderLanes) {
sourceFiber.flags |= 32768;
if (null !== value && "object" === typeof value && "function" === typeof value.then) {
returnFiber = sourceFiber.alternate;
null !== returnFiber && propagateParentContextChanges(returnFiber, sourceFiber, rootRenderLanes, true);
sourceFiber = suspenseHandlerStackCursor.current;
if (null !== sourceFiber) {
switch (sourceFiber.tag) {
case 31:
case 13:
case 19: return null === shellBoundary ? renderDidSuspendDelayIfPossible() : null === sourceFiber.alternate && 0 === workInProgressRootExitStatus && (workInProgressRootExitStatus = 3), sourceFiber.flags &= -257, sourceFiber.flags |= 65536, sourceFiber.lanes = rootRenderLanes, value === noopSuspenseyCommitThenable ? sourceFiber.flags |= 16384 : (returnFiber = sourceFiber.updateQueue, null === returnFiber ? sourceFiber.updateQueue = /* @__PURE__ */ new Set([value]) : returnFiber.add(value), attachPingListener(root, value, rootRenderLanes)), false;
case 22: return sourceFiber.flags |= 65536, value === noopSuspenseyCommitThenable ? sourceFiber.flags |= 16384 : (returnFiber = sourceFiber.updateQueue, null === returnFiber ? (returnFiber = {
transitions: null,
markerInstances: null,
retryQueue: /* @__PURE__ */ new Set([value])
}, sourceFiber.updateQueue = returnFiber) : (sourceFiber = returnFiber.retryQueue, null === sourceFiber ? returnFiber.retryQueue = /* @__PURE__ */ new Set([value]) : sourceFiber.add(value)), attachPingListener(root, value, rootRenderLanes)), false;
}
throw Error(formatProdErrorMessage(435, sourceFiber.tag));
}
attachPingListener(root, value, rootRenderLanes);
renderDidSuspendDelayIfPossible();
return false;
}
if (isHydrating) return returnFiber = suspenseHandlerStackCursor.current, null !== returnFiber ? (0 === (returnFiber.flags & 65536) && (returnFiber.flags |= 256), returnFiber.flags |= 65536, returnFiber.lanes = rootRenderLanes, value !== HydrationMismatchException && (root = Error(formatProdErrorMessage(422), { cause: value }), queueHydrationError(createCapturedValueAtFiber(root, sourceFiber)))) : (value !== HydrationMismatchException && (returnFiber = Error(formatProdErrorMessage(423), { cause: value }), queueHydrationError(createCapturedValueAtFiber(returnFiber, sourceFiber))), root = root.current.alternate, root.flags |= 65536, rootRenderLanes &= -rootRenderLanes, root.lanes |= rootRenderLanes, value = createCapturedValueAtFiber(value, sourceFiber), rootRenderLanes = createRootErrorUpdate(root.stateNode, value, rootRenderLanes), enqueueCapturedUpdate(root, rootRenderLanes), 4 !== workInProgressRootExitStatus && (workInProgressRootExitStatus = 2)), false;
var wrapperError = Error(formatProdErrorMessage(520), { cause: value });
wrapperError = createCapturedValueAtFiber(wrapperError, sourceFiber);
null === workInProgressRootConcurrentErrors ? workInProgressRootConcurrentErrors = [wrapperError] : workInProgressRootConcurrentErrors.push(wrapperError);
4 !== workInProgressRootExitStatus && (workInProgressRootExitStatus = 2);
if (null === returnFiber) return true;
value = createCapturedValueAtFiber(value, sourceFiber);
sourceFiber = returnFiber;
do {
switch (sourceFiber.tag) {
case 3: return sourceFiber.flags |= 65536, root = rootRenderLanes & -rootRenderLanes, sourceFiber.lanes |= root, root = createRootErrorUpdate(sourceFiber.stateNode, value, root), enqueueCapturedUpdate(sourceFiber, root), false;
case 1:
returnFiber = sourceFiber.type;
wrapperError = sourceFiber.stateNode;
if (0 === (sourceFiber.flags & 128) && ("function" === typeof returnFiber.getDerivedStateFromError || null !== wrapperError && "function" === typeof wrapperError.componentDidCatch && (null === legacyErrorBoundariesThatAlreadyFailed || !legacyErrorBoundariesThatAlreadyFailed.has(wrapperError)))) return sourceFiber.flags |= 65536, rootRenderLanes &= -rootRenderLanes, sourceFiber.lanes |= rootRenderLanes, rootRenderLanes = createClassErrorUpdate(rootRenderLanes), initializeClassErrorUpdate(rootRenderLanes, root, sourceFiber, value), enqueueCapturedUpdate(sourceFiber, rootRenderLanes), false;
break;
case 22: if (null !== sourceFiber.memoizedState) return sourceFiber.flags |= 65536, false;
}
sourceFiber = sourceFiber.return;
} while (null !== sourceFiber);
return false;
}
var SelectiveHydrationException = Error(formatProdErrorMessage(461));
var didReceiveUpdate = false;
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
workInProgress.child = null === current ? mountChildFibers(workInProgress, null, nextChildren, renderLanes) : reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) {
Component = Component.render;
var ref = workInProgress.ref;
if ("ref" in nextProps) {
var propsWithoutRef = {};
for (var key in nextProps) "ref" !== key && (propsWithoutRef[key] = nextProps[key]);
} else propsWithoutRef = nextProps;
prepareToReadContext(workInProgress);
nextProps = renderWithHooks(current, workInProgress, Component, propsWithoutRef, ref, renderLanes);
key = checkDidRenderIdHook();
if (null !== current && !didReceiveUpdate) return bailoutHooks(current, workInProgress, renderLanes), bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
isHydrating && key && pushMaterializedTreeId(workInProgress);
workInProgress.flags |= 1;
reconcileChildren(current, workInProgress, nextProps, renderLanes);
return workInProgress.child;
}
function updateMemoComponent(current, workInProgress, Component, nextProps, renderLanes) {
if (null === current) {
var type = Component.type;
if ("function" === typeof type && !shouldConstruct(type) && void 0 === type.defaultProps && null === Component.compare) return workInProgress.tag = 15, workInProgress.type = type, updateSimpleMemoComponent(current, workInProgress, type, nextProps, renderLanes);
current = createFiberFromTypeAndProps(Component.type, null, nextProps, workInProgress, workInProgress.mode, renderLanes);
current.ref = workInProgress.ref;
current.return = workInProgress;
return workInProgress.child = current;
}
type = current.child;
if (!checkScheduledUpdateOrContext(current, renderLanes)) {
var prevProps = type.memoizedProps;
Component = Component.compare;
Component = null !== Component ? Component : shallowEqual;
if (Component(prevProps, nextProps) && current.ref === workInProgress.ref) return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
workInProgress.flags |= 1;
current = createWorkInProgress(type, nextProps);
current.ref = workInProgress.ref;
current.return = workInProgress;
return workInProgress.child = current;
}
function updateSimpleMemoComponent(current, workInProgress, Component, nextProps, renderLanes) {
if (null !== current) {
var prevProps = current.memoizedProps;
if (shallowEqual(prevProps, nextProps) && current.ref === workInProgress.ref) if (didReceiveUpdate = false, workInProgress.pendingProps = nextProps = prevProps, checkScheduledUpdateOrContext(current, renderLanes)) 0 !== (current.flags & 131072) && (didReceiveUpdate = true);
else return workInProgress.lanes = current.lanes, bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
return updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes);
}
function updateOffscreenComponent(current, workInProgress, renderLanes, nextProps) {
var nextChildren = nextProps.children, prevState = null !== current ? current.memoizedState : null;
null === current && null === workInProgress.stateNode && (workInProgress.stateNode = {
_visibility: 1,
_pendingMarkers: null,
_retryCache: null,
_transitions: null
});
if ("hidden" === nextProps.mode) {
if (0 !== (workInProgress.flags & 128)) {
prevState = null !== prevState ? prevState.baseLanes | renderLanes : renderLanes;
if (null !== current) {
nextProps = workInProgress.child = current.child;
for (nextChildren = 0; null !== nextProps;) nextChildren = nextChildren | nextProps.lanes | nextProps.childLanes, nextProps = nextProps.sibling;
nextProps = nextChildren & ~prevState;
} else nextProps = 0, workInProgress.child = null;
return deferHiddenOffscreenComponent(current, workInProgress, prevState, renderLanes, nextProps);
}
if (0 !== (renderLanes & 536870912)) workInProgress.memoizedState = {
baseLanes: 0,
cachePool: null
}, null !== current && pushTransition(workInProgress, null !== prevState ? prevState.cachePool : null), null !== prevState ? pushHiddenContext(workInProgress, prevState) : reuseHiddenContextOnStack(), pushOffscreenSuspenseHandler(workInProgress);
else return nextProps = workInProgress.lanes = 536870912, deferHiddenOffscreenComponent(current, workInProgress, null !== prevState ? prevState.baseLanes | renderLanes : renderLanes, renderLanes, nextProps);
} else null !== prevState ? (pushTransition(workInProgress, prevState.cachePool), pushHiddenContext(workInProgress, prevState), reuseSuspenseHandlerOnStack(), workInProgress.memoizedState = null) : (null !== current && pushTransition(workInProgress, null), reuseHiddenContextOnStack(), reuseSuspenseHandlerOnStack());
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
function bailoutOffscreenComponent(current, workInProgress) {
null !== current && 22 === current.tag || null !== workInProgress.stateNode || (workInProgress.stateNode = {
_visibility: 1,
_pendingMarkers: null,
_retryCache: null,
_transitions: null
});
return workInProgress.sibling;
}
function deferHiddenOffscreenComponent(current, workInProgress, nextBaseLanes, renderLanes, remainingChildLanes) {
var JSCompiler_inline_result = peekCacheFromPool();
JSCompiler_inline_result = null === JSCompiler_inline_result ? null : {
parent: CacheContext._currentValue,
pool: JSCompiler_inline_result
};
workInProgress.memoizedState = {
baseLanes: nextBaseLanes,
cachePool: JSCompiler_inline_result
};
null !== current && pushTransition(workInProgress, null);
reuseHiddenContextOnStack();
pushOffscreenSuspenseHandler(workInProgress);
null !== current && propagateParentContextChanges(current, workInProgress, renderLanes, true);
workInProgress.childLanes = remainingChildLanes;
return null;
}
function mountActivityChildren(workInProgress, nextProps) {
nextProps = mountWorkInProgressOffscreenFiber({
mode: nextProps.mode,
children: nextProps.children
}, workInProgress.mode);
nextProps.ref = workInProgress.ref;
workInProgress.child = nextProps;
nextProps.return = workInProgress;
return nextProps;
}
function retryActivityComponentWithoutHydrating(current, workInProgress, renderLanes) {
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
current = mountActivityChildren(workInProgress, workInProgress.pendingProps);
current.flags |= 2;
popSuspenseHandler(workInProgress);
workInProgress.memoizedState = null;
return current;
}
function updateActivityComponent(current, workInProgress, renderLanes) {
var nextProps = workInProgress.pendingProps, didSuspend = 0 !== (workInProgress.flags & 128);
workInProgress.flags &= -129;
if (null === current) {
if (isHydrating) {
if ("hidden" === nextProps.mode) return current = mountActivityChildren(workInProgress, nextProps), workInProgress.lanes = 536870912, current.memoizedState = {
baseLanes: 0,
cachePool: null
}, bailoutOffscreenComponent(null, current);
pushDehydratedActivitySuspenseHandler(workInProgress);
(current = nextHydratableInstance) ? (current = canHydrateHydrationBoundary(current, rootOrSingletonContext), current = null !== current && "&" === current.data ? current : null, null !== current && (workInProgress.memoizedState = {
dehydrated: current,
treeContext: null !== treeContextProvider ? {
id: treeContextId,
overflow: treeContextOverflow
} : null,
retryLane: 536870912,
hydrationErrors: null
}, renderLanes = createFiberFromDehydratedFragment(current), renderLanes.return = workInProgress, workInProgress.child = renderLanes, hydrationParentFiber = workInProgress, nextHydratableInstance = null)) : current = null;
if (null === current) throw throwOnHydrationMismatch(workInProgress);
workInProgress.lanes = 536870912;
return null;
}
return mountActivityChildren(workInProgress, nextProps);
}
var prevState = current.memoizedState;
if (null !== prevState) {
var dehydrated = prevState.dehydrated;
pushDehydratedActivitySuspenseHandler(workInProgress);
if (didSuspend) if (workInProgress.flags & 256) workInProgress.flags &= -257, workInProgress = retryActivityComponentWithoutHydrating(current, workInProgress, renderLanes);
else if (null !== workInProgress.memoizedState) workInProgress.child = current.child, workInProgress.flags |= 128, workInProgress = null;
else throw Error(formatProdErrorMessage(558));
else if (didReceiveUpdate || propagateParentContextChanges(current, workInProgress, renderLanes, false), didSuspend = 0 !== (renderLanes & current.childLanes), didReceiveUpdate || didSuspend) {
if (null === currentTreeHiddenStackCursor.current) {
nextProps = workInProgressRoot;
if (null !== nextProps && (dehydrated = getBumpedLaneForHydration(nextProps, renderLanes), 0 !== dehydrated && dehydrated !== prevState.retryLane)) throw prevState.retryLane = dehydrated, enqueueConcurrentRenderForLane(current, dehydrated), scheduleUpdateOnFiber(nextProps, current, dehydrated), SelectiveHydrationException;
renderDidSuspendDelayIfPossible();
}
workInProgress = retryActivityComponentWithoutHydrating(current, workInProgress, renderLanes);
} else current = prevState.treeContext, nextHydratableInstance = getNextHydratable(dehydrated.nextSibling), hydrationParentFiber = workInProgress, isHydrating = true, hydrationErrors = null, rootOrSingletonContext = false, null !== current && restoreSuspendedTreeContext(workInProgress, current), workInProgress = mountActivityChildren(workInProgress, nextProps), workInProgress.flags |= 134221824;
return workInProgress;
}
current = createWorkInProgress(current.child, {
mode: nextProps.mode,
children: nextProps.children
});
current.ref = workInProgress.ref;
workInProgress.child = current;
current.return = workInProgress;
return current;
}
function markRef(current, workInProgress) {
var ref = workInProgress.ref;
if (null === ref) null !== current && null !== current.ref && (workInProgress.flags |= 4194816);
else {
if ("function" !== typeof ref && "object" !== typeof ref) throw Error(formatProdErrorMessage(284));
if (null === current || current.ref !== ref) workInProgress.flags |= 4194816;
}
}
function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
prepareToReadContext(workInProgress);
Component = renderWithHooks(current, workInProgress, Component, nextProps, void 0, renderLanes);
nextProps = checkDidRenderIdHook();
if (null !== current && !didReceiveUpdate) return bailoutHooks(current, workInProgress, renderLanes), bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
isHydrating && nextProps && pushMaterializedTreeId(workInProgress);
workInProgress.flags |= 1;
reconcileChildren(current, workInProgress, Component, renderLanes);
return workInProgress.child;
}
function replayFunctionComponent(current, workInProgress, nextProps, Component, secondArg, renderLanes) {
prepareToReadContext(workInProgress);
workInProgress.updateQueue = null;
nextProps = renderWithHooksAgain(workInProgress, Component, nextProps, secondArg);
finishRenderingHooks(current);
Component = checkDidRenderIdHook();
if (null !== current && !didReceiveUpdate) return bailoutHooks(current, workInProgress, renderLanes), bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
isHydrating && Component && pushMaterializedTreeId(workInProgress);
workInProgress.flags |= 1;
reconcileChildren(current, workInProgress, nextProps, renderLanes);
return workInProgress.child;
}
function updateClassComponent(current, workInProgress, Component, nextProps, renderLanes) {
prepareToReadContext(workInProgress);
if (null === workInProgress.stateNode) {
var context = emptyContextObject, contextType = Component.contextType;
"object" === typeof contextType && null !== contextType && (context = readContext(contextType));
context = new Component(nextProps, context);
workInProgress.memoizedState = null !== context.state && void 0 !== context.state ? context.state : null;
context.updater = classComponentUpdater;
workInProgress.stateNode = context;
context._reactInternals = workInProgress;
context = workInProgress.stateNode;
context.props = nextProps;
context.state = workInProgress.memoizedState;
context.refs = {};
initializeUpdateQueue(workInProgress);
contextType = Component.contextType;
context.context = "object" === typeof contextType && null !== contextType ? readContext(contextType) : emptyContextObject;
context.state = workInProgress.memoizedState;
contextType = Component.getDerivedStateFromProps;
"function" === typeof contextType && (applyDerivedStateFromProps(workInProgress, Component, contextType, nextProps), context.state = workInProgress.memoizedState);
"function" === typeof Component.getDerivedStateFromProps || "function" === typeof context.getSnapshotBeforeUpdate || "function" !== typeof context.UNSAFE_componentWillMount && "function" !== typeof context.componentWillMount || (contextType = context.state, "function" === typeof context.componentWillMount && context.componentWillMount(), "function" === typeof context.UNSAFE_componentWillMount && context.UNSAFE_componentWillMount(), contextType !== context.state && classComponentUpdater.enqueueReplaceState(context, context.state, null), processUpdateQueue(workInProgress, nextProps, context, renderLanes), suspendIfUpdateReadFromEntangledAsyncAction(), context.state = workInProgress.memoizedState);
"function" === typeof context.componentDidMount && (workInProgress.flags |= 4194308);
nextProps = true;
} else if (null === current) {
context = workInProgress.stateNode;
var unresolvedOldProps = workInProgress.memoizedProps, oldProps = resolveClassComponentProps(Component, unresolvedOldProps);
context.props = oldProps;
var oldContext = context.context, contextType$jscomp$0 = Component.contextType;
contextType = emptyContextObject;
"object" === typeof contextType$jscomp$0 && null !== contextType$jscomp$0 && (contextType = readContext(contextType$jscomp$0));
var getDerivedStateFromProps = Component.getDerivedStateFromProps;
contextType$jscomp$0 = "function" === typeof getDerivedStateFromProps || "function" === typeof context.getSnapshotBeforeUpdate;
unresolvedOldProps = workInProgress.pendingProps !== unresolvedOldProps;
contextType$jscomp$0 || "function" !== typeof context.UNSAFE_componentWillReceiveProps && "function" !== typeof context.componentWillReceiveProps || (unresolvedOldProps || oldContext !== contextType) && callComponentWillReceiveProps(workInProgress, context, nextProps, contextType);
hasForceUpdate = false;
var oldState = workInProgress.memoizedState;
context.state = oldState;
processUpdateQueue(workInProgress, nextProps, context, renderLanes);
suspendIfUpdateReadFromEntangledAsyncAction();
oldContext = workInProgress.memoizedState;
unresolvedOldProps || oldState !== oldContext || hasForceUpdate ? ("function" === typeof getDerivedStateFromProps && (applyDerivedStateFromProps(workInProgress, Component, getDerivedStateFromProps, nextProps), oldContext = workInProgress.memoizedState), (oldProps = hasForceUpdate || checkShouldComponentUpdate(workInProgress, Component, oldProps, nextProps, oldState, oldContext, contextType)) ? (contextType$jscomp$0 || "function" !== typeof context.UNSAFE_componentWillMount && "function" !== typeof context.componentWillMount || ("function" === typeof context.componentWillMount && context.componentWillMount(), "function" === typeof context.UNSAFE_componentWillMount && context.UNSAFE_componentWillMount()), "function" === typeof context.componentDidMount && (workInProgress.flags |= 4194308)) : ("function" === typeof context.componentDidMount && (workInProgress.flags |= 4194308), workInProgress.memoizedProps = nextProps, workInProgress.memoizedState = oldContext), context.props = nextProps, context.state = oldContext, context.context = contextType, nextProps = oldProps) : ("function" === typeof context.componentDidMount && (workInProgress.flags |= 4194308), nextProps = false);
} else {
context = workInProgress.stateNode;
cloneUpdateQueue(current, workInProgress);
contextType = workInProgress.memoizedProps;
contextType$jscomp$0 = resolveClassComponentProps(Component, contextType);
context.props = contextType$jscomp$0;
getDerivedStateFromProps = workInProgress.pendingProps;
oldState = context.context;
oldContext = Component.contextType;
oldProps = emptyContextObject;
"object" === typeof oldContext && null !== oldContext && (oldProps = readContext(oldContext));
unresolvedOldProps = Component.getDerivedStateFromProps;
(oldContext = "function" === typeof unresolvedOldProps || "function" === typeof context.getSnapshotBeforeUpdate) || "function" !== typeof context.UNSAFE_componentWillReceiveProps && "function" !== typeof context.componentWillReceiveProps || (contextType !== getDerivedStateFromProps || oldState !== oldProps) && callComponentWillReceiveProps(workInProgress, context, nextProps, oldProps);
hasForceUpdate = false;
oldState = workInProgress.memoizedState;
context.state = oldState;
processUpdateQueue(workInProgress, nextProps, context, renderLanes);
suspendIfUpdateReadFromEntangledAsyncAction();
var newState = workInProgress.memoizedState;
contextType !== getDerivedStateFromProps || oldState !== newState || hasForceUpdate || null !== current && null !== current.dependencies && checkIfContextChanged(current.dependencies) ? ("function" === typeof unresolvedOldProps && (applyDerivedStateFromProps(workInProgress, Component, unresolvedOldProps, nextProps), newState = workInProgress.memoizedState), (contextType$jscomp$0 = hasForceUpdate || checkShouldComponentUpdate(workInProgress, Component, contextType$jscomp$0, nextProps, oldState, newState, oldProps) || null !== current && null !== current.dependencies && checkIfContextChanged(current.dependencies)) ? (oldContext || "function" !== typeof context.UNSAFE_componentWillUpdate && "function" !== typeof context.componentWillUpdate || ("function" === typeof context.componentWillUpdate && context.componentWillUpdate(nextProps, newState, oldProps), "function" === typeof context.UNSAFE_componentWillUpdate && context.UNSAFE_componentWillUpdate(nextProps, newState, oldProps)), "function" === typeof context.componentDidUpdate && (workInProgress.flags |= 4), "function" === typeof context.getSnapshotBeforeUpdate && (workInProgress.flags |= 1024)) : ("function" !== typeof context.componentDidUpdate || contextType === current.memoizedProps && oldState === current.memoizedState || (workInProgress.flags |= 4), "function" !== typeof context.getSnapshotBeforeUpdate || contextType === current.memoizedProps && oldState === current.memoizedState || (workInProgress.flags |= 1024), workInProgress.memoizedProps = nextProps, workInProgress.memoizedState = newState), context.props = nextProps, context.state = newState, context.context = oldProps, nextProps = contextType$jscomp$0) : ("function" !== typeof context.componentDidUpdate || contextType === current.memoizedProps && oldState === current.memoizedState || (workInProgress.flags |= 4), "function" !== typeof context.getSnapshotBeforeUpdate || contextType === current.memoizedProps && oldState === current.memoizedState || (workInProgress.flags |= 1024), nextProps = false);
}
context = nextProps;
markRef(current, workInProgress);
nextProps = 0 !== (workInProgress.flags & 128);
context || nextProps ? (context = workInProgress.stateNode, Component = nextProps && "function" !== typeof Component.getDerivedStateFromError ? null : context.render(), workInProgress.flags |= 1, null !== current && nextProps ? (workInProgress.child = reconcileChildFibers(workInProgress, current.child, null, renderLanes), workInProgress.child = reconcileChildFibers(workInProgress, null, Component, renderLanes)) : reconcileChildren(current, workInProgress, Component, renderLanes), workInProgress.memoizedState = context.state, current = workInProgress.child) : current = bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
return current;
}
function mountHostRootWithoutHydrating(current, workInProgress, nextChildren, renderLanes) {
resetHydrationState();
workInProgress.flags |= 256;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
var SUSPENDED_MARKER = {
dehydrated: null,
treeContext: null,
retryLane: 0,
hydrationErrors: null
};
function mountSuspenseOffscreenState(renderLanes) {
return {
baseLanes: renderLanes,
cachePool: getSuspendedCache()
};
}
function getRemainingWorkInPrimaryTree(current, primaryTreeDidDefer, renderLanes) {
current = null !== current ? current.childLanes & ~renderLanes : 0;
primaryTreeDidDefer && (current |= workInProgressDeferredLane);
return current;
}
function updateSuspenseComponent(current, workInProgress, renderLanes) {
var nextProps = workInProgress.pendingProps, showFallback = false, didSuspend = 0 !== (workInProgress.flags & 128), JSCompiler_temp;
(JSCompiler_temp = didSuspend) || (JSCompiler_temp = null !== current && null === current.memoizedState ? false : 0 !== (suspenseStackCursor.current & 2));
JSCompiler_temp && (showFallback = true, workInProgress.flags &= -129);
JSCompiler_temp = 0 !== (workInProgress.flags & 32);
workInProgress.flags &= -33;
if (null === current) {
if (isHydrating) {
showFallback ? pushPrimaryTreeSuspenseHandler(workInProgress) : reuseSuspenseHandlerOnStack();
(current = nextHydratableInstance) ? (current = canHydrateHydrationBoundary(current, rootOrSingletonContext), current = null !== current && "&" !== current.data ? current : null, null !== current && (workInProgress.memoizedState = {
dehydrated: current,
treeContext: null !== treeContextProvider ? {
id: treeContextId,
overflow: treeContextOverflow
} : null,
retryLane: 536870912,
hydrationErrors: null
}, renderLanes = createFiberFromDehydratedFragment(current), renderLanes.return = workInProgress, workInProgress.child = renderLanes, hydrationParentFiber = workInProgress, nextHydratableInstance = null)) : current = null;
if (null === current) throw throwOnHydrationMismatch(workInProgress);
isSuspenseInstanceFallback(current) ? workInProgress.lanes = 32 : workInProgress.lanes = 536870912;
return null;
}
didSuspend = nextProps.children;
nextProps = nextProps.fallback;
if (showFallback) return reuseSuspenseHandlerOnStack(), showFallback = workInProgress.mode, didSuspend = mountWorkInProgressOffscreenFiber({
mode: "hidden",
children: didSuspend
}, showFallback), nextProps = createFiberFromFragment(nextProps, showFallback, renderLanes, null), didSuspend.return = workInProgress, nextProps.return = workInProgress, didSuspend.sibling = nextProps, workInProgress.child = didSuspend, nextProps = workInProgress.child, nextProps.memoizedState = mountSuspenseOffscreenState(renderLanes), nextProps.childLanes = getRemainingWorkInPrimaryTree(current, JSCompiler_temp, renderLanes), workInProgress.memoizedState = SUSPENDED_MARKER, bailoutOffscreenComponent(null, nextProps);
pushPrimaryTreeSuspenseHandler(workInProgress);
return mountSuspensePrimaryChildren(workInProgress, didSuspend);
}
var prevState = current.memoizedState;
if (null !== prevState) {
var dehydrated$96 = prevState.dehydrated;
if (null !== dehydrated$96) return updateDehydratedSuspenseComponent(current, workInProgress, didSuspend, JSCompiler_temp, nextProps, dehydrated$96, prevState, renderLanes);
}
if (showFallback) return reuseSuspenseHandlerOnStack(), showFallback = nextProps.fallback, didSuspend = workInProgress.mode, prevState = current.child, dehydrated$96 = prevState.sibling, nextProps = createWorkInProgress(prevState, {
mode: "hidden",
children: nextProps.children
}), nextProps.subtreeFlags = prevState.subtreeFlags & 1206910976, null !== dehydrated$96 ? showFallback = createWorkInProgress(dehydrated$96, showFallback) : (showFallback = createFiberFromFragment(showFallback, didSuspend, renderLanes, null), showFallback.flags |= 2), showFallback.return = workInProgress, nextProps.return = workInProgress, nextProps.sibling = showFallback, workInProgress.child = nextProps, bailoutOffscreenComponent(null, nextProps), nextProps = workInProgress.child, showFallback = current.child.memoizedState, null === showFallback ? showFallback = mountSuspenseOffscreenState(renderLanes) : (didSuspend = showFallback.cachePool, null !== didSuspend ? (prevState = CacheContext._currentValue, didSuspend = didSuspend.parent !== prevState ? {
parent: prevState,
pool: prevState
} : didSuspend) : didSuspend = getSuspendedCache(), showFallback = {
baseLanes: showFallback.baseLanes | renderLanes,
cachePool: didSuspend
}), nextProps.memoizedState = showFallback, nextProps.childLanes = getRemainingWorkInPrimaryTree(current, JSCompiler_temp, renderLanes), workInProgress.memoizedState = SUSPENDED_MARKER, bailoutOffscreenComponent(current.child, nextProps);
pushPrimaryTreeSuspenseHandler(workInProgress);
renderLanes = current.child;
current = renderLanes.sibling;
renderLanes = createWorkInProgress(renderLanes, {
mode: "visible",
children: nextProps.children
});
renderLanes.return = workInProgress;
renderLanes.sibling = null;
null !== current && (JSCompiler_temp = workInProgress.deletions, null === JSCompiler_temp ? (workInProgress.deletions = [current], workInProgress.flags |= 16) : JSCompiler_temp.push(current));
workInProgress.child = renderLanes;
workInProgress.memoizedState = null;
return renderLanes;
}
function mountSuspensePrimaryChildren(workInProgress, primaryChildren) {
primaryChildren = mountWorkInProgressOffscreenFiber({
mode: "visible",
children: primaryChildren
}, workInProgress.mode);
primaryChildren.return = workInProgress;
return workInProgress.child = primaryChildren;
}
function mountWorkInProgressOffscreenFiber(offscreenProps, mode) {
offscreenProps = createFiberImplClass(22, offscreenProps, null, mode);
offscreenProps.lanes = 0;
return offscreenProps;
}
function retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes) {
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
current = mountSuspensePrimaryChildren(workInProgress, workInProgress.pendingProps.children);
current.flags |= 2;
workInProgress.memoizedState = null;
return current;
}
function updateDehydratedSuspenseComponent(current, workInProgress, didSuspend, didPrimaryChildrenDefer, nextProps, suspenseInstance, suspenseState, renderLanes) {
if (didSuspend) {
if (workInProgress.flags & 256) return pushPrimaryTreeSuspenseHandler(workInProgress), workInProgress.flags &= -257, retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes);
if (null !== workInProgress.memoizedState) return reuseSuspenseHandlerOnStack(), workInProgress.child = current.child, workInProgress.flags |= 128, null;
reuseSuspenseHandlerOnStack();
suspenseInstance = nextProps.fallback;
suspenseState = workInProgress.mode;
nextProps = mountWorkInProgressOffscreenFiber({
mode: "visible",
children: nextProps.children
}, suspenseState);
suspenseInstance = createFiberFromFragment(suspenseInstance, suspenseState, renderLanes, null);
suspenseInstance.flags |= 2;
nextProps.return = workInProgress;
suspenseInstance.return = workInProgress;
nextProps.sibling = suspenseInstance;
workInProgress.child = nextProps;
reconcileChildFibers(workInProgress, current.child, null, renderLanes);
nextProps = workInProgress.child;
nextProps.memoizedState = mountSuspenseOffscreenState(renderLanes);
nextProps.childLanes = getRemainingWorkInPrimaryTree(current, didPrimaryChildrenDefer, renderLanes);
workInProgress.memoizedState = SUSPENDED_MARKER;
return bailoutOffscreenComponent(null, nextProps);
}
pushPrimaryTreeSuspenseHandler(workInProgress);
if (isSuspenseInstanceFallback(suspenseInstance)) {
didPrimaryChildrenDefer = suspenseInstance.nextSibling && suspenseInstance.nextSibling.dataset;
if (didPrimaryChildrenDefer) var digest = didPrimaryChildrenDefer.dgst;
didPrimaryChildrenDefer = digest;
"" !== didPrimaryChildrenDefer && (nextProps = Error(formatProdErrorMessage(419)), nextProps.stack = "", nextProps.digest = didPrimaryChildrenDefer, queueHydrationError({
value: nextProps,
source: null,
stack: null
}));
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes);
}
didReceiveUpdate || propagateParentContextChanges(current, workInProgress, renderLanes, false);
didPrimaryChildrenDefer = 0 !== (renderLanes & current.childLanes);
if (didReceiveUpdate || didPrimaryChildrenDefer) {
if (null !== currentTreeHiddenStackCursor.current) return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes);
didPrimaryChildrenDefer = workInProgressRoot;
if (null !== didPrimaryChildrenDefer && (nextProps = getBumpedLaneForHydration(didPrimaryChildrenDefer, renderLanes), 0 !== nextProps && nextProps !== suspenseState.retryLane)) throw suspenseState.retryLane = nextProps, enqueueConcurrentRenderForLane(current, nextProps), scheduleUpdateOnFiber(didPrimaryChildrenDefer, current, nextProps), SelectiveHydrationException;
isSuspenseInstancePending(suspenseInstance) || renderDidSuspendDelayIfPossible();
return retrySuspenseComponentWithoutHydrating(current, workInProgress, renderLanes);
}
if (isSuspenseInstancePending(suspenseInstance)) return workInProgress.flags |= 192, workInProgress.child = current.child, null;
current = suspenseState.treeContext;
nextHydratableInstance = getNextHydratable(suspenseInstance.nextSibling);
hydrationParentFiber = workInProgress;
isHydrating = true;
hydrationErrors = null;
rootOrSingletonContext = false;
null !== current && restoreSuspendedTreeContext(workInProgress, current);
workInProgress = mountSuspensePrimaryChildren(workInProgress, nextProps.children);
workInProgress.flags |= 134221824;
return workInProgress;
}
function scheduleSuspenseWorkOnFiber(fiber, renderLanes, propagationRoot) {
fiber.lanes |= renderLanes;
var alternate = fiber.alternate;
null !== alternate && (alternate.lanes |= renderLanes);
scheduleContextWorkOnParentPath(fiber.return, renderLanes, propagationRoot);
}
function findLastContentRow(firstChild) {
for (var lastContentRow = null; null !== firstChild;) {
var currentRow = firstChild.alternate;
null !== currentRow && null === findFirstSuspended(currentRow) && (lastContentRow = firstChild);
firstChild = firstChild.sibling;
}
return lastContentRow;
}
function initSuspenseListRenderState(workInProgress, isBackwards, tail, lastContentRow, tailMode, treeForkCount) {
var renderState = workInProgress.memoizedState;
null === renderState ? workInProgress.memoizedState = {
isBackwards,
rendering: null,
renderingStartTime: 0,
last: lastContentRow,
tail,
tailMode,
treeForkCount
} : (renderState.isBackwards = isBackwards, renderState.rendering = null, renderState.renderingStartTime = 0, renderState.last = lastContentRow, renderState.tail = tail, renderState.tailMode = tailMode, renderState.treeForkCount = treeForkCount);
}
function reverseChildren(fiber) {
var row = fiber.child;
for (fiber.child = null; null !== row;) {
var nextRow = row.sibling;
row.sibling = fiber.child;
fiber.child = row;
row = nextRow;
}
}
function updateSuspenseListComponent(current, workInProgress, renderLanes) {
var nextProps = workInProgress.pendingProps, revealOrder = nextProps.revealOrder, tailMode = nextProps.tail;
nextProps = nextProps.children;
var suspenseContext = suspenseStackCursor.current;
if (workInProgress.flags & 128) return pushSuspenseListContext(workInProgress, suspenseContext), null;
var shouldForceFallback = 0 !== (suspenseContext & 2);
shouldForceFallback ? (suspenseContext = suspenseContext & 1 | 2, workInProgress.flags |= 128) : suspenseContext &= 1;
pushSuspenseListContext(workInProgress, suspenseContext);
"backwards" === revealOrder && null !== current ? (reverseChildren(current), reconcileChildren(current, workInProgress, nextProps, renderLanes), reverseChildren(current)) : reconcileChildren(current, workInProgress, nextProps, renderLanes);
nextProps = isHydrating ? treeForkCount : 0;
if (!shouldForceFallback && null !== current && 0 !== (current.flags & 128)) a: for (current = workInProgress.child; null !== current;) {
if (13 === current.tag) null !== current.memoizedState && scheduleSuspenseWorkOnFiber(current, renderLanes, workInProgress);
else if (19 === current.tag) scheduleSuspenseWorkOnFiber(current, renderLanes, workInProgress);
else if (null !== current.child) {
current.child.return = current;
current = current.child;
continue;
}
if (current === workInProgress) break a;
for (; null === current.sibling;) {
if (null === current.return || current.return === workInProgress) break a;
current = current.return;
}
current.sibling.return = current.return;
current = current.sibling;
}
switch (revealOrder) {
case "backwards":
renderLanes = findLastContentRow(workInProgress.child);
null === renderLanes ? (revealOrder = workInProgress.child, workInProgress.child = null) : (revealOrder = renderLanes.sibling, renderLanes.sibling = null, reverseChildren(workInProgress));
initSuspenseListRenderState(workInProgress, true, revealOrder, null, tailMode, nextProps);
break;
case "unstable_legacy-backwards":
renderLanes = null;
revealOrder = workInProgress.child;
for (workInProgress.child = null; null !== revealOrder;) {
current = revealOrder.alternate;
if (null !== current && null === findFirstSuspended(current)) {
workInProgress.child = revealOrder;
break;
}
current = revealOrder.sibling;
revealOrder.sibling = renderLanes;
renderLanes = revealOrder;
revealOrder = current;
}
initSuspenseListRenderState(workInProgress, true, renderLanes, null, tailMode, nextProps);
break;
case "together":
initSuspenseListRenderState(workInProgress, false, null, null, void 0, nextProps);
break;
case "independent":
workInProgress.memoizedState = null;
break;
default: renderLanes = findLastContentRow(workInProgress.child), null === renderLanes ? (revealOrder = workInProgress.child, workInProgress.child = null) : (revealOrder = renderLanes.sibling, renderLanes.sibling = null), initSuspenseListRenderState(workInProgress, false, revealOrder, renderLanes, tailMode, nextProps);
}
return workInProgress.child;
}
function updateContextProvider(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
pushProvider(workInProgress, workInProgress.type, newProps.value);
reconcileChildren(current, workInProgress, newProps.children, renderLanes);
return workInProgress.child;
}
function bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes) {
null !== current && (workInProgress.dependencies = current.dependencies);
workInProgressRootSkippedLanes |= workInProgress.lanes;
if (0 === (renderLanes & workInProgress.childLanes)) if (null !== current) {
if (propagateParentContextChanges(current, workInProgress, renderLanes, false), 0 === (renderLanes & workInProgress.childLanes)) return null;
} else return null;
if (null !== current && workInProgress.child !== current.child) throw Error(formatProdErrorMessage(153));
if (null !== workInProgress.child) {
current = workInProgress.child;
renderLanes = createWorkInProgress(current, current.pendingProps);
workInProgress.child = renderLanes;
for (renderLanes.return = workInProgress; null !== current.sibling;) current = current.sibling, renderLanes = renderLanes.sibling = createWorkInProgress(current, current.pendingProps), renderLanes.return = workInProgress;
renderLanes.sibling = null;
}
return workInProgress.child;
}
function checkScheduledUpdateOrContext(current, renderLanes) {
if (0 !== (current.lanes & renderLanes)) return true;
current = current.dependencies;
return null !== current && checkIfContextChanged(current) ? true : false;
}
function attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes) {
switch (workInProgress.tag) {
case 3:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
pushProvider(workInProgress, CacheContext, current.memoizedState.cache);
resetHydrationState();
break;
case 27:
case 5:
pushHostContext(workInProgress);
break;
case 4:
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
break;
case 10:
pushProvider(workInProgress, workInProgress.type, workInProgress.memoizedProps.value);
break;
case 31:
if (null !== workInProgress.memoizedState) return workInProgress.flags |= 128, pushDehydratedActivitySuspenseHandler(workInProgress), null;
break;
case 13:
var state$108 = workInProgress.memoizedState;
if (null !== state$108) {
if (null !== state$108.dehydrated) return pushPrimaryTreeSuspenseHandler(workInProgress), workInProgress.flags |= 128, null;
state$108 = propagateParentContextChanges(current, workInProgress, renderLanes, false);
var primaryChildLanes = workInProgress.child.childLanes;
if (state$108 || 0 !== (renderLanes & primaryChildLanes)) return updateSuspenseComponent(current, workInProgress, renderLanes);
pushPrimaryTreeSuspenseHandler(workInProgress);
current = bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
return null !== current ? current.sibling : null;
}
pushPrimaryTreeSuspenseHandler(workInProgress);
break;
case 19:
if (workInProgress.flags & 128) return updateSuspenseListComponent(current, workInProgress, renderLanes);
primaryChildLanes = 0 !== (current.flags & 128);
state$108 = 0 !== (renderLanes & workInProgress.childLanes);
state$108 || (propagateParentContextChanges(current, workInProgress, renderLanes, false), state$108 = 0 !== (renderLanes & workInProgress.childLanes));
if (primaryChildLanes) {
if (state$108) return updateSuspenseListComponent(current, workInProgress, renderLanes);
workInProgress.flags |= 128;
}
primaryChildLanes = workInProgress.memoizedState;
null !== primaryChildLanes && (primaryChildLanes.rendering = null, primaryChildLanes.tail = null, primaryChildLanes.lastEffect = null);
pushSuspenseListContext(workInProgress, suspenseStackCursor.current);
if (state$108) break;
else return null;
case 22: return workInProgress.lanes = 0, updateOffscreenComponent(current, workInProgress, renderLanes, workInProgress.pendingProps);
case 24: pushProvider(workInProgress, CacheContext, current.memoizedState.cache);
}
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
function beginWork(current, workInProgress, renderLanes) {
if (null !== current) if (current.memoizedProps !== workInProgress.pendingProps) didReceiveUpdate = true;
else {
if (!checkScheduledUpdateOrContext(current, renderLanes) && 0 === (workInProgress.flags & 128)) return didReceiveUpdate = false, attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
didReceiveUpdate = 0 !== (current.flags & 131072) ? true : false;
}
else didReceiveUpdate = false, isHydrating && 0 !== (workInProgress.flags & 1048576) && pushTreeId(workInProgress, treeForkCount, workInProgress.index);
workInProgress.lanes = 0;
switch (workInProgress.tag) {
case 16:
a: {
var props = workInProgress.pendingProps;
current = resolveLazy(workInProgress.elementType);
workInProgress.type = current;
if ("function" === typeof current) shouldConstruct(current) ? (props = resolveClassComponentProps(current, props), workInProgress.tag = 1, workInProgress = updateClassComponent(null, workInProgress, current, props, renderLanes)) : (workInProgress.tag = 0, workInProgress = updateFunctionComponent(null, workInProgress, current, props, renderLanes));
else {
if (void 0 !== current && null !== current) {
var $$typeof = current.$$typeof;
if ($$typeof === REACT_FORWARD_REF_TYPE) {
workInProgress.tag = 11;
workInProgress = updateForwardRef(null, workInProgress, current, props, renderLanes);
break a;
} else if ($$typeof === REACT_MEMO_TYPE) {
workInProgress.tag = 14;
workInProgress = updateMemoComponent(null, workInProgress, current, props, renderLanes);
break a;
} else if ($$typeof === REACT_CONTEXT_TYPE) {
workInProgress.tag = 10;
workInProgress.type = current;
workInProgress = updateContextProvider(null, workInProgress, renderLanes);
break a;
}
}
workInProgress = getComponentNameFromType(current) || current;
throw Error(formatProdErrorMessage(306, workInProgress, ""));
}
}
return workInProgress;
case 0: return updateFunctionComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
case 1: return props = workInProgress.type, $$typeof = resolveClassComponentProps(props, workInProgress.pendingProps), updateClassComponent(current, workInProgress, props, $$typeof, renderLanes);
case 3:
a: {
pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
if (null === current) throw Error(formatProdErrorMessage(387));
props = workInProgress.pendingProps;
var prevState = workInProgress.memoizedState;
$$typeof = prevState.element;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, props, null, renderLanes);
var nextState = workInProgress.memoizedState;
props = nextState.cache;
pushProvider(workInProgress, CacheContext, props);
props !== prevState.cache && propagateContextChanges(workInProgress, [CacheContext], renderLanes, true);
suspendIfUpdateReadFromEntangledAsyncAction();
props = nextState.element;
if (prevState.isDehydrated) if (prevState = {
element: props,
isDehydrated: false,
cache: nextState.cache
}, workInProgress.updateQueue.baseState = prevState, workInProgress.memoizedState = prevState, workInProgress.flags & 256) {
workInProgress = mountHostRootWithoutHydrating(current, workInProgress, props, renderLanes);
break a;
} else if (props !== $$typeof) {
$$typeof = createCapturedValueAtFiber(Error(formatProdErrorMessage(424)), workInProgress);
queueHydrationError($$typeof);
workInProgress = mountHostRootWithoutHydrating(current, workInProgress, props, renderLanes);
break a;
} else {
current = workInProgress.stateNode.containerInfo;
switch (current.nodeType) {
case 9:
current = current.body;
break;
default: current = "HTML" === current.nodeName ? current.ownerDocument.body : current;
}
nextHydratableInstance = getNextHydratable(current.firstChild);
hydrationParentFiber = workInProgress;
isHydrating = true;
hydrationErrors = null;
rootOrSingletonContext = true;
renderLanes = mountChildFibers(workInProgress, null, props, renderLanes);
for (workInProgress.child = renderLanes; renderLanes;) renderLanes.flags = renderLanes.flags & -3 | 134221824, renderLanes = renderLanes.sibling;
}
else {
resetHydrationState();
if (props === $$typeof) {
workInProgress = bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
break a;
}
reconcileChildren(current, workInProgress, props, renderLanes);
}
workInProgress = workInProgress.child;
}
return workInProgress;
case 26: return markRef(current, workInProgress), null === current ? (renderLanes = getResource(workInProgress.type, null, workInProgress.pendingProps, null)) ? workInProgress.memoizedState = renderLanes : isHydrating || (workInProgress.stateNode = createHoistableInstance(workInProgress.type, workInProgress.pendingProps, rootInstanceStackCursor.current, workInProgress)) : workInProgress.memoizedState = getResource(workInProgress.type, current.memoizedProps, workInProgress.pendingProps, current.memoizedState), null;
case 27: return pushHostContext(workInProgress), null === current && isHydrating && (props = workInProgress.stateNode = resolveSingletonInstance(workInProgress.type, workInProgress.pendingProps, rootInstanceStackCursor.current), hydrationParentFiber = workInProgress, rootOrSingletonContext = true, $$typeof = nextHydratableInstance, isSingletonScope(workInProgress.type) ? (previousHydratableOnEnteringScopedSingleton = $$typeof, nextHydratableInstance = getNextHydratable(props.firstChild)) : nextHydratableInstance = $$typeof), reconcileChildren(current, workInProgress, workInProgress.pendingProps.children, renderLanes), markRef(current, workInProgress), null === current && (workInProgress.flags |= 4194304), workInProgress.child;
case 5:
if (null === current && isHydrating) {
if ($$typeof = props = nextHydratableInstance) props = canHydrateInstance(props, workInProgress.type, workInProgress.pendingProps, rootOrSingletonContext), null !== props ? (workInProgress.stateNode = props, hydrationParentFiber = workInProgress, nextHydratableInstance = getNextHydratable(props.firstChild), rootOrSingletonContext = false, $$typeof = true) : $$typeof = false;
$$typeof || throwOnHydrationMismatch(workInProgress);
}
pushHostContext(workInProgress);
$$typeof = workInProgress.type;
prevState = workInProgress.pendingProps;
nextState = null !== current ? current.memoizedProps : null;
props = prevState.children;
shouldSetTextContent($$typeof, prevState) ? props = null : null !== nextState && shouldSetTextContent($$typeof, nextState) && (workInProgress.flags |= 32);
null !== workInProgress.memoizedState && ($$typeof = renderWithHooks(current, workInProgress, TransitionAwareHostComponent, null, null, renderLanes), HostTransitionContext._currentValue = $$typeof);
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, props, renderLanes);
return workInProgress.child;
case 6:
if (null === current && isHydrating) {
if (current = renderLanes = nextHydratableInstance) renderLanes = canHydrateTextInstance(renderLanes, workInProgress.pendingProps, rootOrSingletonContext), null !== renderLanes ? (workInProgress.stateNode = renderLanes, hydrationParentFiber = workInProgress, nextHydratableInstance = null, current = true) : current = false;
current || throwOnHydrationMismatch(workInProgress);
}
return null;
case 13: return updateSuspenseComponent(current, workInProgress, renderLanes);
case 4: return pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo), props = workInProgress.pendingProps, null === current ? workInProgress.child = reconcileChildFibers(workInProgress, null, props, renderLanes) : reconcileChildren(current, workInProgress, props, renderLanes), workInProgress.child;
case 11: return updateForwardRef(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
case 7: return props = workInProgress.pendingProps, markRef(current, workInProgress), reconcileChildren(current, workInProgress, props, renderLanes), workInProgress.child;
case 8: return reconcileChildren(current, workInProgress, workInProgress.pendingProps.children, renderLanes), workInProgress.child;
case 12: return reconcileChildren(current, workInProgress, workInProgress.pendingProps.children, renderLanes), workInProgress.child;
case 10: return updateContextProvider(current, workInProgress, renderLanes);
case 9: return $$typeof = workInProgress.type._context, props = workInProgress.pendingProps.children, prepareToReadContext(workInProgress), $$typeof = readContext($$typeof), props = props($$typeof), workInProgress.flags |= 1, reconcileChildren(current, workInProgress, props, renderLanes), workInProgress.child;
case 14: return updateMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
case 15: return updateSimpleMemoComponent(current, workInProgress, workInProgress.type, workInProgress.pendingProps, renderLanes);
case 19: return updateSuspenseListComponent(current, workInProgress, renderLanes);
case 31: return updateActivityComponent(current, workInProgress, renderLanes);
case 22: return updateOffscreenComponent(current, workInProgress, renderLanes, workInProgress.pendingProps);
case 24: return prepareToReadContext(workInProgress), props = readContext(CacheContext), null === current ? ($$typeof = peekCacheFromPool(), null === $$typeof && ($$typeof = workInProgressRoot, prevState = createCache(), $$typeof.pooledCache = prevState, prevState.refCount++, null !== prevState && ($$typeof.pooledCacheLanes |= renderLanes), $$typeof = prevState), workInProgress.memoizedState = {
parent: props,
cache: $$typeof
}, initializeUpdateQueue(workInProgress), pushProvider(workInProgress, CacheContext, $$typeof)) : (0 !== (current.lanes & renderLanes) && (cloneUpdateQueue(current, workInProgress), processUpdateQueue(workInProgress, null, null, renderLanes), suspendIfUpdateReadFromEntangledAsyncAction()), $$typeof = current.memoizedState, prevState = workInProgress.memoizedState, $$typeof.parent !== props ? ($$typeof = {
parent: props,
cache: props
}, workInProgress.memoizedState = $$typeof, 0 === workInProgress.lanes && (workInProgress.memoizedState = workInProgress.updateQueue.baseState = $$typeof), pushProvider(workInProgress, CacheContext, props)) : (props = prevState.cache, pushProvider(workInProgress, CacheContext, props), props !== $$typeof.cache && propagateContextChanges(workInProgress, [CacheContext], renderLanes, true))), reconcileChildren(current, workInProgress, workInProgress.pendingProps.children, renderLanes), workInProgress.child;
case 30: return null === workInProgress.stateNode && (workInProgress.stateNode = {
autoName: null,
paired: null,
clones: null,
ref: null
}), props = workInProgress.pendingProps, null != props.name && "auto" !== props.name ? workInProgress.flags |= null === current ? 18882560 : 18874368 : isHydrating && pushMaterializedTreeId(workInProgress), null !== current && current.memoizedProps.name !== props.name ? workInProgress.flags |= 4194816 : markRef(current, workInProgress), reconcileChildren(current, workInProgress, props.children, renderLanes), workInProgress.child;
case 29: throw workInProgress.pendingProps;
}
throw Error(formatProdErrorMessage(156, workInProgress.tag));
}
function markUpdate(workInProgress) {
workInProgress.flags |= 4;
}
function preloadInstanceAndSuspendIfNeeded(workInProgress, type, oldProps, newProps, renderLanes) {
var JSCompiler_temp;
if (JSCompiler_temp = 0 !== (workInProgress.mode & 32)) JSCompiler_temp = null === oldProps ? maySuspendCommit(type, newProps) : maySuspendCommit(type, newProps) && (newProps.src !== oldProps.src || newProps.srcSet !== oldProps.srcSet);
if (JSCompiler_temp) {
if (workInProgress.flags |= 16777216, (renderLanes & 335544128) === renderLanes) if (workInProgress.stateNode.complete) workInProgress.flags |= 8192;
else if (shouldRemainOnPreviousScreen()) workInProgress.flags |= 8192;
else throw suspendedThenable = noopSuspenseyCommitThenable, SuspenseyCommitException;
} else workInProgress.flags &= -16777217;
}
function preloadResourceAndSuspendIfNeeded(workInProgress, resource) {
if ("stylesheet" !== resource.type || 0 !== (resource.state.loading & 4)) workInProgress.flags &= -16777217;
else if (workInProgress.flags |= 16777216, !preloadResource(resource)) if (shouldRemainOnPreviousScreen()) workInProgress.flags |= 8192;
else throw suspendedThenable = noopSuspenseyCommitThenable, SuspenseyCommitException;
}
function scheduleRetryEffect(workInProgress, retryQueue) {
null !== retryQueue && (workInProgress.flags |= 4);
workInProgress.flags & 16384 && (retryQueue = 22 !== workInProgress.tag ? claimNextRetryLane() : 536870912, workInProgress.lanes |= retryQueue, workInProgressSuspendedRetryLanes |= retryQueue);
}
function cutOffTailIfNeeded(renderState, hasRenderedATailFallback) {
if (!isHydrating) switch (renderState.tailMode) {
case "visible": break;
case "collapsed":
for (var tailNode = renderState.tail, lastTailNode = null; null !== tailNode;) null !== tailNode.alternate && (lastTailNode = tailNode), tailNode = tailNode.sibling;
null === lastTailNode ? hasRenderedATailFallback || null === renderState.tail ? renderState.tail = null : renderState.tail.sibling = null : lastTailNode.sibling = null;
break;
default:
hasRenderedATailFallback = renderState.tail;
for (tailNode = null; null !== hasRenderedATailFallback;) null !== hasRenderedATailFallback.alternate && (tailNode = hasRenderedATailFallback), hasRenderedATailFallback = hasRenderedATailFallback.sibling;
null === tailNode ? renderState.tail = null : tailNode.sibling = null;
}
}
function bubbleProperties(completedWork) {
var didBailout = null !== completedWork.alternate && completedWork.alternate.child === completedWork.child, newChildLanes = 0, subtreeFlags = 0;
if (didBailout) for (var child$113 = completedWork.child; null !== child$113;) newChildLanes |= child$113.lanes | child$113.childLanes, subtreeFlags |= child$113.subtreeFlags & 1206910976, subtreeFlags |= child$113.flags & 1206910976, child$113.return = completedWork, child$113 = child$113.sibling;
else for (child$113 = completedWork.child; null !== child$113;) newChildLanes |= child$113.lanes | child$113.childLanes, subtreeFlags |= child$113.subtreeFlags, subtreeFlags |= child$113.flags, child$113.return = completedWork, child$113 = child$113.sibling;
completedWork.subtreeFlags |= subtreeFlags;
completedWork.childLanes = newChildLanes;
return didBailout;
}
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case 16:
case 15:
case 0:
case 11:
case 7:
case 8:
case 12:
case 9:
case 14: return bubbleProperties(workInProgress), null;
case 1: return bubbleProperties(workInProgress), null;
case 3:
renderLanes = workInProgress.stateNode;
newProps = null;
null !== current && (newProps = current.memoizedState.cache);
workInProgress.memoizedState.cache !== newProps && (workInProgress.flags |= 2048);
popProvider(CacheContext);
popHostContainer();
renderLanes.pendingContext && (renderLanes.context = renderLanes.pendingContext, renderLanes.pendingContext = null);
if (null === current || null === current.child) popHydrationState(workInProgress) ? markUpdate(workInProgress) : null === current || current.memoizedState.isDehydrated && 0 === (workInProgress.flags & 256) || (workInProgress.flags |= 1024, upgradeHydrationErrorsToRecoverable());
bubbleProperties(workInProgress);
return null;
case 26:
var type = workInProgress.type, nextResource = workInProgress.memoizedState;
null === current ? (markUpdate(workInProgress), null !== nextResource ? (bubbleProperties(workInProgress), preloadResourceAndSuspendIfNeeded(workInProgress, nextResource)) : (bubbleProperties(workInProgress), preloadInstanceAndSuspendIfNeeded(workInProgress, type, null, newProps, renderLanes))) : nextResource ? nextResource !== current.memoizedState ? (markUpdate(workInProgress), bubbleProperties(workInProgress), preloadResourceAndSuspendIfNeeded(workInProgress, nextResource)) : (bubbleProperties(workInProgress), workInProgress.flags &= -16777217) : (current = current.memoizedProps, current !== newProps && markUpdate(workInProgress), bubbleProperties(workInProgress), preloadInstanceAndSuspendIfNeeded(workInProgress, type, current, newProps, renderLanes));
return null;
case 27:
popHostContext(workInProgress);
renderLanes = rootInstanceStackCursor.current;
type = workInProgress.type;
if (null !== current && null != workInProgress.stateNode) current.memoizedProps !== newProps && markUpdate(workInProgress);
else {
if (!newProps) {
if (null === workInProgress.stateNode) throw Error(formatProdErrorMessage(166));
bubbleProperties(workInProgress);
workInProgress.subtreeFlags &= -33554433;
return null;
}
current = contextStackCursor.current;
popHydrationState(workInProgress) ? prepareToHydrateHostInstance(workInProgress) : (current = resolveSingletonInstance(type, newProps, renderLanes), workInProgress.stateNode = current, markUpdate(workInProgress));
}
bubbleProperties(workInProgress);
workInProgress.subtreeFlags &= -33554433;
return null;
case 5:
popHostContext(workInProgress);
type = workInProgress.type;
if (null !== current && null != workInProgress.stateNode) current.memoizedProps !== newProps && markUpdate(workInProgress);
else {
if (!newProps) {
if (null === workInProgress.stateNode) throw Error(formatProdErrorMessage(166));
bubbleProperties(workInProgress);
workInProgress.subtreeFlags &= -33554433;
return null;
}
nextResource = contextStackCursor.current;
if (popHydrationState(workInProgress)) prepareToHydrateHostInstance(workInProgress);
else {
var ownerDocument = getOwnerDocumentFromRootContainer(rootInstanceStackCursor.current);
switch (nextResource) {
case 1:
nextResource = ownerDocument.createElementNS("http://www.w3.org/2000/svg", type);
break;
case 2:
nextResource = ownerDocument.createElementNS("http://www.w3.org/1998/Math/MathML", type);
break;
default: switch (type) {
case "svg":
nextResource = ownerDocument.createElementNS("http://www.w3.org/2000/svg", type);
break;
case "math":
nextResource = ownerDocument.createElementNS("http://www.w3.org/1998/Math/MathML", type);
break;
case "script":
nextResource = ownerDocument.createElement("div");
nextResource.innerHTML = "