commit f61200f54e037b3b2e2f1bd135c74f57c87d2b65 Author: liushuang Date: Mon Aug 5 23:45:58 2024 +0800 init diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/.idea/Markdown2Html.iml b/.idea/Markdown2Html.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/Markdown2Html.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e4e4ed1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + 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: + + Copyright (C) + 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 +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d26b93 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +
+ + + +
+

Markdown2Html

+ +## 简介 + +Fork 自 [markdown2html](https://github.com/TaleAi/markdown2html),略有调整。 + +- 支持自定义样式的 Markdown 编辑器 +- 支持微信公众号、知乎和稀土掘金 +- 支持公式 +- 支持 html 转 markdwon +- 支持导出 pdf 和 markdown +- 在线使用: + - https://md.luckday.cn/ \ No newline at end of file diff --git a/config/env.js b/config/env.js new file mode 100644 index 0000000..b0344c5 --- /dev/null +++ b/config/env.js @@ -0,0 +1,93 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js new file mode 100644 index 0000000..8f65114 --- /dev/null +++ b/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js new file mode 100644 index 0000000..07010e3 --- /dev/null +++ b/config/jest/fileTransform.js @@ -0,0 +1,30 @@ +'use strict'; + +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: (props) => ({ + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: null, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/config/paths.js b/config/paths.js new file mode 100644 index 0000000..3003b9c --- /dev/null +++ b/config/paths.js @@ -0,0 +1,84 @@ +"use strict"; + +const path = require("path"); +const fs = require("fs"); +const url = require("url"); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith("/"); + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1); + } else if (!hasSlash && needsSlash) { + return `${inputPath}/`; + } else { + return inputPath; + } +} + +const getPublicUrl = (appPackageJson) => envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right + + + diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..8021471 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,20 @@ +{ + "short_name": "Markdown Nice", + "name": "Markdown Nice", + "icons": [ + { + "src": "https://my-wechat.mdnice.com/mdnice/mdnice%20logo_20191007150129.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "https://my-wechat.mdnice.com/mdnice/mdnice%20logo_20191007150129.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000..06af1ec Binary files /dev/null and b/screenshot.jpg differ diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 0000000..e01b7a5 --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,192 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'production'; +process.env.NODE_ENV = 'production'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const path = require('path'); +const chalk = require('react-dev-utils/chalk'); +const fs = require('fs-extra'); +const webpack = require('webpack'); +const bfj = require('bfj'); +const configFactory = require('../config/webpack.config'); +const paths = require('../config/paths'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); +const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); +const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); +const printBuildError = require('react-dev-utils/printBuildError'); + +const measureFileSizesBeforeBuild = + FileSizeReporter.measureFileSizesBeforeBuild; +const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; +const useYarn = fs.existsSync(paths.yarnLockFile); + +// These sizes are pretty large. We'll warn for bundles exceeding them. +const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; +const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; + +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Process CLI arguments +const argv = process.argv.slice(2); +const writeStatsJson = argv.indexOf('--stats') !== -1; + +// Generate configuration +const config = configFactory('production'); + +// We require that you explicitly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // First, read the current file sizes in build directory. + // This lets us display how much they changed later. + return measureFileSizesBeforeBuild(paths.appBuild); + }) + .then(previousFileSizes => { + // Remove all content but keep the directory so that + // if you're in it, you don't end up in Trash + fs.emptyDirSync(paths.appBuild); + // Merge with the public folder + copyPublicFolder(); + // Start the webpack build + return build(previousFileSizes); + }) + .then( + ({ stats, previousFileSizes, warnings }) => { + if (warnings.length) { + console.log(chalk.yellow('Compiled with warnings.\n')); + console.log(warnings.join('\n\n')); + console.log( + '\nSearch for the ' + + chalk.underline(chalk.yellow('keywords')) + + ' to learn more about each warning.' + ); + console.log( + 'To ignore, add ' + + chalk.cyan('// eslint-disable-next-line') + + ' to the line before.\n' + ); + } else { + console.log(chalk.green('Compiled successfully.\n')); + } + + console.log('File sizes after gzip:\n'); + printFileSizesAfterBuild( + stats, + previousFileSizes, + paths.appBuild, + WARN_AFTER_BUNDLE_GZIP_SIZE, + WARN_AFTER_CHUNK_GZIP_SIZE + ); + console.log(); + + const appPackage = require(paths.appPackageJson); + const publicUrl = paths.publicUrl; + const publicPath = config.output.publicPath; + const buildFolder = path.relative(process.cwd(), paths.appBuild); + printHostingInstructions( + appPackage, + publicUrl, + publicPath, + buildFolder, + useYarn + ); + }, + err => { + console.log(chalk.red('Failed to compile.\n')); + printBuildError(err); + process.exit(1); + } + ) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); + +// Create the production build and print the deployment instructions. +function build(previousFileSizes) { + console.log('Creating an optimized production build...'); + + let compiler = webpack(config); + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + let messages; + if (err) { + if (!err.message) { + return reject(err); + } + messages = formatWebpackMessages({ + errors: [err.message], + warnings: [], + }); + } else { + messages = formatWebpackMessages( + stats.toJson({ all: false, warnings: true, errors: true }) + ); + } + if (messages.errors.length) { + // Only keep the first error. Others are often indicative + // of the same problem, but confuse the reader with noise. + if (messages.errors.length > 1) { + messages.errors.length = 1; + } + return reject(new Error(messages.errors.join('\n\n'))); + } + if ( + process.env.CI && + (typeof process.env.CI !== 'string' || + process.env.CI.toLowerCase() !== 'false') && + messages.warnings.length + ) { + console.log( + chalk.yellow( + '\nTreating warnings as errors because process.env.CI = true.\n' + + 'Most CI servers set it automatically.\n' + ) + ); + return reject(new Error(messages.warnings.join('\n\n'))); + } + + const resolveArgs = { + stats, + previousFileSizes, + warnings: messages.warnings, + }; + if (writeStatsJson) { + return bfj + .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) + .then(() => resolve(resolveArgs)) + .catch(error => reject(new Error(error))); + } + + return resolve(resolveArgs); + }); + }); +} + +function copyPublicFolder() { + fs.copySync(paths.appPublic, paths.appBuild, { + dereference: true, + filter: file => file !== paths.appHtml, + }); +} diff --git a/scripts/start.js b/scripts/start.js new file mode 100644 index 0000000..1dc9805 --- /dev/null +++ b/scripts/start.js @@ -0,0 +1,117 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'development'; +process.env.NODE_ENV = 'development'; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const fs = require('fs'); +const chalk = require('react-dev-utils/chalk'); +const webpack = require('webpack'); +const WebpackDevServer = require('webpack-dev-server'); +const clearConsole = require('react-dev-utils/clearConsole'); +const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); +const { + choosePort, + createCompiler, + prepareProxy, + prepareUrls, +} = require('react-dev-utils/WebpackDevServerUtils'); +const openBrowser = require('react-dev-utils/openBrowser'); +const paths = require('../config/paths'); +const configFactory = require('../config/webpack.config'); +const createDevServerConfig = require('../config/webpackDevServer.config'); + +const useYarn = fs.existsSync(paths.yarnLockFile); +const isInteractive = process.stdout.isTTY; + +// Warn and crash if required files are missing +if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { + process.exit(1); +} + +// Tools like Cloud9 rely on this. +const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 3000; +const HOST = process.env.HOST || '0.0.0.0'; + +if (process.env.HOST) { + console.log( + chalk.cyan( + `Attempting to bind to HOST environment variable: ${chalk.yellow( + chalk.bold(process.env.HOST) + )}` + ) + ); + console.log( + `If this was unintentional, check that you haven't mistakenly set it in your shell.` + ); + console.log( + `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}` + ); + console.log(); +} + +// We require that you explictly set browsers and do not fall back to +// browserslist defaults. +const { checkBrowsers } = require('react-dev-utils/browsersHelper'); +checkBrowsers(paths.appPath, isInteractive) + .then(() => { + // We attempt to use the default port but if it is busy, we offer the user to + // run on a different port. `choosePort()` Promise resolves to the next free port. + return choosePort(HOST, DEFAULT_PORT); + }) + .then(port => { + if (port == null) { + // We have not found a port. + return; + } + const config = configFactory('development'); + const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; + const appName = require(paths.appPackageJson).name; + const urls = prepareUrls(protocol, HOST, port); + // Create a webpack compiler that is configured with custom messages. + const compiler = createCompiler(webpack, config, appName, urls, useYarn); + // Load proxy config + const proxySetting = require(paths.appPackageJson).proxy; + const proxyConfig = prepareProxy(proxySetting, paths.appPublic); + // Serve webpack assets generated by the compiler over a web server. + const serverConfig = createDevServerConfig( + proxyConfig, + urls.lanUrlForConfig + ); + const devServer = new WebpackDevServer(compiler, serverConfig); + // Launch WebpackDevServer. + devServer.listen(port, HOST, err => { + if (err) { + return console.log(err); + } + if (isInteractive) { + clearConsole(); + } + console.log(chalk.cyan('Starting the development server...\n')); + openBrowser(urls.localUrlForBrowser); + }); + + ['SIGINT', 'SIGTERM'].forEach(function(sig) { + process.on(sig, function() { + devServer.close(); + process.exit(); + }); + }); + }) + .catch(err => { + if (err && err.message) { + console.log(err.message); + } + process.exit(1); + }); diff --git a/scripts/test.js b/scripts/test.js new file mode 100644 index 0000000..4172e42 --- /dev/null +++ b/scripts/test.js @@ -0,0 +1,60 @@ +'use strict'; + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.PUBLIC_URL = ''; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('../config/env'); + + +const jest = require('jest'); +const execSync = require('child_process').execSync; +let argv = process.argv.slice(2); + +function isInGitRepository() { + try { + execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +function isInMercurialRepository() { + try { + execSync('hg --cwd . root', { stdio: 'ignore' }); + return true; + } catch (e) { + return false; + } +} + +// Watch unless on CI, in coverage mode, explicitly adding `--no-watch`, +// or explicitly running all tests +if ( + !process.env.CI && + argv.indexOf('--coverage') === -1 && + argv.indexOf('--no-watch') === -1 && + argv.indexOf('--watchAll') === -1 +) { + // https://github.com/facebook/create-react-app/issues/5210 + const hasSourceControl = isInGitRepository() || isInMercurialRepository(); + argv.push(hasSourceControl ? '--watch' : '--watchAll'); +} + +// Jest doesn't have this option so we'll remove it +if (argv.indexOf('--no-watch') !== -1) { + argv = argv.filter(arg => arg !== '--no-watch'); +} + + +jest.run(argv); diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..588245a --- /dev/null +++ b/src/App.css @@ -0,0 +1,210 @@ +.ant-btn-primary { + color: #fff; + background-color: #000; + border-color: #000; +} + +.ant-btn-primary:hover, +.ant-btn-primary:focus { + color: #fff; + background-color: #000; + border-color: #000; +} + +.ant-btn:hover, .ant-btn:focus { + color: #000; + background-color: #fff; + border-color: #000; +} + +.ant-input:hover, .ant-input:focus { + border-color: #000; + border-right-width: 1px !important; + -webkit-box-shadow: 0 0 0 2px rgb(82 82 83 / 20%); + box-shadow: 0 0 0 2px rgb(82 82 83 / 20%); +} + +.ant-select-selection:hover { + border-color: #000; + border-right-width: 1px !important; +} + +.ant-select-focused .ant-select-selection, .ant-select-selection:focus, .ant-select-selection:active, +.ant-input-number:focus, .ant-input-number:active , .ant-input-number:hover { + border-color: #000; + -webkit-box-shadow: 0 0 0 2px rgb(82 82 83 / 20%); + box-shadow: 0 0 0 2px rgb(82 82 83 / 20%); +} + +.ant-select-dropdown-menu-item:hover:not(.ant-select-dropdown-menu-item-disabled) { + background-color: #f1f1f1; +} + +.ant-select-dropdown-menu { + margin: 4px; + padding: 0; +} + +.ant-select-dropdown-menu-item { + border-radius: 4px; + margin: 2px 0; +} + +.ant-menu-item-selected { + color: #000; + /*font-weight: bolder;*/ +} + +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { + background-color: #f6f6f6; +} + +.ant-menu-item { + text-align: center; + margin-right: 20px; +} + +.ant-menu-inline, .ant-menu-vertical, .ant-menu-vertical-left { + margin-right: 20px; +} + +.ant-menu-item:hover, .ant-menu-item-active, .ant-menu:not(.ant-menu-inline) .ant-menu-submenu-open, .ant-menu-submenu-active, .ant-menu-submenu-title:hover { + color: #000; + font-weight: bolder; +} + +.nice-app { + height: 100vh; + width: 100%; + display: flex; + overflow: hidden; + flex-direction: column; + font-family: "PingFang SC", BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serifhtml, body; + font-size: 16px; +} + +.nice-text-container { + display: flex; + height: calc(100vh - 50px); + width: 100%; +} + +.nice-text-container-immersive { + display: flex; + height: 100vh; + width: 100%; +} + +.nice-md-editing-immersive { + padding: 0px; + width: 100%; +} + +.nice-md-editing-immersive .CodeMirror-lines { + padding: 20px; +} + +@media screen and (min-width: 768px) { + .nice-md-editing-immersive .CodeMirror-lines { + padding: 20px 10%; + } +} + +@media screen and (min-width: 1024px) { + .nice-md-editing-immersive .CodeMirror-lines { + padding: 20px 15%; + } +} + +/* 编辑器最多会被分成三份width:33.3%,当两份时根据flex-grow:1伸展 */ +.nice-md-editing, +.nice-style-editing { + position: relative; + width: 33.3%; + height: 88%; + flex-grow: 1; + word-wrap: break-word; + z-index: 1; +} + +.nice-marked-text { + display: flex; + justify-content: center; + width: 33.3%; + flex-grow: 1; + padding: 20px; + margin-bottom: 70px; + word-wrap: break-word; + position: relative; +} + +.nice-marked-text-pc { + padding: 0; +} + +.nice-wx-box { + overflow-y: auto; + padding: 25px 20px; + height: 98%; + width: 375px; + box-shadow: 0 0 60px rgba(0, 0, 0, 0.1); +} + +.nice-wx-box-pc { + width: 100%; + padding: 20px 35px 20px 20px; + box-shadow: none; +} + +.nice-style-editing-hide { + display: none; +} + +.nice-md-editing-hide { + display: none; +} + +.nice-marked-text-hide { + display: none; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + border-radius: 3px; + background: rgba(0, 0, 0, 0.06); + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.08); +} +::-webkit-scrollbar-thumb { + border-radius: 3px; + background: rgba(0, 0, 0, 0.12); + box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2); +} + +@media print { + .nice-md-editing { + display: none; + } + .nice-navbar { + display: none; + } + .nice-sidebar { + display: none; + } + .nice-wx-box { + overflow: visible; + box-shadow: none; + width: 100%; + } + .nice-style-editing { + display: none; + } + #nice-rich-text { + padding: 0 !important; + } + .nice-footer-container { + display: none; + } +} diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..4aa76a6 --- /dev/null +++ b/src/App.js @@ -0,0 +1,410 @@ +import React, {Component} from "react"; +import CodeMirror from "@uiw/react-codemirror"; +import "codemirror/addon/search/searchcursor"; +import "codemirror/keymap/sublime"; +import "antd/dist/antd.css"; +import {observer, inject} from "mobx-react"; +import classnames from "classnames"; +import throttle from "lodash.throttle"; + +import Dialog from "./layout/Dialog"; +import Navbar from "./layout/Navbar"; +import Toobar from "./layout/Toolbar"; +import Footer from "./layout/Footer"; +import Sidebar from "./layout/Sidebar"; +import StyleEditor from "./layout/StyleEditor"; +import EditorMenu from "./layout/EditorMenu"; +import SearchBox from "./component/SearchBox"; + +import "./App.css"; +import "./utils/mdMirror.css"; + +import { + LAYOUT_ID, + BOX_ID, + IMAGE_HOSTING_NAMES, + IMAGE_HOSTING_TYPE, + MJX_DATA_FORMULA, + MJX_DATA_FORMULA_TYPE, +} from "./utils/constant"; +import {markdownParser, markdownParserWechat, updateMathjax} from "./utils/helper"; +import pluginCenter from "./utils/pluginCenter"; +import appContext from "./utils/appContext"; +import {uploadAdaptor} from "./utils/imageHosting"; +import bindHotkeys, {betterTab, rightClick} from "./utils/hotkey"; + +@inject("content") +@inject("navbar") +@inject("footer") +@inject("view") +@inject("dialog") +@inject("imageHosting") +@observer +class App extends Component { + constructor(props) { + super(props); + this.scale = 1; + this.handleUpdateMathjax = throttle(updateMathjax, 1500); + this.state = { + focus: false, + }; + } + + componentDidMount() { + document.addEventListener("fullscreenchange", this.solveScreenChange); + document.addEventListener("webkitfullscreenchange", this.solveScreenChange); + document.addEventListener("mozfullscreenchange", this.solveScreenChange); + document.addEventListener("MSFullscreenChange", this.solveScreenChange); + try { + window.MathJax = { + tex: { + inlineMath: [["$", "$"]], + displayMath: [["$$", "$$"]], + tags: "ams", + }, + svg: { + fontCache: "none", + }, + options: { + renderActions: { + addMenu: [0, "", ""], + addContainer: [ + 190, + (doc) => { + for (const math of doc.math) { + this.addContainer(math, doc); + } + }, + this.addContainer, + ], + }, + }, + }; + // eslint-disable-next-line + require("mathjax/es5/tex-svg-full"); + pluginCenter.mathjax = true; + } catch (e) { + console.log(e); + } + this.setEditorContent(); + this.setCustomImageHosting(); + } + + componentDidUpdate() { + if (pluginCenter.mathjax) { + this.handleUpdateMathjax(); + } + } + + componentWillUnmount() { + document.removeEventListener("fullscreenchange", this.solveScreenChange); + document.removeEventListener("webkitfullscreenchange", this.solveScreenChange); + document.removeEventListener("mozfullscreenchange", this.solveScreenChange); + document.removeEventListener("MSFullscreenChange", this.solveScreenChange); + } + + setCustomImageHosting = () => { + if (this.props.useImageHosting === undefined) { + return; + } + const {url, name, isSmmsOpen, isQiniuyunOpen, isAliyunOpen, isGiteeOpen, isGitHubOpen} = this.props.useImageHosting; + if (name) { + this.props.imageHosting.setHostingUrl(url); + this.props.imageHosting.setHostingName(name); + this.props.imageHosting.addImageHosting(name); + } + if (isSmmsOpen) { + this.props.imageHosting.addImageHosting(IMAGE_HOSTING_NAMES.smms); + } + if (isAliyunOpen) { + this.props.imageHosting.addImageHosting(IMAGE_HOSTING_NAMES.aliyun); + } + if (isQiniuyunOpen) { + this.props.imageHosting.addImageHosting(IMAGE_HOSTING_NAMES.qiniuyun); + } + if (isGiteeOpen) { + this.props.imageHosting.addImageHosting(IMAGE_HOSTING_NAMES.gitee); + } + if (isGitHubOpen) { + this.props.imageHosting.addImageHosting(IMAGE_HOSTING_NAMES.github); + } + + // 第一次进入没有默认图床时 + if (window.localStorage.getItem(IMAGE_HOSTING_TYPE) === null) { + let type; + if (name) { + type = name; + } else if (isSmmsOpen) { + type = IMAGE_HOSTING_NAMES.smms; + } else if (isAliyunOpen) { + type = IMAGE_HOSTING_NAMES.aliyun; + } else if (isQiniuyunOpen) { + type = IMAGE_HOSTING_NAMES.qiniuyun; + } else if (isGiteeOpen) { + type = IMAGE_HOSTING_NAMES.isGitee; + } + this.props.imageHosting.setType(type); + window.localStorage.setItem(IMAGE_HOSTING_TYPE, type); + } + }; + + setEditorContent = () => { + const {defaultText} = this.props; + if (defaultText) { + this.props.content.setContent(defaultText); + } + }; + + setCurrentIndex(index) { + this.index = index; + } + + solveScreenChange = () => { + const {isImmersiveEditing} = this.props.view; + this.props.view.setImmersiveEditing(!isImmersiveEditing); + }; + + getInstance = (instance) => { + instance.editor.on("inputRead", function(cm, event) { + if (event.origin === "paste") { + var text = event.text[0]; // pasted string + var new_text = ""; // any operations here + cm.refresh(); + const {length} = cm.getSelections(); + // my first idea was + // note: for multiline strings may need more complex calculations + cm.replaceRange(new_text, event.from, {line: event.from.line, ch: event.from.ch + text.length}); + // first solution did'nt work (before i guess to call refresh) so i tried that way, works too + if (length === 1) { + cm.execCommand("undo"); + } + // cm.setCursor(event.from); + cm.replaceSelection(new_text); + } + }); + if (instance) { + this.props.content.setMarkdownEditor(instance.editor); + } + }; + + handleScroll = () => { + if (this.props.navbar.isSyncScroll) { + const {markdownEditor} = this.props.content; + const cmData = markdownEditor.getScrollInfo(); + const editorToTop = cmData.top; + const editorScrollHeight = cmData.height - cmData.clientHeight; + this.scale = (this.previewWrap.offsetHeight - this.previewContainer.offsetHeight + 55) / editorScrollHeight; + if (this.index === 1) { + this.previewContainer.scrollTop = editorToTop * this.scale; + } else { + this.editorTop = this.previewContainer.scrollTop / this.scale; + markdownEditor.scrollTo(null, this.editorTop); + } + } + }; + + handleChange = (editor) => { + if (this.state.focus) { + const content = editor.getValue(); + this.props.content.setContent(content); + this.props.onTextChange && this.props.onTextChange(content); + } + }; + + handleFocus = (editor) => { + this.setState({ + focus: true, + }); + this.props.onTextFocus && this.props.onTextFocus(editor.getValue()); + }; + + handleBlur = (editor) => { + this.setState({ + focus: false, + }); + this.props.onTextBlur && this.props.onTextBlur(editor.getValue()); + }; + + getStyleInstance = (instance) => { + if (instance) { + this.styleEditor = instance.editor; + this.styleEditor.on("keyup", (cm, e) => { + if ((e.keyCode >= 65 && e.keyCode <= 90) || e.keyCode === 189) { + cm.showHint(e); + } + }); + } + }; + + handleDrop = (instance, e) => { + // e.preventDefault(); + // console.log(e.dataTransfer.files[0]); + if (!(e.dataTransfer && e.dataTransfer.files)) { + return; + } + for (let i = 0; i < e.dataTransfer.files.length; i++) { + // console.log(e.dataTransfer.files[i]); + uploadAdaptor({file: e.dataTransfer.files[i], content: this.props.content}); + } + }; + + handlePaste = (instance, e) => { + const cbData = e.clipboardData; + + const insertPasteContent = (cm, content) => { + const {length} = cm.getSelections(); + cm.replaceSelections(Array(length).fill(content)); + this.setState( + { + focus: true, + }, + () => { + this.handleChange(cm); + }, + ); + }; + + if (e.clipboardData && e.clipboardData.files) { + for (let i = 0; i < e.clipboardData.files.length; i++) { + uploadAdaptor({file: e.clipboardData.files[i], content: this.props.content}); + } + } + + if (cbData) { + const html = cbData.getData("text/html"); + const text = cbData.getData("TEXT"); + insertPasteContent(instance, text); + console.log(html); + + if (html) { + console.log("htmsdkskdkskdk"); + this.props.footer.setPasteHtmlChecked(true); + this.props.footer.setPasteHtml(html); + this.props.footer.setPasteText(text); + } else { + this.props.footer.setPasteHtmlChecked(false); + } + } + }; + + addContainer(math, doc) { + const tag = "span"; + const spanClass = math.display ? "span-block-equation" : "span-inline-equation"; + const cls = math.display ? "block-equation" : "inline-equation"; + math.typesetRoot.className = cls; + math.typesetRoot.setAttribute(MJX_DATA_FORMULA, math.math); + math.typesetRoot.setAttribute(MJX_DATA_FORMULA_TYPE, cls); + math.typesetRoot = doc.adaptor.node(tag, {class: spanClass, style: "cursor:pointer"}, [math.typesetRoot]); + } + + render() { + const {codeNum, previewType} = this.props.navbar; + const {isEditAreaOpen, isPreviewAreaOpen, isStyleEditorOpen, isImmersiveEditing} = this.props.view; + const {isSearchOpen} = this.props.dialog; + + const parseHtml = + codeNum === 0 + ? markdownParserWechat.render(this.props.content.content) + : markdownParser.render(this.props.content.content); + + const mdEditingClass = classnames({ + "nice-md-editing": !isImmersiveEditing, + "nice-md-editing-immersive": isImmersiveEditing, + "nice-md-editing-hide": !isEditAreaOpen, + }); + + const styleEditingClass = classnames({ + "nice-style-editing": true, + "nice-style-editing-hide": isImmersiveEditing, + }); + + const richTextClass = classnames({ + "nice-marked-text": true, + "nice-marked-text-pc": previewType === "pc", + "nice-marked-text-hide": isImmersiveEditing || !isPreviewAreaOpen, + }); + + const richTextBoxClass = classnames({ + "nice-wx-box": true, + "nice-wx-box-pc": previewType === "pc", + }); + + const textContainerClass = classnames({ + "nice-text-container": !isImmersiveEditing, + "nice-text-container-immersive": isImmersiveEditing, + }); + + return ( + + {({defaultTitle, onStyleChange, onStyleBlur, onStyleFocus, token}) => ( +
+ + +
+
this.setCurrentIndex(1, e)}> + {isSearchOpen && } + +
+
this.setCurrentIndex(2, e)}> + +
{ + this.previewContainer = node; + }} + > +
{ + this.previewWrap = node; + }} + /> +
+
+ + {isStyleEditorOpen && ( +
+ +
+ )} + + + +
+
+
+ )} +
+ ); + } +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000..a754b20 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/src/Lib.js b/src/Lib.js new file mode 100644 index 0000000..ea65612 --- /dev/null +++ b/src/Lib.js @@ -0,0 +1,155 @@ +import React, {Component} from "react"; +import PropTypes from "prop-types"; +import {Result} from "antd"; +import {Provider} from "mobx-react"; + +import "./index.css"; + +import App from "./App"; + +import content from "./store/content"; +import userInfo from "./store/userInfo"; +import navbar from "./store/navbar"; +import footer from "./store/footer"; +import dialog from "./store/dialog"; +import imageHosting from "./store/imageHosting"; +import view from "./store/view"; + +import {isPC} from "./utils/helper"; +import appContext from "./utils/appContext"; +import SvgIcon from "./icon"; +import {solveWeChatMath, solveZhihuMath, solveHtml} from "./utils/converter"; +import {LAYOUT_ID} from "./utils/constant"; + +class Lib extends Component { + getWeChatHtml() { + const layout = document.getElementById(LAYOUT_ID); // 保护现场 + const html = layout.innerHTML; + solveWeChatMath(); + const res = solveHtml(); + layout.innerHTML = html; // 恢复现场 + return res; + } + + getZhihuHtml() { + const layout = document.getElementById(LAYOUT_ID); // 保护现场 + const html = layout.innerHTML; + solveZhihuMath(); + const res = solveHtml(); + layout.innerHTML = html; // 恢复现场 + return res; + } + + render() { + const { + defaultTitle, + defaultText, + onTextChange, + onTextBlur, + onTextFocus, + onStyleChange, + onStyleBlur, + onStyleFocus, + token, + useImageHosting, + } = this.props; + const appCtx = { + defaultTitle, + defaultText, + onTextChange, + onTextBlur, + onTextFocus, + onStyleChange, + onStyleBlur, + onStyleFocus, + token, + useImageHosting, + }; + return ( + + {isPC() ? ( + + + + ) : ( + } + title="请使用 PC 端打开排版工具" + subTitle="更多 Markdown Nice 信息,请扫码关注公众号「编程如画」" + extra={} + /> + )} + + ); + } +} + +const style = { + svgIcon: { + width: "72px", + height: "72px", + }, +}; + +Lib.defaultProps = { + defaultTitle: "", + defaultText: "", + onTextChange: () => {}, + onTextBlur: () => {}, + onTextFocus: () => {}, + onStyleChange: () => {}, + onStyleBlur: () => {}, + onStyleFocus: () => {}, + token: "", + // eslint-disable-next-line react/default-props-match-prop-types + useImageHosting: { + url: "", + name: "", + isSmmsOpen: true, + isQiniuyunOpen: true, + isAliyunOpen: true, + isGiteeOpen: true, + isGitHubOpen: true, + }, +}; +Lib.propTypes = { + defaultTitle: PropTypes.string, + defaultText: PropTypes.string, + onTextChange: PropTypes.func, + onTextBlur: PropTypes.func, + onTextFocus: PropTypes.func, + onStyleChange: PropTypes.func, + onStyleBlur: PropTypes.func, + onStyleFocus: PropTypes.func, + token: PropTypes.string, + // eslint-disable-next-line react/require-default-props + useImageHosting: PropTypes.shape({ + url: PropTypes.string, + name: PropTypes.string, + isSmmsOpen: PropTypes.bool, + isQiniuyunOpen: PropTypes.bool, + isAliyunOpen: PropTypes.bool, + isGiteeOpen: PropTypes.bool, + isGitHubOpen: PropTypes.bool, + }), +}; + +export default Lib; diff --git a/src/component/Dialog/AboutDialog.js b/src/component/Dialog/AboutDialog.js new file mode 100644 index 0000000..b6c6beb --- /dev/null +++ b/src/component/Dialog/AboutDialog.js @@ -0,0 +1,100 @@ +import React, {Component} from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, Button} from "antd"; + +@inject("dialog") +@observer +class AboutDialog extends Component { + handleOk = () => { + this.props.dialog.setAboutOpen(false); + }; + + handleCancel = () => { + this.props.dialog.setAboutOpen(false); + }; + + handleVersion = () => { + this.props.dialog.setAboutOpen(false); + this.props.dialog.setVersionOpen(true); + }; + + render() { + return ( + + 确认 + , + ]} + bodyStyle={{ + paddingTop: "5px", + }} + > +

+ Markdown2Html + + + +

+ +

支持自定义样式的 Markdown 编辑器;

+

支持微信公众号、知乎和稀土掘金;

+

+ 如果你喜欢我们的工具,欢迎关注 + +  GitHub  + +

+
+ ); + } +} + +const style = { + leftImgWidth: { + width: "40%", + height: "100%", + }, + rightImgWidth: { + width: "60%", + height: "100%", + }, + headerMargin: { + marginTop: "5px", + marginBottom: "5px", + color: "black", + }, + lineHeight: { + lineHeight: "26px", + color: "black", + padding: 0, + margin: 0, + }, + img: { + width: "70px", + marginLeft: "10px", + display: "inline-block", + }, + noBorder: { + border: "none", + }, +}; + +export default AboutDialog; diff --git a/src/component/Dialog/FormDialog.js b/src/component/Dialog/FormDialog.js new file mode 100644 index 0000000..095130d --- /dev/null +++ b/src/component/Dialog/FormDialog.js @@ -0,0 +1,102 @@ +import React from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, InputNumber, Form} from "antd"; + +@inject("dialog") +@inject("content") +@observer +class FormDialog extends React.Component { + constructor(props) { + super(props); + this.state = { + ...initialState, + }; + } + + buildRow = (rowNum, columnNum) => { + let appendText = "|"; + if (rowNum === 1) { + appendText += " --- |"; + for (let i = 0; i < columnNum - 1; i++) { + appendText += " --- |"; + } + } else { + appendText += " |"; + for (let i = 0; i < columnNum - 1; i++) { + appendText += " |"; + } + } + return appendText + (/windows|win32/i.test(navigator.userAgent) ? "\r\n" : "\n"); + }; + + buildFormFormat = (rowNum, columnNum) => { + let formFormat = ""; + for (let i = 0; i < 3; i++) { + formFormat += this.buildRow(i, columnNum); + } + for (let i = 3; i <= rowNum; i++) { + formFormat += this.buildRow(i, columnNum); + } + return formFormat; + }; + + handleOk = () => { + const {markdownEditor} = this.props.content; + const cursor = markdownEditor.getCursor(); + + const text = this.buildFormFormat(this.state.rowNum, this.state.columnNum); + markdownEditor.replaceSelection(text, cursor); + + const content = markdownEditor.getValue(); + this.props.content.setContent(content); + + this.handleCancel(); + cursor.ch += 2; + markdownEditor.setCursor(cursor); + markdownEditor.focus(); + }; + + handleCancel = () => { + this.setState(initialState); + this.props.dialog.setFormOpen(false); + }; + + render() { + return ( + + + this.setState({rowNum: value})} + /> + + + this.setState({columnNum: value})} + /> + + + ); + } +} + +const initialState = { + columnNum: 1, + rowNum: 2, +}; + +export default FormDialog; diff --git a/src/component/Dialog/HistoryDialog.js b/src/component/Dialog/HistoryDialog.js new file mode 100644 index 0000000..fd6c527 --- /dev/null +++ b/src/component/Dialog/HistoryDialog.js @@ -0,0 +1,146 @@ +import React, {Component} from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, Empty, message} from "antd"; +import LocalHistory from "../LocalHistory"; +import {AutoSaveInterval, getLocalDocuments, setLocalDocuments, setLocalDraft} from "../LocalHistory/util"; +import IndexDB from "../LocalHistory/indexdb"; +import debouce from "lodash.debounce"; + +const DocumentID = 1; + +@inject("dialog") +@inject("content") +@observer +class HistoryDialog extends Component { + timer = null; + + db = null; + + constructor(props) { + super(props); + this.state = { + documents: [], + }; + } + + async componentDidMount() { + await this.initIndexDB(); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + get editor() { + return this.props.content.markdownEditor; + } + + // + // async UNSAFE_componentWillReceiveProps(nextProps) { + // // 文档 id 变更 + // if (this.props.documentID !== nextProps.documentID && nextProps.documentID != null) { + // if (this.db) { + // await this.overrideLocalDocuments(nextProps.documentID); + // } + // } + // } + // + + closeDialog = () => { + this.props.dialog.setHistoryOpen(false); + }; + + editLocalDocument = (content) => { + this.props.content.setContent(content); + message.success("恢复成功!"); + this.closeDialog(); + }; + + autoSave = async (isRecent = false) => { + const Content = this.props.content.markdownEditor.getValue(); + if (Content.trim() !== "") { + const document = { + Content, + DocumentID: this.props.documentID, + SaveTime: new Date(), + }; + const setLocalDocumentMethod = isRecent && this.state.documents.length > 0 ? setLocalDraft : setLocalDocuments; + await setLocalDocumentMethod(this.db, this.state.documents, document); + await this.overrideLocalDocuments(this.props.documentID); + } + }; + + async initIndexDB() { + try { + const indexDB = new IndexDB({ + name: "mdnice-local-history", + storeName: "customers", + storeOptions: {keyPath: "id", autoIncrement: true}, + storeInit: (objectStore) => { + objectStore.createIndex("DocumentID", "DocumentID", {unique: false}); + objectStore.createIndex("SaveTime", "SaveTime", {unique: false}); + }, + }); + this.db = await indexDB.init(); + + if (this.db && this.props.documentID) { + await this.overrideLocalDocuments(this.props.documentID); + } + // 每隔一段时间自动保存 + this.timer = setInterval(async () => { + await this.autoSave(); + }, AutoSaveInterval); + // 每改变内容自动保存最近的一条 + this.editor.on && + this.editor.on( + "change", + debouce(async () => { + await this.autoSave(true); + }, 1000), + ); + } catch (e) { + console.error(e); + } + } + + // 刷新本地历史文档 + async overrideLocalDocuments(documentID) { + const localDocuments = await getLocalDocuments(this.db, +documentID); + // console.log('refresh local',localDocuments); + this.setState({ + documents: localDocuments, + }); + } + + render() { + return ( + + {this.state.documents && this.state.documents.length > 0 ? ( + + ) : ( + + )} + + ); + } +} + +HistoryDialog.defaultProps = { + documentID: DocumentID, +}; + +export default HistoryDialog; diff --git a/src/component/Dialog/ImageDialog.js b/src/component/Dialog/ImageDialog.js new file mode 100644 index 0000000..0ada692 --- /dev/null +++ b/src/component/Dialog/ImageDialog.js @@ -0,0 +1,180 @@ +import React, {Component} from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, Upload, Tabs, Select} from "antd"; + +import SvgIcon from "../../icon"; + +import AliOSS from "../ImageHosting/AliOSS"; +import QiniuOSS from "../ImageHosting/QiniuOSS"; +import Gitee from "../ImageHosting/Gitee"; +import GitHub from "../ImageHosting/GitHub"; + +import {uploadAdaptor} from "../../utils/imageHosting"; +import {SM_MS_PROXY, IMAGE_HOSTING_TYPE, IMAGE_HOSTING_NAMES} from "../../utils/constant"; +import appContext from "../../utils/appContext"; + +const {Dragger} = Upload; +const {TabPane} = Tabs; +const {Option} = Select; + +@inject("dialog") +@inject("content") +@inject("imageHosting") +@inject("navbar") +@observer +class ImageDialog extends Component { + constructor(props) { + super(props); + this.images = []; + } + + // 确认后将内容更新到编辑器上 + handleOk = () => { + let text = ""; + // 成功后添加url + if (this.props.navbar.isContainImgName) { + this.images.forEach((value) => { + text += `![${value.filename}](${value.url})\n`; + }); + } else { + this.images.forEach((value) => { + text += `![](${value.url})\n`; + }); + } + // 重新初始化 + this.images = []; + const {markdownEditor} = this.props.content; + const cursor = markdownEditor.getCursor(); + markdownEditor.replaceSelection(text, cursor); + // 上传后实时更新内容 + const content = markdownEditor.getValue(); + this.props.content.setContent(content); + + this.props.dialog.setImageOpen(false); + cursor.ch += 2; + markdownEditor.setCursor(cursor); + markdownEditor.focus(); + }; + + handleCancel = () => { + this.props.dialog.setImageOpen(false); + }; + + customRequest = ({action, data, file, headers, onError, onProgress, onSuccess, withCredentials}) => { + const formData = new FormData(); + const {images} = this; + if (data) { + Object.keys(data).forEach((key) => { + formData.append(key, data[key]); + }); + } + // 使用阿里云图床 + if (this.props.imageHosting.type === "阿里云") { + uploadAdaptor({file, onSuccess, onError, images}); + } + // 使用七牛云图床 + else if (this.props.imageHosting.type === "七牛云") { + uploadAdaptor({file, onSuccess, onError, onProgress, images}); + } + // 使用SM.MS图床 + else if (this.props.imageHosting.type === "SM.MS") { + uploadAdaptor({formData, file, action, onProgress, onSuccess, onError, headers, withCredentials}); + } + // 使用Gitee图床 + else if (this.props.imageHosting.type === "Gitee") { + uploadAdaptor({formData, file, action, onProgress, onSuccess, onError, headers, withCredentials, images}); + } + // 使用GitHub图床 + else if (this.props.imageHosting.type === "GitHub") { + uploadAdaptor({formData, file, action, onProgress, onSuccess, onError, headers, withCredentials, images}); + } + // 使用用户提供的图床或是默认mdnice图床 + else { + uploadAdaptor({formData, file, onSuccess, onError, images}); + } + + return { + abort() { + console.log("upload progress is aborted."); + }, + }; + }; + + typeChange = (type) => { + this.props.imageHosting.setType(type); + window.localStorage.setItem(IMAGE_HOSTING_TYPE, type); + }; + + render() { + const {hostingList, type} = this.props.imageHosting; + + const columns = hostingList.map((option, index) => ( + + )); + + const imageHostingSwitch = ( + + ); + + return ( + + + {({useImageHosting}) => ( + + + +

+ +

+

点击或拖拽一张或多张照片上传

+

{"正在使用" + type + "图床"}

+
+
+ {useImageHosting.isAliyunOpen ? ( + + + + ) : null} + {useImageHosting.isQiniuyunOpen ? ( + + + + ) : null} + {useImageHosting.isGiteeOpen ? ( + + + + ) : null} + {useImageHosting.isGitHubOpen ? ( + + + + ) : null} +
+ )} +
+
+ ); + } +} + +const style = { + svgIcon: { + width: "48px", + height: "48px", + }, +}; + +export default ImageDialog; diff --git a/src/component/Dialog/LinkDialog.js b/src/component/Dialog/LinkDialog.js new file mode 100644 index 0000000..afca47b --- /dev/null +++ b/src/component/Dialog/LinkDialog.js @@ -0,0 +1,61 @@ +import React, {Component} from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, Input, Form} from "antd"; + +@inject("dialog") +@inject("content") +@observer +class LinkDialog extends Component { + constructor(props) { + super(props); + this.state = { + link: "", + }; + } + + handleOk = () => { + const {markdownEditor} = this.props.content; + const cursor = markdownEditor.getCursor(); + const selection = markdownEditor.getSelection(); + const text = `[${selection}](${this.state.link})`; + markdownEditor.replaceSelection(text, cursor); + + // 上传后实时更新内容 + const content = markdownEditor.getValue(); + this.props.content.setContent(content); + + this.setState({link: ""}); + this.props.dialog.setLinkOpen(false); + cursor.ch += 1; + markdownEditor.setCursor(cursor); + markdownEditor.focus(); + }; + + handleCancel = () => { + this.setState({link: ""}); + this.props.dialog.setLinkOpen(false); + }; + + handleChange = (e) => { + this.setState({link: e.target.value}); + }; + + render() { + return ( + + + + + + ); + } +} + +export default LinkDialog; diff --git a/src/component/Dialog/SitDownDialog.js b/src/component/Dialog/SitDownDialog.js new file mode 100644 index 0000000..a08985c --- /dev/null +++ b/src/component/Dialog/SitDownDialog.js @@ -0,0 +1,106 @@ +import React, {Component} from "react"; +import {observer, inject} from "mobx-react"; +import {Modal, Input, Select, message} from "antd"; + +import SitDownConverter from "../../utils/sitdownConverter"; +import {SITDOWN_OPTIONS} from "../../utils/constant"; + +const {Option} = Select; +const {TextArea} = Input; + +@inject("dialog") +@inject("content") +@observer +class SitDownDialog extends Component { + constructor(props) { + super(props); + this.state = { + platform: "default", + sourceCode: "", + }; + } + + handleOk = () => { + try { + const {platform, sourceCode} = this.state; + + const domParser = new DOMParser(); + const sourceCodeDom = domParser.parseFromString(sourceCode, "text/html"); + + let content = ""; + + if (platform === "csdn") { + const articleDom = sourceCodeDom.getElementById("content_views"); + content = SitDownConverter.CSDN(articleDom); + } else if (platform === "juejin") { + const articleDom = sourceCodeDom.getElementsByClassName("article-content"); + content = SitDownConverter.Juejin(articleDom[0]); + } else if (platform === "zhihu") { + const articleDom = sourceCodeDom.getElementsByClassName("Post-RichText"); + content = SitDownConverter.Zhihu(articleDom[0]); + } else if (platform === "wechat") { + const articleDom = sourceCodeDom.getElementById("js_content"); + content = SitDownConverter.Wechat(articleDom); + } else { + content = SitDownConverter.GFM(sourceCodeDom); + } + + this.props.content.setContent(content); + + this.props.dialog.setSitDownOpen(false); + + const {markdownEditor} = this.props.content; + // const cursor = markdownEditor.getCursor(); + // cursor.ch += 1; + // markdownEditor.setCursor(cursor); + markdownEditor.focus(); + } catch (e) { + message.error("源代码与已选平台的文章域名不符"); + } + }; + + handleCancel = () => { + this.props.dialog.setSitDownOpen(false); + }; + + handlePlatform = (value) => { + this.setState({platform: value}); + }; + + handleSourceCode = (e) => { + this.setState({sourceCode: e.target.value}); + }; + + render() { + const {sourceCode, platform} = this.state; + return ( + + + +