[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: 🐛 提交 Bug\ndescription: 请提交你的 Bug\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        谢谢帮忙提交 Bug 这会让简悦变得更好。\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: 描述这个错误\n      description: 请尽量详细描述你的错误\n    validations:\n      required: true\n  - type: textarea\n    id: repro-steps\n    attributes:\n      label: 复现步骤\n      description: 请尽量用清晰的步骤（哪怕比较多）的描述你的问题，最好可以的话，请截图或者视频说明。\n      placeholder: |\n        1. 步骤1\n        2. 步骤2\n        3. 步骤3\n    validations:\n      required: true\n  - type: textarea\n    id: others\n    attributes:\n      label: 其他相关说明\n      description: 如果有需要，可以在这里继续说明\n    validations:\n      required: false\n  - type: input\n    id: os\n    attributes:\n      label: 操作系统\n      placeholder: macOS\n    validations:\n      required: true\n  - type: input\n    id: browser\n    attributes:\n      label: 浏览器\n      placeholder: 请说明浏览器以及尽量说明浏览器版本\n    validations:\n      required: true\n  - type: input\n    id: website\n    attributes:\n      label: 发生问题的网址\n      placeholder: 如果任意网站均可的话，请随便留下一个你遇到问题的地址\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 🆘 当升级 Chrome 后无法使用简悦的解决方案\n    url: https://www.yuque.com/kenshin/simpread/fxszlhpp1iht441s\n    about: |\n      当 Chrome 应用商店提示【此扩展程序未遵循 Chrome 扩展程序的最佳实践，因此已无法再使用】的解决方案\n  - name: 📮 订阅中心\n    url: https://simpread.pro/subscribe\n    about: |\n      我每周都会推 3 ~ 4 篇关于简悦的新功能、联动等内容，建议简悦用户（尤其是经常使用简悦高级功能的用户）订阅【发布汇总】。\n  - name: 📚 简悦手册\n    url: https://www.yuque.com/kenshin/simpread/\n    about: |\n      简悦的阅读模式相关功能是不需要设置的。\n      如果你使用了更高级的功能，如：同步助手，或者你是 Obsidian / Logseq 等双链笔记用户，或者你本地快照等功能的话，则需要配置。\n      简悦官方已经准备好了相关的教程。\n  - name: 📦 配置库（强烈建议双链笔记用户使用此方式）\n    url: https://www.yuque.com/kenshin/simpread/ds8zk0\n    about: |\n      这是简悦官方推出的免配置包方案，包括：Notion / Obsidian / Logseq 等用法。\n  - name: 📗 Notion 配置库\n    url: https://www.yuque.com/kenshin/simpread/mwda8s\n    about: |\n      通过简悦导入到 Notion 的内容包括：Notion 图床、题图、Favicon、标签等诸多功能。\n  - name: 📘 Obsidian 配置库\n    url: https://www.yuque.com/kenshin/simpread/psugef\n    about: |\n      简悦与 Obsidian 的联动可以将简悦的标注系统内置到 Obsidian 中。\n  - name: 🖥 如何配置同步助手\n    url: https://www.yuque.com/kenshin/simpread/pwpnsx\n    about: |\n      同步助手相当于一个基于用户系统的服务器，可以实现诸多高级功能（如果你只是阅读模式用户，不需要这些）\n  - name: 💾 如何配置本地快照系统\n    url: https://www.yuque.com/kenshin/simpread/wkswh7\n    about: |\n      简悦最大的优势在于：用户拥有全部的数据所有权，只需要简单的几步就能实现一个基于本地的快照系统。\n  - name: 📓 一站式教程\n    url: https://www.yuque.com/kenshin/simpread/pn4bbg\n    about: |\n      如果不需要配置库方案的话，也可以直接查看 Notion / Obsidian / Logseq 的一站式教程，适合喜欢更灵活方案的用户。\n  - name: 👤 高级账户相关功能\n    url: https://www.yuque.com/kenshin/simpread/xs0gp0\n    about: |\n      这是简悦的高级账户相关操作，基本上你出现的问题大部分都可以通过此方式解决。\n  - name: 🔒 首次绑定高级账户仍出现带🔒的问题\n    url: https://www.yuque.com/kenshin/simpread/xs0gp0\n    about: |\n      首次升级为高级账户后，请重启浏览器，如果没有重启的话，请使用此链接解决。\n  - name: 1️⃣ 如何找回丢失的 UID （高级账户资格）\n    url: https://www.yuque.com/kenshin/simpread/bvcunx\n    about: |\n      如果不小心丢失了 UID 请通过此教程找回。\n  - name: 2️⃣ 找回 UID 后如何恢复高级账户资格\n    url: https://www.yuque.com/kenshin/simpread/pkq3d1\n    about: |\n      找回 UID 后可使用此教程恢复你的高级账户资格。\n  - name: 3️⃣ 在哪里可以重新找到 License（兑换码）\n    url: https://www.yuque.com/kenshin/simpread/hgvw56\n    about: |\n      找回 UID 前需要知道你的 License，一般来说当升级高级账户后会自动下载含有高级账户 UID 与 License 到本地。\n  - name: 📗 Notion 相关问题\n    url: https://www.yuque.com/kenshin/simpread/wwk65u\n    about: |\n      如：无法授权到 Notion，使用导入到 Notion 辅助增强插件失败等常规性问题。\n  - name: 🆘 无法打开简悦的选项页或稍后读\n    url: https://www.yuque.com/kenshin/simpread/lwuterg3u83sq0b2\n    about: |\n      如：一般来说只有使用了同步助手后才会遇到此类问题，可通过此链接解决。\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: ✨ 功能建议或改善\ndescription: 你的建议会让简悦变得更好\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        你的每个想法都是对简悦最大的支持，简悦 60% 的功能都来自用户的简悦。\n  - type: textarea\n    id: feature\n    attributes:\n      label: 请描述你的建议或任何内容\n      description: 如有可能请详细说明它\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: 补充说明\n      description: 如有可能请说明下这个建议具体使用场景（或如果有这个功能后，会改善你的什么流程？\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/new_site.yml",
    "content": "name: 🔗 新站适配\ndescription: 提交需要适配的站点\nlabels: [\"new site\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        大部分网页都可以正常使用简悦的阅读模式，简悦官方收录的新站具有大众类型的网站。\n  - type: textarea\n    id: website\n    attributes:\n      label: 需要适配的新站\n      placeholder: 请输入这个站点的 URL，如果这个站的规则比较多，请多帮忙提供一些 URL 作为参考\n    validations:\n      required: true\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: 有需要请描述这个站的性质\n      description: 尤其是英文类型的网站，我需要判断这个站是否具有适配的必要。\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/yinxiang_code.yml",
    "content": "name: 🚑 印象笔记或代码高亮问题\ndescription: 关于印象笔记或代码高亮的集中反馈渠道\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        谢谢帮忙提交 Bug 这会让简悦变得更好。\n  - type: dropdown\n    id: browsers\n    attributes:\n      label: 请选择\n      multiple: false\n      options:\n        - 印象笔记\n        - 代码高亮\n  - type: input\n    id: website\n    attributes:\n      label: 发生问题的网址\n      placeholder: 如果任意网站均可的话，请随便留下一个你遇到问题的地址\n    validations:\n      required: true\n"
  },
  {
    "path": ".gitignore",
    "content": "# for common\r\nnode_modules/**\r\npublish/**\r\n.vscode/**\r\n\r\n# for chrome\r\nsrc/bundle/**\r\n\r\n# for firefox\r\next/website_list.json\r\next/bundle/**\r\next/focus/**\r\next/read/**\r\next/module/**\r\next/options/**\r\next/assets/**\r\next/_locales/**\r\n\r\n# for mac\r\n**/.DS_Store\r\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {simpread}  Copyright (C) {2017~2020}  {Wang lei}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>."
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"http://sr.ksria.cn/logo%20bigger.png\" /></p>\n<h1 align=\"center\">简悦<sup>1.0</sup> - SimpRead</h1>\n<p align=\"center\">让你瞬间进入沉浸式阅读的扩展，还原阅读的本质，提升你的阅读体验。</p>\n<p align=\"center\">为了达到完美的阅读模式这个小目标 ，我适配了 <a target=\"_blank\" href=\"https://simpread.ksria.cn/sites/\">数百种类型</a> 的网站，因此诞生了简悦。</p>\n<p align=\"center\">\n   <a href=\"https://github.com/kenshin/simpread/releases\"><img src=\"https://img.shields.io/badge/lastest_version-2.2.0-blue.svg\"></a>\n   <a target=\"_blank\" href=\"http://ksria.com/simpread\"><img src=\"https://img.shields.io/badge/website-_simpread.ksria.com-1DBA90.svg\"></a>\n   <a target=\"_blank\" href=\"https://chrome.google.com/webstore/detail/simpread-reader-view/ijllcpnolfcooahcekpamkbidhejabll\"><img src=\"https://badgen.net/chrome-web-store/stars/ijllcpnolfcooahcekpamkbidhejabll?icon=chrome&color=0f9d58\"></a>\n</p>\n<p align=\"center\">\n   <a target=\"_blank\" href=\"https://chrome.google.com/webstore/detail/%E7%AE%80%E6%82%A6-simpread/ijllcpnolfcooahcekpamkbidhejabll\"><img src=\"https://img.shields.io/badge/download-_chrome_webstore-brightgreen.svg\"></a>\n   <a href=\"http://ksria.com/simpread/crx/2.2.0/simpread.crx\"><img src=\"https://img.shields.io/badge/download-_crx-0294b9.png\"></a>\n   <a target=\"_blank\" href=\"https://greasyfork.org/zh-CN/scripts/39998\"><img src=\"https://s1.ax1x.com/2020/07/25/UzMUSS.png\"></a>\n   <a target=\"_blank\" href=\"https://xteko.com/redir?url=http://sr.ksria.cn/jsbox/simpread-1.0.3.box?202010231502&name=%E7%AE%80%E6%82%A6\"><img src=\"https://s1.ax1x.com/2020/07/25/UzMHfK.png\"></a>\n</p>\n<h1 align=\"center\">简悦<sup>2.0</sup> - 真正成为你的生产力工具</h1>\n<h3 align=\"center\">阅读模式 + 剪藏 + 标注 + 稍后读 = 做你的知识管理粘合剂</h3>\n\n***\n\n## 目录\n\n* [下载](#马上使用)\n\n* [入门指南](#入门指南)\n\n* [主要功能一览](#主要功能一览)\n\n* [全部功能](#全部功能)\n\n* [如何使用](#如何使用)\n\n* [简悦各平台版本之间的差异](#简悦各平台版本之间的差异)\n\n* [截图](#截图)\n\n* [投票](#投票)\n\n* [相关链接](#相关链接)\n\n* [贡献者](#感谢)\n\n* [协作开发](#协作开发)\n\n* [开源列表](#简悦的诞生离不开它们)\n\n* [许可](#许可)\n\n***\n\n## 马上使用\n* [Chrome 应用商店](https://chrome.google.com/webstore/detail/%E7%AE%80%E6%82%A6-simpread/ijllcpnolfcooahcekpamkbidhejabll) 或者 [离线下载](http://ksria.com/simpread/crx/2.0.0/simpread.crx)\n\n* [支持 UserScript 的浏览器](https://greasyfork.org/zh-CN/scripts/39998) 如：Apple Safari · Microsoft Edge · Opera · Dolphin 详细 [请看这里](https://github.com/Kenshin/simpread-little)\n\n* [iPhone / iPad 版](https://xteko.com/redir?url=http://sr.ksria.cn/jsbox/simpread-1.0.0.box?201805251238&name=%E7%AE%80%E6%82%A6) 详细 [请看这里](http://ksria.com/simpread/docs/#/JSBox)\n\n* [Android 版](http://ksria.com/simpread/docs/#/Android) 详细 [请看这里](http://ksria.com/simpread/docs/#/Android)\n\n## 入门指南\n\n* 喜欢简悦，但不会用，对新手极度舒适的 [新手入门](http://ksria.com/simpread/guide)\n\n* 参与讨论请加入 [Telegram 群](https://t.me/simpread)\n\n* 想知道简悦的高级玩法，请看简悦的 [文档中心](http://ksria.com/simpread/docs/#) [知识库](https://github.com/Kenshin/simpread/discussions/categories/服务) 与 [帮助中心](https://simpread.pro/help)\n\n* 更多联系方式请看 [相关链接](#相关链接)\n\n## 主要功能一览\n\n#### 阅读模式\n\n- [聚焦模式](http://ksria.com/simpread/docs/#/聚焦模式)  \n\n  > 不改变当前页面的结构，仅仅高亮需要阅读的部分，不分散用户的注意力；适合 `临时阅读` 或者 `未适配阅读模式` 的网站\n\n- [阅读模式](http://ksria.com/simpread/docs/#/阅读模式) ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n  > 简悦 **原创** 功能，逐一适配了 [数百种类型](https://simpread.ksria.cn/sites/) 的网站，自动提取 `标题` `描述` `正文` `媒体资源（ 图片/ 视频 ）` 等，生成 `符合中文阅读` 的页面\n\n  * 支持 [自动生成目录](http://ksria.com/simpread/docs/#/目录)  \n\n  * 支持 [论坛类页面及分页](http://ksria.com/simpread/docs/#/论坛类页面及分页) 如：知乎 · 百度贴吧等\n\n  * 支持 [代码段的高亮](https://github.com/Kenshin/simpread/issues/500)，包含了大部分常见的网站\n\n  * 支持 **本地 HTML** · [TXT 阅读器](http://ksria.com/simpread/docs/#/TXT-阅读器) · 支持 [LaTeX 解析](http://ksria.com/simpread/docs/#/LaTeX-阅读器) · [Markdown 阅读器](http://ksria.com/simpread/docs/#/Markdown-阅读器) \n\n  * 适配了一些 [常见的科研期刊类网站](https://github.com/Kenshin/simpread/discussions/2103)\n\n  * 更符合 `中文阅读` 习惯的设置，包括：`字间距` `行间距` 等 以及 `自定义 CSS` ，详细请看 [自定义样式](http://ksria.com/simpread/docs/#/自定义样式)\n\n- [手动模式](http://ksria.com/simpread/docs/#/手动框选)\n\n  > 将页面内容内容均可生成 聚焦模式 or 阅读模式\n\n#### 丰富的导出功能 ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n  > 借助于简悦的阅读模式优化整理，导入到各种生产力的工具的会比其原生剪辑工具 **更具有优势**，关于这部份内容 [请看这里](http://ksria.com/simpread/docs/#/服务) 。\n\n  - 导出 `Markdown` · `HTML` · `PNG` · [PDF](http://ksria.com/simpread/docs/#/发送到-Epub) · [Epub](http://ksria.com/simpread/docs/#/发送到-Epub) · [离线 HTML](http://ksria.com/simpread/docs/#/离线HTML) · `复制 Markdown 到剪切板` · [Textbundle](http://ksria.com/simpread/docs/#/Textbundle) · [Markdeep](http://ksria.com/simpread/docs/#/定制化导出?id=markdeep) 甚至 [任意格式均可导出](http://ksria.com/simpread/docs/#/定制化导出?id=自定义导出)\n\n  - 发送阅读模式优化后的页面到 `Kindle`，详细配置 [请看这里](http://ksria.com/simpread/docs/#/发送到-Kindle)\n\n  - 导出到 `Pocket` · `Instapaper` 的功能，包括：`当前页面的链接` \n\n  - 导出到生产力工具，包括：`语雀` · [坚果云](http://ksria.com/simpread/docs/#/坚果云) · flomo · [有道云笔记](http://ksria.com/simpread/docs/#/有道云笔记) · [专注笔记](http://ksria.com/simpread/docs/#/专注笔记) · [为知笔记](http://ksria.com/simpread/docs/#/为知笔记) · [Joplin](http://ksria.com/simpread/docs/#/Joplin) · `Dropbox` · [Github](http://ksria.com/simpread/docs/#/Github) · [Notion](http://ksria.com/simpread/docs/#/Notion) · `Onenote` · `Google Drive` · `印象笔记 / Evernote`，详细请看 [导出到生产力](http://ksria.com/simpread/docs/#/导出到生产力工具)\n\n  - 定制化导出，可以方便导入到 Obsidian · Roam Research · Logseq 等双链笔记，更多说明及用法 [请看这里](https://github.com/Kenshin/simpread/discussions/2085)。\n\n  - 支持 [Webhooks](http://ksria.com/simpread/docs/#/%E5%AE%9A%E5%88%B6%E5%8C%96%E5%AF%BC%E5%87%BA?id=webhook)\n\n  - 将多种符合整合在一起的 [自动化方案](http://ksria.com/simpread/docs/#/自动化)，可以理解为 **简悦的 IFTTT**。\n\n#### 标注系统 ![new](https://s1.ax1x.com/2020/07/25/UzKubn.png) ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n> **[标注系统](http://ksria.com/simpread/docs/#/标注)** 是简悦 2.0 最重要的一个功能，借用此功能，可以让简悦真正你的成为生产力工具。\n\n<img src=\"http://sr.ksria.cn/welcome-annoate.webp\" />\n\n> 在 1.0.0 版上线之初就已经构想了此功能，所以在构筑此功能时，加入了很多只有 **简悦 · 标注** 才具有的功能。\n\n- 五种标注颜色\n\n- 四种标注样式\n\n- 自动识别 **文字** · **图片** · **代码段**\n\n- **无限层级** 的标签系统\n\n- 每个标签均支持备注与标签系统\n\n- 结合简悦强大的发送到生产力工具的功能，可将当前标注发送到简悦支持的各种生产力工具\n\n- 连续标注\n\n- [浮动标注栏](http://ksria.com/simpread/docs/#/标注?id=浮动工具栏)\n\n- [标注侧栏](http://ksria.com/simpread/docs/#/标注?id=标注侧栏)\n\n> 如何使用以及详细内容 [请看这里](http://ksria.com/simpread/docs/#/标注)\n\n#### 稍后读 ![new](https://s1.ax1x.com/2020/07/25/UzKubn.png) ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n> 简悦 2.0 启用了全新的 **[稍后读 2.0](http://ksria.com/simpread/docs/#/稍后读)**， 相比较之前的版本，此版本的稍后读算做是一个真正意义的稍后读。\n\n<img src=\"https://s1.ax1x.com/2020/07/21/UI4Jlq.png\" width=\"400\"/> <img src=\"https://s1.ax1x.com/2020/07/21/UI4qnf.png\" width=\"400\"/>\n\n> 简悦的稍后读借鉴了 ZK 笔记法的一些特点，专门针对信息的整合增加了如下一些新功能：\n\n- [**双向链接**](http://ksria.com/simpread/docs/#/双向链接)\n- [**知识图谱**](http://ksria.com/simpread/docs/#/双向链接?id=图谱)\n- [**Mindmap**](http://ksria.com/simpread/docs/#/稍后读?id=Mindmap)\n- **多种信息展示方式（布局）**：[Evergreen](http://ksria.com/simpread/docs/#/稍后读-多种布局?id=Evergreen) · [Workflowy](http://ksria.com/simpread/docs/#/稍后读-多种布局?id=Workflowy) · [Kanban](http://ksria.com/simpread/docs/#/稍后读-多种布局?id=Kanban)\n- [**读取本地 HTML**](https://github.com/Kenshin/simpread/discussions/2146)\n\n![知识图谱](https://z3.ax1x.com/2021/05/16/ggmmSf.gif)\n\n![Evergree Notes](https://z3.ax1x.com/2021/05/17/g2IHyQ.gif)\n\n#### 同步助手 ![new](https://s1.ax1x.com/2020/07/25/UzKubn.png) ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n![](https://z3.ax1x.com/2021/05/19/g4vN9S.png)\n\n> [简悦 · 同步助手](http://ksria.com/simpread/docs/#/Sync) 是 **随着简悦 2.1.0 发布的一个全新的 Desktop App**，用于对简悦已知功能的补充，以及会 **持续提供更多** 的可玩性，包括：\n\n1. **[自动同步](http://ksria.com/simpread/docs/#/自动同步)**\n2. **小书签**\n3. 导出到文件 **本地的任意位置**\n4. **原生 PDF · Epub** 导出\n5. **直接发送到你的 Kindle**\n6. 内置解析\n7. **邮件发送**\n\n同步助手的 Logo 自于社区用户 [Shawn](https://shawnan.design/) 的设计。\n\n#### 插件系统 ![important](https://s1.ax1x.com/2020/07/25/UzKr8O.png)\n\n  > 使用 JavaScript 编写基于 `简悦` 的插件了，请看 [插件中心](https://simpread.ksria.cn/plugins/)\n\n## 全部功能\n\n<details>\n  <img src=\"http://sr.ksria.cn/feature%202.0.0.png?20200723423423\">\n</details>\n\n## 截图\n![简单阅读，愉悦心情！](http://sr.ksria.cn/welcome-readme-1.png)\n\n<details><summary>更多截图</summary>\n  <img src=\"http://sr.ksria.cn/welcome-readme-2.png\"/>\n  <img src=\"http://sr.ksria.cn/welcome-readme-3.png\"/>\n  <img src=\"http://sr.ksria.cn/welcome-readme-4.png\"/>\n\n  <img src=\"https://s1.ax1x.com/2020/07/25/UzuGnA.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/UzuY7t.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/Uzul1e.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/Uzu16H.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/Uzu3Xd.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/UzuQpD.png\"/>\n  <img src=\"https://s1.ax1x.com/2020/07/25/UzuKfO.png\"/>\n\n  <img src=\"https://i.loli.net/2019/06/26/5d12e86b976f450493.png\"/>\n  <img src=\"https://i.loli.net/2019/06/26/5d12e86bd9d6844696.png\"/>\n  <img src=\"https://i.loli.net/2019/06/26/5d12e86bde78615339.png\"/>\n  <img src=\"https://i.loli.net/2019/06/26/5d12e86c9468188114.png\"/>\n</details>\n\n## 如何使用\n\n> 简悦虽然拥有众多功能，但它支持 **开箱即用**，新手（不想折腾党）来说，只需要看懂下面两种操作即可。\n\n### 阅读模式\n\n> `简悦`会自动检测当前页面是否已经适配，如适配则在浏览器右上角显示 ![Imgur](http://i.imgur.com/dyROEBi.png) ，使用以下三种方式启动：\n\n- 点击浏览器右上角 `红色icon`；\n\n- 右键选择 `简悦 - SimpRead` → `阅读模式`；\n\n- 快捷键；_默认为 双击 <kbd>A</kbd>_\n\n- 简悦支持自动进入阅读模式，详细请看 [自动进入阅读模式](http://ksria.com/simpread/docs/#/自动进入阅读模式)\n\n### 聚焦模式\n\n> `聚焦模式` 会自动获取当前鼠标所在的段落并高亮，适合任意页面。\n\n- 在需要高亮的区域，右键选择 `简悦 - SimpRead` → `聚焦模式`；\n\n- 快捷键；_默认为 <kbd>A</kbd> <kbd>S</kbd>_\n\n### 标注模式\n\n> 通过鼠标按下 → 移动 的方式将会产生一个标注，更多细节 [请看这里](http://ksria.com/simpread/docs/#/标注?id=如何使用)\n\n### 稍后读\n\n> 在任意界面可以使用 **标注模式产生一条标注** 或 **快捷键** 方式将当前页面加入，更多细节 [请看这里](http://ksria.com/simpread/docs/#/稍后读)\n\n- 快捷键；_默认为 <kbd>D</kbd> <kbd>D</kbd>_\n\n## 简悦各平台版本之间的差异\n\n> 包括：Chrome / Firefox / 轻阅版（UserScript）/ JSBox ，请访问 [简悦 · 新闻页](https://www.notion.so/9c109ec145134297ab461f5b52dbadc7?v=ce94e37d8a794cfbbd39bf9dfaf9017a)\n\n## 投票\n简悦是一个免费并开源的项目。如果觉得不错，请给我 [投票](https://chrome.google.com/webstore/detail/%E7%AE%80%E6%82%A6-simpread/ijllcpnolfcooahcekpamkbidhejabll/reviews) 。这样让更多人了解并受用与 `简悦` 带来的便利，你的认可是对我最大的鼓励。\n\n## 相关链接\n* [更新日志](http://ksria.com/simpread/changelog.html)\n\n* [帮助中心](http://ksria.com/simpread/docs/#)\n\n* [新手入门](http://ksria.com/simpread/guide)\n\n* [常见问题](http://ksria.com/simpread/docs/#/FAQ)\n\n* [反馈](https://github.com/kenshin/simpread/issues)\n\n* [联系](http://kenshin.wang) · [邮件](kenshin@ksria.com) · [微博](http://weibo.com/23784148) · [Telegram 群](https://t.me/simpread)\n\n## 感谢\n\n[ksky521](https://github.com/ksky521) · [airycanon](https://github.com/airycanon) · [mikelei8291](https://github.com/mikelei8291) · [chenhbc](https://github.com/chenhbc) · [Nihility](https://github.com/NihilityT) · [WangLeto](https://github.com/WangLeto) · [SevenSteven](https://github.com/Seven-Steven) · [Leo](https://github.com/clinyong) · [Jonas · Gao](https://github.com/JonasGao) · [Cologler](https://github.com/Cologler) · [bgh](https://github.com/bldght) · [Ronglong Pu](https://github.com/PuRonglong) · [zqjimlove](https://github.com/zqjimlove) · [javalover123](https://github.com/javalover123) · [1095533987](https://github.com/1095533987)\n\n## 协作开发\n\n> Pull requests 方式：\n\n* 请务必从 **develop** 分支开始；（ **注意：非 develop 分支的 pr 将不会被合并** ）\n\n* Pull requests\n\n* 如果需要合并的话，合并后我会通知你；（在下个版本发布时一起发布）\n\n## 简悦的诞生离不开它们\n<http://ksria.com/simpread/docs/#/开源列表>\n\n## 许可\n\n> 简悦 1.x（截至到 1.1.4.6205）是开源版本，2.x 为闭源版本，且仅闭源了 **标注** 与 **稍后读** 相关功能。\n\n[![license-badge]][license-link]\n\n<!-- Link -->\n[www-badge]:        https://img.shields.io/badge/website-_simpread.ksria.com-1DBA90.svg\n[www-link]:         http://ksria.com/simpread\n[version-badge]:    https://img.shields.io/badge/lastest_version-2.0.0-blue.svg\n[version-link]:     https://github.com/kenshin/simpread/releases\n[chrome-badge]:     https://img.shields.io/badge/download-_chrome_webstore-brightgreen.svg\n[chrome-link]:      https://chrome.google.com/webstore/detail/%E7%AE%80%E6%82%A6-simpread/ijllcpnolfcooahcekpamkbidhejabll\n[offline-badge]:    https://img.shields.io/badge/download-_crx-brightgreen.svg\n[offline-link]:     http://ksria.com/simpread/crx/2.0.0/simpread.crx\n[license-badge]:    https://img.shields.io/badge/license-GPL-blue\n[license-link]:     https://opensource.org/licenses/gpl-license\n"
  },
  {
    "path": "ext/background.js",
    "content": "console.log( \"=== simpread background load ===\" )\r\n\r\nimport local       from 'local';\r\nimport { storage } from 'storage';\r\nimport * as msg    from 'message';\r\nimport {browser}   from 'browser';\r\nimport * as ver    from 'version';\r\nimport * as menu   from 'menu';\r\nimport * as watch  from 'watch';\r\n\r\nimport PureRead    from 'puread';\r\n\r\n/**\r\n * Sevice: storage Get data form chrome storage\r\n */\r\nstorage.Read( () => {\r\n    storage.puread = new PureRead( storage.sites );\r\n    if ( local.Firstload() ) {\r\n        local.Version( ver.version );\r\n        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#firstload?ver=\" + ver.version ) });\r\n    }\r\n    else {\r\n       !local.Count() && storage.GetRemote( \"remote\", ( result, error ) => {\r\n            if ( !error ) {\r\n                storage.pr.Addsites( result );\r\n                storage.Writesite( storage.pr.sites, getNewsitesHandler );\r\n            }\r\n        });\r\n        ver.version != storage.version && storage.GetRemote( \"local\", ( result, error ) => {\r\n            storage.pr.Addsites( result );\r\n            storage.Writesite( storage.pr.sites, () => {\r\n                ver.version != storage.version &&\r\n                storage.Fix( storage.read.sites, storage.version, ver.version, storage.focus.sites );\r\n                ver.version != storage.version && storage.Write( () => {\r\n                        local.Version( ver.version );\r\n                        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#update?ver=\" + ver.version ) });\r\n                }, ver.Verify( storage.version, storage.simpread ) );\r\n                getNewsitesHandler( result );\r\n            });\r\n        });\r\n        // only firefox and only usage 1.1.0.3024\r\n        //if ( ver.version == storage.version && ver.sub_ver == \"3024\" && !localStorage[\"bg-3024\"] ) {\r\n        //    browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#firstload?ver=\" + ver.version ) });\r\n        //    localStorage[\"bg-3024\"] = ver.sub_ver;\r\n        //}\r\n    }\r\n    menu.CreateAll();\r\n});\r\n\r\n/**\r\n * Get newsites handler\r\n * @param {object} count: update site cou\r\n */\r\nfunction getNewsitesHandler( result ) {\r\n    watch.Push( \"site\", true );\r\n}\r\n\r\n/**\r\n * Listen menu event handler\r\n */\r\nmenu.OnClicked( ( info, tab ) => {\r\n    console.log( \"background contentmenu Listener\", info, tab );\r\n    if ( info.menuItemId == \"link\" ) {\r\n        info.linkUrl && browser.tabs.create({ url: info.linkUrl + \"?simpread_mode=read\" });\r\n    } else if ( info.menuItemId == \"list\" ) {\r\n        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#later\" ) });\r\n    } else {\r\n        if ( !tab.url.startsWith( \"moz-extension://\" ) ) browser.tabs.sendMessage( tab.id, msg.Add(info.menuItemId));\r\n    }\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `shortcuts` `browser_action`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    console.log( \"background runtime Listener\", request );\r\n    switch ( request.type ) {\r\n        case msg.MESSAGE_ACTION.shortcuts:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                browser.tabs.sendMessage( tabs[0].id, msg.Add( msg.MESSAGE_ACTION.shortcuts ));\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.browser_action:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                if ( tabs && tabs.length > 0 && tabs[0].url == request.value.url ) {\r\n                    setMenuAndIcon( tabs[0].id, request.value.code );\r\n                } else console.error( request );\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.new_tab:\r\n            browser.tabs.create({ url: request.value.url });\r\n            break;\r\n        case msg.MESSAGE_ACTION.menu:\r\n            const { id, value } = request.value;\r\n            // hack code refresh options menu changed, and not saved storage\r\n            storage.option.menu[id] = value;\r\n            value === true ? menu.Create( id ) : menu.Remove( id );\r\n            break;\r\n        case msg.MESSAGE_ACTION.updated:\r\n            watch.Push( request.value.type, request.value.value );\r\n            break;\r\n        case msg.MESSAGE_ACTION.save_verify:\r\n            sendResponse( watch.Lock( request.value.url ));\r\n            break;\r\n        case msg.MESSAGE_ACTION.auth:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#labs?auth=\" + request.value.name.toLowerCase() ) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.update_site:\r\n            getCurTab({ active: true, url: request.value.url }, tabs => {\r\n                tabs.length > 0 && ( upTabId = tabs[0].id );\r\n                browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?update=\" + encodeURI( JSON.stringify( request.value.site ))) });\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.save_site:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?pending=\" + encodeURI( JSON.stringify( request.value ))) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.temp_site:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?temp=\" + encodeURI( JSON.stringify( request.value ))) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.auth_success:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                if ( tabs && tabs.length > 0 ) {\r\n                    browser.tabs.remove( tabs[0].id );\r\n                    getCurTab( { \"active\": true }, tabs => {\r\n                        tabs.forEach( tab => browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.export, {type: request.value.name.toLowerCase()} )) );\r\n                    });\r\n                }\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.track:\r\n            tracked( request.value );\r\n            break;\r\n        }\r\n});\r\n\r\n/**\r\n * Listen chrome tab active message, include: `tab_selected`\r\n */\r\nbrowser.tabs.onActivated.addListener( function( active ) {\r\n    getCurTab( { \"active\": true, \"currentWindow\": true }, tabs => {\r\n        if ( tabs && tabs.length > 0 && tabs[0].status == \"complete\" ) {\r\n            console.log( \"background tabs Listener:active\", active );\r\n            if ( tabs && tabs.length > 0 && !tabs[0].url.startsWith( \"moz-extension://\" ) ) {\r\n                browser.tabs.sendMessage( tabs[0].id, msg.Add( msg.MESSAGE_ACTION.tab_selected, { is_update: false } ));\r\n            } else {\r\n                setMenuAndIcon( tabs[0].id, -1 );\r\n            }\r\n        } else console.error( \"onActivated.addListener error\" );\r\n    });\r\n});\r\n\r\n/**\r\n * Listen chrome tab update message, include: `tab_selected`\r\n */\r\nbrowser.tabs.onUpdated.addListener( function( tabId, changeInfo, tab ) {\r\n    watch.Pull( tabId );\r\n    if ( changeInfo.status == \"complete\" ) {\r\n        console.log( \"background tabs Listener:update\", tabId, changeInfo, tab );\r\n\r\n        if ( tab.url.startsWith( \"http://ksria.com/simpread/auth.html\" )) {\r\n            const url = tab.url.replace( \"http://ksria.com/simpread/auth.html?id=\", \"\" ),\r\n                  id  = url.includes( \"#\" ) || url.includes( \"&\" ) ? url.substr( 0, url.search( /\\S(#|&)/ ) + 1 ) : url ;\r\n            browser.tabs.query( {}, tabs => {\r\n                const opts = tabs.find( tab => tab.url.includes( browser.extension.getURL( \"options/options.html\" ) ));\r\n                if ( opts ) {\r\n                    browser.tabs.sendMessage( opts.id, msg.Add( msg.MESSAGE_ACTION.redirect_uri, { uri: tab.url, id } ));\r\n                    browser.tabs.remove( tabId );\r\n                }\r\n            });\r\n        } else if ( tab.url.startsWith( \"https://simpread.ksria.cn/plugins/install/\" )) {\r\n            const url = tab.url.replace( \"https://simpread.ksria.cn/plugins/install/\", \"\" );\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#plugins?install=\" + encodeURIComponent(url) ) });\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url.startsWith( \"https://simpread.ksria.cn/sites/install/\" )) {\r\n            const url = tab.url.replace( \"https://simpread.ksria.cn/sites/install/\", \"\" );\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?install=\" + encodeURIComponent(url) ) });\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=success\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n            upTabId > 0 && chrome.tabs.reload( upTabId, () => { upTabId == -1; });\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=failed\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=complete\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=pending\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n            upTabId > 0 && browser.tabs.sendMessage( upTabId, msg.Add( msg.MESSAGE_ACTION.pending_site ));\r\n            upTabId == -1;\r\n        }\r\n\r\n        if ( !tab.url.startsWith( \"moz-extension://\" ) ) {\r\n            browser.tabs.sendMessage( tabId, msg.Add( msg.MESSAGE_ACTION.tab_selected, { is_update: true } ));\r\n            storage.ReadAsync( ( simpread, secret, plugins ) => {\r\n                browser.tabs.sendMessage( tabId, msg.Add( msg.MESSAGE_ACTION.storage, { simpread, secret, plugins } ));\r\n            });\r\n        } else {\r\n            setMenuAndIcon( tab.id, -1 );\r\n        }\r\n    }\r\n});\r\n\r\n/**\r\n * Listen chrome tab remove message\r\n */\r\nbrowser.tabs.onRemoved.addListener( tabId => watch.Pull( tabId ));\r\n\r\n/**\r\n * Listen chrome page, include: `read`\r\n */\r\nbrowser.pageAction.onClicked.addListener( function( tab ) {\r\n    browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.browser_click ));\r\n});\r\n\r\n/**\r\n * Get current tab object\r\n * \r\n * @param {object}   query\r\n * @param {function} callback\r\n */\r\nfunction getCurTab( query, callback ) {\r\n    if ( query.url && query.url.includes( \"#\" ) ) {\r\n        browser.tabs.query( {}, tabs => callback( tabs.filter( tab => tab.url == query.url && tab.active ) ) );\r\n    } else browser.tabs.query( query, function( tabs ) { callback( tabs ); });\r\n}\r\n\r\n/**\r\n * Set page action icon and context menu\r\n * \r\n * @param {int} tab.id\r\n * @param {int} -1: disable icon;\r\n */\r\nfunction setMenuAndIcon( id, code ) {\r\n    let icon = \"\";\r\n    if ( code == -1 ) {\r\n        menu.Update( \"tempread\" );\r\n        browser.pageAction.setTitle({ tabId: id, title: \"简悦 - 当前页面未适配阅读模式，点击进入临时阅读模式\"});\r\n    } else {\r\n        icon = \"-enable\";\r\n        storage.option.menu.read === true && menu.Create( \"read\" );\r\n        menu.Update( \"read\" );\r\n        browser.pageAction.setTitle({ tabId: id, title: \"简悦 - 当前页面已适配阅读模式\"});\r\n    }\r\n    browser.pageAction.show( id );\r\n    browser.pageAction.setIcon({ tabId: id, path: browser.extension.getURL( `assets/images/icon16${icon}.png` ) });\r\n}\r\n\r\n/**\r\n * Listen browser page action\r\n */\r\nbrowser.browserAction.onClicked.addListener( () => browser.runtime.openOptionsPage() );\r\n\r\n/**\r\n * Track\r\n * \r\n * @param {object} google analytics track object\r\n */\r\nfunction tracked({ eventCategory, eventAction, eventLabel }) {\r\n    console.log( \"current track is\", eventCategory, eventAction, eventLabel )\r\n    ga( 'send', {\r\n        hitType      : 'event',\r\n        eventCategory,\r\n        eventAction,\r\n        eventLabel\r\n    });\r\n}\r\n\r\n/**\r\n * Google analytics\r\n */\r\nanalytics();\r\nfunction analytics() {\r\n    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\r\n    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\r\n    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\r\n    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\r\n    ga('create', 'UA-405976-12', 'auto');\r\n    ga('send', 'pageview');\r\n}"
  },
  {
    "path": "ext/contentscripts.js",
    "content": "console.log( \"=== simpread contentscripts load ===\" )\r\n\r\nimport './assets/css/simpread.css';\r\nimport './assets/css/option.css';\r\nimport 'notify_css';\r\n\r\nimport Velocity  from 'velocity';\r\nimport Notify    from 'notify';\r\n\r\nimport {focus}   from 'focus';\r\nimport * as read from 'read';\r\nimport * as modals from 'modals';\r\nimport * as kbd  from 'keyboard';\r\n\r\nimport * as util from 'util';\r\nimport { storage, STORAGE_MODE as mode } from 'storage';\r\nimport * as msg  from 'message';\r\nimport {browser} from 'browser';\r\nimport * as watch from 'watch';\r\n\r\nimport PureRead  from 'puread';\r\nimport * as puplugin from 'puplugin';\r\n\r\nlet pr,                           // pure read object\r\n    is_blacklist = false,\r\n    storage_load = false,         // only usage firefox on windows\r\n    current_url  = location.href; // current page url ( when changed page changed )\r\n\r\n$.fn.sreffect = $.fn.velocity == undefined ? $.fn.animate : $.fn.velocity; // hack code for firefox\r\n\r\n/**\r\n * Sevice: storage Get data form chrome storage\r\n */\r\n/*\r\nstorage.Read( () => {\r\n    if ( blacklist() ) {\r\n        $( \"style\" ).map( ( idx, item ) => {\r\n            if ( item.innerText.includes( \"simpread\"        ) || \r\n                 item.innerText.includes( \"sr-opt-focus\"    ) || \r\n                 item.innerText.includes( \"sr-rd-theme\"     ) || \r\n                 item.innerText.includes( \"notify-gp\"       ) || \r\n                 item.innerText.includes( \"md-waves-effect\" )\r\n            ) {\r\n                $(item).remove();\r\n            }\r\n        });\r\n    } else {\r\n        bindShortcuts();\r\n        autoOpen();\r\n    }\r\n});\r\n*/\r\n\r\n/**\r\n * Blacklist\r\n * \r\n * @return {boolean} true: is blacklist; false: is't blacklist\r\n */\r\nfunction blacklist() {\r\n    for ( const item of storage.option.blacklist ) {\r\n        if ( !item.startsWith( \"http\" ) ) {\r\n            if ( location.hostname.includes( item ) ) {\r\n                is_blacklist = true;\r\n                break;\r\n            }\r\n        } else {\r\n            if ( location.href == item ) {\r\n                is_blacklist = true;\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    console.log( \"current site is blacklist\", is_blacklist )\r\n    return is_blacklist;\r\n}\r\n\r\n/**\r\n * Listen runtime message, include: `focus` `read` `shortcuts` `tab_selected`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    console.log( \"contentscripts runtime Listener\", request );\r\n    if ( is_blacklist ) return;\r\n    switch ( request.type ) {\r\n        case msg.MESSAGE_ACTION.focus_mode:\r\n            if ( storage.option.br_exit ) focus.Exist( false ) ? focus.Exit() : focusMode();\r\n            else focusMode();\r\n            break;\r\n        case msg.MESSAGE_ACTION.shortcuts:\r\n            bindShortcuts();\r\n            break;\r\n        case msg.MESSAGE_ACTION.tab_selected:\r\n            browserAction( request.value.is_update );\r\n            break;\r\n        case msg.MESSAGE_ACTION.read_mode:\r\n        case msg.MESSAGE_ACTION.browser_click:\r\n            watch.Verify( ( state, result ) => {\r\n                if ( state ) {\r\n                    console.log( \"watch.Lock()\", result );\r\n                    new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n                } else {\r\n                     if ( storage.option.br_exit ) {\r\n                        modals.Exist()  && modals.Exit();\r\n                        !modals.Exist() && read.Exist( false ) ? read.Exit() : readMode();\r\n                     }\r\n                     else readMode();\r\n                }\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.storage:\r\n            if ( storage_load ) return;\r\n            storage.WriteAsync( request.value.simpread, request.value.secret, request.value.plugins );\r\n            if ( blacklist() ) {\r\n                $( \"style\" ).map( ( idx, item ) => {\r\n                    if ( item.innerText.includes( \"simpread\"        ) || \r\n                         item.innerText.includes( \"sr-opt-focus\"    ) || \r\n                         item.innerText.includes( \"sr-rd-theme\"     ) || \r\n                         item.innerText.includes( \"notify-gp\"       ) || \r\n                         item.innerText.includes( \"md-waves-effect\" )\r\n                    ) {\r\n                        $(item).remove();\r\n                    }\r\n                });\r\n            } else {\r\n                bindShortcuts();\r\n                autoOpen();\r\n            }\r\n            browserAction( false );\r\n            storage_load = true;\r\n            break;\r\n        case msg.MESSAGE_ACTION.pending_site:\r\n            new Notify().Render({ content: \"是否提交，以便更好的适配此页面？\", action: \"是的\", cancel: \"取消\", callback: type => {\r\n                if ( type == \"cancel\" ) return;\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.save_site, { url: location.href, site: storage.pr.current.site, uid: storage.user.uid, type: \"failed\" }));\r\n            }});\r\n            localStorage.removeItem( \"sr-update-site\" );\r\n            break;\r\n    }\r\n});\r\n\r\n/**\r\n * Keyboard event handler\r\n */\r\nfunction bindShortcuts() {\r\n    kbd.Bind( [ storage.focus.shortcuts.toLowerCase() ], focusMode );\r\n    kbd.Bind( [ storage.read.shortcuts.toLowerCase()  ], readMode  );\r\n    kbd.ListenESC( combo => {\r\n        if ( combo == \"esc\" && storage.option.esc ) {\r\n            modals.Exist()  && modals.Exit();\r\n            !modals.Exist() && focus.Exist() && focus.Exit();\r\n            !modals.Exist() && read.Exist()  && read.Exit();\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Focus mode\r\n */\r\nfunction focusMode() {\r\n    console.log( \"=== simpread focus mode active ===\" )\r\n\r\n    if ( !entry( focus, read, \"阅读\", \"聚焦\" )) return;\r\n\r\n    watch.Verify( ( state, result ) => {\r\n        if ( state ) {\r\n            console.log( \"watch.Lock()\", result );\r\n            new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n        } else {\r\n            getCurrent( mode.focus );\r\n            if ( storage.current.site.name.startsWith( \"txtread:\" ) ) {\r\n                new Notify().Render( \"当前为 <a href='http://ksria.com/simpread/docs/#/TXT-阅读器' target='_blank'>TXT 阅读器模式</a>，并不能使用设定功能。\" )\r\n                return;\r\n            }\r\n            if ( pr.state == \"temp\" && pr.dom ) {\r\n                focus.Render( $(pr.dom), storage.current.bgcolor );\r\n            } else {\r\n                focus.GetFocus( pr.Include(), storage.current.site.include ).done( result => {\r\n                    storage.pr.state == \"none\" && pr.TempMode( mode.focus, result[0] );\r\n                    focus.Render( result, storage.current.bgcolor );\r\n                }).fail( () => {\r\n                    new Notify().Render( 2, \"当前并未获取任何正文，请重新选取。\" );\r\n                });\r\n            }\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Read mode\r\n */\r\nfunction readMode() {\r\n    console.log( \"=== simpread read mode active ===\" )\r\n\r\n    if ( !entry( read, focus, \"聚焦\", \"阅读\" )) return;\r\n\r\n    watch.Verify( ( state, result ) => {\r\n        if ( state ) {\r\n            console.log( \"watch.Lock()\", result );\r\n            new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n        } else {\r\n            getCurrent( mode.read );\r\n            if ( storage.current.site.name != \"\" ) {\r\n                read.Render();\r\n            } else if ( pr.state == \"temp\" && pr.dom ) {\r\n                read.Render();\r\n            } else {\r\n                new Notify().Render( \"当前并未适配阅读模式，请移动鼠标手动生成 <a href='http://ksria.com/simpread/docs/#/临时阅读模式' target='_blank' >临时阅读模式</a>。\" );\r\n                read.Highlight().done( dom => {\r\n                    pr.TempMode( mode.read, dom );\r\n                    read.Render();\r\n                });\r\n            }\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Auto open read mode\r\n */\r\nfunction autoOpen() {\r\n    getCurrent( mode.read );\r\n    if   ( window.location.href.includes( \"simpread_mode=read\"     ) ||\r\n         ( storage.current.auto && util.Exclusion(  puplugin.Plugin( \"minimatch\" ), storage.current )) ||\r\n         ( !storage.current.auto && util.Whitelist( puplugin.Plugin( \"minimatch\" ), storage.current ))\r\n        ) {\r\n        switch ( storage.current.site.name ) {\r\n            case \"my.oschina.net\":\r\n            case \"36kr.com\":\r\n            case \"chiphell.com\":\r\n            case \"question.zhihu.com\":\r\n                $( () => readMode() );\r\n                break;\r\n            case \"post.juejin.im\":\r\n            case \"entry.juejin.im\":\r\n                setTimeout( ()=>readMode(), 2500 );\r\n                break;\r\n            case \"kancloud.cn\":\r\n            case \"sspai.com\":\r\n                setTimeout( ()=>readMode(), 1000 );\r\n                break;\r\n            default:\r\n                pr.state == \"adapter\" && readMode();\r\n                break;\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Focus and Read mode entry\r\n * \r\n * @param  {object}  current mode object\r\n * @param  {object}  other   mode object\r\n * @param  {array}   render str\r\n * @return {boolean} true:continue; false: return\r\n */\r\nfunction entry( current, other, ...str ) {\r\n    if ( other.Exist(false) ) {\r\n        new Notify().Render( `请先退出${str[0]}模式，才能进入${str[1]}模式。` );\r\n        return false;\r\n    }\r\n    if ( current.Exist(true) ) return false;\r\n    return true;\r\n}\r\n\r\n/**\r\n * Get storage.current\r\n * \r\n * @param {string} value is mode.focus or mode.read or undefined\r\n */\r\nfunction getCurrent( mode ) {\r\n    if ( mode && storage.VerifyCur( mode ) ) {\r\n        ( !pr || !pr.Exist() ) && pRead();\r\n        storage.Getcur( mode, pr.current.site );\r\n    }\r\n}\r\n\r\n/**\r\n * Browser action\r\n * \r\n * @param {boolean} when set icon is_update = true\r\n */\r\nfunction browserAction( is_update ) {\r\n    if ( is_update && current_url != location.href ) {\r\n        current_url = location.href;\r\n        autoOpen();\r\n    }\r\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.browser_action, { code: storage.current.site.name == \"\" ? -1 : 0 , url: window.location.href } ));\r\n}\r\n\r\n/** \r\n * Pure Read\r\n*/\r\nfunction pRead() {\r\n    pr = new PureRead( storage.sites );\r\n    pr.AddPlugin( puplugin.Plugin() );\r\n    pr.Getsites();\r\n    storage.puread = pr;\r\n    console.log( \"current puread object is   \", pr )\r\n}\r\n"
  },
  {
    "path": "ext/manifest.json",
    "content": "{\r\n  \"name\"            : \"__MSG_extension_name__\",\r\n  \"default_locale\"  : \"zh_CN\",\r\n  \"version\"         : \"1.1.2.1025\",\r\n  \"short_name\"      : \"SimpRead\",\r\n  \"description\"     : \"__MSG_ext_extension_desc__\",\r\n  \"homepage_url\"    : \"http://ksria.com/simpread\",\r\n  \"author\"          : \"Kenshin Wang <kenshin@ksria.com>\",\r\n  \"icons\" : {\r\n    \"16\"            : \"assets/images/icon16.png\",\r\n    \"48\"            : \"assets/images/icon48.png\",\r\n    \"128\"           : \"assets/images/icon128.png\"\r\n  },\r\n  \"permissions\"     : [\r\n    \"contextMenus\",\r\n    \"tabs\",\r\n    \"storage\",\r\n    \"downloads\",\r\n    \"<all_urls>\"\r\n  ],\r\n  \"background\": {\r\n    \"scripts\"       : [ \"/bundle/common.js\", \"/bundle/background.js\" ]\r\n  },\r\n  \"content_scripts\" : [\r\n    {\r\n      \"matches\"     : [ \"http://*/*\", \"https://*/*\", \"file:///*/*.txt\" ],\r\n      \"exclude_matches\": [ \"http://localhost/*\", \"https://simpread.herokuapp.com/view/*\" ],\r\n      \"js\"          : [\r\n        \"/bundle/common.js\",\r\n        \"/bundle/vendors.js\",\r\n        \"/bundle/contentscripts.js\"\r\n       ],\r\n      \"run_at\"      : \"document_end\"\r\n    }\r\n  ],\r\n  \"browser_action\"  : {\r\n    \"browser_style\" : true,\r\n    \"default_title\" : \"简悦 - 打开选项页\",\r\n    \"default_icon\"  : {\r\n      \"16\"          : \"assets/images/icon48.png\"\r\n    }\r\n  },\r\n  \"page_action\"     : {\r\n    \"browser_style\" : true,\r\n    \"default_icon\"  : {\r\n      \"16\"          : \"assets/images/icon16.png\"\r\n    }\r\n  },\r\n  \"options_ui\": {\r\n    \"page\": \"options/options.html\",\r\n    \"open_in_tab\": true\r\n  },\r\n  \"web_accessible_resources\": [\r\n    \"/assets/images/*\",\r\n    \"website_list.json\"\r\n  ],\r\n  \"content_security_policy\" : \"script-src 'self' https://www.google-analytics.com; object-src 'self'\",\r\n  \"manifest_version\": 2\r\n}\r\n"
  },
  {
    "path": "npmw.cmd",
    "content": "::===========================================================\r\n:: SimpRead : SimpRead development/publish environment\r\n:: HOST     : https://github.com/kenshin/simpread\r\n:: Author   : Kenshin<kenshin@ksria.com>\r\n:: Version  : 0.0.1\r\n::===========================================================\r\n\r\n@echo off\r\n\r\n::===========================================================\r\n:: Initialize\r\n::===========================================================\r\nif \"%1\" == \"run\" (\r\n    if \"%2\" == \"develop\" (\r\n         goto develop\r\n    ) else if \"%2\" == \"publish\"  (\r\n        goto publish\r\n    ) else (\r\n        @echo npmw noly support command: `npmw run develop` and `npmw run publish`.\r\n        goto quit\r\n    )\r\n) else (\r\n    @echo npmw noly support command: `npmw run develop` and `npmw run publish`.\r\n    goto quit\r\n)\r\n\r\n::===========================================================\r\n:: develop : Development environment\r\n::===========================================================\r\n:develop\r\n@echo webpack --devtool=source-map --progress --colors --watch\r\nset NODE_ENV=development\r\nwebpack --devtool=source-map --progress --colors --watch\r\ngoto quit\r\n\r\n::===========================================================\r\n:: publish : Production environment\r\n::===========================================================\r\n:publish\r\n@echo webpack -p --progress --colors\r\nset NODE_ENV=production\r\nwebpack -p --progress --colors\r\ngoto quit\r\n\r\n::===========================================================\r\n:: quit : Quit batch script.\r\n::===========================================================\r\n:quit\r\n@echo npmw batch quit.\r\nexit /b 0\r\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"simpread_project_workflow\",\n  \"version\": \"1.0.0\",\n  \"description\": \"SimpRead develop/deploy\",\n  \"author\": \"Kenshin Wang <kenshin@ksria.com>\",\n  \"license\": \"MIT\",\n  \"homepage\": \"http://ksria.com/simpread\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/kenshin/simpread.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/kenshin/simpread/issues\",\n    \"email\": \"kenshin@ksria.com\"\n  },\n  \"engines\": {\n    \"node\": \">= 0.10.0\"\n  },\n  \"scripts\": {\n    \"develop\": \"cross-env NODE_ENV=development webpack --devtool=eval-source-map --progress --colors --watch\",\n    \"publish\": \"cross-env NODE_ENV=production  webpack -p --progress --colors\",\n    \"ext_dev\": \"cross-env NODE_ENV=development webpack --devtool=source-map --progress --colors --watch --config webpack.config.ext.js\",\n    \"ext_pub\": \"cross-env NODE_ENV=production  webpack -p --progress --color --config webpack.config.ext.js\",\n    \"server\": \"webpack-dev-server --hot --progress --colors\"\n  },\n  \"devDependencies\": {\n    \"autoprefixer\": \"^6.7.7\",\n    \"babel-core\": \"^6.21.0\",\n    \"babel-loader\": \"^6.2.10\",\n    \"babel-polyfill\": \"^6.20.0\",\n    \"babel-preset-es2015\": \"^6.18.0\",\n    \"babel-preset-react\": \"^6.16.0\",\n    \"babel-preset-stage-0\": \"^6.16.0\",\n    \"cross-env\": \"^5.1.4\",\n    \"css-loader\": \"^0.26.1\",\n    \"epub-press-js\": \"^0.5.2\",\n    \"expose-loader\": \"^0.7.1\",\n    \"file-loader\": \"^0.9.0\",\n    \"minimatch\": \"^3.0.4\",\n    \"nanoid\": \"1.0.3\",\n    \"postcss-cssnext\": \"^2.10.0\",\n    \"postcss-loader\": \"^1.3.3\",\n    \"react\": \"^0.14.8\",\n    \"react-dom\": \"^0.14.8\",\n    \"style-loader\": \"^0.13.1\",\n    \"to-markdown\": \"^3.0.4\",\n    \"url-loader\": \"^0.5.7\",\n    \"webpack\": \"^1.14.0\",\n    \"webpack-dev-server\": \"^1.16.2\"\n  },\n  \"dependencies\": {\n    \"clean-webpack-plugin\": \"^0.1.14\",\n    \"copy-webpack-plugin\": \"^4.0.1\"\n  }\n}\n"
  },
  {
    "path": "src/_locales/en/messages.json",
    "content": "{\r\n    \"extension_name\": {\r\n        \"message\": \"SimpRead - Reader View\",\r\n        \"description\": \"similar to Safari reading mode, read mode, reader view module, named simpread.\"\r\n    },\r\n\r\n    \"extension_desc\": {\r\n        \"message\": \"Immersion-reading mode of Chrome extensions, similar to Safari read mode.\",\r\n        \"description\": \"similar to Safari reading mode, read mode, reader view module, named simpread.\"\r\n    },\r\n\r\n    \"ext_extension_desc\": {\r\n        \"message\": \"Immersion-reading mode of extensions, similar to Safari read mode.\",\r\n        \"description\": \"similar to Safari reading mode, read mode, reader view module, named simpread.\"\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/_locales/zh_CN/messages.json",
    "content": "{\r\n    \"extension_name\": {\r\n        \"message\": \"简悦 - SimpRead\",\r\n        \"description\": \"类似 Safari 阅读模式的 Chrome 扩展，阅读模式，阅读器，阅读，命名为 简悦，即：简单阅读，愉悦心情之意。\"\r\n    },\r\n\r\n    \"extension_desc\": {\r\n        \"message\": \"让你瞬间进入沉浸式阅读的 Chrome 扩展，类似 Safari 的阅读模式。\",\r\n        \"description\": \"类似 Safari 阅读模式的 Chrome 扩展，阅读模式，阅读器，阅读，命名为 简悦，即：简单阅读，愉悦心情之意。\"\r\n    },\r\n\r\n    \"ext_extension_desc\": {\r\n        \"message\": \"让你瞬间进入沉浸式阅读的扩展，Chrome 好评率超过 99% 的阅读模式现已来到 Firefox。\",\r\n        \"description\": \"类似 Safari 阅读模式的扩展，阅读模式，阅读器，阅读，命名为 简悦，即：简单阅读，愉悦心情之意。\"\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/_locales/zh_TW/messages.json",
    "content": "{\r\n    \"extension_name\": {\r\n        \"message\": \"簡悅 - SimpRead\",\r\n        \"description\": \"類似 Safari 閱讀模式的 Chrome 擴展，閱讀模式，閱讀器，閱讀，命名為 簡悅，即：簡單閱讀，愉悅心情之意。\"\r\n    },\r\n\r\n    \"extension_desc\": {\r\n        \"message\": \"讓你瞬間進入沉浸式閱讀的 Chrome 擴展，提供與 Safari 類似的 read mode。\",\r\n        \"description\": \"類似 Safari 閱讀模式的 Chrome 擴展，閱讀模式，閱讀器，閱讀，命名為 簡悅，即：簡單閱讀，愉悅心情之意。\"\r\n    },\r\n\r\n    \"ext_extension_desc\": {\r\n        \"message\": \"讓你瞬間進入沉浸式閱讀的擴展，Chrome 好評率超過 99% 的閱讀模式現已來到 Firefox。\",\r\n        \"description\": \"類似 Safari 閱讀模式的擴展，閱讀模式，閱讀器，閱讀，命名為 簡悅，即：簡單閱讀，愉悅心情之意。\"\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/assets/css/options_custom.css",
    "content": "\n.header {\n    background-color: rgb(63, 81, 181);\n    opacity: 1;\n    visibility: visible;\n}\n\n.custom {\n    position: relative;\n\n    display: flex;\n    flex-flow: row;\n\n    top: 65px;\n}\n\n.custom .property {\n    margin: 20px;\n    width: 450px;\n}\n\n.custom .property h1 {\n    margin-bottom: 20px;\n    font-size: 17px;\n}\n\n.custom .property group {\n    display: block;\n}\n\n.custom .property group:not(:last-child) {\n    margin-bottom: 25px;\n}\n\n.custom .preview {\n    display: flex;\n    justify-content: center;\n\n    margin: 20px 20px 0 0;\n    width: 100%;\n\n    box-shadow: 0 2px 5px rgba(0, 0, 0, .26);\n}"
  },
  {
    "path": "src/assets/css/options_notice.css",
    "content": ".header {\n    background-color: #16666f;\n    opacity: 1;\n    visibility: visible;\n}\n\n.notice {\n    margin-top: 65px;\n    width: 100%;\n}\n\n.notice notice {\n    display: flex;\n    flex-direction: row;\n\n    width: 100%;\n}\n\nnotice .loading,\nnotice .failed {\n    position: fixed;\n\n    top: 0;\n    left: 0;\n\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    width: 100%;\n    height: 100%;\n\n    background-color: #fff;\n}\n\nnotice .loading svg {\n    transform: scale(.8);\n}\n\nnotice .failed {\n    flex-direction: column;\n}\n\nnotice .failed span {\n    color: #9b9b9b;\n    font-size: 2.5rem;\n}\n\nnotice .list {\n    margin: 20px 20px 0;\n    width: 300px;\n    min-height: 589px;\n}\n\nnotice .list.controlbar {\n    min-height: initial;\n}\n\nnotice .detail {\n    display: flex;\n    justify-content: center;\n\n    margin: 20px 20px 0 0;\n    padding: 39px 24px 0px;\n\n    width: 100%;\n    background-color: #fff;\n    border-radius: 2px;\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);\n}\n\nnotice .detail img {\n    width: 60%;\n}\n\nnotice .detail .empty {\n    display: flex;\n    justify-content: center;\n    flex-direction: column;\n    align-items: center;\n\n    padding-bottom: 39px;\n    min-height: 550px;\n\n    color: #9b9b9b;\n    font-size: 2.6rem;\n    font-weight: 400;\n}\n\nnotice .detail .empty .icon {\n    width: 80px;\n    height: 80px;\n    background-position: center;\n    background-repeat: no-repeat;\n    background-image: url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBA0LLBOn/NkLAAABtklEQVRYw+2YPyxDURTGf5WOGCwiETH7s4kaDJo2FokYMBglZ66lOtGEqWIQm9zdoJouGERIEwNlE4NE4i0iBoO0CxIMvW1f26evvJY86Tfdc3LP99137vvuTa6HqlAwzxI9X064ISrxagweE5mfOXp18CHjoCDJFHbYlBCAOiywGWzLSYmAauUIn6koI+2g/Bzb0gMMyDWoZ9pNuTQByUKLbkSyhD6PyZroYcwiN0xSoQUYJGhZ+FKjwKtlNsggeAEIFJIbnJqm7BKpSeAIgHkdjbKgRwGucgIdOrEuYXOdXKo1Fm3pF+QOQBI6Tqg3wnleb8nUVHmtRNQBQueX5AZbclmRTVFYqBcbSKpS9jtocVLcFGgK/I6AhQ/UEPHCsf09GMyU287KaBc/Xm4vF+YbBv5oDzYd8FXUWrRIQoQa+wV1hfsFmj6whTt9IPvFRv3L39Q51AR7AKzKctMHtnCnD2wE6oAbpgF4bJCA3HKbH7vcaCsSdf9ZVCrQVyfW/uIwtwdZHcXUEzuSccKt2pglpoOsfkpQPs4a0p8ROc+1KI3RAHqDdPExpIv78nPQId7plgf4BCjPbVayklPeAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTEzVDExOjQ0OjE5KzA4OjAwPYuVdQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0xM1QxMTo0NDoxOSswODowMEzWLckAAAAASUVORK5CYII= );\n}\n\nnotice .detail .preview {\n    width: 100%;\n}\n\nnotice .detail .preview .title {\n    margin-bottom: 20px;\n\n    font-family: PingFang SC,Hiragino Sans GB,Microsoft Yahei,WenQuanYi Micro Hei,sans-serif;\n    font-size: 3.4rem;\n    font-weight: 400;\n    line-height: 1.2;\n\n    overflow: hidden;\n    text-overflow: ellipsis;\n    text-rendering: optimizelegibility;\n    -webkit-line-clamp: 3;\n    -webkit-box-orient: vertical;\n}\n\nnotice .detail .preview .desc {\n    margin-bottom: 20px;\n    padding-bottom: 20px;\n\n    border-bottom: 1px solid #E0E0E0;\n}\n\nnotice .detail .preview blockquote {\n    border-left: 5px solid rgb(122, 122, 122);\n    color: rgb(85, 85, 85);\n    margin: 0;\n    padding: 0 0 0 10px;\n}\n\nnotice .detail .preview pre {\n    white-space: pre-wrap;\n    word-wrap: break-word;\n}\n\nnotice .list {\n    background-color: #fff;\n    border-radius: 2px;\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);\n}\n\nnotice list {\n    display: flex!important;\n    flex-direction: column;\n\n    padding: 16px;\n    min-height: 72px;\n\n    border-bottom: 1px solid #E0E0E0;\n    transition : all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n}\n\nnotice list:hover {\n    background-color: #f5f5f5;\n    transition : all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n}\n\nnotice list:hover .meta {\n    opacity: 1;\n}\n\nnotice list.active {\n    opacity: .5;\n}\n\nnotice list.selected {\n    background-color: #e1f5fe;\n    border-left: 4px solid #29b6f6;\n}\n\nnotice list.active .title {\n    text-decoration: line-through;\n}\n\nnotice list .title {\n    margin-bottom: 4px;\n    padding-right: 25px;\n\n    font-size: 1.5rem;\n    font-weight: 500;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    overflow: hidden;\n}\n\nnotice list .category,\nnotice .detail .preview .category {\n    margin-right: 4px;\n    padding: 2px 7px;\n    padding: 2px 4px;\n\n    color: #fff;\n\n    font-weight: 400;\n    font-size: 0.8rem;\n    border-radius: 2px;\n}\n\nnotice list .meta {\n    position: absolute;\n    right: 0;\n    opacity: 0;\n    transition : all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n}\n\nnotice .date {\n    color: #9e9e9e;\n}"
  },
  {
    "path": "src/assets/css/options_page.css",
    "content": "/**\r\n * Options page style\r\n */\r\n\r\n:root {\r\n  --text-color: #333;\r\n  --secondary-color: color(#333 alpha(-30%));\r\n  --background-color: #fff;\r\n  --width: 835px;\r\n}\r\n\r\n* {\r\n    box-sizing: border-box;\r\n}\r\n\r\nhtml {\r\n    font: 300 16px/1.8 -apple-system, PingFang SC, Microsoft Yahei, Lantinghei SC, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;\r\n    font-size: 62.5%;\r\n\r\n    color: var(--text-color);\r\n    background: var(--background-color);\r\n\r\n    text-rendering: optimizelegibility;\r\n    -webkit-text-size-adjust: 100%;\r\n    -webkit-font-smoothing: antialiased;\r\n}\r\n\r\nbody {\r\n    margin: 0;\r\n    padding: 0;\r\n\r\n    background-color: #fafafa;\r\n}\r\n\r\na {\r\n    color: #4285f4;\r\n    text-decoration: none;\r\n}\r\n\r\ninput, textarea {\r\n    font-family: Raleway, Menlo, \"Dank Mono\", Inconsolata, \"Operator Mono\", Consolas, \"Andale Mono WT\", \"Andale Mono\", \"Lucida Console\", \"Lucida Sans Typewriter\", \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", \"Liberation Mono\", \"Nimbus Mono L\", \"Courier New\", Courier, monospace!important;\r\n}\r\n\r\n.loadingbar {\r\n    position: fixed;\r\n    top: 0;\r\n    left: 0;\r\n\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    height: 100%;\r\n    width: 100%;\r\n\r\n    background-color: #fafafa;\r\n\r\n    z-index: 200;\r\n}\r\n\r\n.animated {\r\n    animation-duration: 1s;\r\n    animation-fill-mode: both;\r\n    animation-iteration-count: infinite;\r\n}\r\n\r\n.heartBeat {\r\n    animation-name: heartBeat;\r\n    animation-duration: 1.3s;\r\n    animation-timing-function: ease-in-out;\r\n}\r\n\r\n@keyframes heartBeat {\r\n    0% {\r\n        transform: scale(1);\r\n    }\r\n\r\n    14% {\r\n        transform: scale(1.3);\r\n    }\r\n\r\n    28% {\r\n        transform: scale(1);\r\n    }\r\n\r\n    42% {\r\n        transform: scale(1.3);\r\n    }\r\n\r\n    70% {\r\n        transform: scale(1);\r\n    }\r\n}\r\n\r\n.topnav {\r\n    position: fixed;\r\n    z-index: 200;\r\n    left: 12px;\r\n    top: 12px;\r\n}\r\n\r\n.header {\r\n    display: flex;\r\n    align-items: center;\r\n\r\n    position: fixed;\r\n\r\n    top: 0;\r\n    left: 0;\r\n\r\n    width: 100%;\r\n    height: 65px;\r\n\r\n    background-color: black;\r\n\r\n    box-shadow: 0 2px 5px rgba(0,0,0,.26);\r\n\r\n    opacity: 0;\r\n    transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\r\n\r\n    z-index: 1000;\r\n}\r\n\r\n.header .nav,\r\n.header .title {\r\n    margin-left: 12px;\r\n}\r\n\r\n.header .title {\r\n    color: var(--background-color);\r\n\r\n    height: 48px;\r\n\r\n    font-size: 2rem;\r\n    font-weight: 700;\r\n\r\n    line-height: 48px;\r\n}\r\n\r\n.top {\r\n    position: fixed;\r\n    top: 0;\r\n    left: 0;\r\n\r\n    width: 100%;\r\n    height: 249px;\r\n\r\n    background-color: #2196f3;\r\n\r\n    overflow: hidden;\r\n\r\n    transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\r\n}\r\n\r\n.main {\r\n    position: relative;\r\n\r\n    top: 200px;\r\n\r\n    margin: 0 auto;\r\n    margin-bottom: 24px;\r\n    padding: 0;\r\n\r\n    max-width: var(--width);\r\n    width: var(--width);\r\n\r\n    background-color: var(--background-color);\r\n\r\n    text-align: center;\r\n\r\n    border-radius: 2px;\r\n    box-shadow: 0 0 2px rgba(0,0,0,0.12), 0 2px 2px rgba(0,0,0,0.26);\r\n}\r\n\r\n.tabscontainer {\r\n    width: 100%;\r\n\r\n    color: inherit;\r\n}\r\n\r\n.tabscontainer section {\r\n    display: -webkit-flex;\r\n    flex-direction:column;\r\n    align-items: center;\r\n\r\n    padding: 50px;\r\n\r\n    overflow-y: auto;\r\n    box-sizing: border-box;\r\n}\r\n\r\n.bottom {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    margin: 80px auto 0;\r\n    padding: 200px 0 50px 0;\r\n\r\n    max-width: var(--width);\r\n    width: var(--width);\r\n\r\n    color: var(--secondary-color);\r\n    font-size: 1.3rem;\r\n\r\n    opacity: 1;\r\n    transition: all .25s ease-out;\r\n}\r\n\r\n.welcome {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    position: fixed;\r\n    \r\n    top: 0;\r\n    left: 0;\r\n\r\n    width: 100%;\r\n    height: 100%;\r\n\r\n    background-color: rgba( 51, 51, 51, .8 );\r\n\r\n    z-index: 3;\r\n}\r\n\r\n.dividers {\r\n    margin: 10px 0;\r\n    width: 100%;\r\n    border-bottom: 1px solid rgba(0,0,0,.12);\r\n}\r\n\r\n/**\r\n *  Labs style\r\n */\r\n.lab {\r\n    /*margin-bottom: 20px;*/\r\n    padding: 10px;\r\n\r\n    width: 100%;\r\n\r\n    background-color: #fff;\r\n    border-radius: 2px;\r\n\r\n    box-shadow: 0 1px 3px rgba(0,0,0,0.12);\r\n    /*border: 1.1px solid #e7e7e7;*/\r\n}\r\n\r\n.main_labs {\r\n    background: transparent;\r\n    box-shadow: none;\r\n}\r\n\r\n#labs .label {\r\n    height: 56px;\r\n    line-height: 56px;\r\n\r\n    color: #616161;\r\n    text-align: left;\r\n\r\n    font-size: 1.5rem;\r\n    font-weight: 700;\r\n\r\n    /*border-bottom: 1px solid rgba(0, 0, 0, .12);*/\r\n}\r\n\r\n#labs .sublabel {\r\n    display: -webkit-box;\r\n    flex-shrink: 1;\r\n\r\n    -webkit-line-clamp: 1;\r\n    -webkit-box-orient: vertical;\r\n\r\n    overflow: hidden;\r\n\r\n    text-overflow: ellipsis;\r\n    text-align: left;\r\n\r\n    color: rgba( 51, 51, 51, .54 );\r\n}\r\n\r\n#labs .more {\r\n    width: 100%;\r\n    /*height: 37px;*/\r\n\r\n    font-size: 1.4rem;\r\n    font-weight: 400;\r\n\r\n    text-align: left;\r\n    /*line-height: 37px;*/\r\n\r\n    user-select: none;\r\n}\r\n\r\n#labs .more .desc {\r\n    color: #757575;\r\n    font-size: 1rem;\r\n}\r\n\r\n#labs .more .arrow {\r\n    display: block;\r\n    position: absolute;\r\n\r\n    right: 22px;\r\n    bottom: 22px;\r\n\r\n    width: 24px;\r\n    height: 24px;\r\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAV1BMVEUAAAD////ExMS2tra/v7/Dw8O8vLzCwsK5ubnCwsK6urq+vr68vLy9vb29vb28vLy9vb29vb29vb29vb29vb29vb29vb29vb2+vr6+vr69vb29vb29vb2oiyseAAAAHHRSTlMAAQ0OEBETFRYZGkpMg8rLzM7R0tPW19je4uTlotOuxwAAAAFiS0dEAf8CLd4AAAB1SURBVCjPrdLHDoAgEATQUbH33vb/v1PEEnVXT85pM48QCAC/JhloC3AOJiO9wDms8UoZdN9KEHZUuAKEPeUuOGw9h0j36nqPHVi/g19RE9y3NoNenylwcGqqfQiANFMQwbIhAz/lB0z0yHg81Hzvp/jfj7AA4P8P+rUn4dEAAAAASUVORK5CYII=);\r\n    background-position: center;\r\n    background-repeat: no-repeat;\r\n}\r\n\r\n/**\r\n * Plugins & sites Card\r\n */\r\n\r\ncards {\r\n    display: flex;\r\n    flex-flow: row wrap;\r\n}\r\n\r\ncard {\r\n    display: flex;\r\n    flex-direction: column;\r\n\r\n    margin: 6px;\r\n\r\n    width: 259px;\r\n\r\n    color: rgba(51, 51, 51, .87);\r\n    background-color: #fff;\r\n\r\n    border-radius: 2px;\r\n    box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12);\r\n\r\n    transition: all .25s ease-out;\r\n}\r\n\r\ncard:hover {\r\n    box-shadow: 0 10px 20px 0 rgba(168,182,191,0.6);\r\n    transform: translateY(-1px);\r\n}\r\n\r\ncard-header {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    justify-content: center;\r\n\r\n    width: 100%;\r\n    height: 196px;\r\n\r\n    background-color: #40C4FF;\r\n}\r\n\r\ncard-header title {\r\n    display: block;\r\n\r\n    font-size: 30px;\r\n    font-weight: 500;\r\n\r\n    line-height: 30px;\r\n}\r\n\r\ncard-header icon {\r\n    display: block;\r\n\r\n    width: 100px;\r\n    height: 100px;\r\n\r\n    font-size: 80px;\r\n}\r\n\r\ncard-content {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-start;\r\n\r\n    width: 100%;\r\n    height: 134px;\r\n\r\n    padding: 16px;\r\n}\r\n\r\ncard-content title {\r\n    display: block;\r\n\r\n    font-size: 20px;\r\n    font-weight: 500;\r\n\r\n    height: 32px;\r\n    line-height: 32px;\r\n}\r\n\r\ncard-content desc {\r\n    display: block;\r\n\r\n    font-size: 14px;\r\n    font-weight: 500;\r\n\r\n    height: 22px;\r\n    line-height: 22px;\r\n}\r\n\r\ncard-content note {\r\n    display: block;\r\n\r\n    margin-top: 16px;\r\n\r\n    text-align: left;\r\n    font-size: 14px;\r\n    font-weight: 400;\r\n\r\n    line-height: 20px;\r\n}\r\n\r\ncard-footer {\r\n    display: flex;\r\n    align-items: flex-end;\r\n    justify-content: flex-end;\r\n\r\n    width: 100%;\r\n    height: 52px;\r\n}\r\n\r\ncard-footer i {\r\n    font-size: 16px;\r\n}\r\n\r\ncard-empty {\r\n    width: 100%;\r\n    padding: 50px;\r\n}\r\n\r\ncard-empty a {\r\n    color: #9b9b9b;\r\n\r\n    font-size: 30px;\r\n    font-weight: 500;\r\n}\r\n\r\n/**\r\n * Account\r\n */\r\n.avatar {\r\n    margin: 10px;\r\n    padding: 5px;\r\n\r\n    width: 100px;\r\n    height: 100px;\r\n    line-height: 80px;\r\n\r\n    font-size: 50px;\r\n    font-weight: bold;\r\n\r\n    color: #fff;\r\n    background-color: rgb(111, 122, 155);\r\n\r\n    border-radius: 50%;\r\n    border: 5px solid #fff;\r\n    box-shadow: 0 10px 20px 0 rgba(168,182,191,0.6);\r\n}\r\n\r\n/**\r\n * Notice bubbles\r\n */\r\n .bubbles {\r\n    position: fixed!important;\r\n    bottom: 20px;\r\n\r\n    display: flex;\r\n    justify-content: center;\r\n    align-content: center;\r\n\r\n    padding: 10px;\r\n\r\n    width: 55px;\r\n    height: 56px;\r\n\r\n    border-radius: 50%;\r\n\r\n    box-shadow: 0 2px 6px 0 rgba(0,0,0,.4);\r\n\r\n    cursor: pointer;\r\n    transition: all 500ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\r\n    overflow: initial;\r\n}\r\n\r\n.bubbles.notice {\r\n    right: 164px;\r\n    background-color: #16666f;\r\n}\r\n\r\n.bubbles.notice:hover {\r\n    background-color: rgba(22, 102, 111, .8);\r\n}\r\n\r\n.bubbles i {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n}\r\n\r\n.bubbles em {\r\n    position: absolute;\r\n    top: 6px;\r\n    right: 11px;\r\n\r\n    width: 18px;\r\n    height: 18px;\r\n    line-height: 18px;\r\n\r\n    color: #fff;\r\n    background-color: #e54545;\r\n\r\n    font-weight: bold;\r\n    text-align: center;\r\n    font-style: initial;\r\n    border-radius: 50%;\r\n}\r\n\r\n.bubbles em.init {\r\n    line-height: 9px;\r\n}\r\n\r\n.bubbles.effect {\r\n    animation-name: popup;\r\n    animation-duration: 1s;\r\n}\r\n\r\n/**\r\n * Help bubbles\r\n */\r\n.bubbles.help {\r\n    right: 94px;\r\n    background-color: #607D8B;\r\n}\r\n\r\n.help:hover {\r\n    background-color: rgba(96, 125, 139, .8);\r\n}\r\n\r\n@keyframes popup {\r\n    0% {\r\n        opacity: 0;\r\n        -webkit-transform: translateY(20px);\r\n        transform: translateY(20px)\r\n    }\r\n\r\n    100% {\r\n        opacity: 1;\r\n        -webkit-transform: translateY(0);\r\n        transform: translateY(0)\r\n    }\r\n}\r\n\r\n@keyframes popdown {\r\n    0% {\r\n        opacity: 0;\r\n        transform: translateY(-20px)\r\n    }\r\n\r\n    100% {\r\n        opacity: 1;\r\n        transform: translateY(0)\r\n    }\r\n}\r\n\r\n@keyframes popclose {\r\n    0% {\r\n        opacity: 1;\r\n        transform: translateY(0px)\r\n    }\r\n\r\n    100% {\r\n        opacity: 0;\r\n        transform: translateY(20px)\r\n    }\r\n}\r\n\r\n/**\r\n * Guide\r\n */\r\n.guide-bg {\r\n    position: fixed;\r\n    right: 10px;\r\n    bottom: 90px;\r\n\r\n    animation-name: popup;\r\n    animation-duration: 1s;\r\n\r\n    z-index: 2147483647;\r\n}\r\n\r\n.guide {\r\n    display: flex;\r\n    flex-direction: column;\r\n    justify-content: flex-start;\r\n    align-content: center;\r\n\r\n    width: 350px;\r\n    height: 400px;\r\n\r\n    background-color: #F5F6F6;\r\n\r\n    border-radius: 2px;\r\n    box-shadow: 0 0 2px rgba(0, 0, 0, .12), 0 2px 2px rgba(0, 0, 0, .26);\r\n\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n}\r\n\r\n.guide .title,\r\n.guide .subtitle {\r\n    height: 48px;\r\n    line-height: 48px;\r\n\r\n    color: #fff;\r\n    background-color: #26d07c;\r\n\r\n    font-size: 17px;\r\n    text-align: center;\r\n\r\n    border-top-left-radius: 2px;\r\n    border-top-right-radius: 2px;\r\n}\r\n\r\n.guide .title {\r\n    position: absolute;\r\n\r\n    left: 0;\r\n    right: 15px;\r\n\r\n    display: flex;\r\n    justify-content: center;\r\n    font-weight: bold;\r\n\r\n    z-index: 2;\r\n}\r\n\r\n.guide .title span {\r\n    animation: .1s reverse fadein,235ms cubic-bezier(.4,0,.2,1) popdown;\r\n}\r\n\r\n.guide .subtitle {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: flex-start;\r\n\r\n    margin-top: 48px;\r\n\r\n    min-height: 55px;\r\n    line-height: initial;\r\n\r\n    font-size: 15px;\r\n}\r\n\r\n.guide .loading {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    margin-bottom: 20px;\r\n\r\n    font-weight: 500;\r\n    font-size: 13px;\r\n\r\n    transition : opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\r\n}\r\n\r\n.guide .loading span {\r\n    padding: 5px;\r\n}\r\n\r\n.guide .group {\r\n    margin-top: -30px;\r\n    padding: 10px;\r\n}\r\n\r\nguid-card {\r\n    display: flex;\r\n\r\n    margin-bottom: 10px;\r\n    padding: 20px;\r\n\r\n    width: 100%;\r\n\r\n    background-color: #ffffff;\r\n\r\n    border-radius: 4px;\r\n    box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(193, 203, 212, 0.7) 0px 0px 0px 1px inset, rgb(193, 203, 212) 0px -1px 0px 0px inset;\r\n    transition: all 550ms cubic-bezier(0.23, 1, 0.32, 1) 0s;\r\n\r\n    cursor: pointer;\r\n}\r\n\r\nguid-card:hover {\r\n    box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(193, 203, 212, 0.7) 0px 0px 0px 1px inset, rgb(193, 203, 212) 0px -1px 0px 0px inset;\r\n    transform: translate(0px, -2px);\r\n}\r\n\r\nguid-card-tips span {\r\n    margin-left: 5px;\r\n    font-size: 14px;\r\n    color: #0B242F;\r\n}\r\n\r\n.guide hr {\r\n    margin: 0;\r\n    border: 0;\r\n\r\n    text-align: center;\r\n    overflow: visible;\r\n}\r\n\r\n.guide hr:before {\r\n    content: '...';\r\n\r\n    position: relative;\r\n    top: -10px;\r\n\r\n    display: inline-block;\r\n\r\n    margin-left: .6em;\r\n\r\n    color: rgba(0,0,0,.68);\r\n\r\n    font-family: medium-content-slab-serif-font,Georgia,Cambria,\"Times New Roman\",Times,serif;\r\n    font-weight: 400;\r\n    font-style: italic;\r\n    font-size: 30px;\r\n    letter-spacing: .6em;\r\n}\r\n\r\n/**\r\n * Feedback bubbles\r\n */\r\n.bubbles.feedback {\r\n    right: 24px;\r\n    background-color: #fb7756;\r\n}\r\n\r\n.feedback:hover {\r\n    background-color: rgba(251, 119, 86, .8);\r\n}\r\n\r\n/**\r\n * URL Scheme source form simpread.css\r\n */\r\n\r\n.simpread-urlscheme,\r\n.simpread-feedback {\r\n    position: fixed;\r\n    right: 20px;\r\n    bottom: 20px;\r\n\r\n    z-index: 2147483646;\r\n}\r\n\r\nsimpread-urlscheme,\r\nsimpread-feedback {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: flex-start;\r\n    flex-direction: column;\r\n\r\n    padding: 20px 20px 0;\r\n\r\n    width: 500px;\r\n\r\n    color: rgba(51, 51, 51, .87);\r\n    background-color: #fff;\r\n    border-radius: 3px;\r\n\r\n    box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 2px, rgba(0, 0, 0, 0.26) 0px 2px 2px;\r\n    overflow: hidden;\r\n\r\n    transform-origin: bottom;\r\n    transition: all .6s ease;\r\n}\r\n\r\nsimpread-urlscheme *,\r\nsimpread-feedback * {\r\n    font-size: 12px!important;\r\n    box-sizing: border-box;\r\n}\r\n\r\nsimpread-urlscheme.active,\r\nsimpread-feedback.active {\r\n    animation-name: srFadeInUp;\r\n    animation-duration: 450ms;\r\n    animation-fill-mode: both;\r\n}\r\n\r\nsimpread-urlscheme.hide,\r\nsimpread-feedback.hide {\r\n    animation-name: srFadeInDown;\r\n    animation-duration: 450ms;\r\n    animation-fill-mode: both;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-label,\r\nsimpread-feedback sr-fb-label {\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-head,\r\nsimpread-feedback sr-fb-head {\r\n    display: flex;\r\n    align-items: center;\r\n    flex-direction: row;\r\n\r\n    margin-bottom: 5px;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-content,\r\nsimpread-feedback sr-fb-content {\r\n    margin-bottom: 5px;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-footer,\r\nsimpread-feedback sr-urls-footer {\r\n    display: flex;\r\n    justify-content: flex-end;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-a,\r\nsimpread-feedback sr-fb-a {\r\n    color: #2163f7;\r\n    cursor: pointer;\r\n}\r\n\r\nsimpread-urlscheme text-field-state,\r\nsimpread-feedback text-field-state {\r\n    border-top: none rgba(34, 101, 247, 0.8)!important;\r\n    border-left: none rgba(34, 101, 247, 0.8)!important;\r\n    border-right: none rgba(34, 101, 247, 0.8)!important;\r\n    border-bottom: 2px solid rgba(34, 101, 247, 0.8)!important;\r\n}\r\n\r\nsimpread-urlscheme switch,\r\nsimpread-feedback switch {\r\n    margin-top: 0!important;\r\n}\r\n\r\n@keyframes srFadeInUp {\r\n    from {\r\n        opacity: 0;\r\n        transform: translateY(100px);\r\n    }\r\n\r\n    to {\r\n        opacity: 1;\r\n        transform: translateY(0);\r\n    }\r\n}\r\n\r\n@keyframes srFadeInDown {\r\n    from {\r\n        opacity: 1;\r\n        transform: translateY(0);\r\n    }\r\n\r\n    to {\r\n        opacity: 0;\r\n        transform: translateY(100px);\r\n    }\r\n}\r\n\r\n/**\r\n * Feeback\r\n */\r\n\r\nsimpread-feedback sr-fb-head {\r\n    font-weight: bold;\r\n}\r\n\r\nsimpread-feedback sr-fb-content {\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\nsimpread-feedback sr-fb-footer {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: flex-end;\r\n\r\n    width: 100%;\r\n}\r\n\r\n/**\r\n * Feeback: stars\r\n */\r\n\r\nsimpread-feedback sr-close {\r\n    position: absolute;\r\n    right: 20px;\r\n    cursor: pointer;\r\n    transition: all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;\r\n    z-index: 200;\r\n}\r\n\r\nsimpread-feedback sr-close:hover {\r\n    transform: rotate(-15deg) scale(1.3);\r\n}\r\n\r\nsimpread-feedback sr-stars {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: center;\r\n\r\n    margin-top: 10px;\r\n}\r\n\r\nsimpread-feedback sr-stars {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: center;\r\n\r\n    margin-top: 10px;\r\n}\r\n\r\nsimpread-feedback sr-stars i {\r\n    margin-right: 10px;\r\n    cursor: pointer;\r\n}\r\n\r\nsimpread-feedback sr-stars i svg {\r\n    transition: all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;\r\n}\r\n\r\nsimpread-feedback sr-stars i svg:hover {\r\n    transform: rotate(-15deg) scale(1.3);\r\n}\r\n\r\nsimpread-feedback sr-stars i.active svg {\r\n    transform: rotate(0) scale(1);\r\n}\r\n\r\nsimpread-feedback sr-emojis {\r\n    display: block;\r\n    height: 100px;\r\n    overflow: hidden;\r\n}\r\n\r\nsimpread-feedback sr-emoji {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    transition: .3s;\r\n}\r\n\r\nsimpread-feedback sr-emoji > svg {\r\n    margin: 15px 0;\r\n    width: 70px;\r\n    height: 70px;\r\n    flex-shrink: 0;\r\n}\r\n\r\nsimpread-feedback sr-stars-footer {\r\n    display: flex;\r\n    justify-content: center;\r\n    margin: 10px 0 20px 0;\r\n}"
  },
  {
    "path": "src/assets/css/options_sitemgr.css",
    "content": "\r\n.hide {\r\n    display: none;\r\n}\r\n\r\n.row {\r\n    display: flex;\r\n    flex-direction: row;\r\n}\r\n\r\n.space {\r\n    width: 30px;\r\n}\r\n\r\n.box-large {\r\n    padding-top:30px;\r\n}\r\n\r\n.header {\r\n    background-color: #8BC34A;\r\n}\r\n\r\n.editor {\r\n    width: 100%;\r\n    padding-right: 20px;\r\n}\r\n\r\n.box {\r\n    margin-top: 20px;\r\n    margin-bottom: 25px;\r\n    padding: 10px;\r\n\r\n    width: 100%;\r\n\r\n    background-color: #fff;\r\n\r\n    border-radius: 2px;\r\n    box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12);\r\n}\r\n\r\n.custom .preview {\r\n    display: block;\r\n    width: 100%;\r\n    padding: 39px 24px 0px;\r\n    background-color: #fff;\r\n}\r\n\r\n.custom .preview .empty {\r\n    display: flex;\r\n    justify-content: center;\r\n    flex-direction: column;\r\n    align-items: center;\r\n\r\n    padding-top: 50px;\r\n    min-height: 550px;\r\n\r\n    color: #9b9b9b;\r\n    font-size: 3.2rem;\r\n}\r\n\r\n.custom .preview .empty .icon {\r\n    width: 80px;\r\n    height: 80px;\r\n    background-position: center;\r\n    background-repeat: no-repeat;\r\n    background-image: url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBA0LLBOn/NkLAAABtklEQVRYw+2YPyxDURTGf5WOGCwiETH7s4kaDJo2FokYMBglZ66lOtGEqWIQm9zdoJouGERIEwNlE4NE4i0iBoO0CxIMvW1f26evvJY86Tfdc3LP99137vvuTa6HqlAwzxI9X064ISrxagweE5mfOXp18CHjoCDJFHbYlBCAOiywGWzLSYmAauUIn6koI+2g/Bzb0gMMyDWoZ9pNuTQByUKLbkSyhD6PyZroYcwiN0xSoQUYJGhZ+FKjwKtlNsggeAEIFJIbnJqm7BKpSeAIgHkdjbKgRwGucgIdOrEuYXOdXKo1Fm3pF+QOQBI6Tqg3wnleb8nUVHmtRNQBQueX5AZbclmRTVFYqBcbSKpS9jtocVLcFGgK/I6AhQ/UEPHCsf09GMyU287KaBc/Xm4vF+YbBv5oDzYd8FXUWrRIQoQa+wV1hfsFmj6whTt9IPvFRv3L39Q51AR7AKzKctMHtnCnD2wE6oAbpgF4bJCA3HKbH7vcaCsSdf9ZVCrQVyfW/uIwtwdZHcXUEzuSccKt2pglpoOsfkpQPs4a0p8ROc+1KI3RAHqDdPExpIv78nPQId7plgf4BCjPbVayklPeAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTEzVDExOjQ0OjE5KzA4OjAwPYuVdQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0xM1QxMTo0NDoxOSswODowMEzWLckAAAAASUVORK5CYII= );\r\n}"
  },
  {
    "path": "src/assets/css/setting.css",
    "content": "\n/**\n *  Setting: Focus/Read setting dailog\n*/\n\nsr-opt-focus,\nsr-opt-read {\n    display: -webkit-flex;\n    flex-direction:column;\n\n    width: 100%;\n}\n\nsr-opt-gp {\n    display: -webkit-flex;\n    position: relative;\n\n    flex-flow:row nowrap;\n    justify-content:flex-start;\n\n    width: 100%;\n    margin-bottom: 25px;\n\n    font-size: 15px;\n}\n\nsr-opt-gp textarea, sr-opt-gp input {\n    font-family: Inconsolata, \"Operator Mono\", Consolas, \"Andale Mono WT\", \"Andale Mono\", \"Lucida Console\", \"Lucida Sans Typewriter\", \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", \"Liberation Mono\", \"Nimbus Mono L\", \"Courier New\", Courier, monospace!important;\n}\n\nsr-opt-gp sr-opt-label {\n    display: block;\n    position: absolute;\n\n    margin: -8px 0 0 0;\n\n    font-size: 14px;\n    font-weight: bold;\n\n    color: rgba(0, 137, 123, .8);\n\n    user-select: none;\n    pointer-events: none;\n\n    transform: scale(0.75) translate( 0px, -8px );\n    transform-origin: left top 0px;\n}\n\nsr-opt-themes {\n    display: -webkit-flex;\n    flex-flow:row nowrap;\n    justify-content:space-between;\n\n    width: 100%;\n    /*height: 100%;*/\n\n    margin: 8px 0 17px;\n    padding: 0;\n}\n\nsr-opt-theme {\n    width: 40px;\n    height: 20px;\n\n    cursor: pointer;\n    list-style: none;\n\n    border-radius: 3px;\n    border: 1px solid #212121;\n    box-sizing: border-box;\n\n    opacity: 1;\n    transition: all 500ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n}\n\nsr-opt-theme:hover {\n    transform: translateY(-1px);\n    box-shadow: 0 5px 10px rgba(0,0,0,.2);\n}\n\nsr-opt-theme:not(:first-child) {\n    margin-left: 5px;\n}\n\nsr-opt-theme[sr-type=\"active\"] {\n    box-shadow: 0 5px 10px rgba(0,0,0,.2);\n    border: none;\n}\n"
  },
  {
    "path": "src/assets/css/simpread.css",
    "content": "/**\r\n * SimpRead: Read mode\r\n */\r\n\r\n.simpread-font {\r\n    font: 300 16px/1.8 -apple-system, PingFang SC, Microsoft Yahei, Lantinghei SC, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;\r\n\r\n    color:#333;\r\n\r\n    text-rendering: optimizelegibility;\r\n    -webkit-text-size-adjust: 100%;\r\n    -webkit-font-smoothing: antialiased;\r\n}\r\n\r\n.simpread-hidden {\r\n    display: none;\r\n}\r\n\r\n/**\r\n *  Read: background( root ) style\r\n */\r\n .simpread-read-root {\r\n    display: -webkit-flex;\r\n    justify-content:center;\r\n    align-items:center;\r\n\r\n    position: relative;\r\n    margin: 0;\r\n\r\n    top: -1000px;\r\n    left: 0;\r\n\r\n    width: 100%;\r\n\r\n    z-index: 2147483646;\r\n\r\n    overflow-x: hidden;\r\n    opacity: 0;\r\n    transition: all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;\r\n}\r\n\r\n.simpread-read-root-show {\r\n    top: 0;\r\n}\r\n\r\n.simpread-read-root-hide {\r\n    top: 1000px;\r\n}\r\n\r\n/**\r\n * Read: Read mode style\r\n */\r\nsr-read {\r\n    display: -webkit-flex;\r\n    flex-flow:column nowrap;\r\n\r\n    margin: 20px 20%;\r\n\r\n    min-width: 400px;\r\n    min-height: 400px;\r\n\r\n    text-align: center;\r\n}\r\n\r\nread-process {\r\n    position: fixed;\r\n\r\n    top: 0;\r\n    left: 0;\r\n\r\n    height: 3px;\r\n    width: 100%;\r\n\r\n    background-color: #64B5F6;\r\n    transition: width 2s;\r\n    z-index: 20000;\r\n}\r\n\r\nsr-rd-content-error {\r\n    display: block;\r\n    position: relative;\r\n\r\n    margin: 0px;\r\n    margin-bottom: 30px;\r\n    padding: 25px;\r\n\r\n    background-color: rgba(0,0,0,0.05);\r\n}\r\n\r\nsr-rd-footer {\r\n    display: flex;\r\n    flex-direction: column;\r\n\r\n    font-size: 14px;\r\n}\r\n\r\nsr-rd-footer-group {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: center;\r\n    align-items: center;\r\n}\r\n\r\nsr-rd-footer-line {\r\n    width: 100%;\r\n    border-top: 1px solid #E0E0E0;\r\n}\r\n\r\nsr-rd-footer-text {\r\n    min-width: 150px;\r\n}\r\n\r\nsr-rd-footer-copywrite {\r\n    margin: 10px 0 0 0;\r\n    color: inherit;\r\n}\r\n\r\nsr-rd-footer-copywrite abbr {\r\n    font-variant: normal;\r\n    text-decoration: none;\r\n    /*border-bottom: 1px dotted;*/\r\n}\r\n\r\nsr-rd-footer-copywrite .second {\r\n    margin: 10px 0;\r\n}\r\n\r\nsr-rd-footer-copywrite .third a:hover {\r\n    border: none!important;\r\n}\r\n\r\nsr-rd-footer-copywrite .third a:first-child {\r\n    margin-right: 50px;\r\n}\r\n\r\nsr-rd-footer-copywrite .sr-icon {\r\n    display: inline-flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    width: 33px;\r\n    height: 33px;\r\n\r\n    opacity: .8;\r\n\r\n    transition: opacity .5s ease;\r\n    cursor: pointer;\r\n}\r\n\r\nsr-rd-footer-copywrite .sr-icon:hover {\r\n    opacity: 1;\r\n}\r\n\r\nsr-rd-footer-copywrite a,\r\nsr-rd-footer-copywrite a:link,\r\nsr-rd-footer-copywrite a:visited {\r\n    margin: 0;\r\n    padding: 0;\r\n\r\n    color: inherit;\r\n    background-color: transparent;\r\n\r\n    font-size: inherit!important;\r\n    line-height: initial;\r\n    text-decoration: none;\r\n    vertical-align: initial;\r\n\r\n    border: none!important;\r\n    /*border-bottom: 1px dotted!important;*/\r\n\r\n    box-sizing: border-box;\r\n}\r\n\r\nsr-rd-footer-copywrite a:hover,\r\nsr-rd-footer-copywrite a:focus,\r\nsr-rd-footer a:active {\r\n    color: inherit;\r\n    text-decoration: none;\r\n    border-bottom: 1px dotted!important;\r\n}\r\n\r\n/**\r\n * Special blocks\r\n */\r\n.simpread-blocks {\r\n    text-decoration: none!important;\r\n}\r\n\r\n.simpread-blocks * {\r\n    margin: 0;\r\n}\r\n\r\n.simpread-blocks a {\r\n    padding: 0;\r\n    text-decoration: none!important;\r\n}\r\n\r\n.simpread-blocks img {\r\n    margin: 0;\r\n    padding: 0;\r\n    border: 0;\r\n    background: transparent;\r\n    box-shadow: none;\r\n}\r\n\r\n/**\r\n *  Focus: Background( root ) style\r\n */\r\n.simpread-focus-root {\r\n    display: block;\r\n    position: fixed;\r\n\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n    bottom: 0;\r\n\r\n    background-color: rgba( 235, 235, 235, 0.9 );\r\n    z-index: 2147483645;\r\n\r\n    opacity: 0;\r\n    transition : opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\r\n}\r\n\r\n.simpread-focus-highlight {\r\n    position: relative;\r\n\r\n    box-shadow: 0 0 0 20px #fff;\r\n    background-color: #fff;\r\n\r\n    overflow: visible;\r\n    z-index: 2147483646;\r\n}\r\n\r\n.sr-controlbar-bg sr-rd-crlbar {\r\n    z-index: 2147483647;\r\n}\r\n\r\n.sr-controlbar-bg sr-rd-crlbar fab {\r\n    z-index: 2147483647;\r\n}\r\n\r\n/**\r\n * Controlbar: focus mode and read mode\r\n */\r\nsr-rd-crlbar.controlbar {\r\n    position: fixed;\r\n\r\n    right: 0;\r\n    bottom: 0;\r\n\r\n    width: 100px;\r\n    height: 100%;\r\n\r\n    opacity: 0;\r\n    transition: opacity .5s ease;\r\n}\r\n\r\nsr-rd-crlbar.controlbar:hover {\r\n    opacity: 1;\r\n}\r\n\r\n/**\r\n * Golbal\r\n*/\r\n@media all and ( max-height: 620px ) {\r\n\r\n    fab {\r\n        zoom: .8;\r\n    }\r\n\r\n}\r\n\r\n@media all and ( max-height: 783px ) {\r\n\r\n    dialog-gp dialog-content {\r\n        max-height: 580px;\r\n    }\r\n\r\n    dialog-gp dialog-footer {\r\n        border-top: 1px solid rgba(224, 224, 224, 1);\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Highlight\r\n */\r\n.simpread-highlight-selector {\r\n    background-color: #fafafa !important;\r\n    outline: 3px dashed #1976d2 !important;\r\n    opacity: .8 !important;\r\n    cursor: pointer !important;\r\n    transition: opacity .5s ease !important;\r\n}\r\n\r\n.simpread-highlight-controlbar {\r\n    position: relative !important;\r\n    background-color: #fafafa !important;\r\n    outline: 3px dashed #1976d2 !important;\r\n    opacity: .8 !important;\r\n    transition: opacity .5s ease !important;\r\n}\r\n\r\nsimpread-highlight,\r\nsr-snapshot-ctlbar {\r\n    position: fixed;\r\n\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    padding: 15px;\r\n\r\n    height: 50px;\r\n\r\n    background-color: rgba(50, 50, 50, .9);\r\n    box-shadow: 0 2px 5px rgba(0, 0, 0, .26);\r\n\r\n    box-sizing: border-box;\r\n    z-index: 2147483640;\r\n}\r\n\r\nsr-highlight-ctl {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    margin: 0 5px;\r\n\r\n    width: 50px;\r\n    height: 20px;\r\n\r\n    color: #fff;\r\n    background-color: #1976d2;\r\n\r\n    border-radius: 4px;\r\n    box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0,0,0,.12);\r\n    cursor: pointer;\r\n}\r\n\r\n/**\r\n * TOC\r\n */\r\ntoc-bg {\r\n    position: fixed;\r\n    left: 0;\r\n    top: 0;\r\n    width: 50px;\r\n    height: 200px;\r\n\r\n    font-size: initial;\r\n}\r\n\r\ntoc-bg:hover {\r\n    z-index: 3;\r\n}\r\n\r\n.toc-bg-hidden {\r\n    opacity: 0;\r\n    transition: opacity .5s ease;\r\n}\r\n\r\n.toc-bg-hidden:hover {\r\n    opacity: 1;\r\n    z-index: 3;\r\n}\r\n\r\n.toc-bg-hidden:hover toc {\r\n    width: 180px;\r\n}\r\n\r\ntoc * {\r\n    all: unset;\r\n}\r\n\r\ntoc {\r\n    position: fixed;\r\n    left: 0;\r\n    top: 100px;\r\n\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-start;\r\n\r\n    padding: 10px;\r\n\r\n    width: 0;\r\n    max-width: 200px;\r\n    max-height: 500px;\r\n\r\n    overflow-x: hidden;\r\n    overflow-y: hidden;\r\n    cursor: pointer;\r\n    border: 1px solid rgba(158, 158, 158, 0.22);\r\n    transition: width .5s;\r\n}\r\n\r\ntoc:hover {\r\n    overflow-y: auto;\r\n}\r\n\r\ntoc::-webkit-scrollbar {\r\n    width: 3px;\r\n}\r\n\r\ntoc::-webkit-scrollbar-thumb {\r\n    border-radius: 10px;\r\n    background-color: rgba(139,137,134,0.5);\r\n}\r\n\r\n\r\ntoc outline {\r\n    position: relative;\r\n    display: -webkit-box;\r\n    -webkit-line-clamp: 1;\r\n    -webkit-box-orient: vertical;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n\r\n    padding: 2px 0;\r\n    min-height: 21px;\r\n\r\n    line-height: 21px;\r\n    text-align: left;\r\n}\r\n\r\ntoc outline a,\r\ntoc outline a:active,\r\ntoc outline a:visited,\r\ntoc outline a:focus\r\n{\r\n    display: block;\r\n\r\n    width: 100%;\r\n\r\n    color: inherit;\r\n    font-size: 11px;\r\n    text-decoration: none!important;\r\n\r\n    white-space: nowrap;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n\r\ntoc outline a:hover {\r\n    font-weight: bold!important;\r\n}\r\n\r\ntoc outline a.toc-outline-theme-dark,\r\ntoc outline a.toc-outline-theme-night {\r\n    color: #fff!important;\r\n}\r\n\r\n.toc-level-h1 {\r\n    padding-left: 5px;\r\n}\r\n.toc-level-h2 {\r\n    padding-left: 15px;\r\n}\r\n.toc-level-h3 {\r\n    padding-left: 25px;\r\n}\r\n.toc-level-h4 {\r\n    padding-left: 35px;\r\n}\r\n\r\n.toc-outline-active {\r\n    border-left: 2px solid rgb(244, 67, 54);\r\n}\r\n\r\ntoc outline active {\r\n    position: absolute;\r\n\r\n    left: 0;\r\n    top: 0;\r\n    bottom: 0;\r\n\r\n    padding: 0 0 0 3px;\r\n\r\n    border-left: 2px solid #e8e8e8;\r\n\r\n}\r\n\r\n/**\r\n * Keyboard\r\n */\r\n\r\nsr-kbd {\r\n    background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgb(255, 247, 133)), to(rgb(255, 197, 66)));\r\n    border-width: 1px;\r\n    border-style: solid;\r\n    border-color: rgb(227, 190, 35);\r\n    border-image: initial;\r\n\r\n    position: absolute;\r\n    left: 0;\r\n    padding: 1px 3px 0px;\r\n\r\n    font-size: 11px!important;\r\n    font-weight: bold;\r\n    box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 7px 0px;\r\n    overflow: hidden;\r\n    border-radius: 3px;\r\n}\r\n\r\n.sr-kbd-a {\r\n    position: relative;\r\n}\r\n\r\nkbd-mapping {\r\n    position: fixed;\r\n    left: 5px;\r\n    bottom: 5px;\r\n\r\n    display: flex;\r\n    flex-direction: column;\r\n    flex-flow: row;\r\n\r\n    width: 500px;\r\n    height: 625px;\r\n\r\n    background-color: #fff;\r\n\r\n    border: 1px solid rgba(158, 158, 158, .22);\r\n    box-shadow: 0 2px 5px rgba(0,0,0,.26);\r\n    border-radius: 3px;\r\n}\r\n\r\nkbd-maps {\r\n    display: flex;\r\n    flex-flow: column wrap;\r\n    margin-top: 40px;\r\n}\r\n\r\nkbd-mapping kbd-map-title {\r\n    position: absolute;\r\n    margin: 5px 0;\r\n    width: 100%;\r\n    font-size: 14px;\r\n    font-weight: bold;\r\n}\r\n\r\nkbd-maps-group {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-start;\r\n}\r\n\r\nkbd-maps-title {\r\n    margin: 5px 0;\r\n    padding-left: 53px;\r\n\r\n    font-size: 12px;\r\n    font-weight: bold;\r\n}\r\n\r\nkbd-map kbd {\r\n    display: inline-block;\r\n    padding: 3px 5px;\r\n    font-size: 11px;\r\n    line-height: 10px;\r\n    color: #444d56;\r\n    vertical-align: middle;\r\n    background-color: #fafbfc;\r\n    border: solid 1px #c6cbd1;\r\n    border-bottom-color: #959da5;\r\n    border-radius: 3px;\r\n    box-shadow: inset 0 -1px 0 #959da5;\r\n}\r\n\r\nkbd-map kbd-name {\r\n    display: inline-block;\r\n    text-align: right;\r\n\r\n    width: 50px;\r\n}\r\n\r\nkbd-map kbd-desc {\r\n    padding-left: 3px;\r\n}\r\n\r\n/**\r\n * Share card\r\n */\r\n\r\nsharecard-bg {\r\n    position: fixed;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n\r\n    background-color: rgba(0, 0, 0, .4);\r\n\r\n    z-index: 2147483647;\r\n}\r\n\r\nsharecard {\r\n    display: flex;\r\n    flex-direction: column;\r\n\r\n    max-width: 450px;\r\n\r\n    background-color: rgb(100, 181, 246);\r\n}\r\n\r\nsharecard-head {\r\n    display: flex;\r\n    flex-direction: column;\r\n\r\n    margin: 25px;\r\n\r\n    color: #fff;\r\n\r\n    border-radius: 10px;\r\n    box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.15);\r\n}\r\n\r\nsharecard-card {\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\nsharecard-top {\r\n    display: flex;\r\n    align-items: center;\r\n    justify-content: center;\r\n\r\n    padding-right: 5px;\r\n\r\n    height: 65px;\r\n    background-color: #fff;\r\n\r\n    color: #878787;\r\n    font-size: 25px;\r\n    font-weight: 500;\r\n\r\n    border-top-left-radius: 10px;\r\n    border-top-right-radius: 10px;\r\n}\r\n\r\nsharecard-top span.logos {\r\n    display: block;\r\n\r\n    width: 48px;\r\n    height: 48px;\r\n\r\n    margin: 5px;\r\n\r\n    background-repeat: no-repeat;\r\n    background-position: 50%;\r\n    background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAABU1BMVEUAAAAnNJMnNZI3Q5onNJInNJMnNJInNJMnNJI8SJ0tOZY/S55EUKAoNJI6RpwoNJNIU6InNJInNJImNJI7SJwmNJJ2fLUiMJFKVaNCTJ9faK1HUaJOWKVSXaUnNJNYY6pye7cmM5JXYKhwebMjMI8mL4719fW9vb0oNZP/UlLz8/QqN5TAwMAnNJPv7+/Pz8/q6+/p6enNzc3Kysry8vMsOJXc3env7/LU1uXo29vR0dHOzs7ExMTwjo73bW37XV3Aj1TCYELl5u3n5+fW2Obn6O7f4OrZ2+g0QJkxPpgvO5bh4uvS1OTP0ePCwsJQW6ZLVqTs7fHd3d3V1dXqv79VX6lET6A1TIxXUIBSgHxWQnpelHf+WVnopkXqbC7j5Ozi4uLDw8NGUaFATJ9SgH3r6+vGyd7BxNva2trX19ejqM2gpczHx8dze7Zha67Z2dlTgH1aXQeSAAAAJnRSTlMA6ff+497Y8NL+/fv49P379sqab/BeOiX06tzVy8m/tKqpalA7G6oKj0EAAAJlSURBVEjHndNXWxpBFIDhcS2ICRLAkt4Dx4WhLk0E6R0MYoIISrWX5P9f5cwSIRC2+T1czMV5n2FnZwn2eWONUqCAv3H2Uf5Ra1hx4+0WEXtDQW0fCPYJ1EffEfIV4CSROAE4jsePoTFsNmTJF/IeIHF2lgCIn57GodlqDWXBK7IwBYatVlMWFAildPKX7I3m74Z9fsCiQChoimoFQAz04Ad2gH1n9fv9n9hgMNDr9euLWD6fLxQKxaLfb7dTSlahbFVdEPwIQtrAihZQgyKCtCagbQe3xh0QFMgy5MR11+ewYY5/qlZ7vT2xu93ULKjbFLpiUxnIIwjgKmVTLDUFXMrAi2NJWCRLIthTBo4xyOLKpwyqU6CuDCI41hFBCVdOhyLw4FgJ1skCAiyl9BSHbCorgo6VJXTru5hrVCQS8Yr5xLzX59YJSFpVFwD9U0BGC3hGdFpATgRupTGe9R9I1b1ePBvXKDyvq/O/44LT4/E4BUbSCAwj8Evq6HlnOBprx6JhJz8Gktc7xeaP9ndY+0coQvCccFBD4JW60UIY50ciLOAODAQRVOeCHm4Q3Xks6uRDY+CQ+AR4T2wMYh6+jMCIQOp78CFoj0H7EQgIuhI3dGaHCrwgADwCPjJvA372GRigCJg49FUdk3D87pq3zp4SA5zc1Zh9DxfwkpjgUg5Mv+lbeE3McC8Lpu7SA3wk2xzcqL2tN5DfIsQC8HB7UamUy6FQOpTO5QKBQDZbKnWSyUzGjdWCwaDA8+7Le4BNgm3qQGWchYh9s5hNq6wVbBlbwhZYOp3OYOA4zmgEypnM2zj8ByIdedKrH8vDAAAAAElFTkSuQmCC\");\r\n\r\n    zoom: .8;\r\n}\r\n\r\nsharecard-content {\r\n    padding: 15px;\r\n\r\n    max-height: 500px;\r\n\r\n    font-size: 20px;\r\n    text-align: justify;\r\n\r\n    background-color: rgb(33, 150, 243);\r\n\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n}\r\n\r\nsharecard-via {\r\n    padding: 10px;\r\n    font-size: 10px;\r\n\r\n    background-color: rgb(33, 150, 243);\r\n}\r\n\r\nsharecard-footer {\r\n    display: flex;\r\n    flex-direction: row;\r\n    align-items: center;\r\n    justify-content: center;\r\n\r\n    padding-right: 5px;\r\n\r\n    height: 100px;\r\n    background-color: #fff;\r\n\r\n    color: #878787;\r\n    font-size: 15px;\r\n    font-weight: 500;\r\n\r\n    border-bottom-left-radius: 10px;\r\n    border-bottom-right-radius: 10px;\r\n}\r\n\r\nsharecard-footer div {\r\n    display: flex;\r\n    align-items: center;\r\n}\r\n\r\nsharecard-footer span.qrcode {\r\n    display: block;\r\n\r\n    width: 100px;\r\n    height: 100px;\r\n\r\n    margin: 5px;\r\n\r\n    background-repeat: no-repeat;\r\n    background-position: 50%;\r\n    background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAMAAAANIilAAAAA7VBMVEUAAAD///8ZGRnw8PBWVlb4+PgeHh719fVEREQlJSUODg6Ojo7Ly8v9/f1NTU2VlZUFBQV6enrCwsLy8vLh4eE0NDQLCwu8vLyXl5dxcXHa2trAwMCFhYVDQ0OysrKampo7OzssLCwICAioqKjJyckhISHu7u4/Pz9TU1NQUFBLS0tAQED6+vrS0tKRkZFISEgvLy+goKB+fn5vb29nZ2fm5uYbGxvk5OTX19d2dnZaWlre3t5hYWEyMjK5ubkoKCgVFRXQ0NDMzMzFxcW0tLSsrKykpKSMjIzq6urU1NSAgICvr6+cnJyHh4dsbGyfc25QAAAFkElEQVRIx4WXB3faMBCA74wHxgMMGAOh1MyyVyBAmtVmd/3/n1OdDtWstt/Li5JD35MtnU4CMnCIlkLEOpSKuMPhuI4FE444L9+dyv3zciad/rAjfU/yOPpGcjWS1NKSGsk29WTSD1IeYsIPkvsAJF+C5BIZkieYMNjJnsF4+JHk61wOSlVDyOIPqKBgZIxYTrqy/AmNtC1ps7yqlgE6dgYa1WqD5aV9SbKDbe6ZNnCq5A5ILlhGjEASIoawJdmHHo98AZLOnmxzKM9yK9Aht63FoAWBBmEgsEHnkfMgsZU8PJbpbXJd3MIep/Lg/MjbRqMRRuNtQ4Tthga5RiNzfmSWD97ZExQ64HhtgLb3CmbBGyj54vixjyeMsKjnV4AvOAHTwsHphKnH9toXki7Li3jLsgswizskv+c3PHKXe7ZHu5E/YMKcM2yqZIJkAclZTPClbJezivI1yTr4f+TeMib5qXxHsr7XtUFJDIc8pLAHA7Su4JXkd8ySnKZddQXH2NohswIutRu0Qu0jyS46JPugU+gISB1R8NBKWegVUsahTKEjAP9Bm5bKAc3DPlzjKaDvscE7PeEJu63WUg9a82tdA1O/ESupL9Cr6C1cXetjIe9zgQ4kvKLgHi6xC5LcGq9hhmiK0AYgUvLQtzm3n/x0jnum/Vo9j1jxg/pL3/f9W8isMOsv6/Ubf47rvl+u17nnZ1xQ+4iIXa6IpRVeimEEE3pnxO8kIz4CbFDyidVSpooBCCLLsj5noFlqUg2rwK0l+Anmm+VhCzKfrRHtqjGOLANxUKIUiYvFEcuaaZpXANniD5ZzpiBDTSRkuDJfWM6awxGuikUArp7fIOE7RlB6OygGTyhfsMyrtwDNIAkcp1YRhKC9Oh2IHUFQ6UPupnLL3icODaD5zVlUto7zhm1n7kmZ5kDSQBxykfZhD66eaQBoWriEGPcA182C4Crst90Z9NwvHgahDTALw/Ae4D500Pjq3oj/4sjtwcwVrCkkgB01HB8cdM0iIlWSr6IpNqFO+1mDHQFWORtO5EIi08b4jxy6giBsgKDnRnEYYdd9nIYvaLmuhS/h9DF5bEFr/7HTKAjUqWY1oUUBKgYEZdgIP6gJE2xQIWVvLhZBcAWx843kz87PDDi4cgR92s8/1FLpAGNeKiUbGtRQEIPkGb9TM1EF8MpCVEni7pIkkUdDs1ZcI/ZUer6YZg4WxTtqMmYsZJWebbOzEekZV4sCKaNhBaXQQ0NtjL71ZooNE1vWLfyyyFUbw7MsD0fWOFMSqAnbwj1Kuk0Aqp4aJ91MZhhvyS7+oQoMy5v63Jfoz/UYfPSiep2KQb5e4/gt1Ycdc7Se6jNyVbpuQNI08FrICQ6ccKnSXddrKCnqkqWFupJFAewKudSTBVAyBEjrLSXjCYnc5rrdQVl6VaiKqOTToi/kaSrlcW5fpGpgrlJTLvoGVxKDOg7PHzc6NLXOmuUHTZQhTWvS4T7T5ixPqGPz/EHXp/azkMeQoGOqBBOSq1gD4vwRe1culz8W8HlZKQt6Sjbm5XeS9eWizJw73HcsOW8mSpa0eT8zfK1w85LdtWKTf5dWfCPzMg5J+MBdsvvy6Q2QD/d91sfzouRz9zAdBp6HCcUzskccyBdKzjTC9ZE8HT8+JHLxtiE4d33Ud0uleOObvpXZk4E4/9h2sKD9t6oxgaCFxs9AHiI3wYJCndMbIMs9lLi7vEHFLxAUURyciOnTyzrLH6qSJwo+8CWuQIFL2wSoVyvQea/qtk2yvPtb4mekZMhJQkPwyvIzBbJGJD+jX3eGcfIFhWVmxsVAG5FMgSzm9y4wKL8aJdzvyctoTqEgep6K5lckWGM3uuuA5DadFvIhiTzBL1xzVtT0UDEDxd9ldeutcJLoyvUaoPgNdiqckZLamd0AAAAASUVORK5CYII=\");\r\n}\r\n\r\nsharecard-control {\r\n    display: flex;\r\n    flex-direction: row;\r\n    align-items: center;\r\n\r\n    padding: 0 19px;\r\n\r\n    height: 80px;\r\n    background-color: #fff;\r\n}\r\n\r\n/**\r\n * Snapshot\r\n */\r\n\r\nsimpread-snapshot {\r\n    position: fixed;\r\n    left: 0;\r\n    top: 0;\r\n\r\n    width: 100%;\r\n    height: 100%;\r\n\r\n    cursor: move;\r\n    z-index: 2147483645;\r\n}\r\n\r\nsr-mask {\r\n    position: fixed;\r\n    left: 0;\r\n    top: 0;\r\n    background-color: rgba(0, 0, 0, .1);\r\n}\r\n\r\n/**\r\n * URL Scheme\r\n */\r\n\r\n.simpread-urlscheme,\r\n.simpread-feedback {\r\n    position: fixed;\r\n    right: 20px;\r\n    bottom: 20px;\r\n\r\n    z-index: 2147483646;\r\n}\r\n\r\nsimpread-urlscheme,\r\nsimpread-feedback {\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: flex-start;\r\n    flex-direction: column;\r\n\r\n    padding: 20px 20px 0;\r\n\r\n    width: 500px;\r\n\r\n    color: rgba(51, 51, 51, .87);\r\n    background-color: #fff;\r\n    border-radius: 3px;\r\n\r\n    box-shadow: rgba(0, 0, 0, 0.12) 0px 0px 2px, rgba(0, 0, 0, 0.26) 0px 2px 2px;\r\n    overflow: hidden;\r\n\r\n    transform-origin: bottom;\r\n    transition: all .6s ease;\r\n}\r\n\r\nsimpread-urlscheme *,\r\nsimpread-feedback * {\r\n    font-size: 12px!important;\r\n    box-sizing: border-box;\r\n}\r\n\r\nsimpread-urlscheme.active,\r\nsimpread-feedback.active {\r\n    animation-name: srFadeInUp;\r\n    animation-duration: 450ms;\r\n    animation-fill-mode: both;\r\n}\r\n\r\nsimpread-urlscheme.hide,\r\nsimpread-feedback.hide {\r\n    animation-name: srFadeInDown;\r\n    animation-duration: 450ms;\r\n    animation-fill-mode: both;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-label,\r\nsimpread-feedback sr-fb-label {\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-head,\r\nsimpread-feedback sr-fb-head {\r\n    display: flex;\r\n    align-items: center;\r\n    flex-direction: row;\r\n\r\n    margin-bottom: 5px;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-content,\r\nsimpread-feedback sr-fb-content {\r\n    margin-bottom: 5px;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-footer,\r\nsimpread-feedback sr-urls-footer {\r\n    display: flex;\r\n    justify-content: flex-end;\r\n    width: 100%;\r\n}\r\n\r\nsimpread-urlscheme sr-urls-a,\r\nsimpread-feedback sr-fb-a {\r\n    color: #2163f7;\r\n    cursor: pointer;\r\n}\r\n\r\nsimpread-urlscheme text-field-state,\r\nsimpread-feedback text-field-state {\r\n    border-top: none rgba(34, 101, 247, 0.8)!important;\r\n    border-left: none rgba(34, 101, 247, 0.8)!important;\r\n    border-right: none rgba(34, 101, 247, 0.8)!important;\r\n    border-bottom: 2px solid rgba(34, 101, 247, 0.8)!important;\r\n}\r\n\r\nsimpread-urlscheme switch,\r\nsimpread-feedback switch {\r\n    margin-top: 0!important;\r\n}\r\n\r\n@keyframes srFadeInUp {\r\n    from {\r\n        opacity: 0;\r\n        transform: translateY(100px);\r\n    }\r\n\r\n    to {\r\n        opacity: 1;\r\n        transform: translateY(0);\r\n    }\r\n}\r\n\r\n@keyframes srFadeInDown {\r\n    from {\r\n        opacity: 1;\r\n        transform: translateY(0);\r\n    }\r\n\r\n    to {\r\n        opacity: 0;\r\n        transform: translateY(100px);\r\n    }\r\n}\r\n\r\n/**\r\n * Feeback\r\n */\r\n\r\nsimpread-feedback sr-fb-head {\r\n    font-weight: bold;\r\n}\r\n\r\nsimpread-feedback sr-fb-content {\r\n    display: flex;\r\n    flex-direction: column;\r\n}\r\n\r\nsimpread-feedback sr-fb-footer {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: flex-end;\r\n\r\n    width: 100%;\r\n}\r\n\r\n/**\r\n * Feeback: stars\r\n */\r\n\r\n simpread-feedback sr-close {\r\n    position: absolute;\r\n    right: 20px;\r\n    cursor: pointer;\r\n    transition: all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;\r\n    z-index: 200;\r\n}\r\n\r\nsimpread-feedback sr-close:hover {\r\n    transform: rotate(-15deg) scale(1.3);\r\n}\r\n\r\nsimpread-feedback sr-stars {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: center;\r\n\r\n    margin-top: 10px;\r\n}\r\n\r\nsimpread-feedback sr-stars {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: center;\r\n\r\n    margin-top: 10px;\r\n}\r\n\r\nsimpread-feedback sr-stars i {\r\n    margin-right: 10px;\r\n    cursor: pointer;\r\n}\r\n\r\nsimpread-feedback sr-stars i svg {\r\n    transition: all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 100ms;\r\n}\r\n\r\nsimpread-feedback sr-stars i svg:hover {\r\n    transform: rotate(-15deg) scale(1.3);\r\n}\r\n\r\nsimpread-feedback sr-stars i.active svg {\r\n    transform: rotate(0) scale(1);\r\n}\r\n\r\nsimpread-feedback sr-emojis {\r\n    display: block;\r\n    height: 100px;\r\n    overflow: hidden;\r\n}\r\n\r\nsimpread-feedback sr-emoji {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n    transition: .3s;\r\n}\r\n\r\nsimpread-feedback sr-emoji > svg {\r\n    margin: 15px 0;\r\n    width: 70px;\r\n    height: 70px;\r\n    flex-shrink: 0;\r\n}\r\n\r\nsimpread-feedback sr-stars-footer {\r\n    display: flex;\r\n    justify-content: center;\r\n    margin: 10px 0 20px 0;\r\n}"
  },
  {
    "path": "src/assets/css/theme_common.css",
    "content": "/**\r\n * Common style, include:\r\n * .simpread-theme-root\r\n * sr-rd-title, sr-rd-desc, sr-rd-content, p, div, a, img, pre, code, sr-blockquote, ul ol\r\n */\r\n \r\n.simpread-theme-root {\r\n    font-size: 62.5%!important;\r\n}\r\n\r\nsr-rd-title, sr-rd-desc, sr-rd-content {\r\n    width: 100%;\r\n}\r\n\r\nsr-rd-title {\r\n    display: -webkit-box;\r\n    margin: 1em 0px 0.5em;\r\n    overflow : hidden;\r\n    text-overflow: ellipsis;\r\n    text-rendering: optimizelegibility;\r\n    -webkit-line-clamp: 3;\r\n    -webkit-box-orient: vertical;\r\n}\r\n\r\nsr-rd-content {\r\n    text-align: left;\r\n    word-break: break-word;\r\n}\r\n\r\nsr-rd-desc {\r\n    text-align: justify;\r\n    line-height: 2.4;\r\n    margin: 0 0 1.2em 0;\r\n}\r\n\r\nsr-rd-content {\r\n    font-size: 1.6rem;\r\n    line-height: 1.6;\r\n}\r\n\r\nsr-rd-content p,\r\nsr-rd-content div {\r\n    display: block;\r\n    float: inherit;\r\n    line-height: 1.6;\r\n    font-size: 1.6rem;\r\n}\r\n\r\nsr-rd-content p,\r\nsr-rd-content div,\r\nsr-rd-content pre,\r\nsr-rd-content sr-blockquote {\r\n    margin: 0 0 1.2em 0;\r\n    word-break: break-word;\r\n    /*\r\n    word-wrap: break-word;\r\n    white-space: pre-wrap;\r\n    */\r\n}\r\n\r\nsr-rd-content a {\r\n    padding: 0 5px;\r\n    vertical-align: initial;\r\n}\r\n\r\nsr-rd-content a, sr-rd-content a:link {\r\n    color: inherit;\r\n    font-size: inherit;\r\n    font-weight: inherit;\r\n    border:none;\r\n}\r\n\r\nsr-rd-content a:hover {\r\n    background: transparent;\r\n}\r\n\r\nsr-rd-content img {\r\n    margin: 10px;\r\n    padding: 5px;\r\n\r\n    max-width: 100%;\r\n\r\n    background: #fff;\r\n    border: 1px solid #bbb;\r\n    box-shadow: 1px 1px 3px #d4d4d4;\r\n}\r\n\r\nsr-rd-content figcaption {\r\n    text-align: center;\r\n    font-size: 14px;\r\n}\r\n\r\nsr-rd-content sr-blockquote {\r\n    display: block;\r\n    position: relative;\r\n    padding: 15px 25px;\r\n    text-align: left;\r\n    line-height: inherit;\r\n}\r\n\r\nsr-rd-content sr-blockquote:before {\r\n    position: absolute;\r\n}\r\n\r\nsr-rd-content sr-blockquote * {\r\n    margin: 0;\r\n    font-size: inherit;\r\n}\r\n\r\nsr-rd-content table {\r\n    width: 100%;\r\n    margin: 0 0 1.2em 0;\r\n    word-break: keep-all;\r\n    word-break:normal;\r\n    overflow: auto;\r\n    border: none;\r\n}\r\n\r\nsr-rd-content table th, sr-rd-content table td {\r\n    border: none;\r\n}\r\n\r\nsr-rd-content ul, sr-rd-content ol {\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n\r\nsr-rd-content ul {\r\n    margin: 0 0 1.2em 0;\r\n    margin-left: 1.3em;\r\n    padding: 0;\r\n    list-style: disc;\r\n}\r\n\r\nsr-rd-content ol {\r\n    list-style: decimal;\r\n    margin-left: 1.3em;\r\n}\r\n\r\nsr-rd-content ul li, sr-rd-content ol li {\r\n    font-size: inherit;\r\n    list-style: disc;\r\n    margin: 0 0 1.2em 0;\r\n}\r\n\r\nsr-rd-content ul li *, sr-rd-content ol li * {\r\n    margin: 0;\r\n    text-align: initial;\r\n}\r\n\r\nsr-rd-content li ul, sr-rd-content li ol {\r\n    margin-bottom: 0.8em;\r\n    margin-left: 2em;\r\n}\r\n\r\nsr-rd-content li ul {\r\n    list-style: circle;\r\n}\r\n\r\nsr-rd-content pre {\r\n    font-family: Consolas, Monaco, 'Andale Mono', \"Source Code Pro\", \"Liberation Mono\", Courier, monospace;\r\n\r\n    display: block;\r\n\r\n    padding: 15px;\r\n\r\n    font-size: 1.1rem;\r\n    line-height: 1.5;\r\n\r\n    word-break: break-all;\r\n    word-wrap: break-word;\r\n    white-space: pre;\r\n\r\n    overflow: auto;\r\n}\r\n\r\nsr-rd-content pre div {\r\n    font-size: 1.1rem;\r\n}\r\n\r\nsr-rd-content pre * {\r\n    font-size: 1.1rem;\r\n}\r\n\r\nsr-rd-content pre,\r\nsr-rd-content li pre code,\r\nsr-rd-content p pre code {\r\n    background-color: transparent;\r\n    border: none;\r\n}\r\n\r\nsr-rd-content pre code {\r\n    margin: 0;\r\n    padding: 0;\r\n    font-size: 1.1rem;\r\n}\r\n\r\nsr-rd-content pre code * {\r\n    font-size: 1.1rem;\r\n}\r\n\r\nsr-rd-content pre p {\r\n    margin: 0;\r\n    padding: 0;\r\n    color: inherit;\r\n    font-size: inherit;\r\n    line-height: inherit;\r\n}\r\n\r\nsr-rd-content li code,\r\nsr-rd-content p code {\r\n    margin: 0 4px;\r\n    padding: 2px 4px;\r\n    font-size: 1.1rem;\r\n}\r\n\r\nsr-rd-content mark {\r\n    margin: 0 5px;\r\n    padding: 2px;\r\n\r\n    background: #fffdd1;\r\n    border-bottom: 1px solid #ffedce;\r\n}\r\n\r\n.sr-rd-content-img {\r\n    width: 90%;\r\n    height: auto;\r\n}\r\n\r\n.sr-rd-content-img-load {\r\n    width: 48px;\r\n    height: 48px;\r\n    margin: 0;\r\n    padding: 0;\r\n    border-style: none;\r\n    border-width: 0;\r\n    background-repeat: no-repeat;\r\n    background-image: url( \"../images/loading.gif\" );\r\n}\r\n\r\n.sr-rd-content-center {\r\n    text-align: center;\r\n    display: -webkit-box;\r\n    -webkit-box-align: center;\r\n    -webkit-box-pack: center;\r\n    -webkit-box-orient: vertical;\r\n}\r\n\r\n.sr-rd-content-center-small {\r\n    display: inline-flex;\r\n}\r\n\r\n.sr-rd-content-center-small img {\r\n    margin: 0;\r\n    padding: 0;\r\n    border: 0;\r\n    box-shadow: none;\r\n}\r\n\r\n.sr-rd-content-nobeautify {\r\n    margin: 0;\r\n    padding: 0;\r\n    border: 0;\r\n    box-shadow: 0 0 0;\r\n}\r\n\r\nsr-rd-mult {\r\n    display: flex;\r\n    flex-direction: row;\r\n\r\n    margin: 0 0 16px 0;\r\n    padding: 16px 0 24px;\r\n\r\n    width: 100%;\r\n\r\n    background-color: #fff;\r\n\r\n    border-radius: 4px;\r\n    box-shadow: 0px 1px 2px 0px rgba(60,64,67,0.3), 0px 2px 6px 2px rgba(60,64,67,0.15);\r\n}\r\n\r\nsr-rd-mult:hover {\r\n    transition: all 450ms 0ms;\r\n    box-shadow: 1px 1px 8px rgba(0, 0, 0, .16);\r\n}\r\n\r\nsr-rd-mult sr-rd-mult-content {\r\n    display: flex;\r\n    flex-direction: column;\r\n\r\n    padding: 0 16px 0;\r\n    overflow: auto;\r\n}\r\n\r\nsr-rd-mult sr-rd-mult-avatar {\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: center;\r\n\r\n    margin: 0 15px;\r\n}\r\n\r\nsr-rd-mult sr-rd-mult-avatar span {\r\n    display: -webkit-box;\r\n\r\n    -webkit-line-clamp: 1;\r\n    -webkit-box-orient: vertical;\r\n\r\n    max-width: 75px;\r\n\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    text-align: left;\r\n\r\n    font-size: 1rem;\r\n}\r\n\r\nsr-rd-mult sr-rd-mult-avatar img {\r\n    margin-bottom: 0;\r\n\r\n    max-width: 50px;\r\n    max-height: 50px;\r\n    width: 50px;\r\n    height: 50px;\r\n\r\n    border-radius: 50%;\r\n}\r\n\r\nsr-rd-mult sr-rd-mult-avatar .sr-rd-content-center {\r\n    margin: 0;\r\n}\r\n\r\nsr-page {\r\n    display: flex;\r\n    flex-direction: row;\r\n    justify-content: space-between;\r\n\r\n    width: 100%;\r\n}"
  },
  {
    "path": "src/assets/css/theme_dark.css",
    "content": "sr-rd-theme-dark{display:none;}\n\n/**\n * Dark style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{font-weight:700;color:#dbdbfd}sr-rd-content h1{font-size:3rem}sr-rd-content h2{font-size:2.8rem}sr-rd-content h3{font-size:2.5rem}sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{color:#549ad8}sr-rd-content h5{font-size:2rem}sr-rd-content h6{font-size:1.8rem}\nsr-rd-content strong {color: #ffffc5;} sr-rd-content em {color: #c885f5;}\nsr-rd-content table{width:100%;line-height:1.6rem;border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}sr-rd-content thead{background-color:#263238;color:#f5f5f5;text-align:left;vertical-align:bottom}sr-rd-content table td,sr-rd-content table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;margin:0;overflow:visible;padding:.5em 1em}\nsr-rd-content sr-blockquote {\n    background-color: rgba(128, 128, 128, 0.05);\n    border-top-right-radius: 5px;\n    border-bottom-right-radius: 5px;\n    border-left: 8px solid #979797;\n    color: rgb(235, 235, 235);\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root, .simpread-multi-root {\n    color: rgb(235, 235, 235);\n    background: rgb(34, 34, 34);\n}\n\nsr-rd-title {\n    padding-bottom: .3em;\n    font-size: 2.8rem;\n    font-weight: bold;\n    line-height: 1.2;\n    color: #dbdbfd;\n    border-bottom: 1px solid #eee;\n}\n\nsr-rd-desc {\n    margin: 20px;\n    margin-left: 0;\n    padding: 5px 20px;\n    font-size: 1.8rem;\n    background-color: rgba(128, 128, 128, 0.05);\n    color: rgb(235, 235, 235);\n    border-top-right-radius: 5px;\n    border-bottom-right-radius: 5px;\n    border-left: 8px solid #979797;\n}\n\nsr-rd-content {\n    line-height: 1.7;\n    color: rgb(235, 235, 235);\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: rgb(235, 235, 235);\n    line-height: 1.7;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #8ac9ff;\n    text-decoration: underline;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    background-color: #2a6496;\n    color: white;\n    text-decoration: none;\n}\n\nsr-rd-content pre {\n    color: #e9eded;\n    background-color: #263238;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    color: #caca16;\n    background-color: transparent;\n}\n\n/**\n * Multiple page\n */\nsr-rd-mult {\n    background-color: rgba(128, 128, 128, 0.1);\n}"
  },
  {
    "path": "src/assets/css/theme_engwrite.css",
    "content": "sr-rd-theme-engwrite{display:none;}\n\n/**\n * EngWrite style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{margin:20px 0 10px;padding:0;font-weight:500;-webkit-font-smoothing:antialiased}sr-rd-content h1{font-weight:300;text-align:center;font-size:2.8rem;color:#933d3f}sr-rd-content h2{font-size:2.4rem;border-bottom:1px solid #ccc;color:#000}sr-rd-content h3{font-size:1.8rem}sr-rd-content h4,sr-rd-content h5{font-size:1.6rem}sr-rd-content h6{color:#777;font-size:1.6rem}\nsr-rd-content ul,sr-rd-content ol{padding-left:30px}sr-rd-content ul li>:first-child,sr-rd-content ol li>:first-child,sr-rd-content ul li ul:first-of-type,sr-rd-content ol li ol:first-of-type,sr-rd-content ul li ol:first-of-type,sr-rd-content ol li ul:first-of-type{margin-top:0}sr-rd-content ul ul,sr-rd-content ul ol,sr-rd-content ol ol,sr-rd-content ol ul{margin-bottom:0}\nsr-rd-content table th{font-weight:bold}sr-rd-content table th,sr-rd-content table td{border:1px solid #ccc;padding:6px 13px}sr-rd-content table tr{border-top:1px solid #ccc;background-color:#fff}sr-rd-content table tr:nth-child(2n){background-color:#f8f8f8}\nsr-rd-content sr-blockquote {\n    text-align: left;\n    border-top: 1px dotted #cdc7bc;\n    border-bottom: 1px dotted #cdc7bc;\n    background-color: #f8edda;\n    color: #777\n}\n\nsr-blockquote >:first-child {\n    margin-top: 0\n}\n\nsr-blockquote >:last-child {\n    margin-bottom: 0\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root, .simpread-multi-root {\n    background-color: rgb(252, 245, 237);\n    color: rgb(51, 51, 51);\n}\n\nsr-rd-title {\n    font-weight: 300;\n    text-align: center;\n    font-size: 2.8rem;\n    color: rgb(147, 61, 63);\n}\n\nsr-rd-desc {\n    padding: 10px;\n    background-color: rgb(248, 237, 218);\n    color: rgb(119, 119, 119);\n    font-size: 2rem;\n    text-align: center;\n    border-top: 1px dotted rgb(205, 199, 188);\n    border-bottom: 1px dotted rgb(205, 199, 188);\n}\n\nsr-rd-content {\n    color: rgb(51, 51, 51);\n    padding: 20px 0;\n    margin: 0px auto;\n    line-height: 1.8;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: rgb(51, 51, 51);\n    line-height: 1.8;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: rgb(174, 55, 55);\n    text-decoration: none;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    text-decoration: underline;\n}\n\nsr-rd-content pre {\n    background-color: transparent;\n    border: 1px solid rgb(204, 204, 204);\n    border-radius: 3px;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    border: 1px solid rgb(234, 234, 234);\n    background-color: rgb(244, 236, 227);\n    border-radius: 3px;\n}\n\n/**\n * Multiple page\n */\nsr-rd-mult {\n    background-color: rgb(248, 237, 218);\n}"
  },
  {
    "path": "src/assets/css/theme_github.css",
    "content": "sr-rd-theme-github{display:none;}\n\n/**\n * Github style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{position:relative;margin-top:1em;margin-bottom:1pc;font-weight:700;line-height:1.4;text-align:left;color:#363636}sr-rd-content h1{padding-bottom:.3em;font-size:3.6rem;line-height:1.2}sr-rd-content h2{padding-bottom:.3em;font-size:2.8rem;line-height:1.225}sr-rd-content h3{font-size:2.4rem;line-height:1.43}sr-rd-content h4{font-size:2rem}sr-rd-content h5{font-size:1.6rem}sr-rd-content h6{font-size:1.6rem;color:#777}\nsr-rd-content ul,sr-rd-content ol{list-style-type: disc;padding:0;padding-left:2em;}sr-rd-content ol ol,sr-rd-content ul ol{list-style-type:lower-roman}sr-rd-content ul ul ol,sr-rd-content ul ol ol,sr-rd-content ol ul ol,sr-rd-content ol ol ol{list-style-type:lower-alpha}\nsr-rd-content table{width:100%;overflow:auto;word-break:normal;word-break:keep-all}sr-rd-content table th{font-weight:bold}sr-rd-content table th,sr-rd-content table td{padding:6px 13px;border:1px solid #ddd}sr-rd-content table tr{background-color:#fff;border-top:1px solid #ccc}sr-rd-content table tr:nth-child(2n){background-color:#f8f8f8}\nsr-rd-content sr-blockquote {\n    border-left: 4px solid #ddd;\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root {\n    background-color: #fff;\n    color:#333;\n}\n\nsr-rd-title {\n    font-family: \"PT Sans\",\"SF UI Display\", \".PingFang SC\",\"PingFang SC\", \"Neue Haas Grotesk Text Pro\", \"Arial Nova\", \"Segoe UI\", \"Microsoft YaHei\", \"Microsoft JhengHei\", \"Helvetica Neue\", \"Source Han Sans SC\", \"Noto Sans CJK SC\", \"Source Han Sans CN\", \"Noto Sans SC\", \"Source Han Sans TC\", \"Noto Sans CJK TC\", \"Hiragino Sans GB\", sans-serif;\n\n    font-size: 3.4rem;\n    font-weight: 700;\n    line-height: 1.3;\n}\n\nsr-rd-desc {\n    position: relative;\n    margin: 0px;\n    margin-bottom: 30px;\n    padding: 25px;\n    padding-left: 56px;\n    font-size: 1.8rem;\n    color: #777;\n    background-color: rgba(0,0,0,0.05);\n}\n\nsr-rd-desc:before {\n    content: '“';\n    position: absolute;\n    top: -9px;\n    left: 16px;\n    font-size: 80px;\n    font-family: \"Arial\";\n    color: rgba(0,0,0,0.15);\n}\n\nsr-rd-content {\n    color: #363636;\n    font-weight: 400;\n    line-height: 1.8;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: #363636;\n    font-weight: 400;\n    line-height: 1.8;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #4183c4;\n    text-decoration: none;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: #4183c4;\n    text-decoration: underline;\n}\n\nsr-rd-content pre {\n    background-color: #f7f7f7;\n    border-radius: 3px;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    background-color: rgba(0,0,0,0.04);\n    border-radius: 3px;\n}\n\n/**\n * Multiple page\n */\n\n.simpread-multi-root {\n    background: #F8F9FA;\n}"
  },
  {
    "path": "src/assets/css/theme_gothic.css",
    "content": "sr-rd-theme-gothic{display:none;}\r\n\r\n/**\r\n * Gothic style\r\n */\r\n\r\n/**\r\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\r\n */\r\n\r\nsr-rd-content h1{line-height:4rem;margin:4rem 0 1.75rem;padding:20px 30px}sr-rd-content h1,sr-rd-content h2{font-weight:400;text-align:center;text-transform:uppercase}sr-rd-content h2{line-height:3rem;margin:3rem 0 1.9375rem;padding:0 30px}sr-rd-content h3,sr-rd-content h4,sr-rd-content h5{font-weight:400}sr-rd-content h6{font-weight:700}sr-rd-content h1{font-size:3.6rem}sr-rd-content h2{font-size:3.2rem}sr-rd-content h3{font-size:2.5rem}sr-rd-content h4{font-size:2.2rem}sr-rd-content h5{font-size:1.9rem}sr-rd-content h6{font-size:1.7rem}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{margin-top:1.2em;margin-bottom:0.6em;color:#111}\r\nsr-rd-content ol,sr-rd-content ul{list-style-type:disc}sr-rd-content ul, sr-rd-content ol{margin-left:3em}sr-rd-content ol ol,sr-rd-content ul ol{list-style-type:lower-roman}sr-rd-content ol ol ol,sr-rd-content ol ul ol,sr-rd-content ul ol ol,sr-rd-content ul ul ol{list-style-type:lower-alpha}\r\nsr-rd-content table{margin-bottom:20px}sr-rd-content table th,sr-rd-content table td{padding:8px;line-height:1.25rem;vertical-align:top;border-top:1px solid #ddd}sr-rd-content table th{font-weight:bold}sr-rd-content table thead th{vertical-align:bottom}sr-rd-content table caption+thead tr:first-child th,sr-rd-content table caption+thead tr:first-child td,sr-rd-content table colgroup+thead tr:first-child th,sr-rd-content table colgroup+thead tr:first-child td,sr-rd-content table thead:first-child tr:first-child th,sr-rd-content table thead:first-child tr:first-child td{border-top:0}sr-rd-content table tbody+tbody{border-top:2px solid #ddd}\r\nsr-rd-content sr-blockquote {\r\n    margin: 0 0 1.11111rem;\r\n    padding: 0.5rem 1.11111rem 0 1.05556rem;\r\n    border-left: 1px solid gray;\r\n}\r\nsr-rd-content sr-blockquote,\r\nsr-rd-content sr-blockquote p {\r\n    line-height: 2;\r\n    color: #6f6f6f;\r\n}\r\n\r\n/**\r\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\r\n */\r\n\r\n.simpread-theme-root, .simpread-multi-root {\r\n    background: rgb(252, 252, 252);\r\n    color: #333;\r\n}\r\n\r\nsr-rd-title {\r\n    font-weight: normal;\r\n    line-height: 4rem;\r\n    text-align: center;\r\n    text-transform: uppercase;\r\n\r\n    color: rgb(17, 17, 17);\r\n    font-size: 3.2rem;\r\n}\r\n\r\nsr-rd-desc {\r\n    margin: 0px 0px 1.11111rem;\r\n    padding: 0.5rem 1.11111rem 0px 1.05556rem;\r\n\r\n    font-size: 2rem;\r\n    line-height: 2;\r\n    color: rgb(111, 111, 111);\r\n\r\n    border-left: 1px solid gray;\r\n}\r\n\r\nsr-rd-content {\r\n    font-weight: 400;\r\n    color: #333;\r\n}\r\n\r\nsr-rd-content *,\r\nsr-rd-content p,\r\nsr-rd-content div {\r\n    color: #333;\r\n}\r\n\r\nsr-rd-content a,\r\nsr-rd-content a:link {\r\n    color: rgb(153, 0, 0);\r\n    text-decoration: none;\r\n}\r\n\r\nsr-rd-content a:hover,\r\nsr-rd-content a:focus,\r\nsr-rd-content a:active {\r\n    color: rgb(153, 0, 0);\r\n    text-decoration: underline;\r\n}\r\n\r\nsr-rd-content pre {\r\n    background-color: transparent;\r\n    border: 1px solid rgb(204, 204, 204);\r\n}\r\n\r\nsr-rd-content li code,\r\nsr-rd-content p code {\r\n    background-color: transparent;\r\n    border: 1px solid rgb(204, 204, 204);\r\n}\r\n\r\n/**\r\n * Multiple page\r\n */\r\nsr-rd-mult {\r\n    background-color: #f2f2f2;\r\n}"
  },
  {
    "path": "src/assets/css/theme_mobile.css",
    "content": "/**\n * Mobile media\n */\n@media (pointer:coarse) {\n    sr-read {\n        margin: 20px 5%!important;\n        min-width: initial!important;\n        max-width: 90%!important;\n    }\n\n    sr-rd-title {\n        margin-top: 0;\n        font-size: 2.7rem;\n    }\n\n    sr-rd-desc,\n    sr-rd-content sr-blockquote {\n        margin: 10 0!important;\n        padding: 0 0 0 10px !important;\n        width: 90%;\n\n        font-size: 1.8rem;\n        font-style: normal;\n        line-height: 1.7;\n        text-align: justify;\n    }\n\n    sr-rd-content {\n        font-size: 1.75rem;\n        font-weight: 300;\n    }\n\n    sr-rd-content figure {\n        margin: 0;\n        padding: 0;\n        text-align: center;\n    }\n\n    sr-rd-content a, sr-rd-content a:link {\n          font-size: inherit;\n    }\n\n    sr-rd-content li code, sr-rd-content p code {\n          font-size: inherit;\n    }\n\n    sr-rd-footer {\n          margin-top: 20px;\n    }\n\n    sr-blockquote, sr-blockquote * {\n          margin: 5px !important;\n          padding: 5px !important;\n    }\n\n    sr-rd-title, sr-rd-content h1, sr-rd-content h2, sr-rd-content h3, sr-rd-content h4, sr-rd-content h5, sr-rd-content h6 {\n        font-family: PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;\n        color: #000;\n        font-weight: 100;\n        line-height: 1.35;\n    }\n\n    sr-rd-content h1, sr-rd-content h2, sr-rd-content h3, sr-rd-content h4, sr-rd-content h5, sr-rd-content h6,\n    sr-rd-content-h1, sr-rd-content-h2, sr-rd-content-h3, sr-rd-content-h4, sr-rd-content-h5, sr-rd-content-h6 {\n      margin-top: 1.2em;\n      margin-bottom: 0.6em;\n      line-height: 1.35;\n    }\n\n    sr-rd-content h1, sr-rd-content-h1 {\n      font-size: 1.8em;\n    }\n\n    sr-rd-content h2, sr-rd-content-h2 {\n      font-size: 1.6em;\n    }\n\n    sr-rd-content h3, sr-rd-content-h3 {\n      font-size: 1.4em;\n    }\n\n    sr-rd-content h4, sr-rd-content-h4 {\n      font-size: 1.2em;\n    }\n\n    sr-rd-content h5, sr-rd-content h6, sr-rd-content-h5, sr-rd-content-h6 {\n      font-size: 1.2em;\n    }\n\n    sr-rd-content ul, sr-rd-content-ul {\n        margin-left: 1.3em!important;\n        list-style: disc;\n    }\n\n    sr-rd-content ol, sr-rd-content-ol {\n        list-style: decimal;\n        margin-left: 1.9em!important;\n    }\n\n    sr-rd-content li ul, sr-rd-content li ol, sr-rd-content-ul ul, sr-rd-content-ul ol, sr-rd-content-ol ul, sr-rd-content-ol ol {\n        margin-bottom: 0.8em;\n        margin-left: 2em!important;;\n    }\n\n    sr-rd-content img {\n        margin: 0;\n        padding: 0;\n        border: 0;\n        max-width: 100%!important;\n        height: auto;\n        box-shadow: 0 20px 20px -10px rgba(0,0,0,0.1);\n    }\n\n    sr-rd-mult {\n        min-width: initial;\n        background-color: #fff;\n        box-shadow: 0 1px 6px rgba(32, 33, 36, 0.28);\n        border-radius: 8px;\n    }\n\n    sr-rd-mult sr-rd-mult-avatar div {\n        margin: 0;\n    }\n\n    sr-rd-mult sr-rd-mult-avatar .sr-rd-content-center-small {\n        margin: 7px 0!important;\n    }\n\n    sr-rd-mult sr-rd-mult-avatar span {\n        display: block;\n    }\n\n    sr-rd-mult sr-rd-mult-content {\n        padding-left: 0;\n    }\n\n    /**\n    * iPad pro 12 and below\n    */\n    @media only screen and (max-device-width: 1024px) {\n        html.simpread-theme-root, .simpread-theme-root {\n            font-size: 80%!important;\n        }\n\n        sr-rd-mult sr-rd-mult-avatar img {\n            width: 50px;\n            height: 50px;\n            min-width: 50px;\n            min-height: 50px;\n        }\n    }\n\n    /**\n    * iPhone XS Max and below\n    */\n    @media only screen and (max-device-width: 414px) {\n        html.simpread-theme-root, .simpread-theme-root {\n            font-size: 70%!important;\n        }\n\n        sr-rd-mult sr-rd-mult-avatar img {\n            width: 30px;\n            height: 30px;\n            min-width: 30px;\n            min-height: 30px;\n        }\n    }\n\n    /**\n    * iPhone SE and below\n    */\n    @media only screen and (max-device-width: 320px) {\n        html.simpread-theme-root, .simpread-theme-root {\n            font-size: 90%!important;\n        }\n\n        sr-rd-content p {\n            margin-bottom: .5em;\n        }\n    }\n}\n"
  },
  {
    "path": "src/assets/css/theme_monospace.css",
    "content": "sr-rd-theme-monospace{display:none;}\n\n/**\n * Monospace style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{font-weight:700;color:#6363ac}sr-rd-content h1{font-size:2.2rem}sr-rd-content h2{font-size:2rem}sr-rd-content h3{font-size:1.8rem}sr-rd-content h4{font-size:1.6rem}sr-rd-content h5{font-size:1.4rem}sr-rd-content h6{font-size:1.3rem}\nsr-rd-content strong{color:#b5302e}sr-rd-content em{font-style:italic;color:#400469}\nsr-rd-content ol,sr-rd-content ul{list-style-type:none}sr-rd-content ol li,sr-rd-content ul li{margin:0}\nsr-rd-content table{line-height:1.6rem;border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}sr-rd-content thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}sr-rd-content td,sr-rd-content th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;margin:0;overflow:visible;padding:.5em 1em}\nsr-rd-content sr-blockquote {\n    background-color: rgba(128, 128, 128, 0.05);\n    border-top-right-radius: 5px;\n    border-bottom-right-radius: 5px;\n    border-left: 8px solid #979797;\n    line-height: 2;\n}\n\nsr-rd-content sr-blockquote * {\n    line-height: inherit;\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root {\n    color: #333333;\n    background: white;\n}\n\nsr-rd-title {\n    font-size: 2.8rem;\n    line-height: 1.2;\n    font-weight: bold;\n    color: #6363ac;\n}\n\nsr-rd-desc {\n    padding: 10px;\n    background-color: rgba(128, 128, 128, 0.05);\n    font-size: 1.8rem;\n    text-align: center;\n    border-top-right-radius: 5px;\n    border-bottom-right-radius: 5px;\n    border-left: 8px solid #979797;\n}\n\nsr-rd-content {\n    color: #333333;\n    line-height: 1.7;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    line-height: 1.7;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #005dad;\n    text-decoration: underline;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: white;\n    background-color: #2a6496;\n    text-decoration: none;\n}\n\nsr-rd-content pre {\n    color: #e9eded;\n    background-color: #263238;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    color: #949415;\n    background-color: transparent;\n}\n\n/**\n * Multiple page\n */\n\n.simpread-multi-root {\n    background: #F8F9FA;\n}"
  },
  {
    "path": "src/assets/css/theme_newsprint.css",
    "content": "sr-rd-theme-newsprint{display:none;}\n\n/**\n * Newsprint style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{font-weight:700}sr-rd-content h1{font-size:3rem;line-height:1.6em;margin-top:2em}sr-rd-content h2,sr-rd-content h3{font-size:2rem;line-height:1.15;margin-top:2.285714em;margin-bottom:1.15em}sr-rd-content h3{font-weight:400}sr-rd-content h4{font-size:1.8rem;margin-top:2.67em}sr-rd-content h5,sr-rd-content h6{font-size:1.6rem}sr-rd-content h1{border-bottom:1px solid;margin-bottom:1.875em;padding-bottom:.8125em}\nsr-rd-content ul, sr-rd-content ol{margin:0 0 1.5em 1.5em}sr-rd-content ol li{list-style-type:decimal;list-style-position:outside}sr-rd-content ul li{list-style-type:disc;list-style-position:outside}\nsr-rd-content table{width:100%;margin-bottom:1.5em;font-size:1.6rem;}sr-rd-content thead th,tfoot th{padding:.25em .25em .25em .4em;text-transform:uppercase}sr-rd-content th{text-align:left}sr-rd-content td{vertical-align:top;padding:.25em .25em .25em .4em}sr-rd-content thead{background-color:#dadada}sr-rd-content tr:nth-child(2n){background:#e8e7e7;}\nsr-rd-content sr-blockquote {\n    padding: 10px 15px;\n    border-left-style: solid;\n    border-left-width: 10px;\n    border-color: #D6DBDF;\n    background: none repeat scroll 0 0 rgba(102,128,153,.05);\n    text-align: left;\n}\n\nsr-rd-content sr-blockquote:before {\n    content: '';\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root, .simpread-multi-root {\n    background-color: #f3f2ee;\n    color: #2C3E50;\n}\n\nsr-rd-title {\n    font-family: PingFang SC,Hiragino Sans GB,Microsoft Yahei,WenQuanYi Micro Hei,sans-serif;\n\n    line-height: 1.5;\n    font-weight: 500;\n    font-size: 3rem;\n\n    color: #0077bb;\n    border-bottom: 1px solid;\n\n    margin-bottom: 1.875em;\n    padding-bottom: .8125em;\n}\n\nsr-rd-desc {\n    color: rgba(102,128,153,0.6);\n    background-color: rgba(102,128,153,0.075);\n    border-radius: 4px;\n\n    margin-bottom: 1em;\n    padding: 15px;\n\n    font-size: 2rem;\n    line-height: 1.5;\n    text-align: center;\n}\n\nsr-rd-content {\n    line-height: 1.8;\n    color: #2C3E50;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: #2C3E50;\n    line-height: 1.8;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #08c;\n    text-decoration: none;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: #5BA4E5;\n}\n\nsr-rd-content pre {\n    background-color: rgb(218, 218, 218);\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    background-color: rgb(218, 218, 218);\n}\n\n/**\n * Multiple page\n */\nsr-rd-mult {\n    background-color: rgba(102,128,153,0.075);\n}"
  },
  {
    "path": "src/assets/css/theme_night.css",
    "content": "sr-rd-theme-night{display:none;}\n\n/**\n * Night style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1{margin-top:2em}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{color:#dedede}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{font-weight:400;clear:both;-ms-word-wrap:break-word;word-wrap:break-word;margin:0;padding:0}sr-rd-content h1{font-size:3.6rem;line-height:4rem;margin-bottom:2.4rem;letter-spacing:-1.5px}sr-rd-content h2{font-size:2.4rem;line-height:3rem}sr-rd-content h2,sr-rd-content h3{margin-bottom:2.4rem;letter-spacing:-1px}sr-rd-content h3{font-size:1.8rem;line-height:2.4rem}sr-rd-content h4{font-size:1.6rem;line-height:2.2rem;margin-bottom:2.4rem}sr-rd-content h5{font-size:1rem;line-height:1.25rem;margin-bottom:1.5rem}sr-rd-content h6{font-size:1.6rem;line-height:1.6rem;margin-bottom:.75rem;font-weight:700}\nsr-rd-content ul,sr-rd-content ol{padding:0 0 0 1.875rem}sr-rd-content ul{list-style:square}sr-rd-content ol{list-style:decimal}sr-rd-content ul ul,sr-rd-content ol ol,sr-rd-content ul ol,sr-rd-content ol ul{margin:0}sr-rd-content li div{padding-top:0}sr-rd-content li p,sr-rd-content li{margin:0;position:relative}\nsr-rd-content table{margin-top:0;margin-bottom:1.5rem;border-collapse:collapse;border-spacing:0;page-break-inside:auto;text-align:left}sr-rd-content table a{color:#dedede}sr-rd-content thead{display:table-header-group}sr-rd-content table td,sr-rd-content table th{border:1px solid #474d54}\nsr-rd-content sr-blockquote {\n    margin: 0 0 1.875rem 1.875rem;\n    border-left: solid 2px #474d54;\n    padding-left: 30px;\n    margin-top: 35px;\n    line-height: 2;\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root, .simpread-multi-root {\n    background: rgb(54, 59, 64);\n    color: #b8bfc6;\n}\n\nsr-rd-title {\n    color: #DEDEDE;\n    font-size: 3.15rem;\n    line-height: 3.5rem;\n    letter-spacing: -1.5px;\n}\n\nsr-rd-desc {\n    margin: 35px;\n    margin-left: 0;\n    padding-left: 1.875rem;\n    font-size: 2rem;\n    line-height: 2;\n    color: #b8bfc6;\n    border-left: solid 2px #474d54;\n}\n\nsr-rd-content {\n    color: #b8bfc6;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: #b8bfc6;\n    margin-top: 0;\n    line-height: 2;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #e0e0e0;\n    text-decoration: underline;\n    transition: all .2s ease-in-out;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: #fff;\n}\n\nsr-rd-content pre {\n    background-color: transparent;\n    border: 1px solid;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    background: rgba(0, 0, 0, 0.05);\n}\n\n/**\n * Multiple page\n */\nsr-rd-mult {\n    background-color: #2d3034;\n}"
  },
  {
    "path": "src/assets/css/theme_octopress.css",
    "content": "sr-rd-theme-octopress{display:none;}\n\n/**\n * Octopress style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1{font-size:3.52rem;line-height:1.92rem}sr-rd-content h1,sr-rd-content h2,sr-rd-content h3,sr-rd-content h4,sr-rd-content h5,sr-rd-content h6{text-rendering:optimizelegibility;margin-bottom:1.3rem;font-weight:700}sr-rd-content h2{font-size:2.4rem}sr-rd-content h3{font-size:2.08rem}sr-rd-content h4{font-size:1.8rem}sr-rd-content h5,sr-rd-content h6{font-size:1.6rem}sr-rd-content h1,sr-rd-content h2{padding-top:1.7rem;padding-bottom:1.2rem;background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAABCAYAAACsXeyTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAFUlEQVQIHWNIS0sr/v//PwMMDzY+ADqMahlW4J91AAAAAElFTkSuQmCC') bottom left repeat-x}sr-rd-content h2{padding-top:1.3rem;padding-bottom:0}\nsr-rd-content ul{list-style-type:disc}sr-rd-content ul ul{list-style-type:circle;margin-bottom:0}sr-rd-content ul ul ul{list-style-type:square;margin-bottom:0}sr-rd-content ol{list-style-type:decimal}sr-rd-content ol ol{list-style-type:lower-alpha;margin-bottom:0}sr-rd-content ol ol ol{list-style-type:lower-roman;margin-bottom:0}sr-rd-content ul,sr-rd-content ul ul,sr-rd-content ul ol,sr-rd-content ol,sr-rd-content ol ul,sr-rd-content ol ol{margin-left:1.3em}sr-rd-content ul ul,sr-rd-content ul ol,sr-rd-content ol ul,sr-rd-content ol ol{margin-bottom:0}\nsr-rd-content table{width:100%;overflow:auto;word-break:normal;word-break:keep-all}sr-rd-content table th{font-weight:bold}sr-rd-content table th,sr-rd-content table td{padding:6px 13px;border:1px solid #ddd}sr-rd-content table tr{background-color:#fff;border-top:1px solid #ccc}sr-rd-content table tr:nth-child(2n){background-color:#f8f8f8}\nsr-rd-content sr-blockquote {\n    font-style: italic;\n    font-size: inherit;\n    line-height: 2;\n    padding-left: 1em;\n    border-left: 4px solid rgba(170,170,170,0.5);\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root, .simpread-multi-root {\n    background: #f8f8f8 url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC') top left;\n    color: #333;\n}\n\nsr-rd-title {\n    font-size: 3.52rem;\n    line-height: 4rem;\n    font-weight: bold;\n    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAABCAYAAACsXeyTAAAACXBIW…sTAAALEwEAmpwYAAAAFUlEQVQIHWNIS0sr/v//PwMMDzY+ADqMahlW4J91AAAAAElFTkSuQmCC) bottom left repeat-x;\n}\n\nsr-rd-desc {\n    font-style: italic;\n    font-size: 1.92rem;\n    line-height: 2;\n    padding-left: 1em;\n    border-left: 4px solid rgba(170,170,170,0.5);\n}\n\nsr-rd-content {\n    line-height: 2;\n    color: #333;\n    margin: 0 auto;\n    padding: 1em 0;\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    line-height: 2;\n    color: #333;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: #1863a1;\n    text-decoration: underline;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: #0181eb;\n    text-decoration: underline;\n}\n\nsr-rd-content pre {\n    color: #586e75;\n    background-color: #fdf6e3;\n    border-radius: 0.4em;\n    border: 1px solid #e7dec3;\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    color: #555;\n    background-color: transparent;\n    border: 1px solid #ddd;\n}\n\n/**\n * Multiple page\n */\nsr-rd-mult {\n    background-color: #ededed;\n}"
  },
  {
    "path": "src/assets/css/theme_pixyii.css",
    "content": "sr-rd-theme-pixyii{display:none;}\n\n/**\n * Pixyii style\n */\n\n/**\n * Common style, include: h1 ~ h6; ol ul; code pre; table; sr-blockquote\n */\n\nsr-rd-content h1,sr-rd-content h1 *,sr-rd-content h2,sr-rd-content h2 *,sr-rd-content h3,sr-rd-content h3 *,sr-rd-content h4,sr-rd-content h4 *,sr-rd-content h5,sr-rd-content h5 *,sr-rd-content h6,sr-rd-content h6 *{color:inherit;font-weight:900;line-height:1.2;margin:1em 0 1em}sr-rd-content h1,sr-rd-content h1 *{font-size:3.92rem}sr-rd-content h2,sr-rd-content h2 *{font-size:3.64rem}sr-rd-content h3,sr-rd-content h3 *{font-size:2.275rem}sr-rd-content h4,sr-rd-content h4 *{font-size:1.82rem}sr-rd-content h5,sr-rd-content h5 *,sr-rd-content h6,sr-rd-content h6 *{font-size:1.573rem}\nsr-rd-content ol,sr-rd-content ul{font-size:1.75rem;line-height:1.5rem}sr-rd-content li{font-size:1.575rem;line-height:1.8;margin:0;position:relative}\nsr-rd-content table{width:100%;font-size:1.575rem}sr-rd-content table>thead>tr>th,sr-rd-content table>thead>tr>td,sr-rd-content table>tbody>tr>th,sr-rd-content table>tbody>tr>td,sr-rd-content table>tfoot>tr>th,sr-rd-content table>tfoot>tr>td{padding:12px;line-height:1.2;vertical-align:top;border-top:1px solid #333}sr-rd-content table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #333}sr-rd-content table>caption+thead>tr:first-child>th,sr-rd-content table>caption+thead>tr:first-child>td,sr-rd-content table>colgroup+thead>tr:first-child>th,sr-rd-content table>colgroup+thead>tr:first-child>td,sr-rd-content table>thead:first-child>tr:first-child>th,sr-rd-content table>thead:first-child>tr:first-child>td{border-top:0}sr-rd-content table>tbody+tbody{border-top:2px solid #333}\nsr-rd-content sr-blockquote {\n    margin: 1rem 0px;\n    padding: 1.33em;\n    font-style: italic;\n    border-left: 5px solid rgb(122, 122, 122);\n    color: rgb(85, 85, 85);\n}\n\n/**\n * Custom style, include: background; title; desc; sr-rd-content; p,div; a; pre\n */\n\n.simpread-theme-root {\n    background-color: rgb(255, 255, 255);\n    color: rgb(85, 85, 85);\n}\n\nsr-rd-title {\n    font-family: PingFang SC,Hiragino Sans GB,Microsoft Yahei,WenQuanYi Micro Hei,sans-serif;\n    font-size: 4.2rem;\n    font-weight: 900;\n    line-height: 1.2;\n}\n\nsr-rd-desc {\n    margin: 1rem 0px;\n    padding: 1.33em;\n    font-style: italic;\n    font-size: 2rem;\n    line-height: 2;\n    border-left: 5px solid rgb(122, 122, 122);\n    color: rgb(85, 85, 85);\n}\n\nsr-rd-content {\n    font-size: 2.1rem;\n    line-height: 1.8;\n    font-weight: 400;\n    color: rgb(85, 85, 85);\n}\n\nsr-rd-content *,\nsr-rd-content p,\nsr-rd-content div {\n    color: rgb(85, 85, 85);\n    font-size: 1.75rem;\n    line-height: 1.8;\n    font-weight: 300;\n}\n\nsr-rd-content strong {\n    font-weight: 400;\n}\n\nsr-rd-content a,\nsr-rd-content a:link {\n    color: rgb(70, 63, 92);\n    text-decoration: underline;\n}\n\nsr-rd-content a:hover,\nsr-rd-content a:focus,\nsr-rd-content a:active {\n    color: rgb(70, 63, 92);\n    text-decoration: underline;\n}\n\nsr-rd-content sr-blockquote code {\n    font-size: inherit;\n}\n\nsr-rd-content pre {\n    color: rgb(122, 122, 122);\n    background-color: transparent;\n    border: 1px solid rgb(122, 122, 122);\n}\n\nsr-rd-content li code,\nsr-rd-content p code {\n    color: rgb(122, 122, 122);\n    background-color: transparent;\n}\n\n/**\n * Multiple page\n */\n\n .simpread-multi-root {\n    background: #F8F9FA;\n}"
  },
  {
    "path": "src/background.js",
    "content": "console.log( \"=== simpread background load ===\" )\r\n\r\nimport local       from 'local';\r\nimport { storage } from 'storage';\r\nimport * as msg    from 'message';\r\nimport {browser}   from 'browser';\r\nimport * as ver    from 'version';\r\nimport * as menu   from 'menu';\r\nimport * as watch  from 'watch';\r\nimport * as WebDAV from 'webdav';\r\nimport * as permission\r\n                   from 'permission';\r\nimport * as tips   from 'tips';\r\nimport PureRead    from 'puread';\r\n\r\n// global update site tab id\r\nlet upTabId = -1;\r\n\r\n/**\r\n * Sevice: storage Get data form chrome storage\r\n */\r\nstorage.Read( () => {\r\n    storage.puread = new PureRead( storage.sites );\r\n    if ( local.Firstload() ) {\r\n        local.Version( ver.version );\r\n        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#firstload?ver=\" + ver.version ) });\r\n    }\r\n    else {\r\n       !local.Count() && storage.GetRemote( \"remote\", ( result, error ) => {\r\n            if ( !error ) {\r\n                storage.pr.Addsites( result );\r\n                storage.Writesite( storage.pr.sites, getNewsitesHandler );\r\n            }\r\n        });\r\n        ver.version != storage.version && storage.GetRemote( \"local\", ( result, error ) => {\r\n            storage.pr.Addsites( result );\r\n            storage.Writesite( storage.pr.sites, () => {\r\n                ver.version != storage.version &&\r\n                storage.Fix( storage.read.sites, storage.version, ver.version, storage.focus.sites );\r\n                ver.version != storage.version && storage.Write( () => {\r\n                        local.Version( ver.version );\r\n                        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#update?ver=\" + ver.version ) });\r\n                }, ver.Verify( storage.version, storage.simpread ) );\r\n                getNewsitesHandler( result );\r\n            });\r\n        });\r\n        ver.version == storage.version && ver.patch != storage.patch &&\r\n            storage.Write(()=> {\r\n                // when x.x.x.yyyy is silent update\r\n                //browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#update?patch=\" + ver.patch ) });\r\n                //localStorage.setItem( \"simpread-patch-update\", true );\r\n                local.Patch( \"add\", true );\r\n            }, ver.FixSubver( ver.patch, storage.simpread ));\r\n    }\r\n    menu.CreateAll();\r\n    setTimeout( ()=>uninstall(), 100 );\r\n});\r\n\r\n/**\r\n * Get newsites handler\r\n * @param {object} count: update site cou\r\n */\r\nfunction getNewsitesHandler( result ) {\r\n    watch.Push( \"site\", true );\r\n}\r\n\r\n/**\r\n * Listen menu event handler\r\n */\r\nmenu.OnClicked( ( info, tab ) => {\r\n    console.log( \"background contentmenu Listener\", info, tab );\r\n    tracked({ eventCategory: \"menu\", eventAction: \"menu\", eventValue: info.menuItemId });\r\n    if ( info.menuItemId == \"link\" ) {\r\n        info.linkUrl && browser.tabs.create({ url: info.linkUrl + \"?simpread_mode=read\" });\r\n    } else if ( info.menuItemId == \"list\" ) {\r\n        browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#later\" ) });\r\n    } else if ( info.menuItemId == \"whitelist\" ) {\r\n        browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.menu_whitelist, {url: info.pageUrl } ));\r\n    } else if ( info.menuItemId == \"exclusion\" ) {\r\n        browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.menu_exclusion, {url: info.pageUrl } ));\r\n    } else if ( info.menuItemId == \"blacklist\" ) {\r\n        browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.menu_blacklist, {url: info.pageUrl } ));\r\n    } else if ( info.menuItemId == \"unrdist\" ) {\r\n        browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.menu_unrdist, {url: info.pageUrl } ));\r\n    } else if ( info.menuItemId == \"lazyload\" ) {\r\n        browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.menu_lazyload, {url: info.pageUrl } ));\r\n    } else {\r\n        if ( !tab.url.startsWith( \"chrome://\" ) ) browser.tabs.sendMessage( tab.id, msg.Add(info.menuItemId));\r\n    }\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `corb`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.CORB ) {\r\n        $.ajax( request.value.settings )\r\n            .done( result => {\r\n                sendResponse({ done: result });\r\n            })\r\n            .fail( ( jqXHR, textStatus, errorThrown ) => {\r\n                sendResponse({ fail: { jqXHR, textStatus, errorThrown }});\r\n            });\r\n    }\r\n    return true;\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `jianguo`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.jianguo ) {\r\n        const { url, user, password, method } = request.value;\r\n        const dav = new WebDAV.Fs( url, user, password );\r\n        if ( method.type == \"folder\" ) {\r\n            dav.dir( method.root ).mkdir( result => {\r\n                dav.dir( method.root + \"/\" + method.folder ).mkdir( result => {\r\n                    sendResponse({ done: result, status: result.status });\r\n                });\r\n            })\r\n        } else if ( method.type == \"file\" ) {\r\n            dav.file( method.path ).write( method.content, result => {\r\n                sendResponse({ done: result, status: result.status });\r\n            });\r\n        } else if ( method.type == \"read\" ) {\r\n            dav.file( method.path ).read( result => {\r\n                sendResponse({ done: result.response, status: result.status });\r\n            });\r\n        }\r\n    }\r\n    //return true;\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `webdav`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.WebDAV ) {\r\n        const { url, user, password, method } = request.value;\r\n        const dav = new WebDAV.Fs( url, user, password );\r\n        if ( method.type == \"folder\" ) {\r\n            dav.dir( method.root ).mkdir( result => {\r\n                sendResponse({ done: result, status: result.status });\r\n            })\r\n        } else if ( method.type == \"file\" ) {\r\n            dav.file( method.root + \"/\" + method.name ).write( method.content, result => {\r\n                sendResponse({ done: result, status: result.status });\r\n            });\r\n        }\r\n    }\r\n    //return true;\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `download`, `base64` && `permission`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.download ) {\r\n        const { data, name } = request.value;\r\n        const blob = new Blob([data], {\r\n            type: \"html/plain;charset=utf-8\"\r\n        });\r\n        const url = URL.createObjectURL(blob);\r\n        browser.downloads.download({\r\n            url     : url,\r\n            filename: name.replace( /[|]/ig, \"\" ),\r\n        }, downloadId => {\r\n            sendResponse({ done: downloadId });\r\n        });\r\n    } else if ( request.type == msg.MESSAGE_ACTION.base64 ) {\r\n        const { url } = request.value;\r\n        fetch( url )\r\n            .then( response => response.blob() )\r\n            .then( blob     => new Promise(( resolve, reject ) => {\r\n                const reader = new FileReader()\r\n                reader.onloadend = event => {\r\n                    sendResponse({ done: { url, uri: event.target.result }});\r\n                };\r\n                reader.onerror = error => {\r\n                    sendResponse({ fail: { error, url } });\r\n                };\r\n                reader.readAsDataURL( blob );\r\n            }))\r\n            .catch( error => {\r\n                sendResponse({ fail: { error, url } });\r\n            });\r\n    } else if ( request.type == msg.MESSAGE_ACTION.permission ) {\r\n        permission.Get({ permissions: [ \"downloads\" ] }, result => {\r\n            sendResponse({ done: result });\r\n        });\r\n    }\r\n    return true;\r\n});\r\n\r\n/**\r\n * Listen runtime message, include: `snapshot`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.snapshot ) {\r\n        const { left, top, width, height } = request.value;\r\n        chrome.tabs.captureVisibleTab( { format: \"png\" }, base64 => {\r\n            const image  = new Image();\r\n            image.src    = base64;\r\n            image.onload = () => {\r\n                const canvas  = document.createElement( \"canvas\" ),\r\n                      ctx     = canvas.getContext( \"2d\" ),\r\n                      dpi     = window.devicePixelRatio,\r\n                      sx      = left   * dpi,\r\n                      sy      = top    * dpi,\r\n                      sWidth  = width  * dpi,\r\n                      sHeight = height * dpi;\r\n                canvas.width  = sWidth;\r\n                canvas.height = sHeight;\r\n                ctx.drawImage( image, sx, sy, sWidth, height * dpi, 0, 0, sWidth, sHeight );\r\n                const uri     = canvas.toDataURL( \"image/png\" );\r\n                sendResponse({ done: uri });\r\n          };\r\n        });\r\n    }\r\n    return true;\r\n});\r\n\r\n/**\r\n * Listen runtime message\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    console.log( \"background runtime Listener\", request );\r\n    switch ( request.type ) {\r\n        case msg.MESSAGE_ACTION.shortcuts:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                browser.tabs.sendMessage( tabs[0].id, msg.Add( msg.MESSAGE_ACTION.shortcuts ));\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.browser_action:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                if ( tabs && tabs.length > 0 && tabs[0].url == request.value.url ) {\r\n                    setMenuAndIcon( tabs[0].id, request.value.code );\r\n                } else console.error( request );\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.new_tab:\r\n            browser.tabs.create({ url: request.value.url });\r\n            break;\r\n        case msg.MESSAGE_ACTION.close_tab:\r\n            getCurTab( { \"active\": true }, tabs => {\r\n                tabs.forEach( tab => {\r\n                    tab.active && tab.url == request.value.url &&\r\n                        browser.tabs.remove( tab.id );\r\n                });\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.menu:\r\n            const { id, value } = request.value;\r\n            // hack code refresh options menu changed, and not saved storage\r\n            storage.option.menu[id] = value;\r\n            value === true ? menu.Create( id ) : menu.Remove( id );\r\n            break;\r\n        case msg.MESSAGE_ACTION.updated:\r\n            watch.Push( request.value.type, request.value.value );\r\n            break;\r\n        case msg.MESSAGE_ACTION.save_verify:\r\n            sendResponse( watch.Lock( request.value.url ));\r\n            break;\r\n        case msg.MESSAGE_ACTION.auth:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#labs?auth=\" + request.value.name.toLowerCase() ) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.update_site:\r\n            getCurTab({ active: true, url: request.value.url }, tabs => {\r\n                tabs.length > 0 && ( upTabId = tabs[0].id );\r\n                browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?update=\" + encodeURI( JSON.stringify( request.value.site ))) });\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.save_site:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?pending=\" + encodeURI( JSON.stringify( request.value ))) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.temp_site:\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?temp=\" + encodeURI( JSON.stringify( request.value ))) });\r\n            break;\r\n        case msg.MESSAGE_ACTION.auth_success:\r\n            getCurTab( { url: request.value.url }, tabs => {\r\n                if ( tabs && tabs.length > 0 ) {\r\n                    browser.tabs.remove( tabs[0].id );\r\n                    getCurTab( { \"active\": true }, tabs => {\r\n                        tabs.forEach( tab => browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.export, {type: request.value.name.toLowerCase()} )) );\r\n                    });\r\n                }\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.track:\r\n            tracked( request.value );\r\n            break;\r\n        case msg.MESSAGE_ACTION.speak:\r\n            browser.tts.speak( request.value.content );\r\n            break;\r\n        case msg.MESSAGE_ACTION.speak_stop:\r\n            browser.tts.stop();\r\n            break;\r\n        case msg.MESSAGE_ACTION.tips:\r\n            tips.Verify( request.value.code, sendResponse );\r\n            break;\r\n        case msg.MESSAGE_ACTION.tips_norepeat:\r\n            tips.Done( request.value.code );\r\n            break;\r\n    }\r\n});\r\n\r\n/**\r\n * Listen chrome tab active message, include: `tab_selected`\r\n */\r\nbrowser.tabs.onActivated.addListener( function( active ) {\r\n    getCurTab( { \"active\": true, \"currentWindow\": true }, tabs => {\r\n        if ( tabs && tabs.length > 0 && tabs[0].status == \"complete\" ) {\r\n            console.log( \"background tabs Listener:active\", active );\r\n            if ( tabs && tabs.length > 0 && !tabs[0].url.startsWith( \"chrome://\" ) ) {\r\n                browser.tabs.sendMessage( tabs[0].id, msg.Add( msg.MESSAGE_ACTION.tab_selected, { is_update: false } ));\r\n            } else {\r\n                setMenuAndIcon( tabs[0].id, -1 );\r\n            }\r\n        } else console.error( \"onActivated.addListener error\" );\r\n    });\r\n});\r\n\r\n/**\r\n * Listen chrome tab update message, include: `tab_selected`\r\n */\r\nbrowser.tabs.onUpdated.addListener( function( tabId, changeInfo, tab ) {\r\n    watch.Pull( tabId );\r\n    if ( changeInfo.status == \"complete\" ) {\r\n        console.log( \"background tabs Listener:update\", tabId, changeInfo, tab );\r\n\r\n        if ( tab.url.startsWith( \"http://ksria.com/simpread/auth.html\" )) {\r\n            const url = tab.url.replace( \"http://ksria.com/simpread/auth.html?id=\", \"\" ),\r\n                  id  = url.includes( \"#\" ) || url.includes( \"&\" ) ? url.substr( 0, url.search( /\\S(#|&)/ ) + 1 ) : url ;\r\n            browser.tabs.query( {}, tabs => {\r\n                const opts = tabs.find( tab => tab.url.includes( browser.extension.getURL( \"options/options.html\" ) ));\r\n                if ( opts ) {\r\n                    browser.tabs.sendMessage( opts.id, msg.Add( msg.MESSAGE_ACTION.redirect_uri, { uri: tab.url, id } ));\r\n                    browser.tabs.remove( tabId );\r\n                }\r\n            });\r\n        } else if ( tab.url.startsWith( \"https://simpread.ksria.cn/plugins/install/\" )) {\r\n            const url = tab.url.replace( \"https://simpread.ksria.cn/plugins/install/\", \"\" );\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#plugins?install=\" + encodeURIComponent(url) ) });\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url.startsWith( \"https://simpread.ksria.cn/sites/install/\" )) {\r\n            const url = tab.url.replace( \"https://simpread.ksria.cn/sites/install/\", \"\" );\r\n            browser.tabs.create({ url: browser.extension.getURL( \"options/options.html#sites?install=\" + encodeURIComponent(url) ) });\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=success\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n            upTabId > 0 && chrome.tabs.reload( upTabId, () => { upTabId == -1; });\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=failed\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=complete\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n        } else if ( tab.url == browser.runtime.getURL( \"options/options.html#sites?update=pending\" ) ) {\r\n            browser.tabs.remove( tabId );\r\n            upTabId > 0 && browser.tabs.sendMessage( upTabId, msg.Add( msg.MESSAGE_ACTION.pending_site ));\r\n            upTabId == -1;\r\n        }\r\n\r\n        if ( !tab.url.startsWith( \"chrome://\" ) ) {\r\n            browser.tabs.sendMessage( tabId, msg.Add( msg.MESSAGE_ACTION.tab_selected, { is_update: true } ));\r\n        } else {\r\n            setMenuAndIcon( tab.id, -1 );\r\n        }\r\n    }\r\n});\r\n\r\n/**\r\n * Listen chrome tab remove message\r\n */\r\nbrowser.tabs.onRemoved.addListener( tabId => watch.Pull( tabId ));\r\n\r\n/**\r\n * Listen chrome page, include: `read`\r\n */\r\nbrowser.pageAction.onClicked.addListener( function( tab ) {\r\n    browser.tabs.sendMessage( tab.id, msg.Add( msg.MESSAGE_ACTION.browser_click ));\r\n});\r\n\r\n/**\r\n * Get current tab object\r\n * \r\n * @param {object}   query\r\n * @param {function} callback\r\n */\r\nfunction getCurTab( query, callback ) {\r\n    if ( query.url && query.url.includes( \"#\" ) ) {\r\n        browser.tabs.query( {}, tabs => callback( tabs.filter( tab => tab.url == query.url && tab.active ) ) );\r\n    } else browser.tabs.query( query, function( tabs ) { callback( tabs ); });\r\n}\r\n\r\n/**\r\n * Set page action icon and context menu\r\n * \r\n * @param {int} tab.id\r\n * @param {int} -1: disable icon;\r\n */\r\nfunction setMenuAndIcon( id, code ) {\r\n    let icon = \"\";\r\n    if ( code == -1 ) {\r\n        browser.pageAction.hide( id );\r\n        menu.Update( \"tempread\" );\r\n    } else {\r\n        icon = \"-enable\";\r\n        browser.pageAction.show( id );\r\n        storage.option.menu.read === true && menu.Create( \"read\" );\r\n        menu.Update( \"read\" );\r\n    }\r\n    browser.pageAction.setIcon({ tabId: id, path: browser.extension.getURL( `assets/images/icon16${icon}.png` ) });\r\n}\r\n\r\n/**\r\n * Track\r\n * \r\n * @param {object} google analytics track object\r\n */\r\nfunction tracked({ eventCategory, eventAction, eventValue }) {\r\n    console.log( \"current track is\", eventCategory, eventAction, eventValue )\r\n    _gaq.push([ '_trackEvent', eventCategory, eventValue ]);\r\n}\r\n\r\n/**\r\n * Uninstall\r\n */\r\nfunction uninstall() {\r\n    browser.runtime.setUninstallURL( storage.option.uninstall ? storage.service + \"/uninstall\" : \"\" );\r\n    tracked({ eventCategory: \"install\", eventAction: \"install\", eventValue: \"uninstall\" });\r\n}"
  },
  {
    "path": "src/contentscripts.js",
    "content": "console.log( \"=== simpread contentscripts load ===\" )\r\n\r\nimport './assets/css/simpread.css';\r\nimport './assets/css/setting.css';\r\nimport 'notify_css';\r\n\r\nimport Velocity       from 'velocity';\r\nimport Notify         from 'notify';\r\n\r\nimport {focus}        from 'focus';\r\nimport * as read      from 'read';\r\nimport * as setting   from 'setting';\r\nimport * as kbd       from 'keyboard';\r\nimport * as highlight from 'highlight';\r\nimport * as scheme    from 'urlscheme';\r\n\r\nimport * as util      from 'util';\r\nimport { storage, STORAGE_MODE as mode } from 'storage';\r\nimport * as msg       from 'message';\r\nimport {browser}      from 'browser';\r\nimport * as watch     from 'watch';\r\n\r\nimport PureRead       from 'puread';\r\nimport * as puplugin  from 'puplugin';\r\n\r\nlet pr,                           // pure read object\r\n    is_blacklist = false,\r\n    current_url  = location.href; // current page url ( when changed page changed )\r\n\r\n$.fn.sreffect = $.fn.velocity == undefined ? $.fn.animate : $.fn.velocity; // hack code for firefox\r\n\r\n/**\r\n * Sevice: storage Get data form chrome storage\r\n */\r\nstorage.Read( () => {\r\n    if ( blacklist() ) {\r\n        $( \"style\" ).map( ( idx, item ) => {\r\n            if ( item.innerText.includes( \"simpread\"        ) || \r\n                 item.innerText.includes( \"sr-opt-focus\"    ) || \r\n                 item.innerText.includes( \"sr-rd-theme\"     ) || \r\n                 item.innerText.includes( \"html.simpread-theme-root, .simpread-theme-root\" ) || \r\n                 item.innerText.includes( \"notify-gp\"       ) || \r\n                 item.innerText.includes( \"md-waves-effect\" )\r\n            ) {\r\n                $(item).remove();\r\n            }\r\n        });\r\n    } else {\r\n        bindShortcuts();\r\n        !isLazyload() && autoOpen();\r\n    }\r\n});\r\n\r\n/**\r\n * Blacklist\r\n * \r\n * @return {boolean} true: is blacklist; false: is't blacklist\r\n */\r\nfunction blacklist() {\r\n    is_blacklist = util.Blacklist( puplugin.Plugin( \"minimatch\" ), storage.option );\r\n    console.log( \"current site is blacklist\", is_blacklist )\r\n    return is_blacklist;\r\n}\r\n\r\n/**\r\n * isLazyload verify\r\n * \r\n * @return {boolen} true: lazyload; false: preload\r\n */\r\n\r\nfunction isLazyload() {\r\n    return util.Lazyload( puplugin.Plugin( \"minimatch\" ), storage.option );\r\n}\r\n\r\n/**\r\n * Listen runtime message, include: `focus` `read` `shortcuts` `tab_selected`\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    console.log( \"contentscripts runtime Listener\", request );\r\n    if ( is_blacklist ) return;\r\n    switch ( request.type ) {\r\n        case msg.MESSAGE_ACTION.focus_mode:\r\n            if ( storage.option.br_exit ) focus.Exist( false ) ? focus.Exit() : focusMode();\r\n            else focusMode();\r\n            break;\r\n        case msg.MESSAGE_ACTION.shortcuts:\r\n            bindShortcuts();\r\n            break;\r\n        case msg.MESSAGE_ACTION.tab_selected:\r\n            if ( isLazyload() ) {\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.browser_action, { code: 0 , url: window.location.href } ));\r\n            } else {\r\n                request.value.is_update ?\r\n                    setTimeout( () => browserAction( request.value.is_update ), 200 )\r\n                    : browserAction( request.value.is_update );\r\n            }\r\n            break;\r\n        case msg.MESSAGE_ACTION.read_mode:\r\n        case msg.MESSAGE_ACTION.browser_click:\r\n            watch.Verify( ( state, result ) => {\r\n                if ( state ) {\r\n                    console.log( \"watch.Lock()\", result );\r\n                    new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n                } else {\r\n                     if ( storage.option.br_exit ) {\r\n                        setting.Exist()  && setting.Exit();\r\n                        !setting.Exist() && read.Exist( false ) ? read.Exit() : readMode();\r\n                     }\r\n                     else readMode();\r\n                }\r\n            });\r\n            break;\r\n        case msg.MESSAGE_ACTION.pending_site:\r\n            new Notify().Render({ content: \"是否提交，以便更好地适配此页面？\", action: \"是的\", cancel: \"取消\", callback: type => {\r\n                if ( type == \"cancel\" ) return;\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.save_site, { url: location.href, site: storage.pr.current.site, uid: storage.user.uid, type: \"failed\" }));\r\n            }});\r\n            break;\r\n        case msg.MESSAGE_ACTION.menu_whitelist:\r\n        case msg.MESSAGE_ACTION.menu_exclusion:\r\n        case msg.MESSAGE_ACTION.menu_blacklist:\r\n        case msg.MESSAGE_ACTION.menu_lazyload:\r\n        case msg.MESSAGE_ACTION.menu_unrdist:\r\n            const menuSrv = ( type, url ) => {\r\n                if ( type == msg.MESSAGE_ACTION.menu_whitelist ) {\r\n                    storage.read.whitelist.push( url );\r\n                    new Notify().Render( \"已加入到白名单。\" );\r\n                } else if ( type == msg.MESSAGE_ACTION.menu_exclusion ) {\r\n                    storage.read.exclusion.push( url );\r\n                    new Notify().Render( \"已加入到排除列表。\" );\r\n                } else if ( type == msg.MESSAGE_ACTION.menu_blacklist ) {\r\n                    storage.option.blacklist.push( url );\r\n                    new Notify().Render( \"已加入到黑名单。\" );\r\n                } else if ( type == msg.MESSAGE_ACTION.menu_lazyload ) {\r\n                    storage.option.lazyload.push( url );\r\n                    new Notify().Render( \"已加入到延迟加载。\" );\r\n                } else if ( type == msg.MESSAGE_ACTION.menu_unrdist ) {\r\n                    storage.UnRead( \"add\", util.GetPageInfo(), success => {\r\n                        success  && new Notify().Render( 0, \"成功加入未读列表。\" );\r\n                        !success && new Notify().Render( 0, \"已加入未读列表，请勿重新加入。\" );\r\n                    });\r\n                }\r\n                storage.Write( () => {\r\n                    watch.SendMessage( \"option\", true );\r\n                });\r\n            };\r\n            if ( storage.option.urlscheme && /whitelist|exclusion|blacklist|lazyload/ig.test( request.type )) {\r\n                scheme.Render( request.type.replace( \"menu_\", \"\" ), storage.option.urlscheme, ( type, off, value ) => {\r\n                    storage.option.urlscheme = off;\r\n                    menuSrv( \"menu_\" + type, value );\r\n                });\r\n            } else menuSrv( request.type, request.value.url );\r\n            break;\r\n    }\r\n});\r\n\r\n/**\r\n * Keyboard event handler\r\n */\r\nfunction bindShortcuts() {\r\n    kbd.Bind( [ storage.focus.shortcuts.toLowerCase() ], focusMode );\r\n    kbd.Bind( [ storage.read.shortcuts.toLowerCase()  ], readMode  );\r\n    kbd.ListenESC( combo => {\r\n        if ( combo == \"esc\" && storage.option.esc ) {\r\n            setting.Exist()  && setting.Exit();\r\n            !setting.Exist() && focus.Exist() && focus.Exit();\r\n            !setting.Exist() && read.Exist()  && read.Exit();\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Focus mode\r\n */\r\nfunction focusMode() {\r\n    console.log( \"=== simpread focus mode active ===\" )\r\n\r\n    if ( !entry( focus, read, \"阅读\", \"聚焦\" )) return;\r\n\r\n    watch.Verify( ( state, result ) => {\r\n        if ( state ) {\r\n            console.log( \"watch.Lock()\", result );\r\n            new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n        } else {\r\n            getCurrent( mode.focus );\r\n            if ( storage.current.site.name.startsWith( \"txtread:\" ) ) {\r\n                new Notify().Render( \"当前为 <a href='http://ksria.com/simpread/docs/#/TXT-阅读器' target='_blank'>TXT 阅读器模式</a>，并不能使用设定功能。\" )\r\n                return;\r\n            }\r\n            if ( pr.state == \"temp\" && pr.dom ) {\r\n                focus.Render( $(pr.dom), storage.current.bgcolor );\r\n            } else {\r\n                focus.GetFocus( pr.Include(), storage.current.site.include ).done( result => {\r\n                    storage.pr.state == \"none\" && pr.TempMode( mode.focus, result[0] );\r\n                    focus.Render( result, storage.current.bgcolor );\r\n                }).fail( () => {\r\n                    new Notify().Render( 2, \"当前并未获取任何正文，请重新选取。\" );\r\n                });\r\n            }\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Read mode\r\n */\r\nfunction readMode() {\r\n    console.log( \"=== simpread read mode active ===\" )\r\n\r\n    if ( !entry( read, focus, \"聚焦\", \"阅读\" )) return;\r\n\r\n    watch.Verify( ( state, result ) => {\r\n        if ( state ) {\r\n            console.log( \"watch.Lock()\", result );\r\n            new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n        } else {\r\n            getCurrent( mode.read );\r\n            if ( storage.current.site.name != \"\" ) {\r\n                read.Render();\r\n            } else if ( pr.state == \"temp\" && pr.dom ) {\r\n                read.Render();\r\n            } else {\r\n                new Notify().Render( \"<a href='http://ksria.com/simpread/docs/#/词法分析引擎?id=智能感知' target='_blank' >智能感知</a> 正文失败，请移动鼠标，并通过 <a href='http://ksria.com/simpread/docs/#/手动框选' target='_blank' >手动框选</a> 的方式生成正文。\" );\r\n                read.Highlight().done( dom => {\r\n                    const rerender = element => {\r\n                        pr.TempMode( mode.read, dom );\r\n                        read.Render();\r\n                    };\r\n                    storage.current.highlight ? \r\n                        highlight.Control( dom ).done( newDom => {\r\n                            rerender( newDom );\r\n                        }) : rerender( dom );\r\n                });\r\n            }\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Auto open read mode, include:\r\n * \r\n * - http://xxxx?simpread_mode=read\r\n * - auto && location.href not include exclusion list\r\n * - location.href include white list\r\n */\r\nfunction autoOpen() {\r\n    getCurrent( mode.read );\r\n    const suffix    = window.location.href.includes( \"simpread_mode=read\" ),\r\n          auto      = storage.current.auto,\r\n          minimatch = puplugin.Plugin( \"minimatch\" ),\r\n          whitelist = util.Whitelist( minimatch, storage.current ),\r\n          exclusion = util.Exclusion( minimatch, storage.current );\r\n    if  (\r\n        suffix || whitelist || ( auto && exclusion == false )\r\n        ) {\r\n        switch ( storage.current.site.name ) {\r\n            case \"my.oschina.net\":\r\n            case \"36kr.com\":\r\n            case \"chiphell.com\":\r\n            case \"question.zhihu.com\":\r\n                $( () => readMode() );\r\n                break;\r\n            case \"post.juejin.im\":\r\n            case \"entry.juejin.im\":\r\n                setTimeout( ()=>readMode(), 2500 );\r\n                break;\r\n            case \"kancloud.cn\":\r\n            case \"sspai.com\":\r\n                setTimeout( ()=>readMode(), 1000 );\r\n                break;\r\n            default:\r\n                pr.state == \"adapter\" && readMode();\r\n                pr.state == \"temp\"    && pr.current.site.html != \"\" && whitelist && readMode();\r\n                break;\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * Focus and Read mode entry\r\n * \r\n * @param  {object}  current mode object\r\n * @param  {object}  other   mode object\r\n * @param  {array}   render str\r\n * @return {boolean} true:continue; false: return\r\n */\r\nfunction entry( current, other, ...str ) {\r\n    if ( other.Exist(false) ) {\r\n        new Notify().Render( `请先退出${str[0]}模式，才能进入${str[1]}模式。` );\r\n        return false;\r\n    }\r\n    if ( current.Exist(true) ) return false;\r\n    return true;\r\n}\r\n\r\n/**\r\n * Get storage.current\r\n * \r\n * @param {string} value is mode.focus or mode.read or undefined\r\n */\r\nfunction getCurrent( mode ) {\r\n    if ( mode && storage.VerifyCur( mode ) ) {\r\n        ( !pr || !pr.Exist() ) && pRead();\r\n        storage.Getcur( mode, pr.current.site );\r\n    }\r\n}\r\n\r\n/**\r\n * Browser action\r\n * \r\n * @param {boolean} when set icon is_update = true\r\n */\r\nfunction browserAction( is_update ) {\r\n    if ( is_update && current_url != location.href ) {\r\n        current_url = location.href;\r\n        autoOpen();\r\n    }\r\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.browser_action, { code: storage.current.site.name == \"\" ? -1 : 0 , url: window.location.href } ));\r\n}\r\n\r\n/** \r\n * Pure Read\r\n*/\r\nfunction pRead() {\r\n    pr = new PureRead( storage.sites );\r\n    pr.cleanup = storage.read.cleanup == undefined ? true  : storage.read.cleanup;\r\n    pr.pure    = storage.read.pure    == undefined ? false : storage.read.pure;\r\n    pr.AddPlugin( puplugin.Plugin() );\r\n    pr.Getsites();\r\n    storage.puread = pr;\r\n    console.log( \"current puread object is   \", pr )\r\n}"
  },
  {
    "path": "src/focus/controlbar.jsx",
    "content": "console.log( \"=== simpread focus controlbar load ===\" )\n\nimport * as setting   from 'setting';\nimport * as se        from 'siteeditor';\n\nimport * as conf      from 'config';\nimport { storage }    from 'storage';\nimport * as output    from 'output';\nimport * as watch     from 'watch';\nimport {browser,br}   from 'browser';\nimport * as msg       from 'message';\nimport * as highlight from 'highlight';\n\nimport Fab         from 'fab';\n\nlet focusItems, $root, selector, callback;\n\nconst tooltip_options = {\n    target   : \"name\",\n    position : \"bottom\",\n    delay    : 50,\n};\n\nclass FControl extends React.Component {\n\n    componentDidMount() {\n        browser.runtime.onMessage.addListener( ( request, sender, sendResponse ) => {\n            if ( request.type == msg.MESSAGE_ACTION.export ) {\n                console.log( \"controlbar runtime Listener\", request );\n                new Notify().Render( \"已重新授权成功！\" );\n                br.isFirefox() ? new Notify().Render( \"请刷新本页才能生效。\" ) : this.onAction( undefined, request.value.type );\n            }\n        });\n    }\n\n    onClose() {\n        setTimeout( ()=> {\n            $( \"fab-bg\" ).css({ width: '100px' })\n            $( \"fab\" ).find( \"ul[type=hori]\" ).css({ opacity: 0, visibility: \"hidden\" })\n        }, 200 );\n    }\n\n    onAction( event, type ) {\n        console.log( \"fab type is =\", type )\n\n        const action = ( event, type ) => {\n            switch ( type ) {\n                case \"exit\":\n                    ReactDOM.unmountComponentAtNode( getRoot() );\n                    break;\n                case \"top\":\n                    $( \"html, body\" ).animate({ scrollTop: 0 }, \"normal\" );\n                    break;\n                case \"setting\":\n                    setting.Render( ()=>setTimeout( ()=>se.Render(), 500 ));\n                    this.onClose();\n                    break;\n                case \"siteeditor\":\n                    se.Render();\n                    this.onClose();\n                    break;\n                case \"remove\":\n                    new Notify().Render( \"移动鼠标选择不想显示的内容，只针对本次有效。\" );\n                    highlight.Start().done( dom => {\n                        $(dom).remove();\n                    });\n                    break;\n                case \"highlight\":\n                    new Notify().Render( \"移动鼠标选择高亮区域，以便生成阅读模式，将会在页面刷新后失效。\" );\n                    ReactDOM.unmountComponentAtNode( getRoot() );\n                    highlight.Start().done( dom => {\n                        callback && callback( dom );\n                    });\n                    break;\n                default:\n                    if ( type.indexOf( \"_\" ) > 0 && type.startsWith( \"share\" ) || \n                         [ \"save\", \"markdown\", \"offlinemarkdown\", \"png\", \"epub\", \"pdf\", \"kindle\", \"temp\", \"bear\", \"ulysses\", \"html\", \"offlinehtml\", \"dropbox\", \"pocket\", \"instapaper\", \"linnk\", \"yinxiang\",\"evernote\", \"onenote\", \"gdrive\", \"jianguo\", \"yuque\", \"notion\", \"youdao\", \"weizhi\" ].includes( type )) {\n                        const [ title, desc, content ] = [ $( \"head title\" ).text().trim(), \"\", $( \".simpread-focus-highlight\" ).html().trim() ];\n                        output.Action( type, title, desc, content );\n                    }\n            };\n        }\n\n        ![ \"exit\", \"top\" ].includes( type ) ? watch.Verify( ( state, result ) => {\n            if ( state ) {\n                console.log( \"watch.Lock()\", result );\n                new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>location.href = location.href );\n            } else action( event, type );\n        }) : action( event, type );\n    }\n\n    componentWillMount() {\n        focusItems = $.extend( true, {}, conf.focusItems );\n        if ( storage.current.site.name.startsWith( \"metaread::\" ) || storage.current.site.name.startsWith( \"txtread::\" ) ) {\n            delete focusItems.option;\n        }\n    }\n\n    componentWillUnmount() {\n        $(this.refs.target).remove();\n        $( \"body\" ).find( selector ).trigger( \"click\", \"okay\" );\n    }\n\n    render() {\n        return (\n            <sr-rd-crlbar class={ this.props.show ? \"\" : \"controlbar\" } style={{ \"zIndex\": 2147483647 }}>\n                <Fab ref=\"target\" tooltip={ tooltip_options } waves=\"md-waves-effect md-waves-circle md-waves-float\" items={ focusItems } onAction={ (event, type)=>this.onAction(event, type ) } />\n            </sr-rd-crlbar>\n        )\n    }\n}\n\nconst fcontrol = new FControl();\n\n/**\n * Render\n * \n * @param {string}   class name, e.g. .xxx\n * @param {string}   class name, e.g. .xxx\n * @param {function} callback\n */\nfunction Render( root, finder, cb ) {\n    callback = cb;\n    selector = finder;\n    $root = $(root);\n    ReactDOM.render( <FControl show={ storage.current.controlbar } />, getRoot() );\n}\n\n/**\n * Get root html\n * \n * @return {object} root html\n */\nfunction getRoot() {\n    return $root[0];\n}\n\nexport { fcontrol as elem, Render };"
  },
  {
    "path": "src/focus/focus.js",
    "content": "console.log( \"=== simpread focus load ===\" );\n\nvar storage  = require( \"storage\" ).storage,\n    util     = require( \"util\" ),\n    highlight= require( \"highlight\"  ),\n    fcontrol = require( \"controlbar\" ),\n    tooltip  = require( \"tooltip\" ),\n    waves    = require( \"waves\" ),\n    browser  = require( \"browser\" ).browser,\n    msg      = require( \"message\" ),\n    focus    = ( function () {\n\n    var $parent,\n        tag,\n        focuscls   = \"simpread-focus-highlight\",\n        focusstyle = \"z-index: 2147483646; overflow: visible; position: relative;\",\n        maskcls    = \"simpread-focus-mask\",\n        maskstyle  = \"z-index: auto; opacity: 1; overflow: visible; transform: none; animation: none; position: relative;\",\n        bgcls      = \"simpread-focus-root\",\n        bgtmpl     = \"<div class=\" + bgcls + \"></div>\",\n        ctrlbar    = \"sr-controlbar-bg\",\n        ctrlbarbg  = \"<div class=\" + ctrlbar + \"></div>\",\n        ctrlbarjq  = \".\" + ctrlbar,\n        bgclsjq    = \".\" + bgcls;\n\n    function Focus() { this.$target = null; }\n\n    /**\n     * Add focus mode\n     * \n     * @param {jquery} jquery object\n     * @param {string} background color style\n     */\n    Focus.prototype.Render = function( $target, bgcolor ) {\n        console.log( \"=== simpread focus add ===\" );\n        this.$target = $target;\n\n        // set include style\n        includeStyle( $target, focusstyle, focuscls, \"add\" );\n\n        // set exclude style\n        excludeStyle( $target, \"delete\" );\n\n        // add simpread-focus-mask\n        $parent = $target.parent();\n        tag     = $parent[0].tagName;\n        while ( tag.toLowerCase() != \"body\" ) {\n            includeStyle( $parent, maskstyle, maskcls, \"add\" );\n            $parent = $parent.parent();\n            tag     = $parent[0].tagName;\n        }\n\n        // add background\n        $( \"body\" ).append( bgtmpl );\n        $( \"html\" ).append( ctrlbarbg );\n\n        // add background color\n        $( bgclsjq )\n            .css({ \"background-color\" : bgcolor })\n            .sreffect({ opacity: 1 });\n\n        // add control bar\n        fcontrol.Render( ctrlbarjq, bgclsjq, dom => {\n            storage.pr.dom = dom;\n            Focus.prototype.Render( $(dom), storage.current.bgcolor );\n        });\n\n        // add tooltip and waves\n        tooltip.Render( bgclsjq );\n        waves.Render({ root: bgclsjq });\n        storage.Statistics( \"focus\" );\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"mode\", eventAction: \"focusmode\", eventValue: \"focusmode\" }) );\n\n        // click mask remove it\n        $( bgclsjq ).on( \"click\", function( event, data ) {\n            if ( ( event.target.tagName.toLowerCase() == \"i\" && event.target.id !=\"exit\" ) ||\n                $( event.currentTarget ).attr(\"class\") != bgcls ||\n                ( !storage.current.mask && !data )) return;\n             $( bgclsjq ).sreffect({ opacity: 0 }, {\n                 complete: ()=> {\n                    includeStyle( $target, focusstyle, focuscls, \"delete\" );\n                    excludeStyle( $target, \"add\" );\n                    tooltip.Exit( bgclsjq );\n                    $( ctrlbarjq ).remove();\n                    $( bgclsjq   ).remove();\n                    $( bgclsjq   ).off( \"click\" );\n                }\n             });\n\n            // remove simpread-focus-mask style\n            $parent = $target.parent();\n            tag     = $parent[0].tagName;\n            while ( tag && tag.toLowerCase() != \"body\" ) {\n                includeStyle( $parent, maskstyle, maskcls, \"delete\" );\n                $parent = $parent.parent();\n                tag     = $parent[0].tagName;\n            }\n\n            console.log( \"=== simpread focus remove ===\" );\n        });\n\n    }\n\n    /**\n     * Get focus\n     * \n     * @param {jquery} focus jquery object\n     * @param {string} storage.current.site.include\n     * @return {jquery} focus jquery object or undefined\n     */\n    Focus.prototype.GetFocus = function( $focus, include ) {\n        var dtd    = $.Deferred(),\n            sel, range, node, tag;\n        if ( storage.current.highlight && $focus.length == 0 ) {\n            new Notify().Render( \"已启动手动聚焦模式，请移动鼠标进行选择，支持 ESC 退出。\" );\n            highlight.Start().done( result => {\n                $focus = $( result );\n                dtd.resolve( $focus );\n            });\n        } else {\n            while ( $focus.length == 0 ) {\n                if ( $( \"body\" ).find( \"article\" ).length > 0 ) {\n                    $focus = $( \"body\" ).find( \"article\" );\n                }\n                else {\n                    try {\n                        sel    = window.getSelection();\n                        range  = sel.getRangeAt( sel.rangeCount - 1 );\n                        node   = range.startContainer.nodeName;\n                    if ( node.toLowerCase() === \"body\" ) throw( \"selection area is body tag.\" );\n                        $focus = $( range.startContainer.parentNode );\n                    } catch ( error ) {\n                        console.log( sel, range, node )\n                        console.error( error )\n                        return dtd.reject();\n                    }\n                }\n            }\n            return dtd.resolve( $focus );\n        }\n        return dtd;\n    }\n\n    /**\n     * Exist\n     * \n     * @param  {boolean} when true, call fcontrol.Click()\n     * @return {boolen} true: exist; false: not exist\n     */\n    Focus.prototype.Exist = function( action ) {\n        if ( $( \"body\" ).find( \".\" + focuscls ).length > 0 ) {\n            if (action) fcontrol.elem.onAction( undefined, \"setting\" );\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Exit\n     */\n    Focus.prototype.Exit = function() {\n        $( bgclsjq ).trigger( \"click\", \"okay\" );\n    }\n\n    return new Focus();\n\n})();\n\n/**\n *  Set include style\n * \n *  @param {jquery} jquery object\n *  @param {string} set style string\n *  @param {string} set class string\n *  @param {string} include 'add' and 'delete'\n*/\nfunction includeStyle( $target, style, cls, type ) {\n    $target.each(function(index){\n        var bakstyle;\n        var selector = $(this);\n        if ( type === \"add\" ) {\n            bakstyle = selector.attr( \"style\" ) == undefined ? \"\" : selector.attr( \"style\" );\n            selector.attr( \"style\", bakstyle + style ).addClass( cls );\n        } else if (  type === \"delete\" ) {\n            bakstyle = selector.attr( \"style\" );\n            bakstyle = bakstyle.replace( style, \"\" );\n            selector.attr( \"style\", bakstyle ).removeClass( cls );\n        }\n    });\n}\n\n/**\n * Set exclude style\n * \n * @param {jquery} jquery object\n * @param {string} include: 'add' 'delete'\n */\nfunction excludeStyle( $target, type ) {\n    const tags = storage.pr.Exclude( $target );\n    if ( type == \"delete\" )   $target.find( tags ).hide();\n    else if ( type == \"add\" ) $target.find( tags ).show();\n}\n\n/**\n * Fix $focus get bad tag, get good tag and return\n * Good tag include: div, article\n * \n * @param  {jquery} jquery object\n * @return {jquery} jquery object\n */\nfunction fixFocus( $focus ) {\n    var tag = $focus[0].tagName.toLowerCase();\n    while ( [ \"p\", \"span\", \"strong\", \"ul\", \"li\", \"code\", \"pre\", \"pre\" ].includes( tag )) {\n            $focus = $focus.parent();\n            tag    = $focus[0].tagName.toLowerCase();\n    }\n    return $focus;\n}\n\nexports.focus       = focus;"
  },
  {
    "path": "src/ga.js",
    "content": "\n/**\n * Track using the asynchronous tracking API.\n *\n * See http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html\n * for information on how to use the asynchronous tracking API.\n * _gaq.push(['_trackEvent', e.target.id, 'clicked']);\n */\n\nvar _AnalyticsCode = 'UA-405976-14';\nvar _gaq = _gaq || [];\n_gaq.push(['_setAccount', _AnalyticsCode]);\n_gaq.push(['_trackPageview']);\n\n(function() {\n    var ga = document.createElement('script');\n    ga.type = 'text/javascript';\n    ga.async = true;\n    ga.src = 'https://ssl.google-analytics.com/ga.js';\n    var s = document.getElementsByTagName('script')[0];\n    s.parentNode.insertBefore(ga, s);\n})();"
  },
  {
    "path": "src/help_tips.json",
    "content": "{\n    \"tips\": [\n        {\n            \"idx\": 0,\n            \"name\": \"文档中心\",\n            \"icon\": \"<i class=\\\"fas fa-info-circle\\\"></i>\",\n            \"url\": \"http://ksria.com/simpread/docs\"\n        },\n        {\n            \"idx\": 1,\n            \"name\": \"新手入门可以看这篇文章\",\n            \"icon\": \"<i class=\\\"fas fa-file-word\\\"></i>\",\n            \"url\": \"http://ksria.com/simpread/guide/\"\n        },\n        {\n            \"idx\": 2,\n            \"name\": \"查看当前版本新增功能\",\n            \"icon\": \"<i class=\\\"fas fa-plus-square\\\"></i>\",\n            \"url\": \"#\"\n        },\n        {\n            \"idx\": 3,\n            \"name\": \"查看当前页的功能描述\",\n            \"icon\": \"<i class=\\\"fas fa-binoculars\\\"></i>\",\n            \"url\": \"#\"\n        },\n        {\n            \"idx\": 4,\n            \"name\": \"简悦的汇总（新闻页）\",\n            \"icon\": \"<i class=\\\"fas fa-bullhorn\\\"></i>\",\n            \"url\": \"https://simp.red/news\"\n        },\n        {\n            \"idx\": 5,\n            \"name\": \"请通过 Github issues 提问\",\n            \"icon\": \"<i class=\\\"fas fa-bug\\\"></i>\",\n            \"url\": \"https://github.com/Kenshin/simpread/issues/new\"\n        },\n        {\n            \"idx\": 6,\n            \"name\": \"如果简悦暂时未支持你使用的导出服务\",\n            \"icon\": \"<i class=\\\"fas fa-file-export\\\"></i>\",\n            \"url\": \"https://github.com/Kenshin/simpread/issues/473\"\n        },\n        {\n            \"idx\": 7,\n            \"name\": \"YouTube 加载出错的朋友请看这里\",\n            \"icon\": \"<i class=\\\"fas fa-bug\\\"></i>\",\n            \"url\": \"https://github.com/Kenshin/simpread/issues/487\"\n        },\n        {\n            \"idx\": 8,\n            \"name\": \"当使用简悦疑似页面变慢的处理方式\",\n            \"icon\": \"<i class=\\\"fas fa-desktop\\\"></i>\",\n            \"url\": \"@performance\"\n        },\n        {\n            \"idx\": 9,\n            \"name\": \"当阅读模式并未符合你的期望时，如何进一步优化？\",\n            \"icon\": \"<i class=\\\"fas fa-book-open\\\"></i>\",\n            \"url\": \"https://github.com/Kenshin/simpread/issues/522\"\n        }\n    ]\n}"
  },
  {
    "path": "src/manifest.json",
    "content": "{\r\n  \"name\"            : \"__MSG_extension_name__\",\r\n  \"default_locale\"  : \"en\",\r\n  \"version\"         : \"1.1.4.6025\",\r\n  \"short_name\"      : \"SimpRead\",\r\n  \"description\"     : \"__MSG_extension_desc__\",\r\n  \"homepage_url\"    : \"http://ksria.com/simpread\",\r\n  \"author\"          : \"Kenshin Wang <kenshin@ksria.com>\",\r\n  \"icons\" : {\r\n    \"16\"            : \"assets/images/icon16.png\",\r\n    \"48\"            : \"assets/images/icon48.png\",\r\n    \"128\"           : \"assets/images/icon128.png\"\r\n  },\r\n  \"permissions\"     : [\r\n    \"contextMenus\",\r\n    \"tabs\",\r\n    \"storage\",\r\n    \"tts\",\r\n    \"<all_urls>\"\r\n  ],\r\n  \"optional_permissions\": [ \"cookies\", \"https://*.youdao.com/\", \"downloads\" ],\r\n  \"background\": {\r\n    \"scripts\"       : [ \"/ga.js\", \"/bundle/common.js\", \"/bundle/background.js\" ]\r\n  },\r\n  \"content_scripts\" : [\r\n    {\r\n      \"matches\"     : [ \"http://*/*\", \"https://*/*\", \"file:///*/*.txt\", \"file:///*/*.md\" ],\r\n      \"exclude_matches\": [ \"http://localhost/*\", \"https://simpread.herokuapp.com/view/*\" ],\r\n      \"js\"          : [\r\n        \"/bundle/common.js\",\r\n        \"/bundle/vendors.js\",\r\n        \"/bundle/contentscripts.js\"\r\n       ],\r\n      \"run_at\"      : \"document_end\"\r\n    }\r\n  ],\r\n  \"page_action\"     : {\r\n    \"default_icon\"  : {\r\n      \"16\"          : \"assets/images/icon16.png\"\r\n    }\r\n  },\r\n  \"options_page\"    : \"options/options.html\",\r\n  \"web_accessible_resources\": [\r\n    \"/assets/images/*\",\r\n    \"website_list.json\",\r\n    \"options/corb.html\"\r\n  ],\r\n  \"offline_enabled\" : true,\r\n  \"update_url\"      : \"https://clients2.google.com/service/update2/crx\",\r\n  \"content_security_policy\" : \"script-src 'self' 'unsafe-eval' https://ssl.google-analytics.com; object-src 'self'\",\r\n  \"manifest_version\": 2\r\n}"
  },
  {
    "path": "src/module/about.jsx",
    "content": "console.log( \"===== simpread option about load =====\" )\r\n\r\nimport {br,browser} from 'browser';\r\n\r\nconst style = {\r\n\r\n    root: {\r\n        padding: '50px 0',\r\n\r\n        backgroundColor: '#fff',\r\n\r\n        fontSize: '1.6rem',\r\n        color: 'rgba(51, 51, 51, 0.87)',\r\n\r\n        boxShadow: '0 1px 3px rgba(0, 0, 0, .12)',\r\n    },\r\n\r\n    title: {\r\n        fontSize: '2rem',\r\n        fontWeight: 800,\r\n    },\r\n\r\n    badges: {\r\n        display: 'flex',\r\n        flexDirection: 'row',\r\n        justifyContent: 'center',\r\n\r\n        margin: '1.2em 0',\r\n    },\r\n\r\n    img: {\r\n        padding: '5px',\r\n    },\r\n\r\n    stat: {\r\n        color: '#ff3f80',\r\n        fontWeight: 600\r\n    },\r\n\r\n    href: {\r\n        color: 'rgba(51, 51, 51, 0.87)',\r\n    },\r\n\r\n    link: {\r\n        borderBottom: '1px solid #4285f4',\r\n    },\r\n\r\n},\r\nurls = {\r\n\r\n    href: {\r\n        version: \"https://github.com/Kenshin/simpread/releases\",\r\n        website: \"http://ksria.com/simpread\",\r\n        githubstar: \"https://github.com/Kenshin/simpread\",\r\n        changelog: \"http://ksria.com/simpread/changelog.html\",\r\n        feedback: \"https://github.com/kenshin/simpread/issues\",\r\n        issues: \"https://github.com/kenshin/simpread/issues\",\r\n    },\r\n\r\n    badges: {\r\n        version: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHoAAAAUCAYAAABCpynOAAAAAXNSR0IArs4c6QAAB/xJREFUaIHtmntQVNcdxz9n79297LqLz1WWoFAh+GBCsKY+sBYbjI1Eq2JRBBOtxmiDLhmNTZ1JO47RVJP4KDPVKVONIRhN2tr4GjXStD47tYqY6qpR6qMqL4081uWxj9M/GK9uAiRaMjaE78yd2dnzPb/nPd9z9t4V6enp4SaTaZUQ4knAzjcYVVkHH3YI/1cIICsE8mNPg3u+ajQac4CJUsqHHdf/jLaQQ2tCgB3E5BDVpqrAU22lQG0lj9aGEDylSilDH3YgrYX2RjcDSajalorTlnJpbbQ3+lsCg5SSh3Vt3LgRVVW/Vh9r4neyM/EKOxOvEGtNaDXunWti+Bx2Jl6hn+2JZjkKRuZ8ZwmbB33CzsQrrHxsmz4WERLNqvjtfDj0Aivjt+PQIvWxUd3T+d2Av/HB4NP8qu8Guhh76GM5j+/RY92ZeIUJ4S+0GOdDXdEjR47E4XAghGgVe03lMvWdZHzuAB/OPobvdqDFVX8/XIBelkeJ04Zy+bMLeN3N86dHvkKEoQ/PbhzJ1ZsXUW0GusRrADh7v8mhMwW8cCSV54a9yNzoFSw6PYmwkF70VuLJ/kMGN6vLWfT0m8yJeo2l554HwF8bIOODJD4tPwWANcqI5RG12VibbXRqaipWq5W8vLxGQ1Yra9asISsri/r6emJjY5k2bRoRERFUVFSQn59PUVERAFu2bCE/P5+UlBT279+P3W5n4MCBmM1mKioqmDdvHgAFBQXMmDEDr9fLgAEDmDp1Kl27dqW4uJjc3FzKysp0e3l5eYwdOxaA9evXc+zYsS/E3FQuneNNjR8E+t3dHO6Ha8DAz6KWsWSPk1WjNzfLNxlCeCZ8Gs++PwJP5DXsfUL0WLtp4fS29sd5IRUt3sd753PISHiRTmo3rt66yPJT8zGHK5h6Cgou/pmfJiwI8hEaa8QeE9Ji/nq8zS31Xbt2MWTIEIQQSCkZOnQoBw4cwOPxYLFYmDFjBitWrGD06NEsXbqUWbNmYTQadWcmk4mMjAxOnDiB1WolNTWV4cOHk5aWFuQnEAgQGhrK3LlzycnJYdy4cZw8eZKsrKyg4hmNRqZMmcLy5cuZPn36fUv4nUK0FjctIot9p7dR3e3anTI3yethjKCuoZbUIZnsSP43G753hB90+zFSSroKB+U1JSgOL0IBxeGlvKaErgYHBk1g7a2ihAgMiiD1sWnsOfvHu7aBlYO38qfEsyyJe5cwrdeDSbfb7ebSpUskJCRw/PhxkpKSWLt2LUIIYmNjiYqKIjc3N2iO3W7n6tWrAGzevBmbzUZFRQWRkZFkZ2fjcrkoLCykoaEh6C6MiYnB5XJx5swZbDYbmzZtYtKkSWiaRl1dXZC9OxyDwYDf7w/y36LUSpDyKx7YvoQb1aEv/UMG8/L1yZjDFN13U3zZYCA0pBNVVTU8+ZtHSeg1iLfGv8upqn8QqA8gkbqCIEAIgd8bbMsZ+wY3KsvZei0Xi6NRnqe+l4S/XtIxpDNzRrzCz/v8lpeKnmk2pWYbbTAY2Lt3L0lJSZSVlWE2mykuLsZkMuHz+SgsLNQl+A7sdjtGo1GfD1BaWsqsWbNITk5m2LBhZGRksGDBAr3ZUkp8Ph9CCDStcd+yWq0A+P3+IIUA0DSNQCCgzw0q6pc2sWU5/qrcJ2zJDHL8kAOTy/Xvcobt4K2z2XxU+n4Qt8xzFYlk+/l3sMR7OV1ziGufXSZcjeZy9Xl62MJRhQlvoB6jQcNuDWucY5QYMLCg7xrqqht4/ZATW8xdxewU31jngNdN/ukctsadeDDpllJy9OhR4uLiGDNmDLt379al2eVyER0dTWZmJpGRkTgcDhwOh36CBnTJVxQFv9/P9u3bWb16NWazGavVGiSRLpeLfv36kZCQgKZppKWlce7cOWpra4OCfxAZ/jqke+M/V5OwrJN+Xbp5nul5T7Pt5KYvcG+LKg4XFzBxwDQ01czgqBE4Ovak+MYZbgSuc77cRXpPJxbFRnpPJ6evF1IZKEdg4Bf91+GprGPZASfW6KZ/nYSGdGZ64ku4SooeTLrvNOvgwYOkpKSwYcMGvQi1tbUsXLiQ7OxsZs6cqUtsZmZmUFMARo0axezZs5FSUllZyZYtWygrK0NR7kqe2+1m8eLFOJ1O7HY7Z8+eZdmyZfrNcq+9ppr+eZ/3YlH/tSSH/QSAtcP3EJB+Rv3VAcDvBx/g7eJfc/jG7vvimsMVzOHKPcsFrFEKms+AlDKIKxR44/DLLPnROqYOn0+p5z+8umMOtzqUoZgEr/1lHktS1jHl+04+vfkvfrl7NuIRiDT3YUT3cdAdxvV9DoAb9SWkH36cnpYY3h5yBAC3r4oT1//Oqx89j4xsoZcTJkxoUcuqqqrweDx0795dbw5AQ0MDNTU1eL1epJQIIQgLCwOgpKQEh6OxQB6Ph6qqKgAURcFisejSXFpaSo8ePRBCUF9fT3V1NX6/H6PRSMeOHVFV9Qv2Pj8vKNYm3l7VXPBRX3HPXi4E3YY0bgO3Tnqx9FTQuhjum3svbhU1YO2tYgw1NMn110vcF3x43RJFA0svVR/zeQK4i/34bkvUDgJrjIpqFvhuB6j8xBvkx6AJunzXhK9WUlnUuPUJVWAMFXSIbDy4NQcxfvz4NvM4qf01ZfNofwT6LYEqpawG2sQbrPZGNw0J1aqUch8w8WEH0xpob3TTkMh9al1dnVPTNB/wjf8rUXujgyGRFULysdtbPf+/MJsCl5brAnUAAAAASUVORK5CYII=\",\r\n        website: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKwAAAAUCAYAAAAQqNbAAAAABmJLR0QA/wD/AP+gvaeTAAAJSklEQVRo3u1beVBU6RGfuIAY43qtq9lNsjHZ2tqqZFP51xSa0opR8QZUvK94LZoNqdSKF7JABAUHFOVcUZSKgIDOoKMgl+MAIma4WRQXhAG5L0GiRveX1z2Zx+MJCLtLpTI1XfWr93X3193f16+nv+8VhWLFihU2GzduVAowCIA5IOLOZAvMC4bwnElKjziFjYKKdcOGDTAnCJuzwAwRmjXhuGL9+vUNAmBOCMueaIE5ImdCg2LdunUwNwi/RAvMFIq1a9fC3BCiG2+BmUKxZs0amBuCb79tgZlCsXr1aowEiIZr8+LFC/5oovGlS5e4+L5N7NPacUPCy1fPEKqbMuT5/2t8883LAXVE39X//0M+FM7OzhgJEA3XZseOHeL41atXfLx/m9inbv1oSPCNskNQxrghzx8ugrUTvld/r4SCHUhH9F3XOdL5+D6gWLVqFeSIiIhAcnIyjw8cOMDJ2L9/P/MpKSkIDw/n8aFDh1BWVobKyko8fPgQnp6eog+i+Ph41tXW1sLf35/l9BWfnZ2N6upqVFVVIS8vT7ShDksfTVeuXGF78l1aWso2g8WSIyhzrIgQ7TuoaEpAS3cxmroKUNmcJOqoowRrJ/OYKLvSned19FQiIX8u8g0nBb6UEX33t6Idkb4mEOUN/8DjjhxcK3Huo8urPoaHTVeQ+cCVZfH6Oajr0Anx89H45B4uF9iL8+83xgoyPcf9ulmNcN37ok5TshptT++z3Z0qby5Y6d6kIKLn6VvjUVp/TkAUwm5PHXDv/a1Tmo/B1iVHQv4fUd+Zy3Obu4qQWDCP5eoiB4EvFOQlMLRn4nzuJ33iDzXfUihWrlwJObZt2waDwcDj6OhoFBcX48KFC8xT8ZF+y5YtePToEZycnDB79mwulKamJu6KNI8oLCyMdXS0d3R0sJ2vry+ysrJYTli6dKkYlwqW5s6fP5877Ny5czFnzhxs3rx50FhynMz4oQh1oRNKqtT4c6AtPjthi4Nnpoq6f798hlOZk3hMdDlnF887c3UNXrzsRmTKYuwJGI3L2r0oqY0W7YhitRvxebgtDkS8h84eAyKyPhB1cYLub6G28Lg4hl90U2cJ3CM/4PiHIz8U5tcIhTWR53uc/wnLKa7mziHcrfJj+ZdZ0/H0WQs8znzM+mT9QS5Y6d6kIArRvotHLcm4qffG3i9toS4YeO/ydcrzMdC65IjQ/QxPnzcjIG4mz/3LibEI0Ewzrv95C7yjPmF5XIYL6jvy+sQfar6lUAh/6YIcy5cvR0NDA3bu3In8/Hy4urpCr9czT3LSU+G1t7dzBySUlJRwke/evZt9EEn9UVclm61bt7IP6uBKpZIL1DSPCpbuoDSmgqXjncZviiXHifQxIoKuf4S2J9Uoqg0XOtZaBKW9I+roBQVlTOQxkfelCUbdtY/Q3dOCvyfaMh+RPBtVDdmiHZFvotGPMmUMCh8m4YreSdT5JEwS56ryndDV0wxDmxa17VoYWrVobKvAuexfsz61bA/q2nVCB85CfVshyg0alqsLnFFUcRV+141+jsT+mAtWujcpiBo79YjP3I0DUbZv3Lt8nfJ8DLQuOVQFK1FWlQoflTFmYJoxJ6r8VSitTIGfxjjvixihg798jpBb7w4731IoqGv1B41Gg9DQUD6ClyxZwsc38SQnvbe3NwoLC8VOSZg1axaWLVvGeiI63k3+cnJy+Bin4p03bx7c3d2RlJSE+vp67pQ0hwqWipTGVLDUSYcSS47ANFsRfhpbuAZOQlSKI+5WhKC952vuIqTjF5Q+gcdEAalGm9D0X6L9SZ3oI+rWTDyqzxV5ouCMaSL/VfUNxOgcRJ3yZm/82KwVqDDcxmcnbUS4+Nvg2DVbXLz7O7Q8eYB9oVOFbmaDMJUDHhjS2S4ua5XwQ0gU/QSlTeGCle5NCqK0e0rcr70mFN34N+5dvk5pPgZblxyxWU4or0l9TR6XvVLIS4pENlbw/xyn0qcMO99SKBwdHdEffHx80NjYCJVKxbxarWb+yJEjzFMn7OzshJeXl2izb98+cUwUGRnJYxcXF+6QVJh0LaCuunDhQixYsACtra3Yvn07z6OCpSKlMfnetGnTkGLJEZA6WkTE7ek4mToZnnE22B85Dp3d9QjXfsg6ekEn08bz2JhAo01o+i/Q3lUn8uf+m0ATT5RZ7sbjs9m/Qve/muGvfv81PwR/1XuCvgWJ+YtEWUzeLH5ezF6IBzW34H/DKC+ujsH9mjQeH1f/FG1dNQjOnMq8pmgTF6zJR4LeHuG3f95nTXvP2CAl7wiqW9O4Uw62d/k6pfkYbF3y2LS/rp5GocjtmD8hFGZw5jQohfXTvqNyfsPy1DIXVNbl4njy6GHnWwqFg4MD+gN1RyLqisTTk4iKjnjqbvRVT0c0Hc+PHz+GTqcT7Yno3kudme69bm5ubHP48GG+jxJqamr4A47usWRDBUt3UBqTnOzInmSDxZJDedNGRFyuPZqeFAmX/2K0dJVCpXXH0atGHb2gE6lv89jYcYzykLTpwi++VuRNCTTx3M0KPPkDo7XrPoITHIWj2+Y1PwSS+563Q01LFlq7v0L70wqU1cazzithDO6Vx6BKuHeW18cgo0DJhUG6Yxqhs10WvhnadCh9fAEZxZ5csCa/TZ2luHzPqc+a6OkWaY2k7IPC9UMH1T+dB9y7fJ3SfAy2Lnls2p9f9O9R13ZX+Fgq41gxuX/g9QfFLRGuKYW878qGDHie/fi19Q4l31Io6IjuD3QNmDFjBndB4ulJPMlNcxYtWsRHs52dHYOOapOO5tJH08yZM1ln8mNvb88yE2iO1IaKmsYkJzuaQ7LBYslxPMVaxBeXrPGpv5URflbYd9ZK1P3Jywr+ycbxFs+3RPnRq9bY4ds7zzvRGruVvTyRW6TR5y7Bp0dMbzypnz72Aca5u45ZwfW00RfFdg22Eq4JVvz8PMIKewJ74xyOMdr9VdBRvK1evbpPB4lLeyQ7sh9o7/2t05SPN61LHtsrwRouSmMMiuUZb5R7xFqLOSJ7H3X/631TvqVQUDGYG/yTrUYURCMdw4L+oaDj2Nzgd+OtEcVmj1EjHsOC/kEF20jHvDnh2PVRFpgjkkc1UMEqFy9eDHPCUc0PLDBHXB91nP5wYCO8ZCraFvqwMQf4XlNYYE7QKFqEJ/+LzH8AJAsHnUo27mMAAAAASUVORK5CYII=\",\r\n        githubstar: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEwAAAAUCAMAAAAQlCuDAAACplBMVEVVVVXVVSvmcjP0gEFNTU1PT09fX1/kbzD2gUJfX1/3g0VdXV3nczT1gkJPT09NTU3kcDE5OTk7Ozs8PDw9PT0+Pj5BQUFCQkJDQ0NERERFRUVISEhJSUlMTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1eXl5fX19gYGBiYmJlZWVnZ2dra2t4eHh8fHx/f3+CgoKGhoaIiIiJiYmKioqLi4uMjIyQkJCTk5OZmZmampqenp6fn5+jUiajo6OkpKSmpqanp6eqqqqrq6usrKyuVyivWCivWCmwsLCxYDSzXCy0XC21Wyq1XCy3XCq4Xi65YTG6Zzq7Xiu9YS/BajvCwsLDYi3Dw8PEZjTEb0DExMTFxcXGxsbHZC7Hx8fIZjLKjGzPz8/QazTSqJDTqJHVbDPVk2/V1dXWazLWcjzW1tbZczzb29vcnXrcrJLc3NzdgE7d3d3e3t7f39/g4ODht6DicjXkcDHkdDfk5OTlcTLlczXlxrXl5eXmcjPmdTfml2znczTodDXoe0DpdTbpdzjpfULp6enqdjfq6urrdzjreTrrejzrhk/rmWzrzr/r1srseDnsez3sgkjs1MfteTrtfUHthU3twant1cjt7e3uejvvezzvfkDv0sLv7+/wfD3wfkDwgEPwhEnwlGHwzLjw8PDxfT7xfkDxoXXxrorx8fHyfj/y8vLzf0Dzpnz0gEH1gUL1so71v6L149n2gkP3g0T3wKL3waP37Ob37+v39/f4vZ34yK74z7n46eD4+Pj50Lr52Mf58/D60r361MD61sL61sP65tv6+vr73s/739D739H75tr759z76N376uD85dj85tr859z87OT9/f3+/fz+/f3///9Ftc+TAAAAEXRSTlMGBo2Njo6Ojo7j4+jo6Ons7CIlfBEAAAABYktHROFfCM+mAAACOUlEQVQYGZXBT0iTYQDH8e/7vs82l6ibYgYWMrE0QaE/VEKXBJMIBKVDHSLoUhSdO3axbqVEERFENwnqVpMIIoIoioIuSRCUFSFWrkzd9u7Z++udUgnhXJ+PQ9Rrp1IjrGx/kDfRzYhKiZXdoc9EROUs5awxraJyopxbRvyHgLKM+A8BZblabswohZTWcmn9UfgrYQv/MGKZoz4XBwIQy4nf8vzRPOnxlyNCrhY1HR5M3ZWumw6vvz9Cy4FjjYqfl9ybEm1Dh9ZqkQ25bdu2ttt1Tnenm9qybZNn7a6mjUlbYkSo5viZTBeC4FlxrFhH/uqGI5eDQAjB/LXG4eE5QgJiz+878f6x02e1e3xWbX1TkP5c10PIiFA8bRMfEaiauiJMJmZ2XhFCCD4lsumqn4R84Mm+7tkJfwe9HoMpZ2raB9uLT8iIUDHiqgqBhASuTAGL8BDEcp4biJAFqkY3bB14VMCPNDSesw09FtZbFhkRmut79X0dAul1/CcgUC5T+6MBQdP75J7HIpQHWqo/PGzNFr/Fsm+/dlc3Z/IQybPIiNDs6ND8y9cCaWTQv40Acan3yzQC72D0wrxLKACyfVHncn39jVPF63avHW8JIGCJ00qJZp2OjgdkEk4u79R8T0Im4fh5x8slycR9xaOUnAQ+vUNN7byZcrdPWGMWdnGvnyVOipLOLi/zMMtqTlCWESUvnsqNe6xGlGVmkoRiMUCspkg5Z002QeUs5SyYwufqWipVZGWjWf0CDsxBCuuBMAQAAAAASUVORK5CYII=\",\r\n        changelog: \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAAUCAYAAABGUvnzAAAABmJLR0QA/wD/AP+gvaeTAAAHa0lEQVRo3uVaaVBU2RV+wYaBmCjjZOJU1kqmUpU/k7/GuFdpLHdFBXcrrj/G/LDUcSGoQUPQCIWZlDNYwdHBYQAdkc1MsyPQsjWLCrgAAg0KCAOoLCL45X6HvGfTpbGtwrFq+lR91eee7fZ7X59zr5Ta8uXLPdavXx+qYFOAK+CdtPLvOmzj0stCtdhYD43krlu3Dq6EcallLgHvlJIQbe3atc0KcCW8nVLqEvBOLWvW1qxZA1eDt7nEZaCtXr0aroaxX1tdBtqqVavgahjzn2KXgbZy5Uo4i/7+frmkvErOm8Tg4OBz7T+8VOQUKM7GjgT6Bp/iXdV1I1lTW7FiBZzF1q1b8SrxrwqOz5GsR4KfZ/9BcqFToDgbOxLoGxjAjy6NbE3Nz88P9uAt02KxoL6+Hnfu3EFRUZHhYwfzkkKdEhUVJXHNzc04cOAAkpKS0NDQINi+fbuRR0lMTMTly5dx48YNHDt2bJgvLi4O+fn5iIiIEFtAQAAqKytRW1uL6upqBAYGGvG5ubmoqamRfQsLC7FhwwbDFxISgqamJsmLjY0Vgh2fjxidVDAMf7RUoqDjIa51deOqwuwrlWKn+Fc2iK22uw9+RbeMnJimNpR0PpKchHvf4Kdmq+GjBFQ1SIxVxdjnrbXexs2HvWI/eMOGgadPDV/fkwG8kzyk+xTeRLmqff1BN7LauvBBZrlTNRyh+fr6wh7BwcHIy8vDjBkzBIsWLTJ8JJhnGHVKWFiYxBw8eBC9vb3Yt28fpk+fjhMnTiAzM9PIoxw5cgSzZs3C4sWLcf/+fWzevHmYb+bMmZg3b54QVldXh2XLlkltEtLa2irdzXjdTvAHceHCBbFv2bIFXV1dMoLpO3XqlBDs+HzE9xPzDfxCEXO/7zGmfB4Hz79/gtHBn+K98+nio2zLKRH77/8djYZHPUbez8JjxE4E5BbjHzdtho+y3GyBZ9gZ/PaTL4y8X6dY0dLbh/c/PgNPtc9HljIMqLGs55HgcUn5+JWKa1Pf6YPwKKn/oTkHRe1dTtVwhKb+kgV7bNy4UTrSbDYjNDRUCNV9JJgvkDplyZIlopOUzs5OLFy4UNY7duzA9evXjTwKiaG+dOlS6UL+kHQfbXos7R0dHaioqBCwjs1mw7Zt28QfHh4u3V1VVSWdXFBQIPajR49KXR8fH1mTSBLs+HyEV8IVA75XKpBWUw/PqJQhW7wFXnE5olO8P0sQ3TM6FT1PnmCMIoDrP5feRm77A+QplLd14FJdk1GTMiby0tD6XCa6+4fyVqi94quq4XU+S3zjT8cLOXoeCX478Qr8VFxKdR28YjPEPjriIh4PDOLHany/rIYjNL54e5C02bNnY//+/TJW7927J91DHwnmGUZdJ4b6pk2bpMv0Grt27RIS9DWFlzN9TVI4dh3rEIcPH0Z5ebnRpcTUqVOl83fv3o3Gxkb5IdHOiWG1WiUvKCgI2dnZRh1+ZxLs+HyEpyJRx7L0IqTdaRxm00HxvJhrrB897od3ggV/yL6KW50PMT7sM3ioLvKJTUZGfdNL8/wyixB/u86wj0+0CDn6mgQzzjejCCm1NsM+WuGxOp/fdaKGIzS+XHtwdLJrOS7nzJmD9vZ2GX/0kWCOTOo6MQS7ngTr6507dwrB+ppy+vRp0dmJ7HYS4FiH4ITgqD106JBh27t3r3zynC8tLZUfIddZWVlCMHXeDVpaWuSHxDWPDxLs+HzEWxfzDPzky6/R0t2DyYo0rvky31PnIHWKfSyJGpuQh3kZxciua8RbX2WLPbqmEelc/y/uRXm/jElBq9rrfXOx2D+6WiPk6HEkmHE/jzajracXv0srEfuHpdUosN1V+11+aQ1HaBxp9uBL5BlI8LJ08uRJOYfpI8EcfdQpeg5HNAnW1zrB+poSGRkpNdmBe/bskY50rEPQzts6xzNH8927d43Ru2DBAqSlpaGkpAQ5OTlykSLBep6/v7/sy/P/7NmzQrDj8xEecbnPoEbotIgYFLZ1olJdaK51PcLM7DLxUexjSdSY+Fx4fZ6M6IrbMDd/g2hbK0KLrwnBetyL8jzUyF19Lhm31D5l6oL0N2sl2hWRehwJHorLxMKzcSjveICqBz3IbGqVs1ziXlLDERq7wR5z587FlClTDPBipPsmTpwoL1LXdTtH5qRJk4z1/PnzZazqawrrsN7kyZNlMtjXdPwOej5jCY5j3UedFzl+8mI2bdo0w8e6zKOP+z2vNuF+IWc4FGGmw/+CKfCfMB36GO5nksQ+am/wsDjTX47CXXWtu+ok07EImILDhz6PR8KkLkN63AvzlD5WnZmmvx6Xff6UakHyzZpncf7P4txPxUuMKfD4UO2oVCPu/9VwhEbCXjco38Y+zsJEgt4QgirrYVX/JGP3pTe24Deffvlaa2gcv68bEyZMwLexj7MYdT77zUF1u5uaFG6qM92CTmDUF+bXWoMEt3DEuhLczmW5Br7KbibBoby8uBK+py4xrgC381kh/EOHh3poktzGy40rQIvJ+G4jNrNNO5ch/2XnvyqleyAtV3GuAAAAAElFTkSuQmCC\",\r\n    },\r\n};\r\n\r\nexport default class About extends React.Component {\r\n\r\n    render() {\r\n        const href = br.isFirefox() ? \"https://addons.mozilla.org/addon/simpread?src=external-ext\" : \"https://chrome.google.com/webstore/detail/simpread-reader-view/ijllcpnolfcooahcekpamkbidhejabll/reviews\";\r\n        return (\r\n            <div id=\"labs\" style={{ width: '100%' }}>\r\n                <div style={ style.root }>\r\n                    <image src={browser.runtime.getURL(\"assets/images/icon128.png\")}></image>\r\n                    <div style={ style.title }>简悦 SimpRead</div>\r\n                    <div>为你提供「如杂志般沉浸式阅读体验」的扩展</div>\r\n                    <div style={ style.badges }>\r\n                        <a href={ urls.href.version      } target=\"_blank\"><img style={ style.img } src={ urls.badges.version }/></a>\r\n                        <a href={ urls.href.website      } target=\"_blank\"><img style={ style.img } src={ urls.badges.website }/></a>\r\n                        <a href={ urls.href.githubstar   } target=\"_blank\"><img style={ style.img } src={ urls.badges.githubstar }/></a>\r\n                        <a href={ urls.href.changelog    } target=\"_blank\"><img style={ style.img } src={ urls.badges.changelog }/></a>\r\n                    </div>\r\n\r\n                    <div>\r\n                        <a style={ style.link } href=\"http://ksria.com/simpread\">简悦</a> 的初衷：还原一个干净的阅读空间，提升你的阅读体验。<br/>\r\n                        截至到目前为止，简悦已经适配了 <spn style={ style.stat }>{ this.props.site }</spn> 类网址，详细请看 <a style={ style.link } href=\"https://simpread.ksria.cn/sites/\" target=\"_blank\">这里</a>。<br/>\r\n                        自从 <span style={ style.stat }>{ this.props.option.create && this.props.option.create.split(\" \")[0] }</span> 安装后，共使用了 <spn style={ style.stat }>{ this.props.statistics.focus }次</spn> 聚焦模式，以及 <span style={ style.stat }>{ this.props.statistics.read }次</span> 阅读模式。<br/>\r\n                        </div>\r\n                </div>\r\n\r\n                <div className=\"label\" data-head-level=\"h1\">帮助</div>\r\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <div><a style={style.href} target=\"_blank\" href=\"http://sr.ksria.cn/zhifu_m2.png\">如果简悦可以解决你在阅读上痛点，可以请我喝杯咖啡</a></div>\r\n                        <span className=\"desc\">简悦是一个免费且开源的项目</span>\r\n                        <span className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <div><a style={style.href} target=\"_blank\" href={href}>如果简悦对你有所帮助，请帮忙投票</a></div>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"https://github.com/Kenshin/simpread\">简悦是一个开源的产品，代码托管在 Github 上</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"http://ksria.com/simpread/guide/\">第一次使用简悦？或者并不了解「阅读模式」请前往 <b>新手入门</b></a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href}><b onClick={()=>this.props.onClick(\"welcome\")}>重看引导页面</b> 或者 <a style={style.href} target=\"_blank\" href=\"http://ksria.com/simpread/welcome/version_1.1.4.html\"><b>重看当前版本</b></a> 的功能介绍</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n\r\n                <div className=\"label\" data-head-level=\"h1\">其它平台的简悦</div>\r\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <div><a style={style.href} target=\"_blank\" href=\"http://ksria.com/simpread/#downloads\">简悦已经上线了 Firefox 版，UserScript 版，JSBox 版，总有一款适合你</a></div>\r\n                        <span className=\"desc\">包括但不限于：Chrome · Firefox · Safari · Apple Safari · Microsoft Edge · Opera · iPhone · iPad</span>\r\n                        <span className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n\r\n                <div className=\"label\" data-head-level=\"h1\">反馈</div>\r\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"https://github.com/kenshin/simpread/issues\">如果有任何问题请提交 issues</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"https://t.me/simpread\">现在就加入 Telegram 群，获取简悦的第一手资料</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\" style={{cursor: 'default'}}>\r\n                        可以在 <a style={style.href} target=\"_blank\" href=\"https://twitter.com/wanglei001\"><b>Twitter</b></a> 或 <a style={style.href} target=\"_blank\" href=\"https://weibo.com/23784148\"><b>新浪微博</b></a> 上关注我\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n\r\n                <div className=\"label\" data-head-level=\"h1\">其它作品</div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"http://ksria.com/gnvm\">GNVM - 使用 Go 语言编写的 Node.js 多版本管理器</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"http://ksria.com/emoji\">+Emoji - 一个 简单、可靠、纯粹、中文语义化的 Emoji 扩展</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"http://ksria.com/sov2ex\">SOV2EX - 一个便捷的 V2EX 站内搜索引擎（前端界面）</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n                <div style={{ 'margin-top': '10px', 'position': 'relative' }} className=\"lab\">\r\n                    <div className=\"more\">\r\n                        <a style={style.href} target=\"_blank\" href=\"http://ksria.com/simptab\">简 Tab - 极简的 Chrome 新标签页扩展，望你每次打开都有好心情</a>\r\n                        <span style={{ bottom: \"11px\" }} className=\"arrow\"></span>\r\n                    </div>\r\n                </div>\r\n            </div>\r\n        )\r\n    }\r\n}"
  },
  {
    "path": "src/module/account.jsx",
    "content": "console.log( \"===== simpread option account load =====\" )\n\nimport {storage} from 'storage';\nimport * as ss   from 'stylesheet';\nimport * as watch from 'watch';\nimport * as run   from 'runtime';\n\nimport TextField from 'textfield';\nimport Button    from 'button';\n\nexport default class AccountOpt extends React.Component {\n\n    static defaultProps = {\n        colors: [\"rgb(255, 114, 129)\", \"rgb(64, 196, 255)\", \"rgb(255, 157, 68)\", \"rgb(140, 216, 66)\", \"rgb(251, 88, 74)\", \"rgb(255, 229, 95)\", \"rgb(0, 230, 118)\", \"rgb(0, 169, 240)\", \"rgb(128, 222, 234)\", \"rgb(247, 77, 95)\", \"rgb(255, 206, 73)\", \"rgb(250, 154, 63)\", \"rgb(255, 114, 129)\", \"rgb(57, 194, 241)\", \"rgb(141, 196, 72)\", \"rgb(128, 222, 234)\", \"rgb(83, 109, 254)\", \"rgb(255, 183, 77)\", \"rgb(197, 231, 99)\", \"rgb(239, 83, 80)\", \"rgb(0, 230, 118)\", \"rgb(57, 194, 241)\", \"rgb(100, 181, 246)\", \"rgb(119, 232, 86)\", \"rgb(239, 83, 80)\", \"rgb(0, 203, 232)\", \"rgb(0, 230, 118)\", \"rgb(255, 196, 0)\", \"rgb(255, 206, 73)\", \"rgb(167, 134, 116)\", \"rgb(86, 209, 216)\", \"rgb(253, 208, 174)\", \"rgb(197, 231, 99)\", \"rgb(239, 88, 74)\", \"rgb(249, 79, 40)\", \"rgb(255, 88, 100)\", \"rgb(197, 231, 99)\", \"rgb(0, 177, 251)\", \"rgb(255, 206, 73)\", \"rgb(251, 182, 75)\", \"rgb(197, 231, 99)\", \"rgb(35, 180, 210)\", \"rgb(255, 206, 73)\", \"rgb(255, 229, 95)\", \"rgb(64, 196, 255)\", \"rgb(255, 114, 129)\", \"rgb(119, 232, 86)\", \"rgb(139, 223, 231)\", \"rgb(0, 169, 240)\"],\n        idx   : \"abcdefghijklmnopqrstuvwxyz0123456789\",\n    }\n\n    state = {\n        uid_err  : \"\",\n        name_err : \"\",\n        email_err: \"\",\n    };\n\n    onChangeName( event ) {\n        const value = event.target.value;\n        if ( /^([\\u2E80-\\u9FFFA-Za-z0-9]+)([ ]?)([\\u2E80-\\u9FFFA-Za-z0-9]*)$/ig.test( value ) ) {\n            this.setState({ name_err : \"\" });\n        } else this.setState({ name_err : \"只能包含中英文 + 数字 + 中间的空格\" });\n        storage.user.name = value;\n    }\n\n    onChangeEmail( event ) {\n        const value = event.target.value;\n        if ( /^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$/ig.test( value ) ) {\n            this.setState({ email_err : \"\" });\n        } else this.setState({ email_err : \"请确保输入正确的 Email 地址\" });\n        storage.user.email = value;\n    }\n\n    onChangeContact( event ) {\n        storage.user.contact = event.target.value.trim();\n    }\n\n    componentWillMount() {\n        if ( storage.user.uid == \"\" ) {\n            storage.user.uid = run.ID( \"user\" );\n            storage.Write( () => {\n                console.log( \"current user info is \", storage.user )\n                this.setState({ uid_err : \"\" });\n                watch.SendMessage( \"option\", true );\n            }, storage.simpread );\n        }\n        this.props.load.first && this.stat();\n    }\n\n    stat() {\n        $.ajax({\n            url   : storage.service + \"/stats/service/count/\",\n            method: \"POST\",\n            data  : {type: this.props.load.update ? \"update\" : \"first\" }\n        }).done( ( result, textStatus, jqXHR ) => {\n            console.log( \"count success \" )\n        }).fail( error => {\n            console.log( \"count failed \", error )\n        });\n    }\n\n    save() {\n        if ( storage.user.name.trim() == \"\" ) {\n            this.setState({ name_err : \"昵称不能为空\" });\n            return;\n        } else if ( storage.user.email.trim() == \"\" ) {\n            this.setState({ email_err : \"昵称不能为空\" });\n            return;\n        }\n        $.ajax({\n            url   : storage.service + \"/users/service/update/\" + storage.user.uid,\n            method: \"POST\",\n            data  : storage.user\n        }).done( ( result, textStatus, jqXHR ) => {\n            result.code == 201 ? \n                storage.Write( ()=> {\n                    watch.SendMessage( \"option\", true );\n                    new Notify().Render( \"更新成功，请刷新本页！\" );\n                }) :\n                new Notify().Render( 2, \"更新出现错误，请稍后再试！\" );\n        }).fail( error => {\n            new Notify().Render( 2, \"更新出现错误，请稍后再试！\" );\n        });\n    }\n\n    render() {\n        let avatar = this.props.user.avatar;\n        if ( avatar == \"\" ) {\n            avatar = this.props.user.name.substr( 0, 1 );\n        }\n        let idx = this.props.idx.indexOf( avatar.toLowerCase() );\n        idx == -1 && ( idx = 0 );\n        const color = this.props.colors[idx];\n        return (\n            <div id=\"labs\" style={{ width: '100%' }}>\n                <div className=\"label\">用户信息</div>\n                <div className=\"lab\" style={{'padding-top': '25px'}}>\n                    <sr-opt-gp style={{'justify-content': 'center'}}>\n                        { avatar && <span className=\"avatar\" style={{ \"background-color\": color }}>{ avatar.toUpperCase() }</span> }\n                    </sr-opt-gp>\n                    <sr-opt-gp>\n                        <TextField \n                            floatingtext=\"标识\" \n                            placeholder=\"系统自动生成，不可更改\" \n                            errortext={ this.state.uid_err }\n                            value={ this.props.user.uid } disable={true} />\n                    </sr-opt-gp>\n                    <sr-opt-gp>\n                        <TextField \n                            floatingtext=\"昵称\" \n                            placeholder=\"只能包含中英文 + 数字 + 中间的空格\" \n                            errortext={ this.state.name_err }\n                            value={ this.props.user.name } onChange={ e=>this.onChangeName(e) } disable={false} />\n                    </sr-opt-gp>\n                    <sr-opt-gp>\n                        <TextField \n                            floatingtext=\"邮箱\" \n                            placeholder=\"请使用真实且有效的邮箱地址\" \n                            errortext={ this.state.email_err }\n                            value={ this.props.user.email } onChange={ e=>this.onChangeEmail(e) } disable={false} />\n                    </sr-opt-gp>\n                    <sr-opt-gp>\n                        <TextField \n                            floatingtext=\"联络方式\" \n                            placeholder=\"微博 / 微信 等一切可以联络到你\b的方式\" \n                            value={ this.props.user.contact } onChange={ e=>this.onChangeContact(e) } disable={false} />\n                    </sr-opt-gp>\n                    <sr-opt-gp>\n                        <Button text=\"保 存\" \n                            waves=\"md-waves-effect\" color=\"#fff\" backgroundColor=\"rgb(111, 122, 155)\" \n                            width=\"100%\" icon={ ss.IconPath( \"save_icon\" ) }\n                            onClick={ ()=>this.save() } />\n                    </sr-opt-gp>\n                </div>\n            </div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/actionbar.jsx",
    "content": "console.log( \"=== simpread action bar load ===\" )\r\n\r\nimport * as ss     from 'stylesheet';\r\n\r\nimport Button      from 'button';\r\nimport * as ttips  from 'tooltip';\r\n\r\n/**\r\n * Action bar\r\n * \r\n * @class\r\n */\r\nexport default class Actionbar extends React.Component {\r\n\r\n    static defaultProps = {\r\n        items: [],\r\n    }\r\n\r\n    static propTypes = {\r\n        items   : React.PropTypes.array,\r\n        onAction: React.PropTypes.func,\r\n    }\r\n\r\n    constructor( props ) {\r\n        super( props );\r\n    }\r\n\r\n    render() {\r\n        const items       = Object.keys(this.props.items).map( key => {\r\n            const action  = this.props.items[key];\r\n            const items   = Object.keys( this.props.items[key].items ).map( item => {\r\n                const obj = this.props.items[key].items[item];\r\n                return (\r\n                    <Button shape=\"circle\"\r\n                        icon={ obj.icon } fontIcon={ obj.fontIcon }\r\n                        color=\"#fff\" backgroundColor={ obj.color }\r\n                        waves=\"md-waves-effect md-waves-button\"\r\n                        tooltip={{ text: obj.name }}\r\n                        onClick={ ()=>this.props.onAction && this.props.onAction( item ) }\r\n                    />\r\n                )\r\n            })\r\n            return (\r\n                <sr-opt-gp>\r\n                    <sr-opt-label>{action.name}</sr-opt-label>\r\n                    <actions style={{ display: \"flex\", margin: \"10px 0\", \"flex-wrap\": \"wrap\" }}>{ items }</actions>\r\n                </sr-opt-gp>\r\n            );\r\n        });\r\n        return (\r\n            <action-bar>\r\n                { items }\r\n            </action-bar>\r\n        )\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/module/authorize.jsx",
    "content": "console.log( \"===== simpread option labs:Authorize load =====\" )\r\n\r\nimport {storage} from 'storage';\r\nimport * as exp  from 'export';\r\nimport * as msg  from 'message';\r\nimport {browser} from 'browser';\r\nimport * as permission\r\n                 from 'permission';\r\n\r\nimport Notify    from 'notify';\r\nimport Switch    from 'switch';\r\nimport TextField from 'textfield';\r\nimport Button    from 'button';\r\nimport Dropdown  from 'dropdown';\r\n\r\nexport default class Auth extends React.Component {\r\n\r\n    static defaultProps = {\r\n        linnk: {\r\n            username: \"\",\r\n            password: \"\",\r\n        },\r\n        instapaper: {\r\n            username: \"\",\r\n            password: \"\",\r\n        },\r\n        jianguo: {\r\n            username: \"\",\r\n            password: \"\",\r\n        },\r\n        weizhi: {\r\n            username: \"\",\r\n            password: \"\",\r\n        }\r\n    }\r\n\r\n    state = {\r\n        secret : undefined,\r\n        linnk  : undefined,\r\n        instapaper : undefined,\r\n        jianguo: undefined,\r\n        weizhi : undefined,\r\n        notion : undefined,\r\n        youdao : undefined,\r\n    }\r\n\r\n    onChange( state, value, flag ) {\r\n        let notify;\r\n        const { dropbox, pocket, instapaper, linnk, evernote, onenote, gdrive, jianguo, yuque, notion, youdao, weizhi } = exp,\r\n            clear = ( id, name ) => {\r\n                Object.keys( storage.secret[id] ).forEach( item => storage.secret[id][item] = \"\" );\r\n                storage.Safe( ()=> {\r\n                    new Notify().Render( `已取消对 ${name} 的授权，也请解除简悦的访问权限， <a target=\"_blank\" href=\"${exp.Unlink(id)}\">点击这里</a>` );\r\n                    this.setState({ secret: storage.secret });\r\n                }, storage.secret );\r\n            },\r\n            success = ( id, name, data ) => {\r\n                notify && notify.complete();\r\n                Object.keys( data ).forEach( item => storage.secret[id][item] = data[item] );\r\n                id == \"jianguo\" && ( storage.secret[id][\"access_token\"] = { username: data.username, password: data.password });\r\n                storage.Safe( () => {\r\n                    new Notify().Render( `已成功授权 ${name} 。` );\r\n                    id == \"linnk\"      && this.setState({ secret: storage.secret, linnk: false });\r\n                    id == \"instapaper\" && this.setState({ secret: storage.secret, instapaper: false });\r\n                    id == \"jianguo\"    && this.setState({ secret: storage.secret, jianguo: false });\r\n                    id == \"weizhi\"     && this.setState({ secret: storage.secret, weizhi: false });\r\n                    id == \"notion\"     && this.setState({ secret: storage.secret, notion: notion.blocks });\r\n                    id == \"youdao\"     && this.setState({ secret: storage.secret, youdao: youdao.folders });\r\n                    if ( location.hash.startsWith( \"#labs?auth=\" ) ) {\r\n                        new Notify().Render( \"3 秒钟将会关闭此页面...\" );\r\n                        setTimeout( () => {\r\n                            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.auth_success, { url: location.href, type: \"auth\", name: id } ));\r\n                        }, 3000 )\r\n                    }\r\n                }, storage.secret );\r\n            },\r\n            failed = ( error, id, name ) => {\r\n                notify && notify.complete();\r\n                console.error( `${name} auth faild, error: ${error}` )\r\n                id == \"youdao\" || id == \"notion\" ? new Notify().Render( 2, `获取 ${name} 授权失败，${error}` ) :\r\n                new Notify().Render( 2, `获取 ${name} 授权失败，请重新获取。` );\r\n                storage.secret[state].access_token = \"\";\r\n                this.setState({ secret: storage.secret });\r\n            };\r\n\r\n        if ( state == \"linnk\" && !flag && !storage.secret.linnk.access_token ) {\r\n            this.setState({ linnk: !this.state.linnk });\r\n            return;\r\n        }\r\n\r\n        if ( state == \"instapaper\" && !flag && !storage.secret.instapaper.access_token ) {\r\n            this.setState({ instapaper: !this.state.instapaper });\r\n            return;\r\n        }\r\n\r\n        if ( state == \"jianguo\" && !flag && !storage.secret.jianguo.username ) {\r\n            this.setState({ jianguo: !this.state.jianguo });\r\n            return;\r\n        }\r\n\r\n        if ( state == \"weizhi\" && !flag && !storage.secret.weizhi.username ) {\r\n            this.setState({ weizhi: !this.state.weizhi });\r\n            return;\r\n        }\r\n\r\n        if ( !value ) {\r\n            state == \"pocket\" && $( this.refs.pocket_tags ).velocity( value ? \"slideDown\" : \"slideUp\" );\r\n            if ( state == \"linnk\" ) {\r\n                this.props.linnk.username = \"\";\r\n                this.props.linnk.password = \"\";\r\n            }\r\n            if ( state == \"instapaper\" ) {\r\n                this.props.instapaper.username = \"\";\r\n                this.props.instapaper.password = \"\";\r\n            }\r\n            if ( state == \"jianguo\" ) {\r\n                this.props.jianguo.username = \"\";\r\n                this.props.jianguo.password = \"\";\r\n            }\r\n            if ( state == \"weizhi\" ) {\r\n                this.props.weizhi.username = \"\";\r\n                this.props.weizhi.password = \"\";\r\n                this.props.weizhi.access_token = \"\";\r\n            }\r\n            if ( state == \"youdao\" ) {\r\n                permission.Remove( youdao.permissions, result => new Notify().Render( `已取消 cookies 权限。` ));\r\n            }\r\n            clear( state, exp.Name( state ));\r\n            return;\r\n        }\r\n\r\n        new Notify().Render({ content: \"授权中，请勿关闭此页面，授权成功后会有提示。\", delay: 10000 } );\r\n\r\n        switch ( state ) {\r\n            case \"dropbox\":\r\n                dropbox.New().Auth();\r\n                dropbox.dtd\r\n                    .done( ()    => success( dropbox.id, dropbox.name, { access_token: dropbox.access_token } ))\r\n                    .fail( error => failed( error, dropbox.id, dropbox.name ));\r\n                break;\r\n            case \"pocket\":\r\n                notify = new Notify().Render({ content: `开始对 ${ pocket.name } 进行授权，请稍等...`, state: \"loading\" });\r\n                pocket.Request( ( result, error ) => {\r\n                    if ( error ) failed( error, pocket.id, pocket.name );\r\n                    else {\r\n                        pocket.New().Login( result.code );\r\n                        pocket.dtd.done( ()=> {\r\n                            pocket.Auth( ( result, error ) => {\r\n                                if ( error ) failed( error, pocket.id, pocket.name );\r\n                                else success( pocket.id, pocket.name, { access_token: pocket.access_token });\r\n                            });\r\n                        }).fail( error => failed( error, pocket.id, pocket.name ));\r\n                    }\r\n                });\r\n                break;\r\n            case \"instapaper\":\r\n                instapaper.Login( this.props.instapaper.username, this.props.instapaper.password, ( result, error ) => {\r\n                    if ( error ) failed( error, instapaper.id, instapaper.name );\r\n                    else success( instapaper.id, instapaper.name, { access_token: instapaper.access_token, token_secret: instapaper.token_secret });\r\n                });\r\n                break;\r\n            case \"linnk\":\r\n                linnk.Login( this.props.linnk.username, this.props.linnk.password, ( result, error ) => {\r\n                    if ( error ) failed( error, linnk.id, linnk.name );\r\n                    else if ( result.code == 200 ) {\r\n                        linnk.Groups( result => {\r\n                            if ( result.code == 200 ) {\r\n                                linnk.GetGroup( \"\", result.data );\r\n                                success( linnk.id, linnk.name, { access_token: linnk.access_token, group_name: linnk.group_name });\r\n                            } else {\r\n                                const msg = linnk.error_code[result.code];\r\n                                new Notify().Render( 2, msg ? msg : `获取 ${ linnk.name } 授权失败，请重新获取。` );\r\n                            }\r\n                        });\r\n                    } else {\r\n                        const msg = linnk.error_code[result.code];\r\n                        new Notify().Render( 2, msg ? msg : `获取 ${ linnk.name } 授权失败，请重新获取。` );\r\n                    }\r\n                });\r\n                break;\r\n            case \"yinxiang\":\r\n            case \"evernote\":\r\n                evernote.env     = state;\r\n                evernote.sandbox = false;\r\n                notify = new Notify().Render({ content: `开始对 ${ evernote.name } 进行授权，请稍等...`, state: \"loading\" });\r\n                evernote.New().RequestToken( ( result, error ) => {\r\n                    if ( error ) failed( error, evernote.id, evernote.name );\r\n                    else {\r\n                        evernote.dtd.done( () => {\r\n                            evernote.Auth( ( result, error ) => {\r\n                                if ( error ) failed( error, evernote.id, evernote.name );\r\n                                else success( evernote.id, evernote.name, { access_token: evernote.access_token });\r\n                            });\r\n                        }).fail( error => failed( error, evernote.id, evernote.name ));\r\n                    }\r\n                });\r\n                break;\r\n            case \"onenote\":\r\n                onenote.New().Login();\r\n                onenote.dtd.done( ()=> {\r\n                    onenote.Auth( ( result, error ) => {\r\n                        if ( error ) failed( error, onenote.id, onenote.name );\r\n                        else success( onenote.id, onenote.name, { access_token: onenote.access_token });\r\n                    });\r\n                }).fail( error => failed( error, onenote.id, onenote.name ));\r\n                break;\r\n            case \"gdrive\":\r\n                gdrive.New().Login();\r\n                gdrive.dtd.done( ()=> {\r\n                    gdrive.Auth( ( result, error ) => {\r\n                        if ( error ) failed( error, gdrive.id, gdrive.name );\r\n                        else success( gdrive.id, gdrive.name, { access_token: gdrive.access_token, folder_id: gdrive.folder_id });\r\n                    });\r\n                }).fail( error => failed( error, gdrive.id, gdrive.name ));\r\n                break;\r\n            case \"yuque\":\r\n                yuque.New().Login();\r\n                yuque.dtd.done( ()=> {\r\n                    yuque.Auth( ( result, error ) => {\r\n                        if ( error ) failed( error, yuque.id, yuque.name );\r\n                        else {\r\n                            yuque.GetUser( ( result, error ) => {\r\n                                if ( error ) failed( error, yuque.id, yuque.name );\r\n                                else {\r\n                                    yuque.GetRepos( ( result, error ) => {\r\n                                        if ( error ) failed( error, yuque.id, yuque.name );\r\n                                        else if ( yuque.repos_id != \"\" ) {\r\n                                            success( yuque.id, yuque.name, { access_token: yuque.access_token, repos_id: yuque.repos_id });\r\n                                        } else {\r\n                                            yuque.CreateRepo( ( result, error ) => {\r\n                                                if ( error ) failed( error, yuque.id, yuque.name );\r\n                                                else success( yuque.id, yuque.name, { access_token: yuque.access_token, repos_id: yuque.repos_id });\r\n                                            });\r\n                                        }\r\n                                    });\r\n                                }\r\n                            });\r\n                        }\r\n                    });\r\n                }).fail( error => failed( error, yuque.id, yuque.name ));\r\n                break;\r\n            case \"notion\":\r\n                notion.Auth( ( result, error, warn ) => {\r\n                    if ( error ) failed( error, notion.id, notion.name );\r\n                    else {\r\n                        warn != \"\" && new Notify().Render({ type: 2, content: `请注意：授权时出现了问题导致 <b>仅成功授权了</b> 您的第一个 Notion Page 虽然不影响导出服务，但建议提 <a target=\"_blank\" href=\"https://github.com/Kenshin/simpread/issues/809\"><b>Issues</b></a>`, state: \"holdon\" });\r\n                        success( notion.id, notion.name, { access_token: notion.access_token, folder_id: notion.folder_id, save_image: notion.save_image, type: notion.type });\r\n                    }\r\n                });\r\n                break;\r\n            case \"youdao\":\r\n                permission.Get( youdao.permissions, result => {\r\n                    if ( !result ) {\r\n                        new Notify().Render( 2, `此功能需要申请 cookies 权限后才能使用，授权成功后会自动取消。` );\r\n                        this.setState({ secret: storage.secret });\r\n                        return;\r\n                    }\r\n                    setTimeout( () => {\r\n                        youdao.Auth( ( result, error ) => {\r\n                            if ( error ) failed( error, youdao.id, youdao.name );\r\n                            else success( youdao.id, youdao.name, { access_token: youdao.access_token, folder_id: youdao.folder_id });\r\n                        });\r\n                    }, 500 );\r\n                });\r\n                break;\r\n            case \"jianguo\":\r\n                jianguo.Auth( this.props.jianguo.username, this.props.jianguo.password, result => {\r\n                    if ( result && result.status == 401 ) {\r\n                        failed( \"授权错误，请重新授权。\", jianguo.id, jianguo.name );\r\n                    } else success( \"jianguo\", \"坚果云\", { username: this.props.jianguo.username, password: this.props.jianguo.password } );\r\n                });\r\n                break;\r\n            case \"weizhi\":\r\n                if ( location.hash.startsWith( \"#labs?auth=\" ) ) {\r\n                    this.props.weizhi.username = storage.secret.weizhi.username;\r\n                    this.props.weizhi.password = storage.secret.weizhi.password;\r\n                }\r\n                weizhi.Auth( this.props.weizhi.username, this.props.weizhi.password, result => {\r\n                    if ( result && result.status == 401 ) {\r\n                        failed( \"授权错误，请重新授权。\", weizhi.id, weizhi.name );\r\n                    } else {\r\n                        if ( result && result.returnCode == 200 ) success( \"weizhi\", \"为知笔记\", { username: this.props.weizhi.username, password: this.props.weizhi.password, access_token: weizhi.access_token } );\r\n                        else failed( \"授权错误，请重新授权。\", weizhi.id, weizhi.name );\r\n                    }\r\n                });\r\n                break;\r\n        }\r\n    }\r\n\r\n    save( state, value ) {\r\n        state == \"pocket\" && ( storage.secret.pocket.tags      = value.trim() );\r\n        state == \"linnk\"  && ( storage.secret.linnk.group_name = value.trim() );\r\n        state == \"youdao\" && ( storage.secret.youdao.folder_id = value.trim() );\r\n        state == \"notion_save_image\" && ( storage.secret.notion.save_image = value );\r\n        if ( state == 'notion' ) {\r\n            const obj = this.state.notion.filter( item => item.value == value.trim() )[0];\r\n            storage.secret.notion.folder_id = value.trim();\r\n            storage.secret.notion.type      = obj.type;\r\n            obj.schema && ( storage.secret.notion.schema = obj.schema );\r\n            obj.type == \"page\" && delete storage.secret.notion.schema;\r\n        }\r\n        storage.Safe( () => this.setState({ secret: storage.secret }), storage.secret );\r\n    }\r\n\r\n    linnkOnChange( state, value ) {\r\n        this.props.linnk[state] = value;\r\n    }\r\n\r\n    instapaperOnChange( state, value ) {\r\n        this.props.instapaper[state] = value;\r\n    }\r\n\r\n    jianguoOnChange( state, value ) {\r\n        this.props.jianguo[state] = value;\r\n    }\r\n\r\n    weizhiOnChange( state, value ) {\r\n        this.props.weizhi[state] = value;\r\n    }\r\n\r\n    webdavOnChange() {\r\n        this.state.secret.webdav = event.target.value.split(\"\\n\");\r\n        storage.Safe( () => this.setState({ secret: storage.secret }), storage.secret );\r\n    }\r\n\r\n    notionChange() {\r\n        const notify = new Notify().Render({ state: \"loading\", content: `正在获取 Notion Page ，请稍等` });\r\n        exp.notion.Auth( ( result, error, warn ) => {\r\n            notify.complete();\r\n            if ( error ) new Notify().Render({ type: 2, content: `Notion.so 并未提供 API 所以会出现 <b>获取失败的情况</b>，如发生此问题，请提 <a target=\"_blank\" href=\"https://github.com/Kenshin/simpread/issues/809\">Issues</a>`, state: \"holdon\" });\r\n            else {\r\n                warn != \"\" && new Notify().Render({ type: 2, content: `请注意：获取时出现了问题导致 <b>仅成功获取了</b> 您的第一个 Notion Page 虽然不影响导出服务，但建议提 <a target=\"_blank\" href=\"https://github.com/Kenshin/simpread/issues/809\"><b>Issues</b></a>`, state: \"holdon\" });\r\n                warn == \"\" && new Notify().Render( 1, \"获取成功，请选择导出的 Notion Page\" );\r\n                this.setState({ secret: storage.secret, notion: exp.notion.blocks });\r\n            }\r\n        });\r\n    }\r\n\r\n    youdaoChange() {\r\n        permission.Get( exp.youdao.permissions, result => {\r\n            if ( !result ) {\r\n                new Notify().Render( 2, `此功能需要申请 cookies 权限后才能使用，授权成功后会自动取消。` );\r\n                this.setState({ secret: storage.secret });\r\n                return;\r\n            }\r\n            setTimeout( () => {\r\n                exp.youdao.Auth( ( result, error ) => {\r\n                    if ( result ) this.setState({ secret: storage.secret, youdao: exp.youdao.folders });\r\n                    else new Notify().Render( 2, `重新获取失败，${error}` );\r\n                });\r\n            }, 500 );\r\n        });\r\n   }\r\n\r\n    webdavAuth() {\r\n        this.state.secret.webdav.forEach( ( item, idx ) => {\r\n            try {\r\n                item = JSON.parse( item );\r\n                if ( Object.keys( item ).join( \"\" ).replace( /url|name|password|user|format/ig, \"\" ) != \"\" ) {\r\n                    throw \"error\";\r\n                }\r\n                exp.webdav.Auth( item.url, item.user, item.password, result => {\r\n                    if ( result && ( result.status == 201 || result.status == 405 )) {\r\n                        new Notify().Render( `${item.name} 验证成功。` );\r\n                    } else {\r\n                        new Notify().Render( 2, `${item.name} 授权失败，请确认用户名和密码。` );\r\n                    }\r\n                });\r\n            } catch( error ) {\r\n                new Notify().Render( 2, `第 ${idx+1} 条数据格式错误，请重新输入。` );\r\n            }\r\n        });\r\n    }\r\n\r\n    componentWillReceiveProps( nextProps ) {\r\n        this.setState({ secret: storage.secret })\r\n    }\r\n\r\n    componentDidMount() {\r\n        storage.Safe( () => {\r\n            this.setState({ secret: storage.secret })\r\n            if ( location.hash.startsWith( \"#labs?auth=\" ) ) {\r\n                this.onChange( location.hash.replace( \"#labs?auth=\", \"\" ), true );\r\n            }\r\n        });\r\n    }\r\n\r\n    render() {\r\n\r\n        let auth;\r\n\r\n        if ( this.state.secret ) {\r\n\r\n            auth = <div>\r\n                        <Switch width=\"100%\" checked={ this.state.secret.dropbox.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.dropbox.access_token ? \"已授权 Dropbox，是否取消授权？\" : \"是否连接并授权 Dropbox ？\" }\r\n                            onChange={ (s)=>this.onChange( \"dropbox\", s ) } />\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.pocket.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.pocket.access_token ? \"已授权 Pocket，是否取消授权？\" : \"是否连接并授权 Pocket ？\" }\r\n                            onChange={ (s)=>this.onChange( \"pocket\", s ) } />\r\n\r\n                        { this.state.secret.pocket.access_token && \r\n                        <div ref=\"pocket_tags\" style={{ \"width\": \"60%\" }}>\r\n                            <TextField\r\n                                placeholder=\"请填入 Pocket 标签，默认为 simpread 每个标签用小写, 分割。\" \r\n                                value={ this.state.secret.pocket.tags }\r\n                                onChange={ (evt)=>this.save( \"pocket\", evt.target.value ) }\r\n                            />\r\n                        </div> }\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.instapaper.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.instapaper.access_token ? \"已授权 Instapaper，是否取消授权？\" : \"是否连接并授权 Instapaper ？\" }\r\n                            onChange={ (s)=>this.onChange( \"instapaper\", s ) } />\r\n\r\n                        { this.state.instapaper && \r\n                        <div ref=\"instapaper\">\r\n                            <div style={{ \"display\": \"flex\", \"flex-direction\": \"row\" }}>\r\n                                <TextField\r\n                                    placeholder=\"请填入 Instapaper 用户名，简悦不会记录你的用户名。\" \r\n                                    onChange={ (evt)=>this.instapaperOnChange( \"username\", evt.target.value ) }\r\n                                />\r\n                                <TextField\r\n                                    password={true}\r\n                                    placeholder=\"请填入 Instapaper 密码，简悦不会记录你的密码。\" \r\n                                    onChange={ (evt)=>this.instapaperOnChange( \"password\", evt.target.value ) }\r\n                                />\r\n                            </div>\r\n\r\n                            <Button type=\"raised\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                text=\"登录 Instapaper 并授权\"\r\n                                color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                waves=\"md-waves-effect md-waves-button\"\r\n                                onClick={ (s)=>this.onChange( \"instapaper\", s, \"login\" ) } />\r\n                        </div> }\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.linnk.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.linnk.access_token ? \"已授权 Linnk，是否取消授权？\" : \"是否连接并授权 Linnk ？\" }\r\n                            onChange={ (s)=>this.onChange( \"linnk\", s ) } />\r\n\r\n                        { this.state.secret.linnk.access_token && \r\n                            <div style={{ \"width\": \"60%\" }}>\r\n                                <TextField\r\n                                    value={ this.state.secret.linnk.group_name }\r\n                                    placeholder=\"请填入 Linnk 收藏夹名称，默认保存到 收件箱。\" \r\n                                    onChange={ (evt)=>this.save( \"linnk\", evt.target.value ) }\r\n                                />\r\n                            </div> }\r\n\r\n                        { this.state.linnk && \r\n                        <div ref=\"linnk\">\r\n                            <div style={{ \"display\": \"flex\", \"flex-direction\": \"row\" }}>\r\n                                <TextField\r\n                                    placeholder=\"请填入 Linnk 用户名，简悦不会记录你的用户名。\" \r\n                                    onChange={ (evt)=>this.linnkOnChange( \"username\", evt.target.value ) }\r\n                                />\r\n                                <TextField\r\n                                    password={true}\r\n                                    placeholder=\"请填入 Linnk 密码，简悦不会记录你的密码。\" \r\n                                    onChange={ (evt)=>this.linnkOnChange( \"password\", evt.target.value ) }\r\n                                />\r\n                            </div>\r\n\r\n                            <Button type=\"raised\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                text=\"登录 Linnk 并授权\"\r\n                                color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                waves=\"md-waves-effect md-waves-button\"\r\n                                onClick={ (s)=>this.onChange( \"linnk\", s, \"login\" ) } />\r\n                        </div> }\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.yinxiang.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.yinxiang.access_token ? \"已授权 印象笔记，是否取消授权？\" : \"是否连接并授权 印象笔记 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"yinxiang\", s ) } />\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.evernote.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.evernote.access_token ? \"已授权 Evernote，是否取消授权？\" : \"是否连接并授权 Evernote ？\" }\r\n                            onChange={ (s)=>this.onChange( \"evernote\", s ) } />\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.onenote.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.onenote.access_token ? \"已授权 Onenote，是否取消授权？\" : \"是否连接并授权 Onenote ？\" }\r\n                            onChange={ (s)=>this.onChange( \"onenote\", s ) } />\r\n\r\n                        <Switch width=\"100%\" checked={ this.state.secret.gdrive.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.gdrive.access_token ? \"已授权 Google 云端硬盘，是否取消授权？\" : \"是否连接并授权 Google 云端硬盘 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"gdrive\", s ) } />\r\n\r\n                        <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"jianguo\">\r\n                        <Switch width=\"100%\" checked={ this.state.secret.jianguo && this.state.secret.jianguo.username != \"\" && this.state.secret.jianguo.password ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.jianguo && this.state.secret.jianguo.username != \"\" ? \"已授权 坚果云，是否取消授权？\" : \"是否连接并授权 坚果云 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"jianguo\", s ) } />\r\n                        </div>\r\n                        { this.state.jianguo && \r\n                        <div ref=\"jianguo\">\r\n                            <div style={{ \"display\": \"flex\", \"flex-direction\": \"row\" }}>\r\n                                <TextField\r\n                                    placeholder=\"请填入 坚果云的 WebDAV 用户名，简悦不会记录你的用户名。\" \r\n                                    onChange={ (evt)=>this.jianguoOnChange( \"username\", evt.target.value ) }\r\n                                />\r\n                                <TextField\r\n                                    password={true}\r\n                                    placeholder=\"请填入 坚果云的 WebDAV 密码，简悦不会记录你的密码。\" \r\n                                    onChange={ (evt)=>this.jianguoOnChange( \"password\", evt.target.value ) }\r\n                                />\r\n                            </div>\r\n\r\n                            <Button type=\"raised\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                text=\"绑定 坚果云 的信息\"\r\n                                color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                waves=\"md-waves-effect md-waves-button\"\r\n                                onClick={ (s)=>this.onChange( \"jianguo\", s, \"login\" ) } />\r\n\r\n                        </div> }\r\n\r\n                        <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"yuque\">\r\n                        <Switch width=\"100%\" checked={ this.state.secret.yuque.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.yuque.access_token ? \"已授权 语雀，是否取消授权？\" : \"是否连接并授权 语雀 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"yuque\", s ) } />\r\n                        </div>\r\n\r\n                        <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"notion\">\r\n                        <Switch width=\"100%\" checked={ this.state.secret.notion.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.notion.access_token ? \"已授权 Notion，是否取消授权？\" : \"是否连接并授权 Notion ？\" }\r\n                            onChange={ (s)=>this.onChange( \"notion\", s ) } />\r\n\r\n                        { this.state.secret.notion.access_token && \r\n                            <div style={{ display: \"flex\",\"flex-direction\": \"column\", \"justify-content\": \"center\" }}>\r\n                            { this.state.notion ? <Dropdown name={ \"请选择保存的位置，默认为第一个\" } items={ this.state.notion } width=\"100%\" onChange={ (v,n)=>this.save( \"notion\", v ) } />\r\n                            : <Button type=\"flat\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                    text=\"重新获取 Notion Page\"\r\n                                    color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                    waves=\"md-waves-effect md-waves-button\"\r\n                                    onClick={ (s)=>this.notionChange() } /> }\r\n\r\n                            <Switch width=\"100%\" checked={ this.state.secret.notion.save_image }\r\n                                    thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                                    label=\"是否使用 Notion.so 作为图床？\"\r\n                                    desc=\"由于 Notion 并未公开 API 所以此方式较慢。\"\r\n                                    onChange={ (s)=>this.save( \"notion_save_image\", s ) } />\r\n\r\n                            <span className=\"desc\" style={{ \"margin-top\": \"8px\", \"text-align\": \"left\" }}>注意：由于 Notion.so 暂未提供 API 所以会出现 <b>授权</b> 或 <b>获取 Notion Page</b> 失败的情况，如遇到此情况，请提 <a target=\"_blank\" href=\"https://github.com/Kenshin/simpread/issues/809\"><b>Issues</b></a></span>\r\n                            </div>}\r\n                        </div>\r\n\r\n                        <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"youdao\">\r\n                        <Switch width=\"100%\" checked={ this.state.secret.youdao.access_token != \"\" ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.youdao.access_token ? \"已授权 有道云笔记\" : \"是否连接并授权 有道云笔记 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"youdao\", s ) } />\r\n\r\n                        { this.state.secret.youdao.access_token && \r\n                            <div style={{ display: \"flex\",\"flex-direction\": \"row\", \"justify-content\": \"center\" }}>\r\n                            { this.state.youdao ? <Dropdown name={ \"请选择保存的位置，默认为第一个\" } items={ this.state.youdao } width=\"100%\" onChange={ (v,n)=>this.save( \"youdao\", v ) } />\r\n                            : <Button type=\"flat\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                    text=\"重新获取 有道云笔记 的文件夹\"\r\n                                    color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                    waves=\"md-waves-effect md-waves-button\"\r\n                                    onClick={ (s)=>this.youdaoChange() } /> }\r\n                            </div> }\r\n                        </div>\r\n\r\n                        <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"weizhi\">\r\n                        <Switch width=\"100%\" checked={ this.state.secret.weizhi && this.state.secret.weizhi.username != \"\" && this.state.secret.weizhi.access_token ? true : false }\r\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\r\n                            label={ this.state.secret.weizhi && this.state.secret.weizhi.username != \"\" ? \"已授权 为知笔记，是否取消授权？\" : \"是否连接并授权 为知笔记 ？\" }\r\n                            onChange={ (s)=>this.onChange( \"weizhi\", s ) } />\r\n                        </div>\r\n                        { this.state.weizhi && \r\n                        <div ref=\"weizhi\">\r\n                            <div style={{ \"display\": \"flex\", \"flex-direction\": \"row\" }}>\r\n                                <TextField\r\n                                    placeholder=\"请填入 为知笔记 的登录邮箱，简悦不会记录你的邮箱。\" \r\n                                    onChange={ (evt)=>this.weizhiOnChange( \"username\", evt.target.value ) }\r\n                                />\r\n                                <TextField\r\n                                    password={true}\r\n                                    placeholder=\"请填入 为知笔记 的密码，简悦不会记录你的密码。\" \r\n                                    onChange={ (evt)=>this.weizhiOnChange( \"password\", evt.target.value ) }\r\n                                />\r\n                            </div>\r\n\r\n                            <Button type=\"raised\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                                text=\"绑定 为知笔记 的信息\"\r\n                                color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                waves=\"md-waves-effect md-waves-button\"\r\n                                onClick={ (s)=>this.onChange( \"weizhi\", s, \"login\" ) } />\r\n\r\n                        </div> }\r\n    \r\n                        <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"webdav\">\r\n                        <div className=\"label\" style={{'margin-bottom':' -15px'}}>WebDAV</div>\r\n                        <div className=\"sublabel\">简悦支持任意 WebDAV 的服务，包括：Box · TeraCLOUD 等</div>\r\n                        <TextField \r\n                            multi={ true } rows={8}\r\n                            placeholder={ `每行一组，格式为：{ \"name\": \"网盘的名称\", \"user\": \"用户名\", \"password\": \"密码\", \"url\": \"webdav 地址\" }` } \r\n                            value={ ( this.state.secret.webdav||[] ).join( \"\\n\" ) }\r\n                            onChange={ (e)=>this.webdavOnChange(e) }\r\n                        />\r\n                        <Button type=\"raised\" width=\"100%\" style={{ \"margin\": \"0\" }}\r\n                            text=\"验证上述 WebDAV 服务\"\r\n                            color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ (s)=>this.webdavAuth() } />\r\n                        </div>\r\n\r\n                    </div>;\r\n        }\r\n\r\n        return (\r\n            <div>{ auth }</div>\r\n        )\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/module/common/editor.jsx",
    "content": "console.log( \"===== simpread option editor load =====\" )\r\n\r\nimport { verifyHtml } from 'util';\r\n\r\nimport TextField      from 'textfield';\r\n\r\nimport Name           from 'name';\r\nimport URL            from 'url';\r\nimport Include        from 'include';\r\nimport Exclude        from 'exclude';\r\n\r\nconst getName = ( value, items ) => {\r\n    for ( const item of items ) {\r\n        if ( value == \"\" ) return item.name;\r\n        else if ( item.value == value ) return item.name;\r\n    }\r\n};\r\n\r\nexport default class Editor extends React.Component {\r\n\r\n    state = {\r\n        err_title   : \"\",\r\n        err_desc    : \"\",\r\n        err_avatar_name : \"\",\r\n        err_avatar_url  : \"\",\r\n        err_paging_prev : \"\",\r\n        err_paging_next : \"\",\r\n    };\r\n\r\n    changeName( value, code ) {\r\n        this.props.site.name  = value;\r\n        this.props.state.name = code;\r\n        console.log( \"this.props.site.name = \", this.props.site.name, code )\r\n    }\r\n\r\n    changeURL( value, code ) {\r\n        this.props.site.url  = value;\r\n        this.props.state.url = code;\r\n        console.log( \"this.props.option.url = \",  this.props.site.url, code )\r\n    }\r\n\r\n    changeTitle( event ) {\r\n        const title = event.target.value.trim();\r\n        if ( title == \"\" ) {\r\n            this.setState({ err_title : \"当前输入不能为空。\" });\r\n            this.props.state.title = -2;\r\n        }\r\n        else if ( verifyHtml( title )[0] != -1 ) {\r\n            this.setState({ err_title : \"\" });\r\n            this.props.site.title  = title;\r\n            this.props.state.title = 0;\r\n            console.log( \"this.props.site.title = \", this.props.site.title )\r\n        } else {\r\n            this.setState({ err_title : \"当前输入为非法。\" });\r\n            this.props.state.title = -1;\r\n        }\r\n    }\r\n\r\n    changeDesc( event ) {\r\n        const desc = event.target.value.trim();\r\n        if ( verifyHtml( desc )[0] != -1 ) {\r\n            this.setState({ err_desc : \"\" });\r\n            this.props.site.desc  = desc;\r\n            this.props.state.desc = 0;\r\n            console.log( \"this.props.site.desc = \", this.props.site.desc )\r\n        } else {\r\n            this.setState({ err_desc : \"当前输入为非法。\" });\r\n            this.props.state.desc = -1;\r\n        }\r\n    }\r\n\r\n    changeInclude( value, code ) {\r\n        this.props.site.include  = value;\r\n        this.props.state.include = code;\r\n        console.log( \"this.props.site.include = \", this.props.site.include, code )\r\n    }\r\n\r\n    changeExclude( value, code ) {\r\n        this.props.site.exclude  = value;\r\n        this.props.state.exclude = code;\r\n        console.log( \"this.props.site.exclude = \", this.props.site.exclude, code )\r\n    }\r\n\r\n    changeAvatar( idx, type, value ) {\r\n        value = value.trim();\r\n        if ( verifyHtml( value )[0] != -1 ) {\r\n            this.setState({ [`err_avatar_${type}`] : \"\" });\r\n            this.props.site.avatar[idx][type] = value;\r\n            this.props.state.avatar[type]     = 0;\r\n            console.log( \"this.props.site.avatar = \", this.props.site.avatar )\r\n        } else {\r\n            this.setState({ [`err_avatar_${type}`] : \"当前输入为非法。\" });\r\n            this.props.state.avatar[type] = -1;\r\n        }\r\n    }\r\n\r\n    changePaging( idx, type, value ) {\r\n        value = value.trim();\r\n        if ( verifyHtml( value )[0] != -1 ) {\r\n            this.setState({ [`err_paging_${type}`] : \"\" });\r\n            this.props.site.paging[idx][type] = value;\r\n            this.props.state.paging[type]     = 0;\r\n            console.log( \"this.props.site.paging = \", this.props.site.paging )\r\n        } else {\r\n            this.setState({ [`err_paging_${type}`] : \"当前输入为非法。\" });\r\n            this.props.state.paging[type] = -1;\r\n        }\r\n    }\r\n\r\n    changeCSS( event ) {\r\n        console.log( \"this.props.site.name = \", event.target.value.trim() )\r\n        this.props.site.css = event.target.value.trim();\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <sr-opt-read>\r\n                <sr-opt-items>\r\n                    <sr-opt-gp>\r\n                        <Name name={ this.props.site.name } changeName={ (v,c)=>this.changeName(v,c) } />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <URL url={  this.props.site.url } changeURL={ (v,c)=>this.changeURL(v,c) } />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField \r\n                            multi={ false }\r\n                            floatingtext=\"标题\"\r\n                            placeholder=\"必填，不可为空。\"\r\n                            value={ this.props.site.title }\r\n                            errortext={ this.state.err_title }\r\n                            onChange={ event=>this.changeTitle(event) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField \r\n                                multi={ false }\r\n                                placeholder=\"默认为空。\"\r\n                                floatingtext=\"描述\"\r\n                                value={ this.props.site.desc }\r\n                                errortext={ this.state.err_desc }\r\n                                onChange={ event=>this.changeDesc(event) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <Include mode=\"read\" include={ this.props.site.include } changeInclude={ (v,c)=>this.changeInclude(v,c) } />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <Exclude exclude={ this.props.site.exclude } changeExclude={ (v,c)=>this.changeExclude(v,c) } />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField multi={ false }\r\n                                placeholder=\"默认为空，只限论坛类页面使用。\" floatingtext=\"头像的名称\"\r\n                                value={ this.props.site.avatar[0].name }\r\n                                errortext={ this.state.err_avatar_name }\r\n                                onChange={ event=>this.changeAvatar( 0, \"name\", event.target.value ) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField multi={ false }\r\n                                placeholder=\"默认为空，只限论坛类页面使用。\" floatingtext=\"头像的地址\"\r\n                                value={ this.props.site.avatar[1].url }\r\n                                errortext={ this.state.err_avatar_url }\r\n                                onChange={ event=>this.changeAvatar( 1, \"url\", event.target.value ) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField multi={ false }\r\n                                placeholder=\"默认为空，只限论坛类页面使用。\" floatingtext=\"前一页\"\r\n                                value={ this.props.site.paging[0].prev }\r\n                                errortext={ this.state.err_paging_prev }\r\n                                onChange={ event=>this.changePaging( 0, \"prev\", event.target.value ) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField multi={ false }\r\n                                placeholder=\"默认为空，只限论坛类页面使用。\" floatingtext=\"后一页\"\r\n                                value={ this.props.site.paging[1].next }\r\n                                errortext={ this.state.err_paging_next }\r\n                                onChange={ event=>this.changePaging( 1, \"next\", event.target.value ) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                    <sr-opt-gp>\r\n                        <TextField multi={ true }\r\n                                placeholder=\"默认为空，输入的 CSS 只针对当前站点有效。\" floatingtext=\"自定义 CSS\"\r\n                                value={ this.props.site.css || \"\" }\r\n                                onChange={ event=>this.changeCSS(event) }\r\n                        />\r\n                    </sr-opt-gp>\r\n                </sr-opt-items>\r\n            </sr-opt-read>\r\n        )\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/module/common/exclude.jsx",
    "content": "console.log( \"===== simpread option common: exclude =====\" )\r\n\r\nimport { verifyHtml } from 'util';\r\nimport TextField      from 'textfield';\r\n\r\nexport default class Exclude extends React.Component {\r\n\r\n    static defaultProps = {\r\n        flag: {},\r\n    }\r\n\r\n    static propType = {\r\n        flag : React.PropTypes.object,\r\n    }\r\n\r\n    state = {\r\n        error : \"\"\r\n    };\r\n\r\n    changeExclude( event ) {\r\n        const { good, bad } = getExclude( event.target.value );\r\n        this.props.changeExclude( good, bad.length > 0 ? -1 : 0 );\r\n        if ( bad.length > 0 ) {\r\n            this.setState({ error: `格式错误：${bad.join(\", \")}` });\r\n        } else {\r\n            this.setState({ error: \"\" });\r\n        }\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <TextField \r\n                multi={ true } \r\n                rows={ this.props.rows }\r\n                placeholder=\"默认为空，每行一个，支持： Html标签, {}, '', //, [] 等，详细请看站点编辑器。\" \r\n                floatingtext=\"隐藏列表\" \r\n                value={ (this.props.exclude||[]).join( \"\\n\" ) }\r\n                errortext={ this.state.error }\r\n                onChange={ evt=>this.changeExclude(evt) }\r\n            />\r\n        )\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Get exclude tags\r\n * \r\n * @param  {string} input exclude html tag, e.g.:\r\n    <div class=\"article fmt article__content\">\r\n    <h1>\r\n    <div class=\"col-xs-12 col-md-9 main \">\r\n    <img id=\"icon4weChat\" style=\"height: 0;width: 0;\">\r\n    <div class=\"wsx_fade\" style=\"pointer-events: none;\"></div>\r\n    sdasdfas\r\n    h1\r\n *\r\n * @return {object}\r\n * good array: verify success htmls e.g.:\r\n        <div class=\"article fmt article__content\">\r\n        <h1>\r\n        <div class=\"col-xs-12 col-md-9 main \">\r\n        <img id=\"icon4weChat\" style=\"height: 0;width: 0;\">\r\n        <div class=\"wsx_fade\" style=\"pointer-events: none;\"></div>\r\n    bad array: error htmls, e.g.:\r\n        sdasdfas\r\n        h1\r\n * \r\n */\r\nfunction getExclude( htmls ) {\r\n    let [ good, bad, obj ]  = [[], [], null ];\r\n    if ( htmls.trim() == \"\" ) return { good, bad };\r\n    const arr = htmls.trim().split( \"\\n\" );\r\n    for( let value of arr ) {\r\n        if ( verifyHtml( value.trim() )[0] > 0 ) {\r\n            good.push( value.trim() );\r\n        } else {\r\n            bad.push( value.trim() );\r\n        }\r\n    }\r\n    return { good, bad };\r\n}\r\n"
  },
  {
    "path": "src/module/common/include.jsx",
    "content": "console.log( \"===== simpread option common: Include =====\" )\r\n\r\nimport { verifyHtml } from 'util';\r\nimport TextField      from 'textfield';\r\n\r\nexport default class Include extends React.Component {\r\n\r\n    static defaultProps = {\r\n        mode: \"\",\r\n        flag: {},\r\n    }\r\n\r\n    static propType = {\r\n        mode : React.PropTypes.oneOf([ \"focus\", \"read\" ]),\r\n        flag : React.PropTypes.object,\r\n    }\r\n\r\n    state = {\r\n        error : \"\"\r\n    };\r\n\r\n    changeInclude( event ) {\r\n        let code = 0;\r\n        if ( this.props.mode == \"read\" && event.target.value.trim() == \"\" ) {\r\n            code = -2;\r\n            this.setState({ error : \"当前输入不能为空。\" });\r\n        }\r\n        else if ( verifyHtml( event.target.value.trim() )[0] != -1 ) {\r\n            this.setState({ error : \"\" });\r\n        } else {\r\n            code = -1;\r\n            this.setState({ error : \"当前输入为非法。\" });\r\n        }\r\n        this.props.changeInclude( event.target.value.trim(), code );\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <TextField \r\n                multi={ false } \r\n                placeholder= \"必填，不可为空。\"\r\n                floatingtext=\"高亮区域\" \r\n                value={ this.props.include }\r\n                errortext={ this.state.error }\r\n                onChange={ evt=>this.changeInclude(evt) }\r\n            />\r\n        )\r\n    }\r\n\r\n} "
  },
  {
    "path": "src/module/common/name.jsx",
    "content": "console.log( \"===== simpread option common: Name =====\" )\n\nimport { storage } from 'storage';\n\nimport TextField   from 'textfield';\n\n//const names = [];\n\nexport default class Name extends React.Component {\n\n    state = {\n        error : \"\"\n    };\n\n    changeName( event ) {\n        let   code = 0;\n        const name = event.target.value.trim();\n        //if ( names.includes( name ) && name != this.props.name ) {\n        //    code = -1;\n        //    this.setState({ error : \"当前值重复，请重新录入。\" });\n        //} else \n        if( name == \"\" ) {\n            code = -2;\n            this.setState({ error : \"当前输入不能为空。\" });\n        } else {\n            this.setState({ error : \"\" });\n        }\n        this.props.changeName( name, code );\n    }\n\n    //componentWillMount() {\n    //    storage.simpread.sites.forEach( site => names.push( site[1].name ) );\n    //}\n\n    render() {\n        return (\n            <TextField \n                multi={ false } \n                placeholder={ \"必填，当前值具有唯一性。\" }\n                floatingtext=\"标识\" \n                value={ this.props.name }\n                errortext={ this.state.error }\n                onChange={ evt=>this.changeName(evt) }\n            />\n        )\n    }\n\n}"
  },
  {
    "path": "src/module/common/shortcuts.jsx",
    "content": "console.log( \"===== simpread option common: Shortcuts =====\" )\r\n\r\nimport TextField from 'textfield';\r\n\r\nlet [ shortcuts, prevShortcuts, keyword ] = [ [], null, null ];\r\n\r\nexport default class Shortcuts extends React.Component {\r\n\r\n    state = {\r\n        id    : Math.round(+new Date()),\r\n        error : \"\",\r\n        value : this.props.shortcuts,\r\n    };\r\n\r\n    changeShortcuts( event ) {\r\n        if ( event.type === \"keydown\" ) {\r\n            const key = fixKey( event );\r\n            keyword   =  key == \"control\" ? \"ctrl\" : key;\r\n            if ( verifyShortkey( keyword )) {\r\n                prevShortcuts = updateShortcuts( this.state.id );\r\n                shortcuts[ this.state.id ] = prevShortcuts;\r\n                this.setState({ error : \"\" });\r\n                this.props.changeShortcuts( prevShortcuts );\r\n            } else if ( keyword.length == 0 || !/^[0-9a-z]{1}$/ig.test( keyword )) {\r\n                this.setState({ error : `当前输入: ${keyword} 不合法，快捷键只能包括：ctrl, shift, alt, 数字, 字母。` });\r\n            }\r\n        } else {\r\n            if ( /^[0-9a-z]{1}$/ig.test( keyword ) ) {\r\n                prevShortcuts = updateShortcuts( this.state.id );\r\n                shortcuts[ this.state.id ] = prevShortcuts;\r\n                this.setState({ error : \"\" });\r\n                this.props.changeShortcuts( prevShortcuts );\r\n            }\r\n        }\r\n        prevShortcuts = shortcuts[ this.state.id ];\r\n        this.setState({ value: prevShortcuts });\r\n    }\r\n\r\n    componentDidMount() {\r\n        prevShortcuts = this.state.value;\r\n        shortcuts[ this.state.id ] = prevShortcuts;\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <TextField \r\n                multi={ false } override=\"true\"\r\n                floatingtext=\"快捷键\" \r\n                value={ this.state.value }\r\n                errortext={ this.state.error }\r\n                onKeyDown={ (event)=> this.changeShortcuts(event) } onChange={ (event)=>this.changeShortcuts(event) }\r\n            />\r\n        )\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Update new shortcuts\r\n * \r\n * @param  {number} this state id\r\n * @return {string} new shortcuts, e.g. [a s]\r\n */\r\nfunction updateShortcuts( id ) {\r\n    prevShortcuts = shortcuts[ id ];\r\n    const arr     = prevShortcuts.toLowerCase().trim().split(\" \");\r\n    let newshort  = null;\r\n    switch ( arr.length ) {\r\n        case 1:\r\n            newshort = `${arr[0]} ${keyword}`;\r\n            break;\r\n        case 2:\r\n            newshort = keyword;\r\n            break;\r\n        default:\r\n            console.log( \"发生了一些错误。\", prevShortcuts, keyword )\r\n            newshort = prevShortcuts;\r\n            break;\r\n    }\r\n    return newshort;\r\n}\r\n\r\n/**\r\n * Verify shortkey\r\n * \r\n * @param  {string} shortkey, only include: ctrl shift alt number letters\r\n *                  e.g. [a b] [a 1] [1 b] [shift a] [a ctrl] [1 alt] [1 shift]\r\n * \r\n * @return {boolean}\r\n */\r\nfunction verifyShortkey( key ) {\r\n    if ([ \"control\", \"ctrl\", \"alt\", \"shift\" ].includes( key )) {\r\n        return true;\r\n    } else {\r\n        return false;\r\n    }\r\n}\r\n\r\n/**\r\n * Fix keyboard event key undefinde\r\n * \r\n * @param  {event} keyboard event\r\n * @return {string} valid key, include 0~9 a~z ctrl shift alt\r\n */\r\nfunction fixKey( event ) {\r\n    const keycode = event.keyCode;\r\n    if ( [ 16, 17, 18 ].includes( keycode ) ) {\r\n        return event.key.toLowerCase().trim();\r\n    } else if ( keycode >= 49 || keycode <= 90 ) {\r\n        //return event.code.toLowerCase().trim().replace( /(digit|key)/ig, \"\" );\r\n        return event.key.toLowerCase();\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/module/common/theme.jsx",
    "content": "console.log( \"===== simpread option common: ThemeSel =====\" )\r\n\r\nexport default class ThemeSel extends React.Component {\r\n\r\n    changeBgColor( event ) {\r\n        if ( event.target.tagName.toLowerCase() == \"sr-opt-theme\" ) {\r\n            const target     = event.target,\r\n                  $target    = $(target),\r\n                  $parent    = $target.parent(),\r\n                  activestyl = \"active\",\r\n                  newval     = $target.attr( \"name\" ),\r\n                  $active    = $parent.find( 'sr-opt-theme[sr-type=\"active\"]' );\r\n            if ( $active ) {\r\n                $active.removeAttr( \"sr-type\" );\r\n                $target.attr( \"sr-type\", activestyl );\r\n            }\r\n            this.props.changeBgColor( newval, $target );\r\n        }\r\n    }\r\n\r\n    componentDidMount() {\r\n        setBgThemeStyle( this.props.theme );\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <sr-opt-themes onClick={ evt=> this.changeBgColor(evt) }>\r\n                { this.props.themes.map( (theme,idx) => <sr-opt-theme style={{backgroundColor: `rgba( ${theme} )`}} name={ this.props.names[idx] } data-tooltip={ this.props.labels[idx] } data-tooltip-position=\"up\" data-tooltip-delay=\"50\"></sr-opt-theme> )}\r\n            </sr-opt-themes>\r\n        )\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Set background style\r\n * \r\n * @param {string} theme name\r\n */\r\nfunction setBgThemeStyle( theme ) {\r\n    const $themes    = $( \"sr-opt-themes\" ).children();\r\n    for ( let i = 0; i < $themes.length; i++ ) {\r\n         const $target = $($themes[i]),\r\n               name    = $target.attr( \"name\" );\r\n         if ( theme === name ) {\r\n             $target.attr( \"sr-type\", \"active\" );\r\n         }\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/module/common/url.jsx",
    "content": "console.log( \"===== simpread option common: URL =====\" )\n\nimport * as puplugin from 'puplugin';\n\nimport TextField from 'textfield';\n\nexport default class URL extends React.Component {\n\n    static defaultProps = {\n        flag: {},\n    }\n\n    static propType = {\n        flag : React.PropTypes.object,\n    }\n\n    state = {\n        error : \"\"\n    };\n\n    changeURL( event ) {\n        let  code = 0;\n        const url = event.target.value.trim(),\n              minimatch = puplugin.Plugin( \"minimatch\" );\n\n        if ( url == \"\" ) {\n            code = -2;\n            this.setState({ error : \"当前输入不能为空。\" });\n        } else if ( url.startsWith( \"[[/\" ) && url.endsWith( \"/]]\" ) && !new RegExp( url.replace( /^\\[\\[\\/|\\/\\]\\]/g, \"\" ) ).test( location.href )) {\n            location.protocol != \"chrome-extension:\" && this.setState({ error : \"请输入与当前网址匹配的域名，正则表达式出现错误。\" });\n        }  else if ( !url.startsWith( \"[[/\" ) && !/^http[s|*]?:\\/\\//.test( url ) ) {\n            code = -1;\n            this.setState({ error : \"请输入有效的 url \" });\n        } else if ( !url.startsWith( \"[[/\" ) && location.protocol.startsWith( \"http\" ) && !minimatch( window.location.href, url ) && url != this.props.url ) {\n            code = -1;\n            this.setState({ error : \"请输入与当前网址匹配的域名，支持 minimatch \" });\n        } else {\n            this.setState({ error : \"\" });\n        }\n        this.props.changeURL( url, code );\n    }\n\n    render() {\n        return (\n            <TextField \n                multi={ false } \n                placeholder={ \"必填，支持 minimatch \" }\n                floatingtext=\"域名\" \n                value={ this.props.url }\n                errortext={ this.state.error }\n                onChange={ evt=>this.changeURL(evt) }\n            />\n        )\n    }\n\n}"
  },
  {
    "path": "src/module/common.jsx",
    "content": "console.log( \"===== simpread option common load =====\" )\r\n\r\nimport Button      from 'button';\r\nimport Notify      from 'notify';\r\n\r\nimport * as ss     from 'stylesheet';\r\nimport { storage, Now } from 'storage';\r\nimport * as ver    from 'version';\r\nimport * as menu   from 'menu';\r\nimport * as watch  from 'watch';\r\nimport * as exp    from 'export';\r\nimport {browser,br}from 'browser';\r\nimport * as msg    from 'message';\r\n\r\nexport default class CommonOpt extends React.Component {\r\n\r\n    static propTypes = {\r\n        sync : React.PropTypes.func,\r\n    }\r\n\r\n    state = {\r\n        sync: ( sync => !sync ? \"从未同步过，建议同步一次！\" : `上次同步时间： ${ sync }` ) ( storage.option.sync )\r\n    };\r\n\r\n    sync() {\r\n        let notify;\r\n        const dbx     = exp.dropbox,\r\n              jianguo = exp.jianguo,\r\n        write         = () => {\r\n            storage.option.sync = Now();\r\n            storage.Write( () => {\r\n                writeConfig();\r\n            });\r\n        },\r\n        readDropbox   = () => {\r\n            notify = new Notify().Render({ content: \"数据同步中，请稍等...\", state: \"loading\" });\r\n            dbx.Exist( dbx.config_name, ( result, error ) => {\r\n                if ( result == -1 ) {\r\n                    write();\r\n                } else {\r\n                    dbx.Read( dbx.config_name, callback );\r\n                }\r\n            });\r\n        },\r\n        readJianguo   = ( obj ) => {\r\n            notify = new Notify().Render({ content: \"数据同步中，请稍等...\", state: \"loading\" });\r\n            jianguo.Read( obj.username, obj.password, jianguo.config_name, result => {\r\n                if ( result && result.status == 404 ) {\r\n                   write();\r\n                } else if ( result && result.status == 200 ) {\r\n                    callback( \"read\", result.done );\r\n                }\r\n            });\r\n        },\r\n        writeConfig   = () => {\r\n            if ( storage.option.save_at == \"dropbox\" ) {\r\n                dbx.Write( dbx.config_name, storage.Export(), callback );\r\n            } else {\r\n                jianguo.Add( storage.secret.jianguo.username, storage.secret.jianguo.password, jianguo.root + \"/\" + jianguo.config_name, storage.Export(), result => {\r\n                    callback( \"write\", undefined, result && [ 201, 204 ].includes( result.status ) ? undefined : \"error\" );\r\n                });\r\n            }\r\n        },\r\n        callback = ( type, result, error ) => {\r\n            notify.complete();\r\n            switch ( type ) {\r\n                case \"write\":\r\n                    !error ? ( location.href = location.origin + location.pathname + \"?simpread_mode=sync\" ) :\r\n                        new Notify().Render( 2, error == \"error\" ? \"远程数据同步失败，请稍后再试！\" : error );\r\n                    break;\r\n                case \"read\":\r\n                    const json   = JSON.parse( result ),\r\n                          local  = storage.option.update ? new Date( storage.option.update.replace( /年|月/ig, \"-\" ).replace( \"日\", \"\" )) : new Date( \"1999-01-01 12:12:12\" ),\r\n                          remote = new Date( json.option.update.replace( /年|月/ig, \"-\" ).replace( \"日\", \"\" ));\r\n                    if ( ver.Compare( json.version ) == 1 ) {\r\n                        new Notify().Render( \"本地版本与远程版本不一致，且本地版本较新，是否覆盖远程版本？\", \"覆盖\", () => {\r\n                            watch.SendMessage( \"import\", true );\r\n                            write();\r\n                        });\r\n                    }\r\n                    else if ( local < remote ) {\r\n                        new Notify().Render( \"远程备份配置文件较新，是否覆盖本地文件？\", \"覆盖\", () => {\r\n                            this.importsecret( json.option.secret, { ...json.secret }, () => {\r\n                                delete json.secret;\r\n                                storage.Write( () => {\r\n                                    watch.SendMessage( \"import\", true );\r\n                                    location.href = location.origin + location.pathname + \"?simpread_mode=reload\";\r\n                                }, json );\r\n                            });\r\n                        });\r\n                    } else if ( local > remote ) {\r\n                        new Notify().Render( \"本地配置文件较新，是否覆盖远程备份文件？\", \"覆盖\", () => {\r\n                            watch.SendMessage( \"import\", true );\r\n                            write();\r\n                        });\r\n                    } else {\r\n                        new Notify().Render( \"本地与远程数据相同，无需重复同步。\" );\r\n                    }\r\n                    break;\r\n            }\r\n        };\r\n\r\n        storage.Safe( ()=> {\r\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"sync\", eventAction: \"sync\", eventValue: storage.option.save_at }) );\r\n            if ( storage.option.save_at == \"dropbox\" ) {\r\n                const sec_dbx = storage.secret.dropbox;\r\n                !sec_dbx.access_token ?\r\n                    new Notify().Render( `未对 ${ dbx.name } 授权，请先进行授权操作。`, \"授权\", () => {\r\n                        dbx.New().Auth();\r\n                        dbx.dtd\r\n                            .done( () => {\r\n                                sec_dbx.access_token = dbx.access_token;\r\n                                storage.Safe( () => {\r\n                                    new Notify().Render( \"授权成功！\" );\r\n                                    readDropbox();\r\n                                }, storage.secret );\r\n                            })\r\n                            .fail( error => {\r\n                                console.error( error )\r\n                                new Notify().Render( 2, `获取 ${ dbx.name } 授权失败，请重新获取。` );\r\n                            });\r\n                    }) : ( () => {\r\n                    dbx.access_token = sec_dbx.access_token;\r\n                    readDropbox();\r\n                })();\r\n            } else {\r\n                const jianguo = storage.secret.jianguo;\r\n                !jianguo.access_token ? new Notify().Render( 2, `坚果云 <b>授权</b> 后才能使用此功能，如何授权 <a href=\"http://ksria.com/simpread/docs/#/坚果云\">请看这里</a>。` ) : readJianguo( storage.secret.jianguo );\r\n            }\r\n        });\r\n    }\r\n\r\n    import() {\r\n        const input  = document.createElement( \"input\" ),\r\n            $input = $(input),\r\n            onload = event => {\r\n                if ( event && event.target && event.target.result ) {\r\n                    try {\r\n                        let json     = ver.FixSubver( ver.patch, JSON.parse( event.target.result ));\r\n                        const result = ver.Compare( json.version );\r\n                        if ( result < 0 ) {\r\n                            result == -1 && new Notify().Render( 2, \"上传失败，当前版本太低，请升级简悦。\" );\r\n                            result == -2 && new Notify().Render( 2, \"上传失败，配置文件版本不存在。\" );\r\n                        } else {\r\n                            if ( result == 0 ) {\r\n                                const obj = storage.Verify( json );\r\n                                if ( obj.option.code != 0 || obj.focus.code != 0 || obj.read.code != 0 || obj.stat.code != 0 ) {\r\n                                    new Notify().Render( 2, \"上传失败，配置项不匹配，请重新上传。\" );\r\n                                    return;\r\n                                }\r\n                            } else if ( result == 1 ) {\r\n                                storage.version != json.version &&\r\n                                    storage.Fix( json.read.sites, json.version, storage.version, json.focus.sites );\r\n                                json = ver.Verify( json.version, json );\r\n                                new Notify().Render({ type: 2, content: `上传版本太低，已自动转换为最新版本。`, state: \"holdon\" });\r\n                            }\r\n                            menu.Refresh( json.option.menu );\r\n                            ver.Incompatible( json.version, json ) && new Notify().Render({ type: 2, content: `检测到你曾经修改过第三方适配源，<b>务必刷新后重新导入</b>！<a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点适配源?id=第三方适配源\">详细说明</a>`, state: \"holdon\" });\r\n                            ver.VerifyPlugins( json.option )       && new Notify().Render({ type: 2, content: `已清理失效的插件，详细请看 <a href=\"http://ksria.com/simpread/welcome/version_${json.version}.html#badplugins\" target=\"_blank\">失效插件</a>`, state: \"holdon\" });\r\n                            json.option.origins && json.option.origins.length > 0 &&\r\n                                new Notify().Render({ content: `导入的配置文件包含了第三方源，刷新后请重新 <b>手动导入</b>。`, state: \"holdon\" });\r\n                            json.option.plugins && json.option.plugins.length > 0 &&\r\n                                new Notify().Render({ content: `导入的配置文件包含了插件，刷新后请重新 <b>手动导入</b>。`, state: \"holdon\" });\r\n                            this.importsecret( json.option.secret, { ...json.secret }, () => {\r\n                                delete json.secret;\r\n                                storage.Write( ()=> {\r\n                                    storage.Plugins( () => {\r\n                                        watch.SendMessage( \"import\", true );\r\n                                        new Notify().Render( \"snackbar\", \"导入成功，请刷新当前页面，以便新配置文件生效。\", \"刷新\", () => {\r\n                                            location.href = location.origin + location.pathname + \"?simpread_mode=reload\";\r\n                                        });\r\n                                    }, {} );\r\n                                }, json );\r\n                            });\r\n                        }\r\n                    } catch ( error ) {\r\n                        new Notify().Render( 2, \"上传失败，配置文件解析失败，请重新确认。\" );\r\n                    }\r\n                }\r\n            };\r\n        $input.attr({ type : \"file\", multiple : \"false\" })\r\n            .one( \"change\", event => {\r\n                    const reader  = new FileReader();\r\n                    reader.onload = onload;\r\n                    reader.readAsText( event.target.files[0] );\r\n        });\r\n        $input.trigger( \"click\" );\r\n    }\r\n\r\n    export() {\r\n        if ( br.isFirefox() ) {\r\n            exp.PrDownload( storage.Export(), `simpread-config.json` );\r\n        } else {\r\n            const data = \"data:text/json;charset=utf-8,\" + encodeURIComponent( storage.Export() );\r\n            exp.Download( data, `simpread-config-${Now()}.json` );\r\n        }\r\n    }\r\n\r\n    oldnewsites() {\r\n        new Notify().Render( \"此功能转移到 <b>站点管理</b> 选项卡里面，3 秒钟后自动切换到此选项卡。\" );\r\n        setTimeout( ()=> {\r\n            location.href = location.origin + \"/options/options.html#labs\";\r\n            window.dispatchEvent( new CustomEvent( msg.MESSAGE_ACTION.turn_tab, { detail: { page: 3 }}));\r\n        }, 3000 );\r\n    }\r\n\r\n    newsites() {\r\n        const notify = new Notify().Render({ content: \"数据同步中，请稍等...\", state: \"loading\" });\r\n        storage.GetRemote( \"remote\", ( result, error ) => {\r\n            notify.complete();\r\n            if ( !error ) {\r\n                const count = storage.pr.Addsites( result );\r\n                storage.Writesite( storage.pr.sites, () => {\r\n                    watch.SendMessage( \"site\", true );\r\n                    count == 0 ? new Notify().Render( \"适配列表已同步至最新版本。\" ) : new Notify().Render( 0, `适配列表已同步成功，本次新增 ${ count } 个站点。` );\r\n                });\r\n            } else {\r\n                new Notify().Render( 3, `同步时发生了一些问题，并不会影响本地配置文件，请稍后再试！` );\r\n            }\r\n        });\r\n    }\r\n\r\n    clear() {\r\n        new Notify().Render( \"snackbar\", \"是否清除掉（已包含账户信息）本地配置文件？\", \"同意 \", () => {\r\n            storage.Clear( \"local\", () => {\r\n                new Notify().Render( \"snackbar\", \"清除成功，此页面需刷新后才能生效！\", \"刷新 \", ()=>{\r\n                    location.href = location.origin + location.pathname + \"?simpread_mode=clear\";\r\n                });\r\n            });\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Import to secret storage\r\n     * \r\n     * @param {boole} option.secret value\r\n     * @param {object} simpread.secret\r\n     * @param {function} callback\r\n     */\r\n    importsecret( state, secret, callback ) {\r\n        state && !$.isEmptyObject( secret ) ? storage.Safe( ()=>{\r\n            callback();\r\n        }, secret ): callback();\r\n    }\r\n\r\n    componentDidMount() {\r\n        if ( this.props.website_sync ) {\r\n            storage.GetRemote( \"versions\", ( result, error ) => {\r\n                if ( !error && result.website == true ) {\r\n                    new Notify().Render( \"正在获取最新的适配列表，请稍等...\" );\r\n                    this.newsites();\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    render() {\r\n        return(\r\n            <div style={{ width: '100%' }}>\r\n                <div className=\"version-tips\" data-hits=\"sync\">\r\n                <Button type=\"raised\" text={ `同步到你的 ${storage.option.save_at == \"dropbox\" ? \"Dropbox\" : \"坚果云\" } 账户` }\r\n                        icon={ ss.IconPath( storage.option.save_at + \"_icon\" ) }\r\n                        color=\"#fff\" backgroundColor=\"#1976D2\"\r\n                        waves=\"md-waves-effect md-waves-button\"\r\n                        tooltip={{ text: this.state.sync }}\r\n                        onClick={ ()=>this.sync() } />\r\n                </div>\r\n                <div className=\"version-tips\" data-hits=\"config\">\r\n                <div style={{ display: 'inline-flex', width: '100%' }}>\r\n                    <Button type=\"raised\" text=\"从本地导入配置文件\" width=\"100%\"\r\n                            icon={ ss.IconPath( \"import_icon\" ) }\r\n                            color=\"#fff\" backgroundColor=\"#FF5252\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            tooltip={{ text: \"上传后的配置将覆盖掉当前，请注意确认！\" }}\r\n                            onClick={ ()=>this.import() } />\r\n                    <Button type=\"raised\" text=\"导出配置文件到本地\" width=\"100%\"\r\n                            icon={ ss.IconPath( \"export_icon\" ) }\r\n                            color=\"#fff\" backgroundColor=\"#2196F3\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ ()=>this.export() } />\r\n                </div>\r\n                </div>\r\n                <div className=\"version-tips\" data-hits=\"oldnewsites\" style={{ display: 'inline-flex', width: '50%' }}>\r\n                    <Button type=\"raised\" text=\"手动同步适配列表\" width=\"100%\"\r\n                            icon={ ss.IconPath( \"update_icon\" ) }\r\n                            color=\"#fff\" backgroundColor=\"#2196F3\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ ()=>this.oldnewsites() } />\r\n                </div>\r\n                <div className=\"version-tips\" data-hits=\"clear\" style={{ display: 'inline-flex', width: '50%' }}>\r\n                    <Button type=\"raised\" text=\"清除数据\" width=\"100%\"\r\n                            icon={ ss.IconPath( \"clear_icon\" ) }\r\n                            tooltip={{ text: \"清除掉本地配置文件，需谨慎！\" }}\r\n                            color=\"#fff\" backgroundColor=\"#757575\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ ()=>this.clear() } />\r\n                </div>\r\n            </div>\r\n        )\r\n\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/module/enhancesite.jsx",
    "content": "console.log( \"===== simpread option enhancesite load =====\" )\n\nimport TextField   from 'textfield';\nimport Button      from 'button';\nimport Dropdown    from 'dropdown';\n\nimport {storage}   from 'storage';\nimport * as run    from 'runtime';\n\nlet notify, secret, cur_user = {}, ori_user = {}, site_info = {}, user_sites = {};\n\nconst category = [\n    {name: \"科技媒体\", value: \"科技媒体\"},\n    {name: \"传统媒体\", value: \"传统媒体\"},\n    {name: \"新闻\", value: \"新闻\"},\n    {name: \"技术\", value: \"技术\"},\n    {name: \"博客\", value: \"博客\"},\n    {name: \"论坛\", value: \"论坛\"},\n    {name: \"其它\", value: \"其它\"},\n];\n\nfunction loadingState( state, str ) {\n    if ( state == \"init\" ) {\n        notify = new Notify().Render({ content: \"数据处理中，请稍等...\", state: \"loading\" });\n    } else if ( state == \"success\" ) {\n        notify && notify.complete();\n        notify = undefined;\n        new Notify().Render( str + \"成功。\" );\n    } else {\n        notify && notify.complete();\n        notify = undefined;\n        new Notify().Render( 2, str );\n    }\n}\n\n// hack code\nwindow.addEventListener( \"siteinfochanged\", event => {\n    site_info = event.data.info;\n    siteinfoRender();\n});\n\n/**\n * Get serivce url\n * \n * @param {string} method name\n */\nfunction getService( method ) {\n    return storage.service + method;\n}\n\n/**\n * Service Fail\n * \n * @param {object} xhr \n * @param {string} textStatus \n * @param {object} error \n */\nfunction fail( xhr, textStatus, error ) {\n    console.error( xhr, status, error )\n    loadingState( \"fail\" );\n}\n\n/**\n * Get sites {name,value} items\n * \n * @param {object} sites object\n */\nfunction getSites( sites ) {\n    let items = [];\n    Object.keys( sites ).forEach( idx => {\n        const desc = sites[idx].global == true ? \"\" : \"（未审核）\"\n        items.push({ name: sites[idx].title + desc, value: idx });\n    })\n    return items;\n}\n\n/**\n * Editor Plugin Empty Render\n */\nfunction editorEmptyRender() {\n    $( \".preview\" ).html( `<div class=\"empty\"><span class=\"icon\"></span><span>当前未选择任何适配站点</span></div>` );\n}\n\n/**\n * Site info Empty Render\n */\nfunction siteinfoEmptyRender() {\n    $( \".siteinfo\" ).addClass( 'hide' );\n}\n\n/**\n * Site info Render\n */\nfunction siteinfoRender() {\n    if ( site_info == undefined || $.isEmptyObject( site_info ) ) {\n        $( \".siteinfo\" ).addClass( 'hide' ).empty();\n    } else {\n        $( \".siteinfo\" ).removeClass( 'hide' ).empty();\n        ReactDOM.render( <SiteInfo />, $( \".siteinfo\" )[0] );\n    }\n}\n\n/**\n * Sites Render\n */\nfunction sitesRender() {\n    console.log( \"current user all sites \", user_sites )\n    $( \".property .sites\" ).empty();\n    if ( $.isEmptyObject( user_sites ) ) {\n        $( \".property .sites\" ).parent().hide();\n    } else {\n        $( \".property .sites\" ).parent().removeAttr(\"style\");\n        ReactDOM.render( <Sites sites={ getSites( user_sites ) } />, $( \".property .sites\" )[0] );\n    }\n}\n\nclass Sites extends React.Component {\n\n    onChange( value, name ) {\n        if ( !$.isEmptyObject( user_sites[value] )) {\n            const temp = user_sites[value];\n            site_info  = JSON.parse(JSON.stringify(temp));\n\n            delete site_info.site;\n            siteinfoRender();\n\n            // hack code\n            const evt  = document.createEvent(\"Event\");\n            evt.data   = {\n                site: temp.site,\n                info: site_info,\n            }\n            evt.initEvent( \"sitechanged\", true, false );\n            window.dispatchEvent( evt );\n        } else new Notify().Render( 2, \"获取站点信息时发生了错误，请重新绑定获取。\" );\n    }\n\n    render() {\n        const items = this.props.sites;\n        return (\n            items.length > 0 ? <div>\n                <Dropdown name={ `共计 ${items.length} 条数据，下拉查看详细信息` } items={ items } width=\"100%\" onChange={ (v,n)=>this.onChange(v,n) } />\n            </div> : <div></div>\n        )\n    }\n}\n\nclass SiteInfo extends React.Component {\n\n    onChange( id, event ) {\n        site_info[id] = event.target.value\n    }\n\n    onDropdownChange( value ) {\n        site_info.category = value;\n    }\n\n    render() {\n        return(\n            <div>\n                <div className=\"row box-large\">\n                    <TextField value={ site_info.id }\n                        multi={ false } floatingtext=\"ID\" disable={true}\n                    />\n                    <span className=\"space\"></span>\n                    <TextField value={ site_info.title }\n                        multi={ false } floatingtext=\"当前站点名称\" disable={false}\n                        onChange={ event=>this.onChange( \"title\", event )}\n                    />\n                </div>\n                <div className=\"row box-large\">\n                    <TextField value={ site_info.create }\n                        multi={ false } floatingtext=\"建立时间\" disable={true}\n                        onChange={ event=>this.onChange( \"create\", event )}\n                    />\n                    <span className=\"space\"></span>\n                    <TextField value={ site_info.update == \"\" ? \"没有任何更新时间\" : site_info.update }\n                        multi={ false } floatingtext=\"更新时间\" disable={true}\n                        onChange={ event=>this.onChange( \"update\", event )}\n                    />\n                </div>\n                <div className=\"row box-large\">\n                    <TextField value={ site_info.color }\n                        multi={ false } floatingtext=\"前景色\" disable={false}\n                        onChange={ event=>this.onChange( \"color\", event )}\n                    />\n                    <span className=\"space\"></span>\n                    <TextField value={ site_info.bgColor }\n                        multi={ false } floatingtext=\"背景色\" disable={false}\n                        onChange={ event=>this.onChange( \"bgColor\", event )}\n                    />\n                </div>\n                <div className=\"row box-large\">\n                    <TextField value={ site_info.global == true || site_info.global == \"true\" ? \"当前站点已经审核通过\" : \"当前站点未经审核\" }\n                        multi={ false } floatingtext=\"是否已经审核\" disable={true}\n                    />\n                    <span className=\"space\"></span>\n                    <Dropdown name={ site_info.category } items={ category } width=\"100%\" onChange={(v)=>this.onDropdownChange(v)} />\n                </div>\n            </div>\n        )\n    }\n}\n\nexport default class Import extends React.Component {\n\n    state = {\n        login: false,\n    }\n\n    onSecretChange( event ) {\n        secret = event.target.value;\n    }\n\n    login() {\n        loadingState( \"init\" );\n        $.ajax({\n            url     : getService( \"/users/service/get/\" + this.props.uid ),\n            type    : \"POST\",\n            data    : {secret}\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result.code == 200 ) {\n                loadingState( \"success\", \"登录\" );\n                cur_user = result.data;\n                ori_user = $.extend( true, {}, cur_user );\n                console.log( \"current user is \", cur_user )\n                this.getSites( this.props.uid, \"all\" );\n                this.setState({ login: true });\n            } else if ( result.code == 401 ) {\n                loadingState( \"faile\", \"管理员登陆失败，请验证管理员密匙！\" );\n            } else loadingState( \"faile\", \"获取后台服务失败，请稍后再试！\" );\n        }).fail( fail );\n    }\n\n    getSites( uid, id ) {\n        loadingState( \"init\" );\n        $.ajax({\n            url     : getService( \"/sites/service/get/\" + id ),\n            data    : { uid },\n            type    : \"POST\",\n        }).done( ( result, textStatus, jqXHR ) => {\n            loadingState( \"success\", \"获取当前用户全部站点\" );\n            if ( result.code == 200 ) {\n                result.data.forEach( item => {\n                    user_sites[item.id] = item;\n                });\n                sitesRender();\n            } else if ( result.code == 404 ) {\n                loadingState( \"faile\", \"当前用户没有任何站点，可以先新建 或 上传一个站点。\" );\n            } else loadingState( \"faile\", \"获取当前用户的站点获取失败，请稍后再试！\" );\n        }).fail( fail );\n    }\n\n    insert( method, site ) {\n        loadingState( \"init\" );\n        if ( site.id.substr(0,8) != cur_user.uid.substr(0,8) ) {\n            new Notify().Render( 2, \"注意：当前站并不是（管理员）的站。\" );\n            delete site.uid;\n        }\n        $.ajax({\n            url     : getService( \"/sites/service/\" + method ),\n            type    : \"POST\",\n            data    : site,\n        }).done( ( result, textStatus, jqXHR ) => {\n            loadingState( \"success\", \"已提交\" );\n            if ( result.code == 201 ) {\n\n                storage.remote.info.id     = result.data.id;\n                storage.remote.info.create = result.data.create;\n                storage.remote.info.update = result.data.update;\n                delete storage.remote.info.global;\n                delete storage.remote.info.release;\n                siteinfoRender();\n\n                user_sites[result.data.id] = result.data;\n                sitesRender();\n\n                this.props.onUpdate && this.props.onUpdate( \"update\" );\n            } else loadingState( \"faile\", \"提交失败，请稍后再试！\" );\n        }).fail( fail );\n    }\n\n    delete( id ) {\n        loadingState( \"init\" );\n        $.ajax({\n            url     : getService( \"/sites/service/delete/\" + id ),\n            type    : \"POST\",\n            data    : { uid: cur_user.uid },\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result.code == 204 ) {\n                loadingState( \"success\", \"已删除\" );\n                delete user_sites[site_info.id];\n                sitesRender();\n                editorEmptyRender();\n                siteinfoEmptyRender();\n                //this.props.onUpdate && this.props.onUpdate( \"safe\" );\n                new Notify().Render({ mode: \"snackbar\", content: \"是否也删除本地站？\", action: \"确认\", cancel: \"取消\", callback: type => {\n                    type != \"cancel\"  && this.props.onUpdate && this.props.onUpdate( \"delete\" );\n                }});\n            } else loadingState( \"faile\", \"删除失败，请稍后再试！\" );\n        }).fail( fail );\n    }\n\n    update() {\n        const insert = () => {\n            const temp   = JSON.parse(JSON.stringify(storage.remote)),\n                  update = temp.info,\n                  method = update.id.startsWith( \"new::\" ) ? \"add\" : \"update\";\n            delete temp.info;\n            update.uid   = cur_user.uid;\n            update.id    = update.id.replace( /^new::/, \"\" );\n            update.site  = temp;\n            delete update.site.target;\n            this.insert( method, update );\n        }\n        if ( $.isEmptyObject( cur_user ) ) {\n            new Notify().Render({ mode: \"snackbar\", content: \"需要先登录到服务器后才能提交！\", action: \"登录\", cancel: \"取消\", callback: type => {\n                type == \"action\" && this.login();\n                return;\n            }});\n            return;\n        }\n        if ( !storage.remote ) {\n            new Notify().Render( \"当前没有选择站点，请通过 新建 或选择一个本地站点。\" );\n            return;\n        }\n        if ( storage.remote.name == \"tempread::\" ) {\n            new Notify().Render( 2, \"新建的站需要保存刷新后才能提交！\" );\n            return;\n        }\n        if ( !storage.remote.info ) {\n            new Notify().Render( \"上传站点时需要录入一些必要信息。\" );\n            site_info         = { domain: run.ID( \"site\" ), title: \"\", category: \"其它\", create: \"<无需填写，自动生成>\", update: \"\", global: false, release: false, color: \"#fff\", bgColor: \"#00bcd4\" };\n            site_info.id      = \"new::\" + storage.user.uid.substr( 0, 8 ) + \"-\" + site_info.domain;\n            storage.remote.info = site_info;\n            siteinfoRender();\n        } else if ( site_info.title == \"\" ) {\n            new Notify().Render( 2, \"请最好填入当作站点的名称。\" )\n        } else {\n            if ( !storage.remote.info.id.startsWith( \"new::\" ) && storage.remote.info.id.substr(0,8) != cur_user.uid.substr(0,8) ) {\n                new Notify().Render({ mode: \"snackbar\", content: \"当前站点并不是由你建立，确定修改？\", action: \"确定\", cancel: \"取消\", callback: type => {\n                    if ( type == \"cancel\" ) return;\n                    site_info.id        = \"new::\" + storage.user.uid.substr( 0, 8 ) + \"-\" + site_info.domain;\n                    site_info.release   = false;\n                    site_info.global    = false\n                    storage.remote.info = site_info;\n                    insert();\n                }});\n            } else insert();\n        }\n        console.log( \"current site is \", storage.remote.info, site_info )\n    }\n\n    remove() {\n        if ( !storage.remote || $.isEmptyObject( site_info )) {\n            new Notify().Render( \"当前没有选择站点，请通过 新建 或选择一个本地站点。\" );\n            return;\n        }\n        if ( site_info.release == true && cur_user.rule != 0 ) {\n            new Notify().Render( \"当前站点已审核通过，无法删除，请联络管理员。\" );\n            return;\n        }\n        new Notify().Render({ mode: \"snackbar\", content: \"确定（从服务器上）删除（包括以审核的表）？\", action: \"确认\", cancel: \"取消\", callback: type => {\n            if ( type == \"cancel\" ) return;\n            this.delete( site_info.id );\n        }});\n    }\n\n    permit() {\n        if ( !storage.remote || $.isEmptyObject( site_info )) {\n            new Notify().Render( \"当前没有选择站点，请通过 新建 或选择一个本地站点。\" );\n            return;\n        }\n        if ( site_info.global == true || site_info.global == \"true\" ) {\n            new Notify().Render( \"当前站点已经审核通过，请勿重复提交\" );\n            return;\n        }\n        new Notify().Render( \"snackbar\", \"是否确认审核通过？\", \"确认\", () => {\n            loadingState( \"init\" );\n            $.ajax({\n                url     : getService( \"/sites/service/permit/\" + site_info.id ),\n                type    : \"GET\",\n            }).done( ( result, textStatus, jqXHR ) => {\n                if ( result.code == 201 ) {\n                    loadingState( \"success\", \"当前站点已审核\" );\n                    console.log( result.data )\n\n                    user_sites[result.data.id] = result.data;\n                    sitesRender();\n\n                    site_info           = result.data;\n                    storage.remote.info = site_info;\n                    siteinfoRender();\n                } else loadingState( \"faile\", \"删除失败，请稍后再试！\" );\n            }).fail( fail );\n        });\n    }\n\n    logout() {\n        location.reload();\n    }\n\n    render() {\n        return (\n            <div>\n                <div style={{display: location.search == '?rule=administrator' ? 'block' : 'none'}}>\n                    <TextField\n                        multi={ false } placeholder=\"请输入管理员密匙\"\n                        onChange={ event=>this.onSecretChange( event )}\n                    />\n                </div>\n                { this.state.login ?\n                    <Button type=\"raised\" text=\"退出登录\"\n                        style={{ \"margin\": \"0\" }} width=\"100%\"\n                        color=\"#fff\" backgroundColor=\"#1976d2\"\n                        waves=\"md-waves-effect md-waves-button\"\n                        onClick={ ()=>this.logout() } /> :\n                    <Button type=\"raised\" text=\"登录到服务器\"\n                        style={{ \"margin\": \"0\" }} width=\"100%\"\n                        color=\"#fff\" backgroundColor=\"#FF5252\"\n                        waves=\"md-waves-effect md-waves-button\"\n                        onClick={ ()=>this.login() } /> }\n                <Button type=\"raised\" text=\"提交到服务器\"\n                    style={{ \"margin\": \"25px 0 0 0\" }} width=\"100%\"\n                    color=\"#fff\" backgroundColor=\"#4CAF50\"\n                    waves=\"md-waves-effect md-waves-button\"\n                    onClick={ ()=>this.update() } />\n                { this.state.login && cur_user.rule == 0 && \n                <Button type=\"raised\" text=\"审核当前站点\"\n                    style={{ \"margin\": \"25px 0 0 0\" }} width=\"100%\"\n                    color=\"#fff\" backgroundColor=\"#3F51B5\"\n                    waves=\"md-waves-effect md-waves-button\"\n                    onClick={ ()=>this.permit() } />}\n                { this.state.login && \n                <Button type=\"raised\" text=\"删除当前站点\"\n                    style={{ \"margin\": \"25px 0 0 0\" }} width=\"100%\"\n                    color=\"#fff\" backgroundColor=\"#1976d2\"\n                    waves=\"md-waves-effect md-waves-button\"\n                    onClick={ ()=>this.remove() } />}\n            </div>\n        )\n    }\n}\n"
  },
  {
    "path": "src/module/feedback.jsx",
    "content": "console.log( \"===== simpread feedback load =====\" )\n\nimport Switch    from 'switch';\nimport TextField from 'textfield';\nimport Button    from 'button';\n\nimport {browser}  from 'browser';\nimport * as msg   from 'message';\n\nexport class Feedback extends React.Component {\n\n    static defaultProps = {\n        user     : {},\n        url      : \"\",\n        version  : \"\",\n        anonymous: false,\n        rate     : false,\n        product  : \"https://support.qq.com/product/\" + 117464,\n        star     : `<svg viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"35\" height=\"35\"><path d=\"M512 837.12L255.6928 950.0672c-14.336 6.3488-31.1296-0.2048-37.4784-14.5408-1.9456-4.5056-2.7648-9.4208-2.2528-14.336l28.16-278.6304-186.5728-208.896c-10.4448-11.6736-9.4208-29.696 2.2528-40.1408 3.6864-3.2768 8.0896-5.5296 12.9024-6.5536L346.5216 327.68 487.424 85.7088c7.8848-13.6192 25.2928-18.1248 38.912-10.24 4.3008 2.4576 7.7824 6.0416 10.24 10.24L677.4784 327.68l273.7152 59.2896c15.36 3.2768 25.088 18.432 21.8112 33.792-1.024 4.8128-3.2768 9.216-6.5536 12.9024l-186.6752 208.896L807.936 921.1904c1.536 15.6672-9.8304 29.5936-25.3952 31.1296-4.9152 0.512-9.8304-0.3072-14.336-2.2528L512 837.12z\" fill=\"#E3E3E3\"></path></svg>`,\n        stared   : `<svg viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"35\" height=\"35\"><path d=\"M512 837.12L255.6928 950.0672c-14.336 6.3488-31.1296-0.2048-37.4784-14.5408-1.9456-4.5056-2.7648-9.4208-2.2528-14.336l28.16-278.6304-186.5728-208.896c-10.4448-11.6736-9.4208-29.696 2.2528-40.1408 3.6864-3.2768 8.0896-5.5296 12.9024-6.5536L346.5216 327.68 487.424 85.7088c7.8848-13.6192 25.2928-18.1248 38.912-10.24 4.3008 2.4576 7.7824 6.0416 10.24 10.24L677.4784 327.68l273.7152 59.2896c15.36 3.2768 25.088 18.432 21.8112 33.792-1.024 4.8128-3.2768 9.216-6.5536 12.9024l-186.6752 208.896L807.936 921.1904c1.536 15.6672-9.8304 29.5936-25.3952 31.1296-4.9152 0.512-9.8304-0.3072-14.336-2.2528L512 837.12z\" fill=\"#FFB82F\"</path></svg>`,\n    }\n\n    static propType = {\n        user     : React.PropTypes.object,\n        url      : React.PropTypes.string,\n        version  : React.PropTypes.string,\n        anonymous: React.PropTypes.bool,\n        rate     : React.PropTypes.bool,\n        product  : React.PropTypes.string,\n    }\n\n    state = {\n        mode: \"github\",\n        rate: this.props.rate,\n        stars: 0,\n    };\n\n    onStarClick( idx ) {\n        $( this.refs.stars ).find( \"i\" ).removeClass( \"active\" );\n        for( let i = 0; i < 5; i++ ) {\n            const $target = $( $( this.refs.stars ).find( \"i\" )[i] );\n            if ( i < idx ) $target.addClass( \"active\" ).html( this.props.stared );\n            else $target.html( this.props.star );\n        }\n        $( this.refs.emoji ).css({ 'transform': `translateY(-${idx}00px)` });\n        this.setState({ stars: idx });\n    }\n\n    onStarHover( idx ) {\n        $( this.refs.emoji ).css({ 'transform': `translateY(-${idx}00px)` });\n    }\n\n    onRateClick() {\n        if ( this.state.stars < 4 ) {\n            this.setState({ rate: false });\n        } else {\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://chrome.google.com/webstore/detail/simpread-reader-view/ijllcpnolfcooahcekpamkbidhejabll/reviews\" }));\n            setTimeout( () => this.onClose(), 200 );\n        }\n    }\n\n    onURLChange( event ) {\n        this.props.url = event.target.value.trim();\n    }\n\n    onAnonymousChange( value ) {\n        this.props.anonymous = value;\n    }\n\n    onChangeMode( mode ) {\n        this.setState({ mode });\n    }\n\n    onClose() {\n        $( this.refs.target )\n            .addClass( \"hide\" )[0]\n            .addEventListener( 'animationend', () => {\n                ReactDOM.unmountComponentAtNode( $( \".simpread-feedback\" )[0] );\n                $( \".simpread-feedback\" ).remove();\n        }, false );\n    }\n\n    onSubmitClick() {\n        this.state.mode == \"github\" ? this.onGithubClick() : this.onTucaoClick();\n    }\n\n    onGithubClick() {\n        const content = `**小提示**\n\n> 简悦已经服务 70K+ 的用户，所以你的很多问题，或许已经被前人解决了，所以试着看看以下几个列表中的内容：\n\n- [用好 Github issues 能解决你大部分的疑问](https://github.com/Kenshin/simpread/issues/533)\n\n- [常见问题汇总](https://github.com/Kenshin/simpread/issues/618)\n\n- [代码段的专项整治](https://github.com/Kenshin/simpread/issues/500)\n\n***\n\n> 如上述内容无法解决你的问题，那么请将上述内容删除，并按照下方的提示书写~  😀 \n\n**请说明发生问题的环境**\n\n> 简悦包含了很多平台的版本，所以为了方便定位，建议告诉我一些必要信息\n\n- 操作系统 **${window.navigator.platform}**\n\n- 浏览器版本 **e.g. Chrome 78.0.3904.108**\n\n- 简悦版本 **${ this.props.version }**\n\n- 发生问题的地址 <${ this.props.url }>\n\n**请描述你的问题**\n\n> 请使用可以 **准确定位到错误** 的语句来告诉我。😀\n\n**截图**\n\n> 一图胜千言，所以方便的话，可以试着贴图。\n`, url = encodeURI( `https://github.com/Kenshin/simpread/issues/new?title=<请描述你的问题>&labels=to do&body=${content}` )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url }));\n    }\n\n    onTucaoClick() {\n        const data = {\n            \"nickname\"     : this.props.anonymous ? \"简悦用户\" : this.props.user.name || \"简悦用户\",\n            \"avatar\"       : `https://api.adorable.io/avatars/285/${ this.props.user.name || this.props.user.uid.substr( 0,13 ) }.png`,\n            \"openid\"       : this.props.user.uid.substr( 0,13 ),\n            \"clientVersion\": this.props.version,\n            \"clientInfo\"   : window.navigator.userAgent,\n            \"customInfo\"   : \"https://github.com/erguotou520/tucao-dingtalk-webhook\"\n        };\n        $.ajax({\n            url: this.props.product,\n            method: \"POST\",\n            data\n        }).done( ( result, textStatus, jqXHR ) => {\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: this.props.product }));\n        }).fail( error => {\n            console.log( \"count failed \", error )\n        });\n    }\n\n    render() {\n        return (\n            <simpread-feedback ref=\"target\" class=\"active\">\n            { this.state.rate == false ?\n                <sr-block style={{ 'width': '100%' }}>\n                    <sr-fb-head>\n                        <sr-fb-label>有了你们的帮助简悦才会变得更好 🙏</sr-fb-label>\n                    </sr-fb-head>\n                    <sr-fb-content>\n                        <sr-fb-label>提交的站点，默认为当前页面的地址，可为空</sr-fb-label>\n                        <TextField multi={ false } value={ this.props.url } onChange={ (e)=>this.onURLChange(e) } />\n                    </sr-fb-content>\n                    <sr-fb-content>\n                        <sr-fb-label>支持两种提交方式</sr-fb-label>\n                        <span style={{ 'display': 'flex' }}>\n                            <Button\n                                text=\"有 Github Issues 帐号\" type=\"raised\" waves=\"md-waves-effect\"\n                                color=\"#fff\" backgroundColor=\"#2196F3\" width=\"50%\" style={{ 'margin-left': '0', 'font-weight': 'bold' }}\n                                tooltip={{ text: \"如果有 Github 帐号，请首选此方式\" }} onClick={ ()=>this.onChangeMode( \"github\" ) } />\n                            <Button\n                                text=\"无 Github Issues 帐号\" type=\"raised\" mode=\"secondary\" waves=\"md-waves-effect\"\n                                color=\"#fff\" backgroundColor=\"#757575\" width=\"50%\" style={{ 'margin-right': '0', 'font-weight': 'bold' }}\n                                tooltip={{ text: \"腾讯旗下的一款用户反馈收集系统，无需注册\" }} onClick={ ()=>this.onChangeMode( \"tucao\" ) } />\n                        </span>\n                    </sr-fb-content>\n                    { this.state.mode == \"tucao\" &&\n                        <sr-fb-content>\n                            <sr-fb-content>\n                                <Switch width=\"100%\" checked={ this.props.anonymous }\n                                        thumbedColor=\"#2163f7\" trackedColor=\"#6699FF\" waves=\"md-waves-effect\"\n                                        label=\"支持匿名提交，但建议不要勾选此项\"\n                                        onChange={ (v)=>this.onAnonymousChange( v ) } />\n                            </sr-fb-content>\n                            <sr-fb-content>\n                                <sr-fb-label><b>吐个槽</b> 是腾讯旗下的一款用户反馈收集系统，具有如下特点：</sr-fb-label>\n                                <sr-fb-label>· 无需注册，点击后会自动使用简悦的注册系统</sr-fb-label>\n                                <sr-fb-label>· 如需实时收到反馈，请根据提示关注（腾讯官方）微信号</sr-fb-label>\n                                <sr-fb-label>· 你的提交内容，他人无法看到</sr-fb-label>\n                            </sr-fb-content>\n                        </sr-fb-content>\n                    }\n                    <sr-fb-content>\n                        <sr-fb-label><b>方便的话，请帮助简悦，使其变得更好 👉 <sr-fb-a onClick={ ()=>window.open( 'https://wj.qq.com/s2/3611463/7260/', '_blank') }>调查问卷</sr-fb-a></b></sr-fb-label>\n                    </sr-fb-content>\n                    <sr-fb-footer>\n                        <Button text=\"评个分\" color=\"#FF5252\" waves=\"md-waves-effect\" style={{ 'font-weight': 'bold' }} onClick={ ()=>this.setState({ rate: true }) } />\n                        <Button text=\"取 消\" mode=\"secondary\" color=\"#333\" waves=\"md-waves-effect\" onClick={ ()=>this.onClose() } />\n                        <Button text=\"提 交\" waves=\"md-waves-effect\" color=\"#2163f7\" style={{ 'font-weight': 'bold' }} onClick={ ()=>this.onSubmitClick() } />\n                    </sr-fb-footer>\n                </sr-block>\n                :\n                <sr-block style={{ 'width': '100%' }}>\n                    <sr-close onClick={ ()=>this.onClose() }><svg viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\" fill=\"#E3E3E3\"></path></svg></sr-close>\n                    <sr-emojis>\n                        <sr-emoji ref=\"emoji\">\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#ffd93b\"></circle><path d=\"M512 256c0 141.44-114.64 256-256 256-80.48 0-152.32-37.12-199.28-95.28 43.92 35.52 99.84 56.72 160.72 56.72 141.36 0 256-114.56 256-256 0-60.88-21.2-116.8-56.72-160.72C474.8 103.68 512 175.52 512 256z\" fill=\"#f4c534\"></path><ellipse transform=\"scale(-1) rotate(31.21 715.433 -595.455)\" cx=\"166.318\" cy=\"199.829\" rx=\"56.146\" ry=\"56.13\" fill=\"#fff\"></ellipse><ellipse transform=\"rotate(-148.804 180.87 175.82)\" cx=\"180.871\" cy=\"175.822\" rx=\"28.048\" ry=\"28.08\" fill=\"#3e4347\"></ellipse><ellipse transform=\"rotate(-113.778 194.434 165.995)\" cx=\"194.433\" cy=\"165.993\" rx=\"8.016\" ry=\"5.296\" fill=\"#5a5f63\"></ellipse><ellipse transform=\"scale(-1) rotate(31.21 715.397 -1237.664)\" cx=\"345.695\" cy=\"199.819\" rx=\"56.146\" ry=\"56.13\" fill=\"#fff\"></ellipse><ellipse transform=\"rotate(-148.804 360.25 175.837)\" cx=\"360.252\" cy=\"175.84\" rx=\"28.048\" ry=\"28.08\" fill=\"#3e4347\"></ellipse><ellipse transform=\"scale(-1) rotate(66.227 254.508 -573.138)\" cx=\"373.794\" cy=\"165.987\" rx=\"8.016\" ry=\"5.296\" fill=\"#5a5f63\"></ellipse><path d=\"M370.56 344.4c0 7.696-6.224 13.92-13.92 13.92H155.36c-7.616 0-13.92-6.224-13.92-13.92s6.304-13.92 13.92-13.92h201.296c7.696.016 13.904 6.224 13.904 13.92z\" fill=\"#3e4347\"></path></svg>\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#ffd93b\"></circle><path d=\"M512 256A256 256 0 0 1 56.7 416.7a256 256 0 0 0 360-360c58.1 47 95.3 118.8 95.3 199.3z\" fill=\"#f4c534\"></path><path d=\"M328.4 428a92.8 92.8 0 0 0-145-.1 6.8 6.8 0 0 1-12-5.8 86.6 86.6 0 0 1 84.5-69 86.6 86.6 0 0 1 84.7 69.8c1.3 6.9-7.7 10.6-12.2 5.1z\" fill=\"#3e4347\"></path><path d=\"M269.2 222.3c5.3 62.8 52 113.9 104.8 113.9 52.3 0 90.8-51.1 85.6-113.9-2-25-10.8-47.9-23.7-66.7-4.1-6.1-12.2-8-18.5-4.2a111.8 111.8 0 0 1-60.1 16.2c-22.8 0-42.1-5.6-57.8-14.8-6.8-4-15.4-1.5-18.9 5.4-9 18.2-13.2 40.3-11.4 64.1z\" fill=\"#f4c534\"></path><path d=\"M357 189.5c25.8 0 47-7.1 63.7-18.7 10 14.6 17 32.1 18.7 51.6 4 49.6-26.1 89.7-67.5 89.7-41.6 0-78.4-40.1-82.5-89.7A95 95 0 0 1 298 174c16 9.7 35.6 15.5 59 15.5z\" fill=\"#fff\"></path><path d=\"M396.2 246.1a38.5 38.5 0 0 1-38.7 38.6 38.5 38.5 0 0 1-38.6-38.6 38.6 38.6 0 1 1 77.3 0z\" fill=\"#3e4347\"></path><path d=\"M380.4 241.1c-3.2 3.2-9.9 1.7-14.9-3.2-4.8-4.8-6.2-11.5-3-14.7 3.3-3.4 10-2 14.9 2.9 4.9 5 6.4 11.7 3 15z\" fill=\"#fff\"></path><path d=\"M242.8 222.3c-5.3 62.8-52 113.9-104.8 113.9-52.3 0-90.8-51.1-85.6-113.9 2-25 10.8-47.9 23.7-66.7 4.1-6.1 12.2-8 18.5-4.2 16.2 10.1 36.2 16.2 60.1 16.2 22.8 0 42.1-5.6 57.8-14.8 6.8-4 15.4-1.5 18.9 5.4 9 18.2 13.2 40.3 11.4 64.1z\" fill=\"#f4c534\"></path><path d=\"M155 189.5c-25.8 0-47-7.1-63.7-18.7-10 14.6-17 32.1-18.7 51.6-4 49.6 26.1 89.7 67.5 89.7 41.6 0 78.4-40.1 82.5-89.7A95 95 0 0 0 214 174c-16 9.7-35.6 15.5-59 15.5z\" fill=\"#fff\"></path><path d=\"M115.8 246.1a38.5 38.5 0 0 0 38.7 38.6 38.5 38.5 0 0 0 38.6-38.6 38.6 38.6 0 1 0-77.3 0z\" fill=\"#3e4347\"></path><path d=\"M131.6 241.1c3.2 3.2 9.9 1.7 14.9-3.2 4.8-4.8 6.2-11.5 3-14.7-3.3-3.4-10-2-14.9 2.9-4.9 5-6.4 11.7-3 15z\" fill=\"#fff\"></path></svg>\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#ffd93b\"></circle><path d=\"M512 256A256 256 0 0 1 56.7 416.7a256 256 0 0 0 360-360c58.1 47 95.3 118.8 95.3 199.3z\" fill=\"#f4c534\"></path><path d=\"M336.6 403.2c-6.5 8-16 10-25.5 5.2a117.6 117.6 0 0 0-110.2 0c-9.4 4.9-19 3.3-25.6-4.6-6.5-7.7-4.7-21.1 8.4-28 45.1-24 99.5-24 144.6 0 13 7 14.8 19.7 8.3 27.4z\" fill=\"#3e4347\"></path><path d=\"M276.6 244.3a79.3 79.3 0 1 1 158.8 0 79.5 79.5 0 1 1-158.8 0z\" fill=\"#fff\"></path><circle cx=\"340\" cy=\"260.4\" r=\"36.2\" fill=\"#3e4347\"></circle><g fill=\"#fff\"><ellipse transform=\"rotate(-135 326.4 246.6)\" cx=\"326.4\" cy=\"246.6\" rx=\"6.5\" ry=\"10\"></ellipse><path d=\"M231.9 244.3a79.3 79.3 0 1 0-158.8 0 79.5 79.5 0 1 0 158.8 0z\"></path></g><circle cx=\"168.5\" cy=\"260.4\" r=\"36.2\" fill=\"#3e4347\"></circle><ellipse transform=\"rotate(-135 182.1 246.7)\" cx=\"182.1\" cy=\"246.7\" rx=\"10\" ry=\"6.5\" fill=\"#fff\"></ellipse></svg>\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#ffd93b\"></circle><path d=\"M407.7 352.8a163.9 163.9 0 0 1-303.5 0c-2.3-5.5 1.5-12 7.5-13.2a780.8 780.8 0 0 1 288.4 0c6 1.2 9.9 7.7 7.6 13.2z\" fill=\"#3e4347\"></path><path d=\"M512 256A256 256 0 0 1 56.7 416.7a256 256 0 0 0 360-360c58.1 47 95.3 118.8 95.3 199.3z\" fill=\"#f4c534\"></path><g fill=\"#fff\"><path d=\"M115.3 339c18.2 29.6 75.1 32.8 143.1 32.8 67.1 0 124.2-3.2 143.2-31.6l-1.5-.6a780.6 780.6 0 0 0-284.8-.6z\"></path><ellipse cx=\"356.4\" cy=\"205.3\" rx=\"81.1\" ry=\"81\"></ellipse></g><ellipse cx=\"356.4\" cy=\"205.3\" rx=\"44.2\" ry=\"44.2\" fill=\"#3e4347\"></ellipse><g fill=\"#fff\"><ellipse transform=\"scale(-1) rotate(45 454 -906)\" cx=\"375.3\" cy=\"188.1\" rx=\"12\" ry=\"8.1\"></ellipse><ellipse cx=\"155.6\" cy=\"205.3\" rx=\"81.1\" ry=\"81\"></ellipse></g><ellipse cx=\"155.6\" cy=\"205.3\" rx=\"44.2\" ry=\"44.2\" fill=\"#3e4347\"></ellipse><ellipse transform=\"scale(-1) rotate(45 454 -421.3)\" cx=\"174.5\" cy=\"188\" rx=\"12\" ry=\"8.1\" fill=\"#fff\"></ellipse></svg>\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><circle cx=\"256\" cy=\"256\" r=\"256\" fill=\"#ffd93b\"></circle><path d=\"M512 256A256 256 0 0 1 56.7 416.7a256 256 0 0 0 360-360c58.1 47 95.3 118.8 95.3 199.3z\" fill=\"#f4c534\"></path><path d=\"M232.3 201.3c0 49.2-74.3 94.2-74.3 94.2s-74.4-45-74.4-94.2a38 38 0 0 1 74.4-11.1 38 38 0 0 1 74.3 11.1z\" fill=\"#e24b4b\"></path><path d=\"M96.1 173.3a37.7 37.7 0 0 0-12.4 28c0 49.2 74.3 94.2 74.3 94.2C80.2 229.8 95.6 175.2 96 173.3z\" fill=\"#d03f3f\"></path><path d=\"M215.2 200c-3.6 3-9.8 1-13.8-4.1-4.2-5.2-4.6-11.5-1.2-14.1 3.6-2.8 9.7-.7 13.9 4.4 4 5.2 4.6 11.4 1.1 13.8z\" fill=\"#fff\"></path><path d=\"M428.4 201.3c0 49.2-74.4 94.2-74.4 94.2s-74.3-45-74.3-94.2a38 38 0 0 1 74.4-11.1 38 38 0 0 1 74.3 11.1z\" fill=\"#e24b4b\"></path><path d=\"M292.2 173.3a37.7 37.7 0 0 0-12.4 28c0 49.2 74.3 94.2 74.3 94.2-77.8-65.7-62.4-120.3-61.9-122.2z\" fill=\"#d03f3f\"></path><path d=\"M411.3 200c-3.6 3-9.8 1-13.8-4.1-4.2-5.2-4.6-11.5-1.2-14.1 3.6-2.8 9.7-.7 13.9 4.4 4 5.2 4.6 11.4 1.1 13.8z\" fill=\"#fff\"></path><path d=\"M381.7 374.1c-30.2 35.9-75.3 64.4-125.7 64.4s-95.4-28.5-125.8-64.2a17.6 17.6 0 0 1 16.5-28.7 627.7 627.7 0 0 0 218.7-.1c16.2-2.7 27 16.1 16.3 28.6z\" fill=\"#3e4347\"></path><path d=\"M256 438.5c25.7 0 50-7.5 71.7-19.5-9-33.7-40.7-43.3-62.6-31.7-29.7 15.8-62.8-4.7-75.6 34.3 20.3 10.4 42.8 17 66.5 17z\" fill=\"#e24b4b\"></path></svg>\n                            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><g fill=\"#ffd93b\"><circle cx=\"256\" cy=\"256\" r=\"256\"></circle><path d=\"M512 256A256 256 0 0 1 56.8 416.7a256 256 0 0 0 360-360c58 47 95.2 118.8 95.2 199.3z\"></path></g><path d=\"M512 99.4v165.1c0 11-8.9 19.9-19.7 19.9h-187c-13 0-23.5-10.5-23.5-23.5v-21.3c0-12.9-8.9-24.8-21.6-26.7-16.2-2.5-30 10-30 25.5V261c0 13-10.5 23.5-23.5 23.5h-187A19.7 19.7 0 0 1 0 264.7V99.4c0-10.9 8.8-19.7 19.7-19.7h472.6c10.8 0 19.7 8.7 19.7 19.7z\" fill=\"#e9eff4\"></path><path d=\"M204.6 138v88.2a23 23 0 0 1-23 23H58.2a23 23 0 0 1-23-23v-88.3a23 23 0 0 1 23-23h123.4a23 23 0 0 1 23 23z\" fill=\"#45cbea\"></path><path d=\"M476.9 138v88.2a23 23 0 0 1-23 23H330.3a23 23 0 0 1-23-23v-88.3a23 23 0 0 1 23-23h123.4a23 23 0 0 1 23 23z\" fill=\"#e84d88\"></path><g fill=\"#38c0dc\"><path d=\"M95.2 114.9l-60 60v15.2l75.2-75.2zM123.3 114.9L35.1 203v23.2c0 1.8.3 3.7.7 5.4l116.8-116.7h-29.3z\"></path></g><g fill=\"#d23f77\"><path d=\"M373.3 114.9l-66 66V196l81.3-81.2zM401.5 114.9l-94.1 94v17.3c0 3.5.8 6.8 2.2 9.8l121.1-121.1h-29.2z\"></path></g><path d=\"M329.5 395.2c0 44.7-33 81-73.4 81-40.7 0-73.5-36.3-73.5-81s32.8-81 73.5-81c40.5 0 73.4 36.3 73.4 81z\" fill=\"#3e4347\"></path><path d=\"M256 476.2a70 70 0 0 0 53.3-25.5 34.6 34.6 0 0 0-58-25 34.4 34.4 0 0 0-47.8 26 69.9 69.9 0 0 0 52.6 24.5z\" fill=\"#e24b4b\"></path><path d=\"M290.3 434.8c-1 3.4-5.8 5.2-11 3.9s-8.4-5.1-7.4-8.7c.8-3.3 5.7-5 10.7-3.8 5.1 1.4 8.5 5.3 7.7 8.6z\" fill=\"#fff\" opacity=\".2\"></path></svg>\n                        </sr-emoji>\n                    </sr-emojis>\n                    <sr-stars ref=\"stars\">\n                        <i data-balloon-pos=\"up\" aria-label=\"吐个槽 😡\" onMouseEnter={ () => this.onStarHover( 1 ) } onClick={ () => this.onStarClick( 1 ) } dangerouslySetInnerHTML={{__html: this.props.star }}></i>\n                        <i data-balloon-pos=\"up\" aria-label=\"一般般 💔\" onMouseEnter={ () => this.onStarHover( 2 ) } onClick={ () => this.onStarClick( 2 ) } dangerouslySetInnerHTML={{__html: this.props.star }}></i>\n                        <i data-balloon-pos=\"up\" aria-label=\"还不错 😁\" onMouseEnter={ () => this.onStarHover( 3 ) } onClick={ () => this.onStarClick( 3 ) } dangerouslySetInnerHTML={{__html: this.props.star }}></i>\n                        <i data-balloon-pos=\"up\" aria-label=\"我喜欢 😘\" onMouseEnter={ () => this.onStarHover( 4 ) } onClick={ () => this.onStarClick( 4 ) } dangerouslySetInnerHTML={{__html: this.props.star }}></i>\n                        <i data-balloon-pos=\"up\" aria-label=\"棒棒哒 👍\" onMouseEnter={ () => this.onStarHover( 5 ) } onClick={ () => this.onStarClick( 5 ) } dangerouslySetInnerHTML={{__html: this.props.star }}></i>\n                    </sr-stars>\n                    <sr-stars-footer>\n                        { this.state.stars == 0 && <Button text=\"投个票，有你的参与简悦才能变得更美好\" waves=\"md-waves-effect\" color=\"#2163f7\" style={{ 'font-weight': 'bold' }} /> }\n                        { this.state.stars > 0 && this.state.stars < 4 && <Button text=\"吐个槽？\" waves=\"md-waves-effect\"  color=\"#FF5252\" style={{ 'font-weight': 'bold' }} onClick={ () => this.onRateClick() }/> }\n                        { this.state.stars > 3 && <Button color=\"#fff\" backgroundColor=\"#2196F3\" text=\"谢谢，方便请前往 Chrome 应用商店投票 👉\" waves=\"md-waves-effect\" style={{ 'font-weight': 'bold' }} onClick={ () => this.onRateClick() }/> }\n                    </sr-stars-footer>\n                </sr-block> }\n            </simpread-feedback>\n        )\n    }\n}\n\n/**\n * \n * @param {string} storage.version\n * @param {object} storage.user\n * @param {boolen} rate, true: show rating; false: show feedback\n */\nfunction Render( version, user, rate = false ) {\n    if ( $( \"simpread-feedback\" ).length > 0 ) return;\n    $( \"html\" ).append( `<div class=\"simpread-feedback\"></div>` );\n    ReactDOM.render( <Feedback version={ version } user={ user } url={ location.href } rate={ rate }/>, $( \".simpread-feedback\" )[0] );\n}\n\nexport {\n    Render\n}"
  },
  {
    "path": "src/module/focus.jsx",
    "content": "console.log( \"===== simpread option focus mode load =====\" )\r\n\r\nimport ThemeSel  from 'themesel';\r\nimport Shortcuts from 'shortcuts';\r\n\r\nimport Slider    from 'slider';\r\n\r\nimport * as ss   from 'stylesheet';\r\nimport * as conf from 'config';\r\n\r\nexport default class FocusOpt extends React.Component {\r\n\r\n    changeBgColor( bgcolor, $target ) {\r\n        bgcolor = $target.css( \"background-color\" );\r\n        this.props.option.bgcolor = ss.BgColor( bgcolor, this.props.option.opacity );\r\n        console.log( \"this.props.option.bgcolor = \", this.props.option.bgcolor )\r\n    }\r\n\r\n    changeOpacity( value ) {\r\n        const bgcolor = ss.Opacity( value );\r\n        bgcolor && ( this.props.option.bgcolor = bgcolor );\r\n        this.props.option.opacity = value;\r\n        console.log( \"this.props.option.opacity = \", this.props.option.opacity )\r\n    }\r\n\r\n    changeShortcuts( shortcuts ) {\r\n        this.props.option.shortcuts = shortcuts;\r\n        console.log( \"this.props.option.shortcuts = \", this.props.option.shortcuts )\r\n    }\r\n\r\n    render() {\r\n        const slider_width = location.protocol.includes( \"extension\" ) ? \"660.09px\" : undefined;\r\n        return (\r\n            <sr-opt-focus>\r\n                <sr-opt-gp>\r\n                    <sr-opt-label>主题色</sr-opt-label>\r\n                    <ThemeSel themes={ conf.focusThemes } names={ conf.focusThemes } labels={ conf.focusLabels } theme={ ss.GetColor(this.props.option.bgcolor) + \", 0.9\" } changeBgColor={ (val,target)=>this.changeBgColor(val,target) } />\r\n                </sr-opt-gp>\r\n                <sr-opt-gp>\r\n                    <sr-opt-label>透明度</sr-opt-label>\r\n                    <Slider min=\"50\" max=\"95\" step=\"1\" width={slider_width} value={ this.props.option.opacity } onChange={ (v)=>this.changeOpacity(v) }/>\r\n                </sr-opt-gp>\r\n                <sr-opt-gp>\r\n                    <Shortcuts shortcuts={ this.props.option.shortcuts } changeShortcuts={ val=>this.changeShortcuts(val) } />\r\n                </sr-opt-gp>\r\n            </sr-opt-focus>\r\n        )\r\n    }\r\n}"
  },
  {
    "path": "src/module/guide.jsx",
    "content": "console.log( \"===== simpread option guide load =====\" )\r\n\r\nimport intro              from 'intro';\r\n\r\nimport {browser}          from 'browser';\r\nimport * as msg           from 'message';\r\nimport {storage}          from 'storage';\r\nimport * as ver           from 'version';\r\n\r\nclass Guide extends React.Component {\r\n\r\n    static defaultProps = {\r\n        tips: []\r\n    };\r\n\r\n    static propsType = {\r\n        tips: React.PropTypes.array,\r\n    };\r\n\r\n    state = {\r\n        tips: [],\r\n    }\r\n\r\n    onClick( event, idx, url, detail ) {\r\n        if ( url.startsWith( \"http\" ) ) {\r\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url }));\r\n        } else if ( url.startsWith( \"@\" ) ) {\r\n            start( url );\r\n            this.props.onExit && this.props.onExit();\r\n        } else if ( url.startsWith( \"!!\" ) ) {\r\n            start( url, true, detail );\r\n            this.props.onExit && this.props.onExit();\r\n        } else if ( idx == 2 ) {\r\n            start( storage.version );\r\n            this.props.onExit && this.props.onExit();\r\n        } else {\r\n            const id = location.hash == \"\" ? \"common\" : location.hash.replace( \"#\", \"\" );\r\n            if ( id == \"account\" || id == \"about\" ) {\r\n                new Notify().Render( \"此页面没有功能描述。\" );\r\n            } else {\r\n                start( id, false );\r\n                this.props.onExit && this.props.onExit();\r\n            }\r\n        }\r\n    }\r\n\r\n    onLoadingClick() {\r\n        $( \".guide .loading\" ).html( `<svg width=\"20\" height=\"20\" viewBox=\"0 0 38 38\" stroke=\"#26d07c\"> <g fill=\"none\" fill-rule=\"evenodd\"> <g transform=\"translate(1 1)\" stroke-width=\"2\"> <circle stroke-opacity=\".5\" cx=\"18\" cy=\"18\" r=\"18\"/> <path d=\"M36 18c0-9.94-8.06-18-18-18\"> <animateTransform attributeName=\"transform\" type=\"rotate\" from=\"0 18 18\" to=\"360 18 18\" dur=\"1s\" repeatCount=\"indefinite\"/> </path> </g> </g></svg>` );\r\n        const ajax = () => {\r\n            $.ajax({\r\n                url   : storage.help_service + \"?=\" + Math.round(+new Date()),\r\n                method: \"GET\",\r\n            }).done( ( result, textStatus, jqXHR ) => {\r\n                if ( result && result.tips.length == 0 ) {\r\n                    $( \".guide .loading\" ).html( '<span>没有新的消息</span>' ).css({\"animation\": \".1s reverse fadein,235ms cubic-bezier(.4,0,.2,1) popup\"});\r\n                } else {\r\n                    $( \".guide\" ).find( \"hr\" ).remove();\r\n                    this.setState({tips: this.state.tips.concat( result.tips ) });\r\n                    $( \".guide .loading\" ).remove();\r\n                }\r\n            }).fail( error => {\r\n                $( \".guide .loading\" ).html( `<i class=\"fas fa-bug\" style=\"color:#FF5252;\"></i><span style=\"color:#FF5252;\">发生了一些错误，请稍后再试。</span>` )\r\n            });\r\n        };\r\n        setTimeout( ajax, 1000 );\r\n    }\r\n\r\n    componentWillMount() {\r\n        storage.GetRemote( \"help_tips\", ( result, error ) => {\r\n            result && result.tips && this.setState({ tips: result.tips });\r\n        });\r\n    }\r\n\r\n    componentDidUpdate() {\r\n        $( \"guid-card[id='3']\" ).after( \"<hr>\" );\r\n    }\r\n\r\n    componentDidMount() {\r\n        $( \".guide\" ).scroll( event => {\r\n            if ( $( event.target ).scrollTop() > 35 ) {\r\n                $( \".guide .title\" )\r\n                    .css({\"box-shadow\": \"2px 4px 10px rgba(0,0,0,.2)\" })\r\n                    .find( \"span\" ).text( \"帮助中心 > 快捷答案\" ).css({\"font-weight\": \"normal\", \"animation\": \".1s reverse fadein,235ms cubic-bezier(.4,0,.2,1) popup\" });\r\n            } else {\r\n                $( \".guide .title\" )\r\n                    .removeAttr( \"style\" )\r\n                    .find( \"span\" ).text( \"帮助中心\" ).removeAttr( \"style\" );\r\n            }\r\n        });\r\n    }\r\n\r\n    render() {\r\n        const tips = this.state.tips.map( item => {\r\n            return (\r\n                <guid-card id={ item.idx } class=\"md-waves-effect\" onClick={ e=>this.onClick( e, item.idx, item.url, item.detail ) }>\r\n                    <guid-card-tips>\r\n                        <span dangerouslySetInnerHTML={{__html: item.icon }} ></span>\r\n                        <span>{ item.name }</span>\r\n                    </guid-card-tips>\r\n                </guid-card>\r\n            )\r\n        });\r\n        return (\r\n            <div className=\"guide\">\r\n                <div className=\"title\"><span>帮助中心</span></div>\r\n                <div className=\"subtitle\"><span>快捷答案</span></div>\r\n                <div className=\"group\">\r\n                    { tips }\r\n                </div>\r\n                <div className=\"loading\" onClick={ ()=>this.onLoadingClick() }><span className=\"md-waves-effect\">加载更多</span></div>\r\n            </div>\r\n        )\r\n    }\r\n}\r\n\r\n/**\r\n * Show current version intro\r\n * \r\n * @param {string}  id     include: version id | hash id, e,g. 1.1.3, common, simple\r\n * @param {boolean} verify current url and intros.start()\r\n * @param {boolean} detail @see ver.tips data structure\r\n */\r\nfunction start( id, verify = true, detail = {} ) {\r\n    const [ rm_idx, rm_target, rm_steps ] = [ detail.idx, detail.target, detail.steps ],\r\n          target = rm_target ? rm_target : ver.tips[ id ].target,\r\n          idx    = rm_idx    ? rm_idx    : ver.tips[ id ].idx,\r\n          steps  = (() => {\r\n              const  items = rm_steps ? rm_steps : ver.tips[ id ].items;\r\n              return items.map( item => { return { element: $( ver.tips.root( item.id ) )[0], intro: item.intro }})\r\n          })(),\r\n          intros = intro(),\r\n          start  = () => {\r\n            intros.setOptions({\r\n                hintButtonLabel: \"确认\",\r\n                nextLabel: \"下一条 →\",\r\n                prevLabel: \"← 上一条\",\r\n                skipLabel: \"\",\r\n                doneLabel: \"完成\",\r\n                hidePrev: true,\r\n                hideNext: true,\r\n                steps\r\n            });\r\n            intros.start();\r\n    };\r\n    if ( verify && location.hash != `#${ target }` ) {\r\n        location.href = location.origin + \"/options/options.html#labs\";\r\n        window.dispatchEvent( new CustomEvent( msg.MESSAGE_ACTION.turn_tab, { detail: { page: idx }}));\r\n        setTimeout( start, 500 );\r\n    } else {\r\n        start();\r\n    }\r\n}\r\n\r\nexport {\r\n    Guide,\r\n    start as Start\r\n}"
  },
  {
    "path": "src/module/keyboard.js",
    "content": "console.log( \"=== simpread keyboard ===\" )\n\nimport Mousetrap   from 'mousetrap';\n\nimport {browser}   from 'browser';\nimport * as msg    from 'message';\nimport * as conf   from 'config';\n\nlet   $root, current_mode = \"\"; // include: keyboard\nconst trigger = \"s r\",\n      key     = [ \".\", \",\" ],\n      global_keys = key.map( item => `${trigger} ${item}` );\n\n/***********************\n * Entry: Render\n ***********************/\n\nfunction render( $target ) {\n    $root = $target;\n}\n\n/***********************\n * Golbal\n ***********************/\n\nMousetrap.bind( trigger, () => {\n    if ( $( \"html\" ).find( \".simpread-read-root\" ).length == 0 ) return;\n    if ( current_mode == \"\" ) {\n        current_mode = \"keyboard\";\n        new Notify().Render( \"已进入快捷键操作模式，退出请使用 sr，帮助请按,\" );\n    } else {\n        current_mode = \"\";\n        new Notify().Render( \"已退出快捷键操作模式！\" );\n    }\n});\n\nMousetrap.bind( key, ( event, combo ) => {\n    current_mode == \"keyboard\" && task( combo );\n});\n\nfunction task( key ) {\n    switch ( key ) {\n        case \".\":\n            openLink();\n            break;\n        case \",\":\n            keyboradmap();\n            break;\n    }\n}\n\nfunction listenESC( callback ) {\n    const cb = $.Callbacks();\n    cb.add( callback );\n    Mousetrap.bind( \"esc\", ( event, combo ) => {\n        if ( helpExist() ) {\n            removeHelp();\n        } else if ( openLinkExist() ) {\n            removeOpenLink();\n        } else if ( current_mode == \"keyboard\" ) {\n            Mousetrap.trigger( trigger );\n        } else {\n            cb.fire( combo );\n        }\n    });\n}\n\nfunction listen( callback ) {\n    const cb = $.Callbacks();\n    cb.add( callback );\n    let keys = new Map();\n    Object.values( conf.keyboard ).forEach( item => {\n        Object.values( item ).forEach( obj => {\n            const kbd  = `${obj.kbd[0]} ${obj.kbd[1]}`,\n                  loop = ( item, idx ) => {\n                const type = obj.type.replace( \"_\", \"\" );\n                keys.delete( kbd );\n                keys.set( `${kbd} ${idx+1}`, obj.type + conf.shortcuts[type].name[idx] );\n            };\n            keys.set( kbd, obj.type );\n            switch ( obj.type ) {\n                case \"fontfamily_\":\n                    Array(5).fill(1).forEach( loop );\n                    break;\n                case \"fontsize_\":\n                case \"layout_\":\n                    Array(3).fill(1).forEach( loop );\n                    break;\n                case \"theme_next\":\n                    keys.delete( kbd );\n                    keys.set( `${kbd} right`, obj.type );\n                    break;\n                case \"theme_prev\":\n                    keys.delete( kbd );\n                    keys.set( `${kbd} left`, obj.type );\n                    break;\n            }\n        });\n    });\n    console.log( \"current shortcuts is\", keys )\n    Mousetrap.bind( [ ...keys.keys() ] , ( event, combo ) => {\n        current_mode == \"keyboard\" && cb.fire( keys.get( combo ) )\n    });\n}\n\nfunction bind( combo, callback ) {\n    Mousetrap.bind( combo, callback );\n}\n\n/***********************\n * Task: Open link\n ***********************/\nlet links     = [];\nconst charts  = [ \"A\",\"B\",\"C\",\"D\",\"E\",\"F\",\"G\",\"H\",\"I\",\"J\",\"K\",\"L\",\"M\",\"N\",\"O\",\"P\",\"Q\",\"R\",\"S\",\"T\",\"U\",\"V\",\"W\",\"X\",\"Y\",\"Z\" ],\n      getName = idx => {\n        switch ( true ) {\n            case idx < 26:\n                return charts[idx];\n            case idx >= 26 && idx <= 99:\n                return charts[idx[0]] + charts[idx[1]];\n            case idx > 99:\n                return charts[idx[0]] + charts[idx[1]] + charts[idx[2]];\n        }\n};\n\nfunction openLink() {\n    if ( openLinkExist() ) {\n        removeOpenLink();\n        return;\n    }\n    $root.find( \"a\" ).map( ( idx, item ) => {\n        const key = getName( idx + \"\" );\n        $(item).addClass( \"sr-kbd-a\" ).append( `<sr-kbd id=${key}>${key}</sr-kbd>` );\n        links.push( key.toLowerCase() );\n    });\n    links.length > 0 && $( \"html\" ).on( \"keypress\", openLinkEventHander );\n}\n\nfunction openLinkEventHander( event ) {\n    const combo  = event.key.toUpperCase(),\n          result = links.join(\"\").match( new RegExp( combo, \"ig\" ) );\n    if ( combo == \".\" ) return;\n    if ( result ) {\n        if ( result.length == 1 ) {\n            const $target = $root.find( `sr-kbd[id=${combo}]` );\n            if ( $target && $target.is( \"sr-kbd\" ) ) {\n                const url = $target.parent()[0].href;\n                url && url.startsWith( \"http\" ) && browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n                url && url.startsWith( \"http\" ) && removeOpenLink();\n            }\n        } else {\n            new Notify().Render( \"已缩小查询范围。\" );\n            links   = [];\n            let idx = 0;\n            $root.find( `sr-kbd` ).map( ( _, item ) => {\n                if ( item.id.includes( combo ) ) {\n                    const key      = getName( idx + \"\" );\n                    item.outerHTML = `<sr-kbd id=${key}>${key}</sr-kbd>`\n                    links.push( key.toLowerCase() );\n                    idx++;\n                } else {\n                    $(item).remove();\n                }\n            });\n        }\n    } else {\n        new Notify().Render( \"当前已进入链接模式，如需其它操作，请使用 ESC 退出此模式。\" );\n    }\n}\n\nfunction removeOpenLink() {\n    $( \"sr-kbd\" ).remove();\n    $root.find( \"a\" ).removeClass( \"sr-kbd-a\" );\n    $( \"html\" ).off( \"keypress\", openLinkEventHander );\n    links        = [];\n}\n\nfunction openLinkExist() {\n    return $root && $root.find( $( \"sr-kbd\" ) ).length > 0 ? true : false;\n}\n\n/***********************\n * Keyboard map\n ***********************/\n\nfunction keyboradmap() {\n    let html = \"\";\n    Object.keys( conf.keyboard ).forEach( item => {\n        let maptml = \"\";\n        Object.keys( conf.keyboard[item] ).forEach( ( map, idx ) => {\n            const obj = conf.keyboard[item][map];\n            maptml += `<kbd-map><kbd-name><kbd id=\"${obj.type}\">${obj.kbd}</kbd></kbd-name><kbd-desc>${obj.desc}</kbd-desc></kbd-map>`\n        });\n        html += `<kbd-maps-group><kbd-maps-title>${item}</kbd-maps-title>${maptml}</kbd-maps-group>`;\n    });\n    const tmpl    = `\n    <kbd-mapping>\n        <kbd-map-title>快捷键一览</kbd-map-title>\n        <kbd-maps>\n            <kbd-maps-group>\n                <kbd-maps-title>全局</kbd-maps-title>\n                <kbd-map><kbd-name><kbd>esc</kbd></kbd-name><kbd-desc>退出当前模式</kbd-desc></kbd-map>\n                <kbd-map><kbd-name><kbd>←</kbd></kbd-name><kbd-desc>前一页</kbd-desc></kbd-map>\n                <kbd-map><kbd-name><kbd>→</kbd></kbd-name><kbd-desc>后一页</kbd-desc></kbd-map>\n            </kbd-maps-group>\n            <kbd-maps-group>\n                <kbd-maps-title>阅读模式</kbd-maps-title>\n                <kbd-map><kbd-name><kbd>sr</kbd></kbd-name><kbd-desc>快捷键开启/关闭条件</kbd-desc></kbd-map>\n                <kbd-map><kbd-name><kbd>,</kbd></kbd-name><kbd-desc>打开/关闭快捷键一览</kbd-desc></kbd-map>\n                <kbd-map><kbd-name><kbd>.</kbd></kbd-name><kbd-desc>打开当前页面的任意链接</kbd-desc></kbd-map>\n            </kbd-maps-group>\n            ${ html }\n        </kbd-maps>\n    </kbd-mapping>\n    `;\n    helpExist() ? removeHelp() : $root.parent().append( `<kbd-bg>${tmpl}</kbd-bg>` );\n}\n\nfunction removeHelp() {\n    $root.parent().find( \"kbd-bg\" ).remove();\n}\n\nfunction helpExist() {\n    return $( \"kbd-bg\" ).length > 0 ? true : false;\n}\n/***********************\n * Export\n ***********************/\n\nexport {\n    render    as Render,\n    listenESC as ListenESC,\n    listen    as Listen,\n    bind      as Bind,\n}"
  },
  {
    "path": "src/module/labs.jsx",
    "content": "console.log( \"===== simpread option labs load =====\" )\n\nimport {browser} from 'browser';\nimport * as menu from 'menu';\nimport {storage} from 'storage';\n\nimport Switch    from 'switch';\nimport TextField from 'textfield';\nimport Button    from 'button';\n\nimport Auth      from 'authorize';\n\nexport default class LabsOpt extends React.Component {\n\n    static defaultProps = {\n        option : {},\n        read   : {},\n        focus  : {},\n    }\n\n    static propTypes = {\n        option : React.PropTypes.object,\n        read   : React.PropTypes.object,\n        focus  : React.PropTypes.object,\n        onChange : React.PropTypes.func,\n    }\n\n    onChange( value, model, state, child ) {\n        !child && ( this.props[model][state]=value );\n        child  && ( this.props[model][state][child]=value );\n        child  && menu.Refresh( this.props[model][state] );\n        if ( model == \"option\" && state == \"save_at\" ) {\n            this.props[model][state] = value ? \"dropbox\" : \"jianguo\";\n        }\n        this.props.onChange && this.props.onChange( true );\n        model == \"read\" && state == \"auto\" && this.exclusionState( value );\n        model == \"read\" && state == \"toc\"  && this.tocState( value );\n        model == \"read\" && state == \"cleanup\" && this.cleanupState( value );\n        model == \"option\" && state == \"preload\" && this.lazyloadState( value );\n    }\n\n    changeExclusion( event ) {\n        this.props.read.exclusion = event.target.value.split(\"\\n\");\n        this.props.onChange && this.props.onChange( false );\n    }\n\n    changeWhitelist( event ) {\n        this.props.read.whitelist = event.target.value.split(\"\\n\");\n        this.props.onChange && this.props.onChange( false );\n    }\n\n    changeLazyload( event ) {\n        this.props.option.lazyload = event.target.value.split(\"\\n\");\n        this.props.onChange && this.props.onChange( false );\n    }\n\n    tocState( value ) {\n        $( this.refs.toc ).velocity( value ? \"slideDown\" : \"slideUp\" );\n    }\n\n    cleanupState( value ) {\n        $( this.refs.cleanup ).velocity( value ? \"slideDown\" : \"slideUp\" );\n    }\n\n    lazyloadState( value ) {\n        $( this.refs.lazyload ).velocity( value ? \"slideDown\" : \"slideUp\" );\n    }\n\n    exclusionState( value ) {\n        $( this.refs.exclusion  ).velocity( value ? \"slideDown\" : \"slideUp\" );\n        $( this.refs.whitelist ).velocity( !value ? \"slideDown\" : \"slideUp\" );\n    }\n\n    blacklist( event ) {\n        this.props.option.blacklist = event.target.value.split(\"\\n\");\n        this.props.onChange && this.props.onChange( false );\n    }\n\n    componentDidMount() {\n        this.exclusionState( this.props.read.auto );\n        this.tocState( this.props.read.toc );\n        this.cleanupState( this.props.read.cleanup == undefined ? true : this.props.read.cleanup );\n        this.lazyloadState( this.props.option.preload );\n    }\n\n    onClick( state ) {\n        state == \"custom\"  && ( location.href = location.origin + \"/options/custom.html\" );\n        state == \"notice\"  && ( location.href = location.origin + \"/options/notice.html?is_update=\" + sessionStorage.getItem( \"is_update\" ));\n    }\n\n    render() {\n        return (\n            <div id=\"labs\" style={{ width: '100%' }}>\n                <div className=\"label\" data-head-level=\"h1\">全局</div>\n                <div className=\"lab\">\n                    <div className=\"version-tips\" data-hits=\"esc\">\n                    <Switch width=\"100%\" checked={ this.props.option.esc }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否启用 「ESC」 退出方式？\"\n                            desc=\"包括：聚焦模式与阅读模式\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"esc\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"br_exit\">\n                    <Switch width=\"100%\" checked={ this.props.option.br_exit }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"动作栏图标是否改为 「进入/退出 」模式？\"\n                            desc=\"包括：聚焦模式和阅读模式，默认（关闭）为「弹出设定对话框」\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"br_exit\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"blacklist\">\n                    <div style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>\n                        <div className=\"label\" style={{'margin-bottom':' -15px'}}>黑名单</div>\n                        <div className=\"sublabel\">加入其中后，不再启动简悦，有别于白名单和排除列表，黑名单则彻底不加载。</div>\n                        <TextField \n                            multi={ true } rows={8}\n                            placeholder=\"每行一个，支持： URL， hostname 等。\" \n                            value={ ( this.props.option.blacklist||[] ).join( \"\\n\" ) }\n                            onChange={ (e)=>this.blacklist(e) }\n                        />\n                    </div>\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"secret\">\n                    <Switch width=\"100%\" checked={ this.props.option.secret }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"同步时是否包含授权服务中的授权码？\"\n                            desc=\"包括：导出配置文件到本地，默认（关闭）为不同步；启用后，请妥善保管你的授权码\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"secret\") } />\n                    </div>\n                    <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"save_at\">\n                    <Switch width=\"100%\" checked={ this.props.option.save_at == \"dropbox\" ? true : false }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"保存配置到 Dropbox ？\"\n                            desc=\"注意：默认（已勾选状态）保存到 Dropbox ；选否后（非勾选状态）保存到 【坚果云】。\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"save_at\") } />\n                    </div>\n                    <Switch width=\"100%\" checked={ this.props.option.uninstall ? true : false }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"删除后是否给我反馈？\"\n                            desc=\"建议开启此选项，简悦不会知道你是谁，但你可以帮助简悦变得更好。\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"uninstall\") } />\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"menu\">\n                <div className=\"label\" data-head-level=\"h1\">右键菜单</div>\n                <div style={{ 'padding-top': '10px' }} className=\"lab\">\n                    <Switch width=\"100%\" checked={ this.props.option.menu.focus }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「聚焦模式」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"focus\" ) } />\n                    <Switch width=\"100%\" checked={ this.props.option.menu.read }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「阅读模式」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"read\" ) } />\n                    <Switch width=\"100%\" checked={ this.props.option.menu.link }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「使用阅读模式打开此链接」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"link\" ) } />\n                    <div className=\"dividers\"></div>\n                    <Switch width=\"100%\" checked={ this.props.option.menu.list }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「打开稍后读」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"list\" ) } />\n                    <Switch width=\"100%\" checked={ this.props.option.menu.unrdist }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「加入到稍后读」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"unrdist\" ) } />\n                    <div className=\"dividers\"></div>\n                    <Switch width=\"100%\" checked={ this.props.option.menu.whitelist }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「加入白名单」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"whitelist\" ) } />\n                    <Switch width=\"100%\" checked={ this.props.option.menu.exclusion }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「加入到排除列表」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"exclusion\" ) } />\n                    <Switch width=\"100%\" checked={ this.props.option.menu.blacklist }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「加入到黑名单」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"blacklist\" ) } />\n                    <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"lazyload\">\n                    <Switch width=\"100%\" checked={ this.props.option.menu.lazyload }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示「加入到延迟加载」？\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"menu\", \"lazyload\" ) } />\n                    </div>\n                    <div className=\"version-tips\" data-version=\"1.1.4\" data-hits=\"urlscheme\">\n                    <Switch width=\"100%\" checked={ this.props.option.urlscheme }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"默认弹出编辑框，取消后意味着直接保存\"\n                            desc=\"包括：黑名单 · 白名单 · 排除列表 · 延迟加载均可使用\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"urlscheme\" ) } />\n                    </div>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"focusconfig\">\n                <div className=\"label\" data-head-level=\"h1\">聚焦模式</div>\n                <div style={{ 'padding-top': '10px' }} className=\"lab\">\n                    <Switch width=\"100%\" checked={ this.props.focus.mask }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否启用点击空白（遮罩）退出功能？\"\n                            onChange={ (s)=>this.onChange(s, \"focus\", \"mask\") } />\n                    <Switch width=\"100%\" checked={ this.props.focus.controlbar }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否一直显示右下角的控制栏？\"\n                            desc=\"关闭意味着「鼠标移上时才显示」\"\n                            onChange={ (s)=>this.onChange(s, \"focus\", \"controlbar\") } />\n                    <Switch width=\"100%\" checked={ this.props.focus.highlight }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否启用手动聚焦模式？\"\n                            desc=\"关闭意味着使用「自动聚焦模式」\"\n                            onChange={ (s)=>this.onChange(s, \"focus\", \"highlight\") } />\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"readconfig\">\n                <div className=\"label\" data-head-level=\"h1\">阅读模式</div>\n                <div style={{ 'padding-top': '10px' }} className=\"lab\">\n                    <div className=\"version-tips\" data-hits=\"progress\">\n                    <Switch width=\"100%\" checked={ this.props.read.progress }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否显示阅读进度？\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"progress\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"readcontrolbar\">\n                    <Switch width=\"100%\" checked={ this.props.read.controlbar }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否一直显示右下角的控制栏？\"\n                            desc=\"关闭意味着「鼠标移上时才显示」\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"controlbar\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"fap\">\n                    <Switch width=\"100%\" checked={ this.props.read.fap }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                            label=\"是否启用高级控制栏面板？\"\n                            desc=\"关闭意味着「使用浮动控制栏」\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"fap\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"highlight\">\n                    <Switch width=\"100%\" checked={ this.props.read.highlight }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                            label=\"手动框选时是否启动二次确认模式？\"\n                            desc=\"二次确认模式能精准的定位需要阅读模式的内容。\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"highlight\") } />\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"toc\">\n                    <Switch width=\"100%\" checked={ this.props.read.toc }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                            label=\"是否自动生成大纲（目录）？\"\n                            desc=\"只整理 h1, h2, h3, h4 的内容为大纲\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"toc\") } />\n                    <div ref=\"toc\">\n                        <Switch width=\"100%\" checked={ this.props.read.toc_hide }\n                                thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                                label=\"大纲（目录）是否开启「鼠标移动到左上角」自动显示？\"\n                                desc=\"关闭意味着「一直显示」\"\n                                onChange={ (s)=>this.onChange(s, \"read\", \"toc_hide\") } />\n                    </div>\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"readauto\">\n                    <Switch width=\"100%\" checked={ this.props.read.auto }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                            desc=\"注意：此功能只包含已适配的站点，智能识别出正文的站点无法使用此功能，但仍可通过手动方式进入阅读模式。\"\n                            label=\"如果当前页面为适配站点，是否自动进入阅读模式？\"\n                            onChange={ (s)=>this.onChange(s, \"read\", \"auto\") } />\n\n                    </div>\n                    <div className=\"version-tips\" data-hits=\"exclusion\">\n                    <div ref=\"exclusion\" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>\n                        <div className=\"label\" style={{'margin-bottom':' -15px'}}>排除列表</div>\n                        <div className=\"sublabel\">加入其中后将不会自动进入阅读模式，仅当启用「自动进入阅读模式」有效。</div>\n                        <TextField \n                            multi={ true } rows={8}\n                            placeholder=\"每行一个，支持： URL, minimatch 等。\" \n                            value={ ( this.props.read.exclusion||[] ).join( \"\\n\" ) }\n                            onChange={ (e)=>this.changeExclusion(e) }\n                        />\n                    </div>\n\n                    <div ref=\"whitelist\" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>\n                        <div className=\"label\" style={{'margin-bottom':' -15px'}}>白名单</div>\n                        <div className=\"sublabel\">加入其中后将自动进入阅读模式，仅当禁用「自动进入阅读模式」有效。</div>\n                        <TextField \n                            multi={ true } rows={8}\n                            placeholder=\"每行一个，支持： URL, minimatch 等。\" \n                            value={ ( this.props.read.whitelist||[] ).join( \"\\n\" ) }\n                            onChange={ (e)=>this.changeWhitelist(e) }\n                        />\n                    </div>\n                    </div>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"pured\">\n                <div className=\"label\" data-head-level=\"h1\" data-head-title=\"词法分析引擎\">词法分析引擎 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\" style={{ color:' #FF5252', borderBottom: '2px dotted', fontSize: '10px', fontWeight: 'bold', cursor: 'pointer' }}>测试版</a></div>\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\">\n                    <Switch width=\"100%\" checked={ this.props.read.cleanup == undefined ? true : this.props.read.cleanup }\n                                thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                                label=\"是否启用增强解析模式？\"\n                                desc=\"增强解析模式会对版面重新设计，包括：去除多余空格、优化版面结构等，此功能为测试版，遇到解析失败时，请关闭此功能。\"\n                                onChange={ (s)=>this.onChange(s, \"read\", \"cleanup\") } />\n                    <div className=\"version-tips\" data-hits=\"puredpure\">\n                    <div ref=\"cleanup\" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>\n                        <Switch width=\"100%\" checked={ this.props.read.pure }\n                                thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                                label=\"纯粹模式\"\n                                desc=\"比【增强解析模式】还要彻底优化版本，包括：字形、颜色、字号、代码段等，专治页面及不规范，如：微信订阅号，CSDN 等。\"\n                                onChange={ (s)=>this.onChange(s, \"read\", \"pure\") } />\n                        <div className=\"sublabel\">如果经常阅读代码的话，请安装 <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/klGUASLasg\">代码段增强</a> 包括：高亮，去重，支持 CSDN 等特殊情况的代码段</div>\n                    </div>\n                    </div>\n                    <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"preload\">\n                    <Switch width=\"100%\" checked={ this.props.option.preload }\n                            thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                            label=\"是否启用预加载机制？\"\n                            desc=\"1. 简悦的词法分析引擎采用了预加载机制，如果你觉得影响性能的话，请关闭此功能。\"\n                            onChange={ (s)=>this.onChange(s, \"option\", \"preload\") } />\n                    <div className=\"sublabel\">2. 关闭此功能后，只有进入阅读模式时才会对页面进行解析，所以经常使用简悦的用户请勿关闭它。</div>\n                    <div className=\"sublabel\">3. 此功能的优先级比「自动进入阅读模式」高；当关闭此功能时，自动进入阅读模式将不会工作。</div>\n                    </div>\n                    <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"lazyload\">\n                    <div ref=\"lazyload\" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }}>\n                        <div className=\"label\" style={{'margin-bottom':' -15px'}}>延迟加载列表</div>\n                        <div className=\"sublabel\">加入其中后的网址将不会启用预加载功能。</div>\n                        <div className=\"sublabel\">此功能适合「经常使用简悦但又性能不够」的用户、需要动态加载及支持 Mathjax 解析的页面等。</div>\n                        <TextField \n                            multi={ true } rows={8}\n                            placeholder=\"每行一个，支持： URL, minimatch 等。\" \n                            value={ ( this.props.option.lazyload||[] ).join( \"\\n\" ) }\n                            onChange={ (e)=>this.changeLazyload(e) }\n                        />\n                    </div>\n                    </div>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"auth\">\n                <div className=\"label\" data-head-level=\"h1\">授权管理</div>\n                <div style={{ 'padding-top': '10px' }} className=\"lab\">\n                    <Auth/>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"custom\">\n                <div className=\"label\" data-head-level=\"h1\">自定义样式</div>\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\" onClick={ ()=>this.onClick('custom') }>\n                    <div className=\"more\" style={{ 'cursor': 'pointer' }}>\n                        <div>增强「中文阅读体验」设置</div>\n                        <span className=\"desc\">包括：标题、描述、正文的字间距、行间距、首行缩进等及自定义 CSS。</span>\n                        <span className=\"arrow\"></span>\n                    </div>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-version=\"1.1.3\" data-hits=\"notice\">\n                <div className=\"label\" data-head-level=\"h1\">消息中心</div>\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\">\n                    <Switch width=\"100%\" checked={ this.props.option.notice }\n                        thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\"\n                        label=\"当没有未读信息时，是否显示右下角提示框？\"\n                        desc=\"关闭后，当没有新的未读信息时，隐藏未读提示；当有新的消息时，仍会在右下角显示未读提示。\"\n                        onChange={ (s)=>this.onChange(s, \"option\", \"notice\") } />\n                </div>\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\" onClick={ ()=>this.onClick('notice') }>\n                    <div className=\"more\" style={{ 'cursor': 'pointer' }}>\n                        <div>查看全部消息</div>\n                        <span className=\"desc\">简悦会不定期发送一些消息，包括：新的插件上线、新的适配站点上线、修复 Bug 等</span>\n                        <span className=\"arrow\"></span>\n                    </div>\n                </div>\n                </div>\n\n            </div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/notice.jsx",
    "content": "console.log( \"=== simpread notice load ===\" )\n\nimport {storage}     from 'storage';\nimport th            from 'theme';\nimport * as ss       from 'stylesheet';\nimport * as puplugin from 'puplugin';\nimport * as watch    from 'watch';\nimport Button        from 'button';\n\n/**\n * Write storage\n * \n * @param {func} callback \n */\nconst write = ( callback ) => {\n    storage.Write( () => {\n        console.log( \"current notice is \", storage.notice )\n        watch.SendMessage( \"option\", true );\n        callback && callback();\n    });\n}\n\nexport default class Notice extends React.Component {\n\n    static defaultProps = {\n        is_update: false,\n        step: 20,\n    };\n\n    static propsType = {\n        step: React.PropTypes.number,\n        is_update: React.PropTypes.bool,\n    };\n\n    state = {\n        items: [],\n        detail: \"\",\n        failed: false,\n\n        total: 0,\n        page : 1,\n    }\n\n    onClick( event, id ) {\n        const markdown  = puplugin.Plugin( \"markdown\" ),\n              converter = new markdown.default.Converter(),\n              obj       = this.state.items.filter( item => item.id == id ),\n              item      = obj[0],\n              html      = converter.makeHtml( item.content ),\n              tmpl      =  `<div class=\"preview\">\n                                <div class=\"title\">${item.title}</div>\n                                <div class=\"desc\">\n                                    <span style=\"background-color: ${item.category.color}\" class=\"category\">${item.category.name}</span>\n                                    <span class=\"date\">发布于 ${item.date}</span>\n                                </div>\n                                <sr-rd-content>${html}</sr-rd-content>\n                            </div>`;\n        $( \"notice .detail\" ).addClass( \"simpread-theme-root\" ).html( tmpl );\n    }\n\n    onReadallClick() {\n        new Notify().Render( \"snackbar\", \"是否将全部消息标记为已读？\", \"确认\", () => {\n            storage.notice.read = [];\n            this.state.items.forEach( ( item, idx ) => { storage.notice.read.push( idx + 1 ) });\n            write( ()=> {\n                new Notify().Render( \"已全部设置为已读，3 秒后自动刷新本页...\" );\n                setTimeout( ()=>location.reload(), 3000 )\n            });\n        });\n    }\n\n    onLoadmoreClick() {\n        this.setState({ page: this.state.page + 1 });\n    }\n\n    componentWillMount() {\n        if ( this.props.is_update ) {\n            $.ajax( storage.notice_service.message + \"?\" + Math.round(+new Date()) )\n            .done( result => {\n                storage.Notice( undefined, result.notice );\n                storage.notice.latest = result.notice.length;\n                write();\n                this.setState({ items: result.notice, total: Math.ceil( result.notice.length / this.props.step ) });\n            })\n            .fail( ( jqXHR, textStatus, errorThrown ) => {\n                this.setState({ failed: true });\n            });\n        } else {\n            storage.Notice( result => {\n                this.setState({ items: result.notice, total: Math.ceil( result.notice.length / this.props.step ) });\n            });\n        }\n    }\n\n    componentDidMount() {\n        th.Change( \"pixyii\" );\n    }\n\n    render() {\n        let   dom;\n        const items = this.state.items.slice( 0, this.state.page * this.props.step );\n        if ( this.state.failed ) {\n            dom =\n                <notice>\n                    <div className=\"failed\">\n                        <div dangerouslySetInnerHTML={{__html: `<svg viewBox=\"0 0 1024 1024\" version=\"1.1\" p-id=\"2377\" width=\"120\" height=\"120\"><defs><style type=\"text/css\"></style></defs><path d=\"M512.041444 243.968477c-148.07343 0-268.049942 120.022561-268.049942 268.051989 0 147.988496 119.976512 268.053012 268.049942 268.053012 147.987472 0 268.051989-120.064516 268.051989-268.053012C780.093433 363.991038 660.028916 243.968477 512.041444 243.968477zM556.716946 690.765453 467.367989 690.765453l0-89.351004 89.348957 0L556.716946 690.765453zM556.716946 556.652989 467.367989 556.652989l0-223.37751 89.348957 0L556.716946 556.652989zM780.093433 65.22349 243.991502 65.22349c-98.774631 0-178.700985 80.101339-178.700985 178.744987l0 536.105001c0 98.730629 79.925331 178.700985 178.700985 178.700985l536.102954 0c98.600669 0 178.615027-79.969333 178.615027-178.700985L958.709483 243.968477C958.70846 145.32483 878.695125 65.22349 780.093433 65.22349zM869.44546 735.397976c0 73.994248-60.033281 134.069485-134.027529 134.069485L288.667004 869.467461c-73.994248 0-134.115534-60.075237-134.115534-134.069485L154.55147 288.599977c0-73.994248 60.121286-133.939525 134.115534-133.939525l446.750927 0c73.994248 0 134.027529 59.945277 134.027529 133.939525L869.44546 735.397976z\" p-id=\"2378\" fill=\"#16666f\"></path></svg>` }}></div>\n                        <span>消息中心暂时无法访问，请稍后再试！</span>\n                    </div>\n                </notice>;\n        } else if ( this.state.items.length == 0 ) {\n            dom =\n                <notice>\n                    <div className=\"loading\" dangerouslySetInnerHTML={{__html: `<svg width=\"105\" height=\"105\" viewBox=\"0 0 105 105\" fill=\"#16666f\"> <circle cx=\"12.5\" cy=\"12.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"0s\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"12.5\" cy=\"52.5\" r=\"12.5\" fill-opacity=\".5\"> <animate attributeName=\"fill-opacity\" begin=\"100ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"52.5\" cy=\"12.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"300ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"52.5\" cy=\"52.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"600ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"92.5\" cy=\"12.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"800ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"92.5\" cy=\"52.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"400ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"12.5\" cy=\"92.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"700ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"52.5\" cy=\"92.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"500ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> <circle cx=\"92.5\" cy=\"92.5\" r=\"12.5\"> <animate attributeName=\"fill-opacity\" begin=\"200ms\" dur=\"1s\" values=\"1;.2;1\" calcMode=\"linear\" repeatCount=\"indefinite\"/> </circle> </svg>` }}></div>\n                </notice>;\n        } else if ( this.state.items.length > 0 ) {\n            dom =\n                <notice>\n                    <div>\n                        <div className=\"list controlbar\">\n                            <Button type=\"raised\" text=\"全部标记为已读\"\n                                style={{ \"margin\": \"0\" }} width=\"100%\"\n                                color=\"#fff\" backgroundColor=\"#FF5252\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>this.onReadallClick() } />\n                        </div>\n                        <List list={ items } onClick={ ( e, id )=> this.onClick( e, id ) } />\n                        { this.state.page < this.state.total &&\n                            <div className=\"list controlbar\">\n                                <Button type=\"raised\" text=\"加载更多\"\n                                    style={{ \"margin\": \"0\" }} width=\"100%\"\n                                    color=\"#fff\" backgroundColor=\"#16666f\"\n                                    waves=\"md-waves-effect md-waves-button\"\n                                    onClick={ ()=>this.onLoadmoreClick() } />\n                            </div>\n                        }\n                    </div>\n                    <div className=\"detail\">\n                        <div className=\"empty\">\n                            <span className=\"icon\"></span>\n                            <span>当前未查看任何内容</span>\n                        </div>\n                    </div>\n                </notice>;\n        }\n        return (\n            <div>{ dom }</div>\n        )\n    }\n}\n\nclass List extends React.Component {\n\n    static defaultProps = {\n        list: [],\n        onClick: undefined,\n    };\n\n    static propTypes = {\n        list   : React.PropTypes.array,\n        onClick: React.PropTypes.func,\n    }\n\n    onClick( event, id ) {\n        $( \"list\" ).removeClass( \"selected\" );\n        $( `list[id=\"${id}\"]` ).addClass( \"selected\" );\n        this.props.onClick( event, id );\n    }\n\n    onActive( event, id ) {\n        $( `list[id=\"${id}\"]` ).addClass( \"active\" );\n        storage.notice.read.push( id );\n        write( () => new Notify().Render( \"已设置为已读。\" ) );\n    }\n\n    render() {\n        const list = this.props.list.map( item => {\n            const active = storage.notice.read.findIndex( value=>value==item.id ) != -1 ? \" active\" : \"\";\n            return (\n                <list id={ item.id } className={ \"md-waves-effect\" + active } onClick={ e => this.onClick( e, item.id ) }>\n                    <div className=\"title\">{ item.title }</div>\n                    <span>\n                        <span style={{ backgroundColor: item.category.color }} className=\"category\">{ item.category.name }</span>\n                        <span className=\"date\">{ item.date }</span>\n                    </span>\n                    { active == \"\" &&\n                    <div className=\"meta\">\n                        <Button type=\"raised\" text=\"\" shape=\"circle\"\n                            icon={ ss.IconPath( \"read_icon\" ) }\n                            tooltip={{ text: \"已读\" }}\n                            color=\"#fff\" backgroundColor=\"transparent\"\n                            waves=\"md-waves-effect md-waves-circle\" hoverColor=\"transparent\"\n                            onClick={ e => this.onActive( e, item.id ) }\n                        />\n                    </div>\n                    }\n                </list>\n            )\n        });\n        return (\n            <div className=\"list\">\n                { list }\n            </div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/pluginbar.jsx",
    "content": "console.log( \"=== simpread plugin bar load ===\" )\n\nimport {storage} from 'storage';\n\nimport Button    from 'button';\n\nexport default class Pluginbar extends React.Component {\n\n    state = {\n        category: []\n    };\n\n    category = {};\n\n    getCategory() {\n        Object.values( storage.plugins ).forEach( ( item, idx ) => {\n            if ( this.category[item.category] ) {\n                this.category[item.category].push( item );\n            } else {\n                this.category[item.category] = [];\n                this.category[item.category].push( item );\n            }\n        });\n        this.setState({ category: Object.keys( this.category ) });\n    }\n\n    enable( id ) {\n        console.log( id, storage.plugins[id].enable )\n        const plugin  = storage.plugins[id];\n        plugin.enable = !plugin.enable;\n        storage.Plugins( () => {\n            new Notify().Render( \"当前插件已\" + ( plugin.enable ? \"启用\" : \"禁用\" ) + \"，请重新进入阅读模式以便生效。\" );\n            // hack code\n            const bgColor = ( plugin.enable == undefined || plugin.enable == true ) ? plugin.icon.bgColor : \"#c3c6c7\";\n            $( this.refs[id].refs.mask ).parent().css( \"background-color\", bgColor );\n        }, storage.plugins );\n    }\n\n    componentWillMount() {\n        storage.Plugins( () => {\n            this.category = {};\n            this.getCategory();\n        });\n    }\n\n    render() {\n        const child = this.state.category.map( item => {\n\n            const actions = this.category[item].map( plugin => {\n                const bgColor = ( plugin.enable == undefined || plugin.enable == true ) ? plugin.icon.bgColor : \"#c3c6c7\";\n                plugin.enable == undefined && ( plugin.enable = true );\n                return (\n                    <Button ref={plugin.id}\n                            shape=\"circle\" type=\"flat\"\n                            color={ plugin.icon.color } backgroundColor={ bgColor }\n                            tooltip={{ text: plugin.name }}\n                            fontIcon={ plugin.icon.type }\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.enable(plugin.id) } />\n                )\n            });\n\n            return (\n                <sr-opt-gp>\n                    <sr-opt-label>{ item }</sr-opt-label>\n                    <actions style={{ display: \"flex\", margin: \"10px 0\" }}>{ actions }</actions>\n                </sr-opt-gp>\n            )\n        });\n\n        if ( child.length == 0 ) {\n            child.push( <plugin-bar-empty style={{'font-size':'17px!important','color': 'rgba(51, 51, 51, 0.87)!important'}} dangerouslySetInnerHTML={{__html: `暂无任何可用插件<br>请通过「选项页 → 插件管理」添加。` }}></plugin-bar-empty> );\n        }\n\n        return (\n            <plugin-bar>{child}</plugin-bar>\n        )\n    }\n}"
  },
  {
    "path": "src/module/plugins.jsx",
    "content": "console.log( \"===== simpread option plugins load =====\" )\n\nimport {storage} from 'storage';\nimport * as run  from 'runtime';\nimport * as ss   from 'stylesheet';\nimport {browser} from 'browser';\nimport * as msg  from 'message';\nimport * as watch from 'watch';\n\nimport Button    from 'button';\n\nclass Card extends React.Component {\n\n    static defaultProps = {\n        plugin         : {},\n    };\n\n    static propTypes = {\n        plugin         : React.PropTypes.object,\n    };\n\n    update() {\n        run.Install( this.props.plugin.id, undefined, result => {\n            if ( result ) {\n                if ( this.props.plugin.version != result.version ) {\n                    storage.plugins[this.props.plugin.id] = result;\n                    this.props.onChange( \"update\" );\n                } else {\n                    new Notify().Render( \"当前插件为最新版，无需更新。\" );\n                }\n            } else new Notify().Render( 2, \"更新失败，请稍后再试。\" );\n        });\n    }\n\n    delete() {\n        new Notify().Render({ mode:\"snackbar\", content: \"是否删除当前插件？\", action: \"确认\", cancel: \"取消\", callback: type => {\n            if ( type == \"cancel\" ) return;\n            delete storage.plugins[ this.props.plugin.id ];\n            storage.option.plugins = Object.keys( storage.plugins );\n            this.props.onChange( \"delete\" );\n        }});\n    }\n\n    enable() {\n        this.props.plugin.enable = this.props.plugin.enable == undefined || this.props.plugin.enable ? false : true;\n        this.props.onChange( \"enable\" );\n    }\n\n    addmore() {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://simpread.ksria.cn/plugins/details/\" + this.props.plugin.id }));\n    }\n\n    render() {\n        const enable = this.props.plugin.enable == undefined || this.props.plugin.enable ? true : false;\n        return (\n            <card>\n                <card-header style={{ backgroundColor: this.props.plugin.icon.bgColor }}>\n                    <icon style={{ color: this.props.plugin.icon.color }} dangerouslySetInnerHTML={{__html: this.props.plugin.icon.type }} ></icon>\n                </card-header>\n                <card-content>\n                    <desc>by { this.props.plugin.user.name }</desc>\n                    <note>{ this.props.plugin.name }</note>\n                </card-content>\n                <card-footer>\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: enable == true ? \"禁用当前插件\" : \"启用当前插件\" }}\n                            fontIcon={`<i class=\"fas fa-eye${ enable ? \"\" : \"-slash\" }\"></i>`}\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.enable() } />\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"删除当前插件\" }}\n                            fontIcon='<i class=\"fas fa-trash-alt\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.delete() } />\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"更新当前插件到最新版本\" }}\n                            fontIcon='<i class=\"fas fa-cloud\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.update() } />\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"查看当前插件的详细信息\" }}\n                            fontIcon='<i class=\"fas fa-ellipsis-h\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.addmore() } />\n                </card-footer>\n            </card>\n        )\n    }\n}\n\nclass Cards extends React.Component {\n\n    static defaultProps = {\n        plugins         : [],\n    };\n\n    static propTypes = {\n        plugins         : React.PropTypes.array,\n        onChange        : React.PropTypes.func,\n    };\n\n    render() {\n        const card = this.props.plugins.length > 0 ? this.props.plugins.map( ( item, idx ) => {\n            return (\n                <Card plugin={ item } onChange={t=>this.props.onChange(t)} />\n            )\n        }) : <card-empty><a href=\"https://simpread.ksria.cn/plugins\" target=\"_blank\">没有任何插件，点击打开「插件中心」添加。</a></card-empty>;\n        return (\n            <cards>{ card }</cards>\n        )\n    }\n}\n\nexport default class PluginsOpt extends React.Component {\n\n    state = {\n        plugins: []\n    };\n\n    writecb() {\n        watch.SendMessage( \"option\", true );\n    }\n\n    install() {\n        const id = decodeURIComponent( location.hash ).replace( \"#plugins?install=\", \"\" );\n        run.Install( id, undefined, result => {\n            if ( result ) {\n                const add = () => {\n                    storage.plugins[result.id] = result;\n                    storage.Plugins( result => {\n                        new Notify().Render( \"当前插件已安装成功，2 秒后自动刷新当前页面。\" );\n                        setTimeout( ()=> {\n                            location.href = location.origin + location.pathname + \"#plugins\";\n                            location.reload();\n                        }, 2000 );\n                    }, storage.plugins );\n                };\n                if ( !storage.option.plugins.includes( result.id ) ) {\n                    add();\n                    storage.option.plugins.push( result.id );\n                    storage.Write( this.writecb );\n                } else if ( storage.plugins[result.id].version != result.version ) {\n                    new Notify().Render({ content: \"本地版本与安装版本不一致，是否安装新版本？\", action: \"安装\", cancel: \"取消\", callback: type => {\n                        type == \"action\" && add();\n                    }});\n                } else {\n                    new Notify().Render({ content: \"本地版本与安装版本一致，是否重新安装？\", action: \"安装\", cancel: \"取消\", callback: type => {\n                        type == \"action\" && add();\n                    }});\n                }\n            } else new Notify().Render( 2, id + \" 获取失败，请稍后再试。\" );\n        });\n    }\n\n    clear() {\n        new Notify().Render({ mode: \"snackbar\", content: \"是否清除本地全部插件？\", action: \"是的\", cancel: \"取消\", callback: type => {\n            if ( type == \"action\" ) {\n                storage.option.plugins = [];\n                storage.Write( this.writecb );\n                storage.Plugins( () => {\n                    new Notify().Render( \"snackbar\", \"清除成功，此页面需刷新后才能生效！\", \"刷新 \", ()=>{\n                        location.href = location.origin + location.pathname + \"#plugins\";\n                        location.reload();\n                    });\n                }, {} );\n            }\n        }});\n    }\n\n    addmore() {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://simpread.ksria.cn/plugins\" }));\n    }\n\n    update() {\n        let is_update = false, count = 0;\n        storage.option.plugins.forEach( id => {\n            run.Install( id, undefined, result => {\n                if ( !result ) {\n                    new Notify().Render( 2, id + \" 获取失败，请稍后再试。\" );\n                }\n                count++;\n                if ( storage.plugins[id].version != result.version ) {\n                    storage.plugins[result.id] = result;\n                    is_update = true;\n                }\n                count == storage.option.plugins.length && complete();\n            });\n        });\n        const complete = () => {\n            if ( is_update ) {\n                storage.Plugins( result => {\n                    new Notify().Render( \"本地插件已全部更新完毕。\" );\n                    this.setState({ plugins: Object.values( storage.plugins ) });\n                }, storage.plugins );\n            } else {\n                new Notify().Render( \"无任何可用更新。\" );\n            }\n        }\n    }\n\n    import() {\n        let newPlugins = {};\n        if ( storage.option.plugins.length == 0 ) {\n            new Notify().Render( \"当前配置文件没有任何插件。\" );\n            return;\n        }\n        new Notify().Render({ mode:\"snackbar\", content: \"导入意味着从配置文件覆盖当前的插件！\", action: \"确认\", cancel: \"取消\", callback: type => {\n            if ( type == \"cancel\" ) return;\n            let count = 0;\n            storage.option.plugins.forEach( id => {\n                run.Install( id, undefined, result => {\n                    if ( !result ) {\n                        new Notify().Render( 2, id + \" 获取失败，请稍后再试。\" );\n                    } else newPlugins[result.id] = result;\n                    count++;\n                    count == storage.option.plugins.length && complete();\n                });\n            });\n        }});\n        const complete = () => {\n            storage.Plugins( result => {\n                storage.Write( () => {\n                    new Notify().Render( \"已从配置文件导入完毕。\" );\n                    this.setState({ plugins: Object.values( storage.plugins ) });\n                });\n            }, newPlugins );\n        }\n    }\n\n    onChange( type ) {\n        storage.Write( this.writecb );\n        storage.Plugins( () => {\n            type == \"update\" && new Notify().Render( \"当前插件已更新成功。\" );\n            type == \"delete\" && new Notify().Render( \"当前插件已删除成功。\" );\n            type == \"enable\" && new Notify().Render( \"当前插件已更改成功。\" );\n            this.setState({ plugins: Object.values( storage.plugins ) });\n        }, storage.plugins );\n    }\n\n    componentWillMount() {\n        $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/solid.min.css\" />' );\n        $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/brands.min.css\" />' );\n        $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/fontawesome.min.css\" />' );\n        storage.Plugins( () => {\n            decodeURIComponent( location.href ).includes( \"#plugins?install=\" ) && this.install();\n            this.setState({ plugins: Object.values( storage.plugins ) });\n        });\n    }\n\n    render() {\n        return (\n            <div id=\"labs\" style={{ width: '100%', overflow: 'hidden' }}>\n                <div className=\"label\">管理</div>\n                <div className=\"lab\">\n                    <div style={{ display: 'inline-flex', width: '100%' }}>\n                        <div className=\"version-tips\" data-hits=\"pluginconfig\" style={{ display: 'inline-flex', width: '50%' }}>\n                        <Button type=\"raised\" text=\"从配置文件导入插件\" width=\"100%\"\n                            tooltip={{ text: \"注意：本操作并不能更新本地插件。\" }}\n                            icon={ ss.IconPath( \"import_icon\" ) }\n                            color=\"#fff\" backgroundColor=\"#00BCD4\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.import() } />\n                        </div>\n                        <div className=\"version-tips\" data-hits=\"pluginsite\" style={{ display: 'inline-flex', width: '50%' }}>\n                        <Button type=\"raised\" text=\"查看并获取更多的插件\" width=\"100%\"\n                            fontIcon={`<i class=\"fas fa-external-link-square-alt\"></i>`}\n                            color=\"#fff\" backgroundColor=\"#00BCD4\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.addmore() } />\n                        </div>\n                    </div>\n                    <div style={{ display: 'inline-flex', width: '100%' }}>\n                        <div className=\"version-tips\" data-hits=\"pluginupdate\" style={{ display: 'inline-flex', width: '50%' }}>\n                        <Button type=\"raised\" text=\"更新本地全部插件\" width=\"100%\"\n                                icon={ ss.IconPath( \"update_icon\" ) }\n                                color=\"#fff\" backgroundColor=\"#FF5252\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>this.update() } />\n                        </div>\n                        <div className=\"version-tips\" data-hits=\"pluginclear\" style={{ display: 'inline-flex', width: '50%' }}>\n                        <Button type=\"raised\" text=\"清除本地全部插件\" width=\"100%\"\n                                icon={ ss.IconPath( \"clear_icon\" ) }\n                                color=\"#fff\" backgroundColor=\"#757575\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>this.clear() } />\n                        </div>\n                    </div>\n                </div>\n\n                <div className=\"label\">\n                    <span>{ this.state.plugins.length == 0 ? \"\" : \"已安装 \" + this.state.plugins.length + \" 个插件 \" }</span>\n                    { this.state.plugins.length > 5 && <a target=\"_blank\" style={{ color:' #FF5252', borderBottom: '2px dotted', fontSize: '10px', fontWeight: 'bold' }}>过多的插件会使进入阅读模式变慢，建议不要超过 6 个</a> }\n                </div>\n                <div className=\"version-tips\" data-hits=\"pluginmange\">\n                <div style={{ 'padding-top': '10px' }} className=\"lab\">\n                    <Cards plugins={ this.state.plugins } onChange={ t=>this.onChange(t) } />\n                </div>\n                </div>\n\n            </div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/read.jsx",
    "content": "console.log( \"===== simpread option read mode load =====\" )\n\nimport th             from 'theme';\nimport * as ss        from 'stylesheet';\nimport * as conf      from 'config';\n\nimport TextField      from 'textfield';\nimport SelectField    from 'selectfield';\nimport Slider         from 'slider';\nimport AC             from 'ac';\n\nimport ThemeSel       from 'themesel';\nimport Shortcuts      from 'shortcuts';\n\nconst getName = ( value, items ) => {\n    for ( const item of items ) {\n        if ( value == \"\" ) return item.name;\n        else if ( item.value == value ) return item.name;\n    }\n};\n\nexport default class ReadOpt extends React.Component {\n\n    parse( value ) {\n        const news = parseInt( value );\n        return isNaN(news) ? 0 : news;\n    }\n\n    changeBgColor( theme ) {\n        this.props.option.theme = theme;\n        th.Change( this.props.option.theme );\n        this.props.onChange && this.props.onChange( `theme_${theme}` );\n        console.log( \"this.props.option.theme = \", this.props.option.theme )\n    }\n\n    changeShortcuts( shortcuts ) {\n        this.props.option.shortcuts = shortcuts;\n        this.props.onChange && this.props.onChange( `shortcuts_${shortcuts}` );\n        console.log( \"this.props.option.shortcuts = \", this.props.option.shortcuts )\n    }\n\n    changeFontfamily( name, value ) {\n        value.trim() == \"\" && ( value = \"default\" );\n        conf.fontfamily.forEach( obj => {\n            return obj.name == name && ( value = obj.value );\n        })\n        ss.FontFamily( value );\n        this.props.option.fontfamily = value;\n        this.props.onChange && this.props.onChange( `fontfamily_${value}` );\n        console.log( \"this.props.option.fontfamily = \", value, name )\n    }\n\n    changeFontsize( value ) {\n        this.props.option.fontsize = value + \"%\";\n        ss.FontSize( this.props.option.fontsize );\n        this.props.onChange && this.props.onChange( `fontsize_${this.props.option.fontsize}` );\n        console.log( \"this.props.option.fontsize = \", this.props.option.fontsize )\n    }\n\n    changeLayout( value ) {\n        this.props.option.layout = `${ 100 - value }%`;\n        ss.Layout( this.props.option.layout );\n        this.props.onChange && this.props.onChange( `layout_${this.props.option.layout}` );\n        console.log( \"this.props.option.layout = \", this.props.option.layout )\n    }\n\n    changeStyle( value, type ) {\n        let news = value\n        if ( value == 0 ) {\n            news = \"\";\n        } else news = type != \"lineHeight\" ? value + \"px\" : value;\n        this.props.option.custom.art[type] = news;\n        ss.Custom( \"art\", this.props.option.custom.art );\n        this.props.onChange && this.props.onChange( `custom_${this.props.option.custom.art[type]}`, type );\n        console.log( \"this.props.option.custom.art\", this.props.option.custom.art[type] )\n    }\n\n    render() {\n        const slider_width = location.protocol.includes( \"extension\" ) ? \"660.09px\" : undefined;\n        return (\n            <sr-opt-read>\n                <sr-opt-gp>\n                    <sr-opt-label>主题色</sr-opt-label>\n                    <ThemeSel themes={ th.colors } names={ th.names } labels={ conf.readLabels } theme={ this.props.option.theme } changeBgColor={ val=>this.changeBgColor(val) } />\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <Shortcuts shortcuts={ this.props.option.shortcuts } changeShortcuts={ val=>this.changeShortcuts(val) } />\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>字体类型</sr-opt-label>\n                    <AC value={ this.props.option.fontfamily }\n                        placeholder=\"请输入 font-family 值\"\n                        items={ conf.fontfamily }\n                        onChange={ (n,v)=>this.changeFontfamily(n,v) }\n                    />\n                </sr-opt-gp>\n                <sr-opt-gp>\n                <sr-opt-label>字体大小</sr-opt-label>\n                    <Slider min=\"45\" max=\"100\" step=\"1\" width={slider_width} value={ this.parse( this.props.option.fontsize == \"\" ? \"60%\" : this.props.option.fontsize ) } onChange={ (v)=>this.changeFontsize(v) }/>\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>版面宽度</sr-opt-label>\n                    <Slider min=\"70\" max=\"100\" step=\"1\" width={slider_width} value={ 100 - this.parse( this.props.option.layout == \"\" ? \"20%\" : this.props.option.layout ) } onChange={ (v)=>this.changeLayout(v) }/>\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>字间距</sr-opt-label>\n                    <Slider min=\"0\" max=\"10\" step=\"1\" width={slider_width} value={ this.parse( this.props.option.custom.art.letterSpacing ) } onChange={ (v)=>this.changeStyle(v, \"letterSpacing\") }/>\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>行间距</sr-opt-label>\n                    <Slider min=\"0\" max=\"5\" step=\"1\" width={slider_width} value={ this.parse( this.props.option.custom.art.lineHeight ) } onChange={ (v)=>this.changeStyle(v, \"lineHeight\") }/>\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>单词间距</sr-opt-label>\n                    <Slider min=\"0\" max=\"10\" step=\"1\" width={slider_width} value={ this.parse( this.props.option.custom.art.wordSpacing ) } onChange={ (v)=>this.changeStyle(v, \"wordSpacing\") }/>\n                </sr-opt-gp>\n                <sr-opt-gp>\n                    <sr-opt-label>首行缩进</sr-opt-label>\n                    <Slider min=\"0\" max=\"30\" step=\"1\" width={slider_width} value={ this.parse( this.props.option.custom.art.textIndent ) } onChange={ (v)=>this.changeStyle(v, \"textIndent\") }/>\n                </sr-opt-gp>\n            </sr-opt-read>\n        )\n    }\n}\n"
  },
  {
    "path": "src/module/setting.jsx",
    "content": "console.log( \"=== simpread option setting ===\" )\r\n\r\nimport FocusOpt     from 'focusopt';\r\nimport ReadOpt      from 'readopt';\r\n\r\nimport { storage, STORAGE_MODE } from 'storage';\r\nimport * as msg     from 'message';\r\nimport {browser}    from 'browser';\r\nimport th           from 'theme';\r\nimport Notify       from 'notify';\r\nimport * as ss      from 'stylesheet';\r\nimport * as watch   from 'watch';\r\n\r\nimport Button       from 'button';\r\nimport * as tooltip from 'tooltip';\r\nimport * as waves   from 'waves';\r\nimport * as dia     from 'dialog';\r\n\r\nconst root   = \"simpread-option-root\",\r\n      rootjq = `.${root}`;\r\nlet   callback;\r\n\r\n/**\r\n * Modals Rect component\r\n */\r\nclass Modals extends React.Component {\r\n\r\n    // close setting\r\n    close( restore = rollback() ) {\r\n        dia.Close();\r\n    }\r\n\r\n    // save setting focus option\r\n    save() {\r\n        console.log( \"setting click submit button.\", storage.current )\r\n        watch.Verify( ( state, result ) => {\r\n            if ( state ) {\r\n                console.log( \"watch.Lock()\", result );\r\n                new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n            } else {\r\n                storage.Setcur( storage.current.mode );\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.shortcuts, { url: window.location.href } ));\r\n                watch.SendMessage( \"site\", true );\r\n                new Notify().Render( 0, \"更新成功，刷新当前页面后才能生效！\" )\r\n                this.close( false );\r\n            }\r\n        });\r\n    }\r\n\r\n    siteeditor() {\r\n        callback();\r\n        this.close();\r\n    }\r\n\r\n    constructor( props ) {\r\n        super( props );\r\n    }\r\n\r\n    componentDidMount() {\r\n        waves.Render({ root: rootjq });\r\n        tooltip.Render( rootjq );\r\n    }\r\n\r\n    render() {\r\n        const Option = storage.current.mode == STORAGE_MODE.focus ? FocusOpt : ReadOpt;\r\n\r\n        return (\r\n            <dia.Dialog>\r\n                <dia.Content>\r\n                    <Option option={ storage.current } />\r\n                </dia.Content>\r\n                <dia.Footer>\r\n                    <Button text=\"站点编辑器\" waves=\"md-waves-effect\" color=\"#fff\" backgroundColor=\"#4caf50\" onClick={ ()=>this.siteeditor() } width=\"70%\" />\r\n                    <div style={{ width: \"100%\" }}></div>\r\n                    <Button text=\"取 消\" mode=\"secondary\" waves=\"md-waves-effect\" onClick={ ()=>this.close() } />\r\n                    <Button text=\"确 认\" waves=\"md-waves-effect\" onClick={ ()=>this.save() } />\r\n                </dia.Footer>\r\n            </dia.Dialog>\r\n        )\r\n\r\n    }\r\n}\r\n\r\n/**\r\n * Roll back when cancel button click\r\n */\r\nfunction rollback() {\r\n    storage.Restore( storage.current.mode );\r\n    if ( storage.current.mode == STORAGE_MODE.focus ) $( \".simpread-focus-root\" ).css({ \"background-color\" : storage.current.bgcolor });\r\n    if ( storage.current.mode == STORAGE_MODE.read ) {\r\n        th.theme != storage.current.theme && th.Change( storage.current.theme );\r\n        ss.FontFamily( storage.current.fontfamily );\r\n        ss.FontSize( storage.current.fontsize );\r\n        ss.Layout( storage.current.layout );\r\n        ss.Preview( storage.read.custom );\r\n    }\r\n}\r\n\r\n/**\r\n * Modals Render\r\n * \r\n * @param {func} callback\r\n */\r\nfunction Render( cb ) {\r\n    callback = cb;\r\n    !dia.Popup( rootjq ) && !storage.current.fap && dia.Open( <Modals/>, root );\r\n    storage.current.mode == STORAGE_MODE.read && storage.current.fap && new Notify().Render( \"已经启用控制栏面板，所以无法使用此功能。\" );\r\n}\r\n\r\n/**\r\n * Exist\r\n * \r\n * @return {boolean}\r\n */\r\nfunction Exist() {\r\n    return dia.Popup( rootjq );\r\n}\r\n\r\n/**\r\n * Exit\r\n */\r\nfunction Exit() {\r\n    rollback();\r\n    dia.Close();\r\n}\r\n\r\nexport{ Render, Exist, Exit }"
  },
  {
    "path": "src/module/sharecard.jsx",
    "content": "console.log( \"=== simpread share card load ===\" )\r\n\r\nimport * as exp from 'export';\r\nimport * as ss  from 'stylesheet';\r\n\r\nimport Button   from 'button';\r\n\r\nclass ShareCard extends React.Component {\r\n\r\n    download() {\r\n        exp.PNG( $(\"sharecard-card\")[0] , `simpread-${ this.props.title }.png`, result => {\r\n            !result && new Notify().Render( 2, \"下载失败，请稍后再试！\" );\r\n            result  && this.exit();\r\n        });\r\n    }\r\n\r\n    exit() {\r\n        ReactDOM.unmountComponentAtNode( $( \"sharecard-bg\" )[0] );\r\n    }\r\n\r\n    componentWillUnmount() {\r\n        $( \"sharecard-bg\" ).remove();\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            <sharecard>\r\n                <sharecard-head>\r\n                    <sharecard-card>\r\n                        <sharecard-top><span className=\"logos\"></span>简 悦</sharecard-top>\r\n                        <sharecard-content dangerouslySetInnerHTML={{__html: this.props.content }}></sharecard-content>\r\n                        <sharecard-via>摘自 《{ this.props.title }》</sharecard-via>\r\n                        <sharecard-footer><div><span className=\"qrcode\"></span><span style={{ display: \"none\" }} dangerouslySetInnerHTML={{__html: \"长按扫码<br>获取简悦\" }}></span></div><div>还原阅读本质 · 提升阅读体验</div></sharecard-footer>\r\n                    </sharecard-card>\r\n                </sharecard-head>\r\n                <sharecard-control>\r\n                    <Button type=\"raised\" text=\"保 存\" width=\"100%\"\r\n                            color=\"#fff\" backgroundColor=\"#2196F3\"\r\n                            icon={ ss.IconPath( \"save_icon\" ) }\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ ()=>this.download() } />\r\n                    <Button type=\"raised\" text=\"关 闭\" width=\"100%\"\r\n                            fontIcon={ `<i class=\"fas fa-window-close\"></i>` }\r\n                            color=\"#fff\" backgroundColor=\"#2196F3\"\r\n                            waves=\"md-waves-effect md-waves-button\"\r\n                            onClick={ ()=>this.exit() } />\r\n                </sharecard-control>\r\n            </sharecard>\r\n        )\r\n    }\r\n}\r\n\r\nfunction Render( root, title, txt ) {\r\n    $( root ).append( `<sharecard-bg></sharecard-bg>` );\r\n    ReactDOM.render( <ShareCard title={ title } content={ txt.replace(/\\n/ig, \"<br>\" )} />, $( \"sharecard-bg\" )[0] )\r\n}\r\n\r\nexport {\r\n    Render\r\n}"
  },
  {
    "path": "src/module/sitebar.jsx",
    "content": "console.log( \"=== simpread site bar load ===\" )\n\nimport {storage} from 'storage';\nimport * as watch from 'watch';\n\nimport Button    from 'button';\n\nexport default class Sitebar extends React.Component {\n\n    static defaultProps = {\n        bgColors : {\n            \"global\": \"#fb584a\",\n            \"custom\": \"#fa9a3f\",\n            \"local\": \"#00a9f0\",\n        },\n        labels: {\n            \"global\": \"官方适配源\",\n            \"person\": \"站点集市适配源\",\n            \"custom\": \"第三方适配源\",\n            \"local\": \"自定义适配源\",\n        },\n        icons: {\n            \"global\": '<i class=\"fas fa-globe\"></i>',\n            \"person\": '<i class=\"fas fa-user\"></i>',\n            \"custom\": '<i class=\"fas fa-newspaper\"></i>',\n            \"local\": '<i class=\"fas fa-laptop\"></i>',\n        }\n    };\n\n    state = {\n        category: []\n    };\n\n    active( obj ) {\n        const target = obj.pop();\n        Object.keys( storage.pr.sites ).forEach( key => {\n            if ( key == target ) {\n                storage.pr.sites[target].forEach( item => {\n                    if ( JSON.stringify( item ) == JSON.stringify( obj ) ) {\n                        item[1].active = true;\n                    } else delete item[1].active;\n                });\n            } else {\n                storage.pr.sites[key].forEach( item => {\n                    delete item[1].active;\n                });\n            }\n        });\n        storage.Writesite( storage.pr.sites, ()=> {\n            console.log( \"current site is \", storage.pr.sites )\n            watch.SendMessage( \"site\", true );\n            new Notify().Render( \"已成功切到换到此适配站点，请刷新本页。\" );\n        });\n    }\n\n    componentWillMount() {\n        const category = {};\n        !storage.pr.current.site.matching && ( storage.pr.current.site.matching = [] );\n        storage.pr.current.site.matching.forEach( item => {\n            if ( category[item[2]] ) {\n                category[item[2]].push( item );\n            } else {\n                category[item[2]] = [ item ];\n            }\n        });\n        this.setState({ category });\n    }\n\n    render() {\n        const child = Object.keys( this.state.category ).map( item => {\n            const label   = this.props.labels[item];\n            const actions = this.state.category[item].map( arr => {\n                const [ site, type ] = [ arr[1], arr[2] ],\n                      tooltip = site.info ? site.info.title : \"点击后默认进入当前站点\",\n                      color   = type != \"person\" ? \"#fff\" : site.info.color,\n                      bgColor = type != \"person\" ? this.props.bgColors[type] : site.info.bgColor;\n                return (\n                    <Button shape=\"circle\" type=\"flat\"\n                            color={ color } backgroundColor={ bgColor }\n                            fontIcon={ this.props.icons[type] }\n                            tooltip={{ text: tooltip + ( site.active ? '（已启用）' : '（未启用）' ) }}\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.active( arr ) } />\n                )\n            });\n\n            return (\n                <sr-opt-gp>\n                    <sr-opt-label>{ label }</sr-opt-label>\n                    <actions style={{ display: \"flex\", margin: \"10px 0\" }}>{ actions }</actions>\n                </sr-opt-gp>\n            )\n        });\n\n        if ( child.length == 0 ) {\n            child.push( <site-bar-empty style={{'font-size':'17px!important','color': 'rgba(51, 51, 51, 0.87)!important'}}>当前模式下无法使用此功能</site-bar-empty> );\n        }\n\n        return (\n            <site-bar>{child}</site-bar>\n        )\n    }\n}"
  },
  {
    "path": "src/module/siteeditor.jsx",
    "content": "console.log( \"=== simpread option siteeditor load ===\" )\r\n\r\nimport { storage }  from 'storage';\r\nimport * as watch   from 'watch';\r\nimport {browser}    from 'browser';\r\nimport * as msg     from 'message';\r\n\r\nimport Editor       from 'editor';\r\n\r\nimport Button       from 'button';\r\nimport * as tooltip from 'tooltip';\r\nimport * as waves   from 'waves';\r\nimport * as dia     from 'dialog';\r\n\r\nconst root   = \"simpread-option-root\",\r\n      rootjq = `.${root}`;\r\nlet site,\r\n    state    = { name: 0, url: 0, title: 0, desc: 0, include: 0, exclude: 0, avatar:{ name: 0, url: 0 }, paging: { prev:0, next: 0} }; // 0: success -1: faield -2: not empty\r\n\r\n/**\r\n * SiteEditor Rect component\r\n */\r\nclass SiteEditor extends React.Component {\r\n\r\n    close() {\r\n        dia.Close();\r\n    }\r\n\r\n    action( type ) {\r\n        watch.Verify( ( state, result ) => {\r\n            if ( state ) {\r\n                console.log( \"watch.Lock()\", result );\r\n                new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>window.location.reload() );\r\n            } else {\r\n                type == \"save\" ? this.save() : this.delete();\r\n            }\r\n        });\r\n    }\r\n\r\n    delete() {\r\n        console.log( \"siteeditor click delete button.\", storage.current.site )\r\n        new Notify().Render( \"是否删除当前适配站点？\", \"删除\", () => {\r\n            site.name.startsWith( \"tempread::\" ) ? new Notify().Render( 2, `当前站点为自动识别，无误删除。` ) :\r\n                storage.pr.Deletesite( storage.current.site.target, site.url, result => {\r\n                    if ( result == -1 ) new Notify().Render( 2, `此站已被删除，请勿重复操作。` );\r\n                    else {\r\n                        storage.Writesite(storage.pr.sites, () => {\r\n                            new Notify().Render( \"删除成功，如需生效，请刷新本页。\" );\r\n                            watch.SendMessage( \"site\", true );\r\n                        });\r\n                    }\r\n                });\r\n        });\r\n    }\r\n\r\n    // save siteeditor focus option\r\n    save() {\r\n        console.log( \"siteeditor click save button.\", storage.current.site, site, state )\r\n        if ( [ \"url\", \"name\", \"title\", \"include\" ].findIndex( key => site[key] == \"\" ) != -1 ) {\r\n            new Notify().Render( 3, \"【标识、域名、标题、高亮】不能为空。\" );\r\n        }\r\n        else if ( Object.values( state ).findIndex( key => typeof key == \"number\" && key != 0 ) != -1 ||\r\n           ( state.avatar.name != 0 || state.avatar.url  != 0 ) ||\r\n           ( state.paging.prev != 0 || state.paging.next != 0 )\r\n        ) {\r\n            new Notify().Render( 3, \"请正确填写【标识、域名、标题、高亮】后再提交。\" );\r\n        } else if (( site.avatar[0].name != \"\" && site.avatar[1].url == \"\" ) || ( site.avatar[0].name == \"\" && site.avatar[1].url != \"\" )) {\r\n            new Notify().Render( 3, \"【头像的名称与地址】必须同时设定。\" );\r\n        } else if (( site.paging[0].prev != \"\" && site.paging[1].next == \"\" ) || ( site.paging[0].prev == \"\" && site.paging[1].next != \"\" )) {\r\n            new Notify().Render( 3, \"【前一页与后一页】必须同时设定。\" );\r\n        } else if ( site.name.startsWith( \"tempread::\" ) ) {\r\n            new Notify().Render( 2, \"标识不能包含 tempread:: 请删除。\" );\r\n        } else if ( site.include.trim() == \"\" ) {\r\n            new Notify().Render( 2, \"高亮区域不能为空。\" );\r\n        } else {\r\n            // changed storage.current.site.target to 'local'\r\n            storage.pr.Updatesite( 'local', storage.current.url, [ site.url, storage.pr.Cleansite(site) ]);\r\n            storage.Writesite( storage.pr.sites, () => {\r\n                new Notify().Render( 0, \"更新成功，页面刷新后生效！\" );\r\n                watch.SendMessage( \"site\", true );\r\n            });\r\n        }\r\n    }\r\n\r\n    submit() {\r\n        const news = { ...site };\r\n        delete news.html;\r\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.temp_site, { url: location.href, site: news, uid: storage.user.uid, type: \"temp\" }));\r\n    }\r\n\r\n    componentDidMount() {\r\n        waves.Render({ root: rootjq });\r\n        tooltip.Render( rootjq );\r\n    }\r\n\r\n    render() {\r\n        site = { ...storage.pr.current.site };\r\n        if ( storage.pr.state == \"temp\" && storage.pr.dom ) {\r\n            site.name   = site.name.replace( \"tempread::\", \"\" );\r\n            let include = storage.pr.Utils().dom2Xpath( storage.pr.dom );\r\n            if ( include != \"\" ) {\r\n                site.include    = `[[\\`${include}\\`]]`;\r\n            } else site.include = storage.pr.dom.outerHTML.replace( storage.pr.dom.innerHTML, \"\" ).replace( /<\\/\\S+>$/i, \"\" )\r\n        }\r\n        return (\r\n            <dia.Dialog>\r\n                <dia.Content>\r\n                    <Editor site={ site } state={ state } />\r\n                </dia.Content>\r\n                <dia.Footer>\r\n                    <Button text=\"删 除\" waves=\"md-waves-effect\" color=\"#fff\" backgroundColor=\"#F44336\" onClick={ ()=>this.action( \"delete\" ) } />\r\n                    { storage.pr.state == \"temp\" ?\r\n                        <Button text=\"提交临时阅读模式\" waves=\"md-waves-effect\" color=\"#fff\" backgroundColor=\"#4CAF50\" width=\"100%\" onClick={ ()=>this.submit() } /> :\r\n                        <div style={{ width: \"100%\" }}></div> }\r\n                    <Button text=\"退 出\" mode=\"secondary\" waves=\"md-waves-effect\" onClick={ ()=>this.close() } />\r\n                    <Button text=\"保 存\" waves=\"md-waves-effect\" onClick={ ()=>this.action( \"save\" ) } />\r\n                </dia.Footer>\r\n            </dia.Dialog>\r\n        )\r\n    }\r\n}\r\n\r\n/**\r\n * Modals Render\r\n */\r\nfunction Render() {\r\n    switch ( true ) {\r\n        case storage.pr.state == \"meta\":\r\n            new Notify().Render( \"当前为 <a href='http://ksria.com/simpread/docs/#/主动适配阅读模式' target='_blank'>主动适配阅读模式</a>，并不能使用设定功能。\" )\r\n            break;\r\n        case storage.pr.state == \"txt\":\r\n            new Notify().Render( \"当前为 <a href='http://ksria.com/simpread/docs/#/TXT-阅读器' target='_blank'>TXT 阅读器模式</a>，并不能使用设定功能。\" )\r\n            break;\r\n        default:\r\n            !dia.Popup( rootjq ) && dia.Open( <SiteEditor/>, root );\r\n    }\r\n}\r\n\r\n/**\r\n * Exist\r\n * \r\n * @return {boolean}\r\n */\r\nfunction Exist() {\r\n    return dia.Popup( rootjq );\r\n}\r\n\r\n/**\r\n * Exit\r\n */\r\nfunction Exit() {\r\n    dia.Close();\r\n}\r\n\r\nexport{ Render, Exist, Exit }"
  },
  {
    "path": "src/module/sites.jsx",
    "content": "console.log( \"===== simpread option sites load =====\" )\n\nimport {storage}  from 'storage';\nimport {browser}  from 'browser';\nimport * as msg   from 'message';\nimport * as watch from 'watch';\nimport * as ss    from 'stylesheet';\n\nimport TextField  from 'textfield';\nimport Button     from 'button';\n\nclass Card extends React.Component {\n\n    static defaultProps = {\n        info         : {},\n    };\n\n    static propTypes = {\n        info         : React.PropTypes.object,\n    };\n\n    update() {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://simpread.ksria.cn/sites/details/\" + this.props.info.domain + \"?type=update\" }));\n    }\n\n    delete() {\n        new Notify().Render({ mode:\"snackbar\", content: \"是否删除当前站点？\", action: \"确认\", cancel: \"取消\", callback: type => {\n            if ( type == \"cancel\" ) return;\n            storage.pr.Deletesite( \"person\", this.props.url, flag => {\n                flag == -1 && new Notify().Render( \"当前站点已删除，请勿重复提交。\" );\n                flag != -1 && storage.Writesite( storage.pr.sites, ()=> {\n                    console.log( \"current site is \", storage.pr.sites )\n                    watch.SendMessage( \"site\", true );\n                    new Notify().Render( \"删除成功。\" );\n                    this.props.onChange( \"delete\" );\n                });\n            });\n        }});\n    }\n\n    addmore() {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://simpread.ksria.cn/sites/details/\" + this.props.info.domain }));\n    }\n\n    render() {\n        return (\n            <card>\n                <card-header style={{ backgroundColor: this.props.info.bgColor }}>\n                    <title style={{ color: this.props.info.color }}>{ this.props.info.title }</title>\n                </card-header>\n                <card-footer>\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"删除当前站点\" }}\n                            fontIcon='<i class=\"fas fa-trash-alt\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.delete() } />\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"更新当前站点到最新版本\" }}\n                            fontIcon='<i class=\"fas fa-cloud\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.update() } />\n                    <Button shape=\"circle\" type=\"flat\"\n                            color=\"#c3c6c7\" hoverColor=\"rgba( 153, 153, 153, .1)\"\n                            tooltip={{ text: \"查看当前站点的详细信息\" }}\n                            fontIcon='<i class=\"fas fa-ellipsis-h\"></i>'\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.addmore() } />\n                </card-footer>\n            </card>\n        )\n    }\n}\n\nclass Cards extends React.Component {\n\n    state = {\n        sites    : storage.pr.sites.person\n    }\n\n    static propTypes = {\n        onChange : React.PropTypes.func,\n    };\n\n    onChange( type ) {\n        this.setState({\n            sites: storage.pr.sites.person\n        });\n    }\n\n    render() {\n        const card = this.state.sites && this.state.sites.length > 0 ? this.state.sites.map( item => {\n            return (\n                <Card url={ item[0] } info={ item[1].info } onChange={t=>this.onChange(t)} />\n            )\n        }) : <card-empty><a href=\"https://simpread.ksria.cn/sites\" target=\"_blank\">没有任何站点，点击打开「站点集市」添加。</a></card-empty>;\n        return (\n            <cards>{ card }</cards>\n        )\n    }\n}\n\nexport default class SitesOpts extends React.Component {\n\n    newsites() {\n        const notify = new Notify().Render({ content: \"数据同步中，请稍等...\", state: \"loading\" });\n        storage.GetRemote( \"remote\", ( result, error ) => {\n            notify.complete();\n            if ( !error ) {\n                const count = storage.pr.Addsites( result );\n                storage.Writesite( storage.pr.sites, () => {\n                    watch.SendMessage( \"site\", true );\n                    count == 0 ? new Notify().Render( \"适配列表已同步至最新版本。\" ) : new Notify().Render( 0, `适配列表已同步成功，本次新增 ${ count } 个站点，2 秒后自动自动刷新。` );\n                    count > 0 && setTimeout( ()=>location.reload(), 2000 );\n                });\n            } else {\n                new Notify().Render( 3, `同步时发生了一些问题，并不会影响本地配置文件，请稍后再试！` );\n            }\n        });\n    }\n\n    onClick( state ) {\n        state == \"sitemgr\" && ( location.href = location.origin + \"/options/sitemgr.html\" );\n    }\n\n    changeOrigins() {\n        this.props.option.origins = event.target.value.split(\"\\n\");\n        storage.pr.origins        = this.props.option.origins;\n        this.props.onChange && this.props.onChange( false );\n    }\n\n    origins( type ) {\n        /*\n        if ( type == \"origins\" ) {\n            storage.GetRemote( \"origins\", ( result, error ) => {\n                if ( error ) new Notify().Render( 2, \"获取失败，请稍后重新加载。\" );\n                else {\n                    this.props.option.origins = storage.pr.Origins( result );\n                    this.props.onChange && this.props.onChange( false );\n                    $( this.refs.origins ).find( \"textarea\" ).val( this.props.option.origins.join( \"\\n\" ) );\n                    new Notify().Render( \"官方源加载成功。\" );\n                }\n            });\n        } else\n        */\n        if ( type == \"import\" ) {\n            new Notify().Render( \"snackbar\", \"导入后会覆盖掉原来的第三方适配列表，请问是否覆盖？\", \"确认\", () => {\n                const urls = this.props.option.origins.filter( item => {\n                    return item.trim() != \"\" && item.trim().startsWith( \"http\" ) && item.trim().endsWith( \".json\" )\n                });\n                const max  = urls.length;\n                let   idx  = 0, arr = [], count = 0;\n                if ( urls.length != this.props.option.origins.length ) {\n                    this.props.option.origins = [ ...urls ];\n                    this.props.onChange && this.props.onChange( false );\n                    $( this.refs.origins ).find( \"textarea\" ).val( this.props.option.origins.join( \"\\n\" ) );\n                    new Notify().Render( \"已剔除掉不符合规范的第三方源。\" );\n                }\n                this.props.option.origins.forEach( item => {\n                    storage.GetRemote( item, ( result, error ) => {\n                        idx++;\n                        if ( result && result.sites.length > 0 ) {\n                            count++;\n                            arr = arr.concat( storage.pr.Formatsites( result ) );\n                        } else new Notify().Render( `导入失败 ${ item }` );\n                        if ( idx == max ) {\n                            arr.length > 0 && ( storage.websites.custom = storage.pr.Addorigins( arr ) );\n                            console.log( \"current storage websites.custom is \", arr );\n                            new Notify().Render( `已完成导入，本次共计：${ count } 个站点， ${ arr.length } 条数据。` );\n                            this.props.onChange && this.props.onChange( false );\n                        }\n                    });\n                });\n            });\n        } else if ( type == \"clear\" ) {\n            new Notify().Render( \"snackbar\", \"只能清除第三方源的适配站点，请问是否清除？\", \"确认\", () => {\n                new Notify().Render( `已完成清除，共计：${ storage.pr.Clearorigins() } 条数据。` );\n                storage.websites.custom = storage.pr.sites.custom;\n                this.props.onChange && this.props.onChange( false );\n            });\n        }\n    }\n\n    onChange( type ) {\n        console.log( type )\n    }\n\n    clear() {\n        new Notify().Render({ mode: \"snackbar\", content: \"是否清除「站点集市」的全部站点？\", action: \"是的\", cancel: \"取消\", callback: type => {\n            if ( type == \"action\" ) {\n                storage.pr.sites.person = [];\n                storage.Writesite( storage.pr.sites, ()=> {\n                    console.log( \"current site is \", storage.pr.sites )\n                    watch.SendMessage( \"site\", true );\n                    new Notify().Render( \"已清除成功，2 秒后自动刷新当前页面。\" );\n                    setTimeout( ()=> {\n                        location.href = location.origin + location.pathname + \"#sites\";\n                        location.reload();\n                    }, 2000 );\n                });\n            }\n        }});\n    }\n\n    addmore() {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"https://simpread.ksria.cn/sites\" }));\n    }\n\n    install() {\n        try {\n            const url  = decodeURIComponent( location.hash ).replace( \"#sites?install=\", \"\" ),\n                  arr  = url.split( \"?site=\" ),\n                  id   = arr[0],\n                  news = JSON.parse( decodeURI( arr[1] )),\n                  old  = storage.pr.sites.person.filter( site => {\n                      return site[1].info.id == news.id;\n                  }),\n                  add = () => {\n                      const url  = old.length > 0 ? old[0][0] : news.site.url,\n                            site = { ...news.site };\n\n                      delete news.user;\n                      delete news.site;\n                      delete news.view;\n                      delete news.download;\n                      delete news.href;\n                      site.info = news;\n\n                      storage.pr.Updatesite( \"person\", url, [ url, storage.pr.Cleansite(site) ] );\n                      storage.Writesite( storage.pr.sites, ()=> {\n                        console.log( \"current site is \", storage.pr.sites )\n                        watch.SendMessage( \"site\", true );\n                        new Notify().Render( \"当前站点已安装成功，2 秒后自动刷新当前页面。\" );\n                        setTimeout( ()=> {\n                            location.href = location.origin + location.pathname + \"#sites\";\n                            location.reload();\n                        }, 2000 );\n                    });\n                  };\n            if ( old.length == 0 ) {\n                add();\n            } else if ( news.create != old[0][1].info.create ) {\n                new Notify().Render({ content: \"本地版本与安装版本不一致，是否安装新版本？\", action: \"安装\", cancel: \"取消\", callback: type => {\n                    type == \"action\" && add();\n                }});\n            } else {\n                new Notify().Render({ content: \"是否重新当前站点安装？\", action: \"安装\", cancel: \"取消\", callback: type => {\n                    type == \"action\" && add();\n                }});\n            }\n        } catch( error ) {\n            new Notify().Render( 2, \"获取失败，请稍后再试。\" );\n        }\n    }\n\n    update() {\n        const url      = decodeURIComponent( location.hash ).replace( \"#sites?update=\", \"\" ),\n              org_site = JSON.parse( url ),\n              type     = org_site.target,\n              site     = storage.pr.Cleansite( { ...org_site } );\n        site.url       = org_site.url;\n        $.ajax({\n            url: storage.service + \"/sites/service/query\",\n            method: \"POST\",\n            data:{ type, site }\n        }).done( ( result, textStatus, jqXHR ) => {\n           console.log( result.site )\n           if ( result.code == 200 ) {\n                storage.pr.Updatesite( type, org_site.url, [ result.site.url, storage.pr.Cleansite( result.site ) ]);\n                storage.Writesite( storage.pr.sites, () => {\n                    new Notify().Render( 0, \"更新成功，2 秒后自动关闭页面，失效的页面会自动刷新。\" );\n                    setTimeout( ()=>location.href = location.protocol + location.pathname + \"#sites?update=success\", 2000 );\n                    watch.SendMessage( \"site\", true );\n                });\n            } else if ( result.code == 404 ) {\n                new Notify().Render( \"无任何可用更新，2 秒后页面将会关闭！\" );\n                setTimeout( ()=>location.href = location.protocol + location.pathname + \"#sites?update=pending\", 2000 );\n            } else {\n                new Notify().Render( 2, \"暂时无法使用此功能，请稍候再试，2 秒后页面将会关闭！\" );\n                setTimeout( ()=>location.href = location.protocol + location.pathname + \"#sites?update=failed\", 2000 );\n           }\n        }).fail( error => {\n            new Notify().Render( 2, \"自动更新出现错误，请稍后再试！\" );\n        });\n    }\n\n    pending() {\n        const url  = decodeURIComponent( location.hash ).replace( \"#sites?pending=\", \"\" ),\n              data = JSON.parse( url );\n        $.ajax({\n            url   : storage.service + \"/sites/service/pending\",\n            method: \"POST\",\n            data,\n        }).done( ( result, textStatus, jqXHR ) => {\n            new Notify().Render( \"提交成功，谢谢对简悦作出的贡献，2 秒后页面将会关闭！\" );\n            setTimeout( ()=>location.href = location.protocol + location.pathname + \"#sites?update=complete\", 2000 );\n        }).fail( error => {\n            new Notify().Render( 2, \"自动更新出现错误，请稍后再试！\" );\n        });\n    }\n\n    temp() {\n        const url  = decodeURIComponent( location.hash ).replace( \"#sites?temp=\", \"\" ),\n              data = JSON.parse( url );\n        $.ajax({\n            url   : storage.service + \"/sites/service/pending\",\n            method: \"POST\",\n            data,\n        }).done( ( result, textStatus, jqXHR ) => {\n            new Notify().Render( \"提交成功，谢谢对简悦作出的贡献，2 秒后页面将会关闭！\" );\n            setTimeout( ()=>location.href = location.protocol + location.pathname + \"#sites?update=complete\", 2000 );\n        }).fail( error => {\n            new Notify().Render( 2, \"自动更新出现错误，请稍后再试！\" );\n        });\n    }\n\n    componentWillMount() {\n        decodeURIComponent( location.href ).includes( \"#sites?install=\" ) && this.install();\n        decodeURIComponent( location.href ).includes( \"#sites?update=\"  ) && this.update();\n        decodeURIComponent( location.href ).includes( \"#sites?pending=\" ) && this.pending();\n        decodeURIComponent( location.href ).includes( \"#sites?temp=\"    ) && this.temp();\n    }\n\n    render() {\n        return (\n            <div id=\"labs\" style={{ width: '100%' }}>\n                <div className=\"version-tips\" data-hits=\"newsites\">\n                <div className=\"label\" data-head-level=\"h1\" data-head-title=\"官方主适配源\">官方主适配源 <a target=\"_blank\" href=\"https://simpread.ksria.cn/sites/\" style={{ color:' #FF5252', borderBottom: '2px dotted', fontSize: '10px', fontWeight: 'bold', cursor: 'pointer' }}>共计 { storage.simpread.sites.length } 类</a></div>\n                <div className=\"lab\">\n                    <Button type=\"raised\" text=\"手动同步适配列表\" width=\"100%\"\n                            icon={ ss.IconPath( \"update_icon\" ) }\n                            color=\"#fff\" backgroundColor=\"rgb(103, 58, 183)\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.newsites() } />\n                </div>\n                </div>\n\n                <div className=\"label\" data-head-level=\"h1\">第三方适配源</div>\n                <div ref=\"origins\" style={{ 'padding-top': '10px', 'margin-bottom': '8px;' }} className=\"lab\">\n                    <div className=\"version-tips\" data-hits=\"customsites\">\n                    <TextField \n                        multi={ true } rows={4}\n                        placeholder=\"仅支持 URL 地址，每行一个。\" \n                        value={ ( this.props.option.origins||[] ).join( \"\\n\" ) }\n                        onChange={ ()=>this.changeOrigins() }\n                    />\n                    <div style={{ \"display\": \"flex\" }}>\n                        <Button type=\"raised\" text=\"加载第三方适配列表\"\n                            width=\"100%\" style={{ \"display\": \"none\", \"margin\": \"0\" }}\n                            color=\"#fff\" backgroundColor=\"#4CAF50\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.origins( \"origins\" ) } />\n                        <Button type=\"raised\" text=\"导入到第三方适配列表\"\n                            width=\"100%\" style={{ \"margin\": \"0 10px\" }}\n                            color=\"#fff\" backgroundColor=\"rgb(103, 58, 183)\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.origins( \"import\" ) } />\n                        <Button type=\"raised\" text=\"清除第三方适配列表\" \n                            width=\"100%\" style={{ \"margin\": \"0\" }}\n                            color=\"#fff\" backgroundColor=\"#FF5252\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.origins( \"clear\" ) } />\n                    </div>\n                    </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"personsites\">\n                <div className=\"label\" data-head-level=\"h1\" data-head-title=\"站点集市\">站点集市 <a target=\"_blank\" href=\"https://simpread.ksria.cn/sites/\" style={{ color:' #FF5252', borderBottom: '2px dotted', fontSize: '10px', fontWeight: 'bold', cursor: 'pointer' }}>共计 { storage.pr.sites.person.length } 类</a></div>\n                <div className=\"lab\">\n                    <div style={{ display: 'inline-flex', width: '100%' }}>\n                        <Button type=\"raised\" text=\"打开「站点集市」\" width=\"100%\"\n                            fontIcon={`<i class=\"fas fa-external-link-square-alt\"></i>`}\n                            color=\"#fff\" backgroundColor=\"rgb(103, 58, 183)\"\n                            waves=\"md-waves-effect md-waves-button\"\n                            onClick={ ()=>this.addmore() } />\n                        <Button type=\"raised\" text=\"清除「站点集市」的全部站点\" width=\"100%\"\n                                icon={ ss.IconPath( \"clear_icon\" ) }\n                                color=\"#fff\" backgroundColor=\"#757575\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>this.clear() } />\n                    </div>\n\n                    <div style={{ 'padding-top': '10px' }}>\n                        <Cards onChange={ t=>this.onChange(t) } />\n                    </div>\n                </div>\n                </div>\n\n                <div className=\"version-tips\" data-hits=\"sitemgr\">\n                <div className=\"label\" data-head-level=\"h1\">站点管理器</div>\n                <div style={{ 'padding-top': '10px', 'position': 'relative' }} className=\"lab\" onClick={ ()=>this.onClick('sitemgr') }>\n                    <div className=\"more\" style={{ 'cursor': 'pointer' }}>\n                        <div>可以管理全部的适配站点</div>\n                        <span className=\"desc\">包括：官方适配源、第三方适配源、站点集市适配源、自定义适配源。</span>\n                        <span className=\"arrow\"></span>\n                    </div>\n                </div>\n                </div>\n            </div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/unrdist.jsx",
    "content": "console.log( \"===== simpread option unread list load =====\" )\n\nimport List      from 'list';\nimport Button    from 'button';\n\nimport {storage} from 'storage';\nimport * as conf from 'config';\nimport * as exp  from 'export';\n\nimport timeago   from 'timeago';\n\nconst style = {\n        root: {\n            display: 'flex',\n            flexDirection: 'column',\n            justifyContent: 'center',\n            alignItems: 'center',\n        },\n        text: {\n            fontSize: '1.6rem',\n            color: '#9b9b9b',\n        },\n        icon : {\n            width: '80px',\n            height: '80px',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n            backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBA0LLBOn/NkLAAABtklEQVRYw+2YPyxDURTGf5WOGCwiETH7s4kaDJo2FokYMBglZ66lOtGEqWIQm9zdoJouGERIEwNlE4NE4i0iBoO0CxIMvW1f26evvJY86Tfdc3LP99137vvuTa6HqlAwzxI9X064ISrxagweE5mfOXp18CHjoCDJFHbYlBCAOiywGWzLSYmAauUIn6koI+2g/Bzb0gMMyDWoZ9pNuTQByUKLbkSyhD6PyZroYcwiN0xSoQUYJGhZ+FKjwKtlNsggeAEIFJIbnJqm7BKpSeAIgHkdjbKgRwGucgIdOrEuYXOdXKo1Fm3pF+QOQBI6Tqg3wnleb8nUVHmtRNQBQueX5AZbclmRTVFYqBcbSKpS9jtocVLcFGgK/I6AhQ/UEPHCsf09GMyU287KaBc/Xm4vF+YbBv5oDzYd8FXUWrRIQoQa+wV1hfsFmj6whTt9IPvFRv3L39Q51AR7AKzKctMHtnCnD2wE6oAbpgF4bJCA3HKbH7vcaCsSdf9ZVCrQVyfW/uIwtwdZHcXUEzuSccKt2pglpoOsfkpQPs4a0p8ROc+1KI3RAHqDdPExpIv78nPQId7plgf4BCjPbVayklPeAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTA0LTEzVDExOjQ0OjE5KzA4OjAwPYuVdQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0wNC0xM1QxMTo0NDoxOSswODowMEzWLckAAAAASUVORK5CYII= )',\n        }\n},\n    ago = timeago();\n\nexport default class Unrdist extends React.Component {\n\n    static defaultProps = {\n        list: [],\n        step: 5,\n    };\n\n    static propsType = {\n        list: React.PropTypes.array,\n        step: React.PropTypes.number,\n        onLoadMoreClick: React.PropTypes.func,\n    };\n\n    state = {\n        title: `未读列表：${ this.props.list.length } 条`,\n        items: this.props.list.map( item => {\n            item.priType  = \"text\";\n            item.secType  = \"text\";\n            item.priValue = item.title.substr( 0, 1 );\n            item.secValue = ago.format( item.create.replace( /(年|月)/g, \"-\" ).replace( \"日\", \"\" ), \"zh_CN\" )\n            item.url      = item.url + ( window.location.search ? \"&\" : \"?\" ) + \"simpread_mode=read\";\n            return item;\n        }),\n\n        total: Math.ceil( this.props.list.length / this.props.step ),\n        page : 1,\n    };\n\n    onAction( event, ...rests ) {\n        const { pocket, linnk, instapaper } = exp,\n              [ id, _, data ]   = rests;\n\n        [ \"pocket\", \"instapaper\", \"linnk\" ].includes( id ) &&\n            exp.VerifySvcWrapper( storage, exp[id], id, exp.Name( id ), new Notify(), false )\n                .done( type => {\n                    if ( type == \"pocket\" ) {\n                        pocket.Add( data.url, data.title.trim(), ( result, error ) => exp.svcCbWrapper( result, error, pocket.name, type, new Notify() ));\n                    } else if ( type == \"linnk\" ) {\n                        linnk.GetSafeGroup( linnk.group_name, ( result, error ) => {\n                            if ( !error ) {\n                                linnk.group_id = result.data.groupId;\n                                linnk.Add( data.url, data.title.trim(), ( result, error ) => exp.svcCbWrapper( result, error, linnk.name, type, new Notify() ));\n                            } else new Notify().Render( 2, `${ linnk.name } 保存失败，请稍后重新再试。` );\n                        });\n                    } else {\n                        instapaper.Add( data.url, data.title.trim(), \"\", ( result, error ) => exp.svcCbWrapper( result, error, instapaper.name, type, new Notify() ));\n                    }\n                });\n\n        id == \"remove\" &&\n            storage.UnRead( id, data.idx, success => {\n                success && this.state.items.splice( this.state.items.findIndex( item => item.idx == data.idx ), 1 );\n                success && this.setState({\n                    title: `未读列表：${ this.state.items.length } 条`,\n                    total: Math.ceil( this.state.items.length / this.props.step ),\n                });\n                success && new Notify().Render( 0, \"删除成功\" );\n            });\n    }\n\n    onClick() {\n        this.setState({ page: this.state.page + 1 });\n        this.props.onLoadMoreClick && this.props.onLoadMoreClick();\n    }\n\n    render() {\n        const content_style = {\n            display: 'flex',\n            flexDirection: 'row',\n            alignItems: 'flex-end',\n            width: '100%',\n        };\n        const disable = this.state.page >= this.state.total ? true : false,\n              items   = this.state.items.slice( 0, this.state.page * this.props.step ),\n              content = this.state.items && this.state.items.length > 0 ?\n            <div>\n                <div className=\"version-tips\" data-hits=\"laterlist\">\n                <List acIconWaves=\"md-waves-effect md-waves-circle\"\n                      acItemWaves=\"md-waves-effect\"\n                      title={ this.state.title } contentStyle={ content_style }\n                      items={ items } actionItems={ conf.actionItems }\n                      priBgColor =\"#E1BEE7\"\n                      onAction={ (e,i,t,d)=>this.onAction(e,i,t,d) } />\n                </div>\n                <div className=\"version-tips\" data-hits=\"latermore\">\n                <Button type=\"raised\" width=\"100%\"\n                        text={ disable ? \"加载完毕\" : \"加载更多\" } disable={ disable }\n                        color=\"#fff\" backgroundColor=\"rgb(156, 39, 176)\"\n                        waves=\"md-waves-effect md-waves-button\"\n                        onClick={ ()=>this.onClick() } />\n                </div>\n            </div>\n            : <div style={ style.root }>\n                <span style={ style.icon }></span>\n                <span style={ style.text }>没有任何未读列表!</span>\n              </div>;\n        return (\n            <div>{ content }</div>\n        )\n    }\n}"
  },
  {
    "path": "src/module/urlscheme.jsx",
    "content": "console.log( \"===== simpread url scheme load =====\" )\n\nimport Switch    from 'switch';\nimport TextField from 'textfield';\nimport Button    from 'button';\nimport Dropdown  from 'dropdown';\n\nimport * as puplugin from 'puplugin';\n\nconst category = [\n    { name: \"黑名单\", value: \"blacklist\" },\n    { name: \"白名单\", value: \"whitelist\" },\n    { name: \"排除列表\", value: \"exclusion\" },\n    { name: \"延迟加载\", value: \"lazyload\" },\n];\n\nexport class URLScheme extends React.Component {\n\n    static defaultProps = {\n        type: \"\",\n        url:  \"\",\n        off:  false,\n    }\n\n    static propType = {\n        type     : React.PropTypes.string,\n        url      : React.PropTypes.string,\n        off      : React.PropTypes.bool,\n        onChange : React.PropTypes.func,\n    }\n\n    state = {\n        error : \"\",\n        disable: false,\n    };\n\n    onDropdownChange( value ) {\n        this.props.type = value;\n    }\n\n    onURLChange( event ) {\n        const minimatch = puplugin.Plugin( \"minimatch\" ),\n              value     = event.target.value.trim();\n        if ( value == \"\" ) {\n            this.setState({ error : \"不能为空\", disable: true });\n        } else if ( value.startsWith( \"[[/\" ) && value.endsWith( \"/]]\" ) && !new RegExp( value.replace( /\\[\\[\\/|\\/\\]\\]/ig, \"\" ) ).test( location.href ) ) {\n            this.setState({ error : \"正则表达式错误\", disable: true });\n        } else if ( !value.startsWith( \"[[/\" ) && !value.startsWith( \"http\" ) && value != location.hostname.replace( \"www.\", \"\" ) ) {\n            this.setState({ error : \"主域名不匹配\", disable: true });\n        } else if ( !value.startsWith( \"[[/\" ) && value.startsWith( \"http\" ) && !minimatch( location.href, value ) ) {\n            this.setState({ error : \"minimatch 适配错误\", disable: true });\n        } else {\n            this.setState({ error : \"\", disable: false });\n            this.props.url = value;\n        }\n    }\n\n    onOpenedChange( value ) {\n        this.props.off = value;\n    }\n\n    onClose() {\n        $( this.refs.target )\n            .addClass( \"hide\" )[0]\n            .addEventListener( 'animationend', () => {\n                ReactDOM.unmountComponentAtNode( $( \".simpread-urlscheme\" )[0] );\n                $( \".simpread-urlscheme\" ).remove();\n        }, false );\n    }\n\n    onSave() {\n        this.props.onChange && this.props.onChange( this.props.type, this.props.off, this.props.url );\n        this.onClose();\n    }\n\n    render() {\n        return (\n            <simpread-urlscheme ref=\"target\" class=\"active\">\n                <sr-urls-head>\n                    <sr-urls-label>请选择添加模式</sr-urls-label>\n                    <Dropdown name={ category.filter( item => item.value == this.props.type )[0].name } items={ category } width=\"100%\" onChange={ (v)=>this.onDropdownChange(v) } />\n                </sr-urls-head>\n                <sr-urls-content>\n                    <sr-urls-label>支持 域名 · 主域名 · 正则表达式 · minimatch 等规则，详细 <sr-urls-a onClick={ ()=>window.open( 'http://ksria.com/simpread/docs/#/右键菜单?id=URL编辑器', '_blank') }>请看这里</sr-urls-a> </sr-urls-label>\n                    <TextField\n                        multi={ false }\n                        value={ this.props.url }\n                        errortext={ this.state.error }\n                        onChange={ (e)=>this.onURLChange(e) }\n                    />\n                </sr-urls-content>\n                <sr-urls-content>\n                    <Switch width=\"100%\" checked={ this.props.off }\n                            thumbedColor=\"#2163f7\" trackedColor=\"#6699FF\" waves=\"md-waves-effect\"\n                            label=\"默认弹出编辑框，取消后意味着直接保存\"\n                            onChange={ (v)=>this.onOpenedChange( v ) } />\n                </sr-urls-content>\n                <sr-urls-footer>\n                    <Button text=\"取 消\" mode=\"secondary\" color=\"#333\" waves=\"md-waves-effect\" onClick={ ()=>this.onClose() } />\n                    <Button text=\"确 认\" waves=\"md-waves-effect\" color=\"#2163f7\" disable={ this.state.disable } style={{ 'font-weight': 'bold' }} onClick={ ()=>this.onSave() } />\n                </sr-urls-footer>\n            </simpread-urlscheme>\n        )\n    }\n}\n\nfunction Render( type, opened, callback ) {\n    if ( $( \"simpread-urlscheme\" ).length > 0 ) return;\n    $( \"html\" ).append( `<div class=\"simpread-urlscheme\"></div>` );\n    ReactDOM.render( <URLScheme type={ type } off={ opened } url={ location.href } onChange={ (t,f,v)=>callback(t,f,v) } />, $( \".simpread-urlscheme\" )[0] );\n}\n\nexport {\n    Render\n}"
  },
  {
    "path": "src/module/welcome.jsx",
    "content": "console.log( \"===== simpread option welcome page load =====\" )\n\nimport 'carous_css';\nimport 'carousel';\n\nimport Button   from 'button';\n\nimport * as ss  from 'stylesheet';\nimport {br}     from 'browser';\nimport * as msg from 'message';\n\nconst welcbgcls   = \"welcome\",\n      welcbgclsjq = `.${welcbgcls}`,\n      welcbg      = `<div class=\"${ welcbgcls }\"></div>`;\nlet   curidx, max = [ 0, 0 ];\n\nconst style = {\n\n    root: {\n        display: 'flex',\n        flexDirection: 'column',\n        justifyContent: 'center',\n        alignItems: 'center',\n\n        position: 'relative',\n\n        minWidth: '400px',\n        minHeight: '400px',\n\n        width: '650px',\n        height: '600px',\n\n        backgroundColor: '#fff',\n\n        borderRadius: '3px',\n        boxShadow: 'rgba(0, 0, 0, .247059) 0px 14px 45px',\n\n        userSelect: 'none',\n    },\n\n    section: {\n        textAlign: 'center',\n    },\n\n    h2: {\n        marginBottom: 0,\n\n        color: 'inherit',\n\n        fontSize: '24px',\n        fontWeight: 'bold',\n\n        lineHeight: '32px',\n        textAlign: 'center',\n    },\n\n    desc: {\n        padding: '5px',\n\n        color: 'rgba(51, 51, 51, 0.7)',\n\n        fontSize: '15px',\n        fontWeight: 400,\n\n        lineHeight: '32px',\n        textAlign: 'center',\n    },\n\n    img: {\n        width: '100%',\n        marginTop: '-82px',\n    },\n\n    gif : {\n        paddingTop: '58px',\n        width: '550px',\n    },\n\n    strong: {\n        fontWeight: 'normal',\n        color: '#3f51b5',\n    },\n\n    footer: {\n        display: 'flex',\n        flexDirection: 'row',\n\n        marginBottom: '24px',\n    },\n\n    close: {\n        position: 'absolute',\n\n        top: 0,\n        right: 0,\n    },\n\n}\n\nclass Welcome extends React.Component {\n\n    state = {\n        style: { display: \"none\" },\n        state: \"next_icon\",\n    }\n\n    prevClick() {\n        $( '.carousel.carousel-slider' ).carousel( \"prev\" );\n    }\n\n    nextClick() {\n        if ( curidx != max ) {\n            $( '.carousel.carousel-slider' ).carousel( \"next\" );\n        } else this.closeClick();\n    }\n\n    closeClick() {\n        window.dispatchEvent( new CustomEvent( msg.MESSAGE_ACTION.welcome_close, { detail: { first: this.props.first, version: this.props.version }}));\n        exit();\n    }\n\n    carousel() {\n        $( \".carousel-item\" ).map( ( _, item ) => {\n            const $item = $(item),\n                  version = $item.attr( \"id\" );\n            version != \"end\" && version != \"start\" && version != this.props.version && $item.remove();\n        });\n    }\n\n    componentDidMount() {\n        !this.props.first && this.carousel();\n        max = $( \".carousel-item\" ).length - 1;\n        $( '.carousel.carousel-slider' ).carousel({\n            fullWidth: true,\n            onCycleTo: idx => {\n                curidx = idx;\n                if ( curidx == max ) {\n                    this.setState({\n                        style: { display: \"block\" },\n                        state: \"right_icon\",\n                    });\n                } else if ( curidx == 0 ) {\n                    this.setState({\n                        style: { display: \"none\" },\n                        state: \"next_icon\",\n                    });\n                } else {\n                    this.state.style.display != \"block\" && this.setState({ style: { display: \"block\" } });\n                    this.state.state != \"next_icon\"     && this.setState({ state: \"next_icon\" });\n                }\n            }\n        });\n    }\n\n    componentWillUnmount() {\n        $( welcbgclsjq ).remove();\n    }\n\n    render() {\n        const { first, version } = this.props;\n        return (\n            <welcome style={ style.root }>\n                <div className=\"carousel carousel-slider\" data-indicators=\"true\">\n\n                    <div className=\"carousel-item chrome\" id=\"start\">\n                        <section style={ style.section }>\n                        <img src={ ss.IconPath( \"welcome\" )} style={ style.img }/>\n                            <h2 style={{ ...style.h2, ...{ 'margin-bottom': 0 } }}>{ this.props.first ? \"欢迎使用 简悦\": \"简悦 已升至最新版\" }</h2>\n                            <div style={ style.desc }>\n                                { br.isFirefox() ? \"Chrome 好评率超过 99% 的阅读模式现已来到 Firefox\" : \"让你瞬间进入沉浸式阅读的 Chrome 扩展，类似 Safari 的阅读模式\" }<br/>\n                                去掉干扰元素，提升阅读体验，<strong style={ style.strong }>「简」</strong>单阅读，愉<strong style={ style.strong }>「悦」</strong>心情<br/>\n                                为了达到 <strong style={ style.strong }>「完美」</strong> 的阅读模式，简悦适配了 <strong style={ style.strong }><a target=\"_blank\" href=\"https://simpread.ksria.cn/sites/\">数百种类型</a></strong> 的网站\n                            </div>\n                        </section>\n                    </div>\n\n                    { first &&\n                        <div className=\"carousel-item\">\n                            <section style={ style.section }>\n                                <img src={ ss.IconPath( \"welcome-mode\" )} style={ style.img }/>\n                                <h2 style={ style.h2 }>阅读模式 与 聚焦模式</h2>\n                                <div style={ style.desc }>\n                                    阅读模式 → <strong>独有功能</strong>，自动提取适配页面的标题、描述、正文、媒体等资源<br/>\n                                    支持 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选\">手动框选</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/主动适配阅读模式\">主动适配模式</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\">智能适配模式</a>\n                                    · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/论坛类页面及分页\">论坛类页面 / 分页</a><br/>\n                                    <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/聚焦模式\">聚焦模式</a> → 高亮鼠标所在的文章段落，不改变当前页面的结构<br/>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { (( !first && version == \"1.1.1\" ) || version == \"all\" ) && \n                        <div className=\"carousel-item\" id=\"1.1.1\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-adapter.png\" style={ style.img }/>\n                                <h2 style={ style.h2 }>更智能的正文提取功能</h2>\n                                <div style={ style.desc }>\n                                    全新的 <b>词法分析引擎</b><sup>2.0</sup>，简悦可以识别出 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/TXT-阅读器\">TXT</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=markdown-识别\">Markdown</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=latex-识别\">LaTeX</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=代码段的高亮\">代码段</a><br/>\n                                    Wordpress · Hexo · Ghost · Discuz 等博客 / 论坛的页面了！<br/>\n                                    甚至，只要是结构良好的页面，（无需适配）自动生成阅读模式，详细 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\">请看这里</a>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { first &&\n                        <div className=\"carousel-item\" id=\"1.0.3\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-service-v2.png?201806111215\" style={ style.img }/>\n                                <h2 style={ style.h2 }>连接你的生产力工具</h2>\n                                <div style={ style.desc }>\n                                    支持下载 HTML · PDF · Markdown · PNG · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/发送到-Epub\">Epub</a> 到本地 以及 发送到 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/发送到-Kindle\">Kindle</a><br/>\n                                    支持输出到 坚果云 · 有道云笔记 · 为知笔记 · 语雀 · 印象笔记 · Dropbox · Onenote · Notion 等<br/>\n                                    发送页面链接到 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/稍后读\">稍后读</a> · Pocket · Instapaper · Linnk，详细 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/导出到生产力工具\">请看这里</a>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { first &&\n                        <div className=\"carousel-item\" id=\"1.1.0\">\n                            <section style={ style.section }>\n                                <img src={ ss.IconPath( \"welcome-custom\" )} style={ style.img }/>\n                                <h2 style={ style.h2 }>站点编辑器 · 站点适配源 · 站点管理器</h2>\n                                <div style={ style.desc }>\n                                    页面上任意元素均可隐藏，更支持编程，详细请看 <a href=\"http://ksria.com/simpread/docs/#/站点编辑器\" target=\"_blank\">站点编辑器</a><br/>\n                                    更灵活、社区化的多种 <a href=\"http://ksria.com/simpread/docs/#/站点适配源\" target=\"_blank\">站点适配源</a><br/>\n                                    内置了 <a href=\"http://ksria.com/simpread/docs/#/站点管理器\" target=\"_blank\">站点管理器</a>，方便管理全部的适配站点\n                                </div>\n                            </section>\n                        </div> }\n\n                    { !first && version == \"1.1.1\" && \n                        <div className=\"carousel-item\" id=\"1.1.1\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-fap.png\" style={ style.img }/>\n                                <h2 style={ style.h2 }>全新的控制栏面板</h2>\n                                <div style={ style.desc }>\n                                    「告别」传统、单一的控制栏，全部功能「一览无余」<br/>\n                                    主题、字体样式、大小、版面布局更改一键完成<br/>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { (( !first && version == \"1.1.2\" ) || version == \"all\" ) && \n                        <div className=\"carousel-item\" id=\"1.1.2\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-plugins.png\" style={ style.img }/>\n                                <h2 style={ style.h2 }>插件系统</h2>\n                                <div style={ style.desc }>\n                                    <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/kw36BtjGu0\">字数统计</a> · <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/klGUASLasg\">代码段增强</a> · <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/VQOZdNET2d\">点击查看大图（Lightbox）</a> · <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/ohnTKVHz4a\">划词翻译</a> 一个不能少 <br/>\n                                    使用 JavaScript 编写基于简悦的插件，详细说明请看 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/插件系统\">说明文档</a><br/>\n                                    现在就安装适合你的插件吧 → <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/\">插件中心</a>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { (( !first && version == \"1.1.2\" ) || version == \"all\" ) && \n                        <div className=\"carousel-item\" id=\"1.1.2\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-sites.png\" style={ style.img }/>\n                                <h2 style={ style.h2 }>站点集市</h2>\n                                <div style={ style.desc }>\n                                    方便提交，让你的站点为数以万计的简悦用户使用<br/>\n                                    官方主适配源、第三方适配源、站点集市适配源、自定义适配源一站式浏览<br/>\n                                    现在就访问 <a target=\"_blank\" href=\"https://simpread.ksria.cn/sites/\">站点集市</a> 吧，看看有什么增加的新适配站点\n                                </div>\n                            </section>\n                        </div> }\n\n                    { !first &&\n                            <div className=\"carousel-item\" id=\"5005\">\n                                <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-puread-ii.png\" style={ style.img }/>\n                                    <h2 style={ style.h2 }>词法分析引擎 2.0</h2>\n                                    <div style={ style.desc }>\n                                        更加智能，更加专业，Markdown / LaTeX / 代码段 均不在话下<br/>\n                                        重新整理并根据更适合中文阅读的方式排版，让你爱上在 PC 阅读文章<br/>\n                                        详细说明请看 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\">词法分析引擎</a>\n                                    </div>\n                                </section>\n                            </div> }\n\n                    { !first && version == \"1.1.3\" &&\n                        <div className=\"carousel-item\" id=\"1.1.3\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-newservice.png?201906301335\" style={ style.img }/>\n                                <h2 style={ style.h2 }>导出服务又添新成员，更支持 WebDAV</h2>\n                                <div style={ style.desc }>\n                                    期待已久的 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/导出到生产力工具\">语雀</a> 和 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/坚果云\">坚果云</a> 现已加入 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/导出到生产力工具\">导出服务</a> 豪华大礼包<br/>\n                                    配置文件的同步也可使用 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/同步\">坚果云</a> 了<br/>\n                                    不仅如此，只要是支持 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/WebDAV\">WebDAV</a> 的服务均可使用简悦的导出功能\n                                </div>\n                            </section>\n                        </div> }\n\n                    { !first && version == \"1.1.4\" &&\n                        <div className=\"carousel-item\" id=\"1.1.4\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-newservice-ii.png?202001181335\" style={ style.img }/>\n                                <h2 style={ style.h2 }>更强大，更易用的导出服务</h2>\n                                <div style={ style.desc }>\n                                    期待已久的 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/Notion\">Notion</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/有道云笔记\">有道云笔记</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/为知笔记\">为知笔记</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/URLSCHEME\">Bear</a> · <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/URLSCHEME\">Ulysses</a> 来啦~<br/>\n                                    原生的 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/离线HTML\">离线 HTML / Markdown</a> 下载功能，还有截取任意位置的 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/截图\">截图</a> 功能<br/>\n                                    <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/WebDAV?id=定制\">WebDAV</a> 现已定制导出格式，包括： <span>Markdown</span> · <span>HTML</span>\n                                </div>\n                            </section>\n                        </div> }\n\n                    { (( !first && version == \"1.1.3\" ) || version == \"all\" ) && \n                        <div className=\"carousel-item\" id=\"1.1.3\">\n                            <section style={ style.section }>\n                                <img src=\"http://sr.ksria.cn/welcome-notice.png?20190630\" style={ style.img }/>\n                                <h2 style={ style.h2 }>消息中心 · 帮助中心 · 新手入门</h2>\n                                <div style={ style.desc }>\n                                    <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/消息中心\">消息中心</a> 让沟通更加便利<br/>\n                                    内置常用的文档说明、常见问题、及选项页全部功能说明的 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/帮助中心\">帮助中心</a><br/>\n                                    功能太多，无从下手？<a target=\"_blank\" href=\"http://ksria.com/simpread/guide\">新手入门</a> 不再让新手望而却步\n                                </div>\n                            </section>\n                        </div> }\n\n                    <div className=\"carousel-item\" id=\"end\">\n                        <section style={ style.section }>\n                        <img src={ ss.IconPath( \"welcome-others\" )} style={ style.img }/>\n                            <h2 style={ style.h2 }>更多功能 等你发现！</h2>\n                            { !first && version == \"5005\" && <div style={ style.desc }>\n                                分享卡，右键菜单添加 「白名单 / 排除列表 / 黑名单」等<br/>\n                                详细说明请看 <a target=\"_blank\" href=\"http://ksria.com/simpread/welcome/version_1.1.2.5005.html\">更新日志</a>\n                            </div> }\n                            { !first && version == \"1.1.3\" && <div style={ style.desc }>\n                                <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=预加载机制\">预加载</a> <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=延迟加载\">延迟加载</a> <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=智能感知\">智能感知</a> 与 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选?id=二次确认\">更便捷的手动框选</a> 等诸多新功能<br/>\n                                详细说明请看 <a target=\"_blank\" href=\"http://ksria.com/simpread/welcome/version_1.1.3.html\">更新日志</a>\n                            </div> }\n                        </section>\n                    </div>\n                </div>\n                <footer style={ style.footer }>\n                    <Button style={ this.state.style }\n                        shape=\"circle\" width=\"40px\"\n                        color=\"#fff\" backgroundColor=\"#C8E6C9\"\n                        icon={ ss.IconPath( \"prev_icon\" ) }\n                        waves=\"md-waves-effect md-waves-button\"\n                        onClick={ ()=>this.prevClick() } />\n                    <Button\n                        shape=\"circle\" width=\"40px\"\n                        color=\"#fff\" backgroundColor=\"#4CAF50\"\n                        icon={ ss.IconPath( this.state.state ) }\n                        waves=\"md-waves-effect md-waves-button\"\n                        onClick={ ()=>this.nextClick() } />\n                </footer>\n                <div style={ style.close }>\n                    <Button\n                        shape=\"circle\" width=\"36px\"\n                        color=\"#fff\" backgroundColor=\"transparent\" hoverColor=\"transparent\"\n                        icon={ ss.IconPath( \"close_icon\" ) }\n                        onClick={ ()=>this.closeClick() } />\n                </div>\n            </welcome>\n        )\n    }\n}\n\n/**\n * Exit\n */\nfunction exit() {\n    $( welcbgclsjq ).velocity({ opacity: 0 }, { complete: () => {\n        ReactDOM.unmountComponentAtNode( $(welcbgclsjq)[0] );\n    }});\n}\n\n/**\n * Welcome Render\n * \n * @param {string} root name\n * @param {boolean} true: first load\n * @param {string} version\n */\nexport function Render( root, first, version ) {\n    const $root = $( root );\n    if ( $root.find( \".\" + welcbgcls ).length == 0 ) {\n        $root.append( welcbg );\n    }\n    ReactDOM.render( <Welcome first={ first } version={ version } />, $( welcbgclsjq )[0] );\n}"
  },
  {
    "path": "src/options/corb.html",
    "content": "<html>\n<script src=\"../bundle/common.js\"></script>\n<script src=\"../bundle/vendors.js\"></script>\n<script src=\"../bundle/corb.js\"></script>\n</html>"
  },
  {
    "path": "src/options/corb.js",
    "content": "\nimport axios      from 'axios';\n\nimport {browser}  from 'browser';\nimport * as msg   from 'message';\n\n/**\n * Listen runtime message, include: `axios`\n */\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\n    if ( request.type == msg.MESSAGE_ACTION.AXIOS ) {\n        if ( request.value.type == \"post\" ) {\n            if ( request.value.form ) {\n                request.value.data = new FormData();\n                Object.keys( request.value.form ).forEach( key => request.value.data.append( key, request.value.form[key] ) );\n            }\n            axios.post( request.value.url, request.value.data )\n                .then( response => sendResponse({ done: response }))\n                .catch( error   => sendResponse({ fail: error    }));\n        } else if ( request.value.type == 'put' ) {\n            axios.put( request.value.url, request.value.content, request.value.data )\n                .then( response => sendResponse({ done: response }))\n                .catch( error   => sendResponse({ fail: error    }));\n        } else if( request.value.type == 'download' ) {\n            axios( request.value )\n                .then( response => sendResponse({ done: response }))\n                .catch( error   => sendResponse({ fail: error    }));\n        }\n    }\n    return true;\n});\n\n/**\n * Listen runtime message, include: `notion`\n */\nconst downLoadCache = new Map();\n\nbrowser.runtime.onMessage.addListener( async function ( request, sender, sendResponse ) {\n    if ( request.type == msg.MESSAGE_ACTION.notion_dl_img ) {\n        try {\n            const option  = request.value,\n                  { url, protocol } = option,\n                  dlRes   = await axios({ method: 'get', url: url.replace( /https?:/, protocol ), responseType: 'blob', });\n            let blob      = dlRes.data;\n\n            if ( blob.type === 'image/webp' ) {\n                blob = blob.slice( 0, blob.size, 'image/jpeg' );\n            }\n            downLoadCache.set( url, blob );\n            sendResponse({\n                done: {\n                    type: blob.type,\n                    size: blob.size,\n                    url,\n                },\n            });\n        } catch ( err ) {\n            sendResponse({ fail: err });\n        }\n    } else if ( request.type == msg.MESSAGE_ACTION.notion_up_img ) {\n        try {\n            const option = request.value,\n                  { url, upUrl } = option,\n                  blob   = downLoadCache.get( url );\n\n            await axios.put( upUrl, blob, {\n                headers: {\n                    'Content-Type': blob.type,\n                },\n            });\n\n            downLoadCache.delete( url );\n            sendResponse({ done: true });\n        } catch ( err ) {\n            sendResponse({ fail: err });\n        }\n    }\n    return true;\n});"
  },
  {
    "path": "src/options/custom.html",
    "content": "<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>自定义样式 - 简悦 · 选项页</title>\n</head>\n<body style=\"opacity: 0;\">\n    <div class=\"header\"><div class=\"nav\"></div><div class=\"title\">自定义样式</div></div>\n    <div class=\"custom\">\n        <div class=\"property\">Property</div>\n        <div class=\"preview simpread-theme-root\">\n            <sr-read>\n                <sr-rd-title>简悦 SimpRead - 为你提供「如杂志般沉浸式阅读体验」的扩展</sr-rd-title>\n                <sr-rd-desc>为了达到完美的阅读模式这个小目标 ，我适配了 数百种类型 的网站，因此诞生了简悦。</sr-rd-desc>\n                <sr-rd-content>\n                    <sr-blockquote>\n                        为了完美阅读模式这个小目标 ，我适配了 数百种类型 的网站，因此诞生了它 - 简悦 ：沉浸式阅读的扩展\n                    </sr-blockquote>\n                    <p>大家好，我叫 <a href=\"http://weibo.com/23784148/\" title=\"Kenshin\">Kenshin</a>，简悦的作者。</p>\n                    <h4>主要功能包括：</h4>\n                    <ul><li>聚焦模式 / 阅读模式 / 临时阅读模式 / 主动阅读模式 / TXT 阅读器；</li><li>站点编辑器，站点适配源，站点管理器；</li> <li>丰富的导出功能，包括：Pocket / Instapaper / Evernote / Onenote / Dropdox 等；</li> <li>多种字体样式、大小、版面、主题模式等；</li> <li>导出 / 导入 配置文件；</li><li>自定义样式，详细请看 <a href=\"http://ksria.com/simpread/docs/#/自定义样式\" target=\"_blank\">自定义主题</a>；</li></ul>\n                    <h4>阅读模式 <code>可编程</code> 支持：</h4>\n                    <pre class=\"hljs objectivec\"><code class=\"objectivec\">    {\n        <span class=\"hljs-string\">\"name\"</span>    : <span class=\"hljs-string\">\"qdaily.com\"</span>,\n        <span class=\"hljs-string\">\"url\"</span>     : <span class=\"hljs-string\">\"http://www.qdaily.com/articles/\"</span>,\n        <span class=\"hljs-string\">\"title\"</span>   : <span class=\"hljs-string\">\"&lt;h2 class='title'&gt;\"</span>,\n        <span class=\"hljs-string\">\"desc\"</span>    : <span class=\"hljs-string\">\"&lt;p class='excerpt'&gt;\"</span>,\n        <span class=\"hljs-string\">\"include\"</span> : <span class=\"hljs-string\">\"&lt;div class='article-detail-bd'&gt;\"</span>,\n        <span class=\"hljs-string\">\"exclude\"</span> : [\n            <span class=\"hljs-string\">\"&lt;div class='author-share'&gt;\"</span>,\n            <span class=\"hljs-string\">\"&lt;div class='embed-control'&gt;\"</span>\n            ]\n    }\n                    </code></pre>\n                    <h4>H1 ~ H6 的样式：</h4>\n                    <div>\n                        <h1>我是 H1</h1>\n                        <h2>我是 H2</h2>\n                        <h3>我是 H3</h3>\n                        <h4>我是 H4</h4>\n                        <h5>我是 H5</h5>\n                        <h6>我是 H6</h6>\n                    </div>\n                    <h4>表格样式：</h4>\n                    <table>\n                        <caption>Kenshin Wang 开源项目一览</caption>\n                        <thead>\n                            <tr>\n                                <th>名称</th>\n                                <th>作者</th>\n                                <th>主页</th>\n                            </tr>\n                        </thead>\n                        <tfoot>\n                            <tr>\n                                <td>名称</td>\n                                <td>作者</td>\n                                <td>主页</td>\n                            </tr>\n                        </tfoot>\n                        <tbody>\n                            <tr>\n                                <td>简悦</td>\n                                <td>Kenshin Wang</td>\n                                <td><a href=\"http://ksria.com/simpread\" target=\"_blank\">主页地址</a></td>\n                            </tr>\n                            <tr>\n                                <td>简 Tab</td>\n                                <td>（同上）</td>\n                                <td><a href=\"http://ksria.com/simptab\" target=\"_blank\">主页地址</a></td>\n                            </tr>\n                            <tr>\n                                <td>gnvm</td>\n                                <td>（同上）</td>\n                                <td><a href=\"http://ksria.com/simptab\" target=\"_blank\">主页地址</a></td>\n                            </tr>\n                        </tbody>\n                    </table>\n                </sr-rd-content>\n            </sr-read>\n        </div>\n    </div>\n    <div class=\"bottom\">\n        <span>简悦 SimpRead - 为你提供「如杂志般沉浸式阅读体验」的扩展</span> <span>&nbsp;©&nbsp;2017&nbsp;-&nbsp;2020&nbsp;<a href=\"http://ksria.com/simpread\">ksria.com</a> by <a href=\"http://kenshin.wang\" target=\"_blank\">Kenshin Wang</a></span>\n    </div>\n    <script src=\"../ga.js\"></script>\n    <script src=\"../bundle/common.js\"></script>\n    <script src=\"../bundle/vendors.js\"></script>\n    <script src=\"../bundle/custom.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "src/options/custom.js",
    "content": "console.log( \"==== simpread options page: custom load ====\" )\n\nimport '../assets/css/simpread.css';\nimport '../assets/css/options_page.css';\nimport '../assets/css/options_custom.css';\nimport 'notify_css';\n\nimport Velocity   from 'velocity';\nimport Notify     from 'notify';\n\nimport TextField  from 'textfield';\nimport SelField   from 'selectfield';\n\nimport Button     from 'button';\nimport * as waves from 'waves';\nimport * as tt    from 'tooltip';\nimport Switch     from 'switch';\n\nimport { storage, STORAGE_MODE as mode } from 'storage';\nimport * as ss    from 'stylesheet';\nimport * as conf  from 'config';\nimport * as ver   from 'version';\nimport * as watch from 'watch';\nimport th         from 'theme';\n\nlet cur_custom;\n\n/**\n * Entry:\n * - storage get data form chrome storage\n * - waves.Render()\n * - tooltip.Render()\n */\nstorage.Read( () => {\n    console.log( \"simpread storage get success!\", storage.focus, storage.read );\n    cur_custom = storage.read.custom;\n    navRender();\n    propertyRender();\n    setPreviewStyle();\n    tt.Render( \"body\" );\n    waves.Render({ root: \"body\" });\n    $( \"body\" ).velocity({ opacity: 1 }, { duration: 1000, complete: ()=> {\n        $( \"body\" ).removeAttr( \"style\" );\n    }});\n}); \n\n/**\n * navigation Render\n */\nfunction navRender() {\n    const navClick = () => {\n        location.href = location.origin + \"/options/options.html#labs\";\n    };\n    const button = <Button waves=\"md-waves-effect md-waves-circle\" hoverColor=\"transparent\" icon={ ss.IconPath( \"gohome_icon\" ) } onClick={ ()=>navClick() } />;\n    ReactDOM.render( button, $( \".header .nav\" )[0] );\n}\n\n/**\n * Set preview style\n */\nfunction setPreviewStyle() {\n    th.Change( storage.read.theme );\n    ss.FontFamily( storage.read.fontfamily );\n    ss.FontSize( storage.read.fontsize );\n    ss.Layout( storage.read.layout );\n    ss.Preview( storage.read.custom );\n}\n\n/**\n * Property Render\n */\nfunction propertyRender() {\n    const getThemes = ( names, values ) => {\n        const arr = [];\n        return names.map( ( name, idx ) => {\n            return { value: values[idx], name : name }\n        });\n    },\n    changeTheme = ( value, name ) => {\n        storage.read.theme = th.names[conf.readLabels.indexOf( name )];\n        th.Change( storage.read.theme );\n    },\n    change     = ( type, props, value ) => {\n        typeof value == \"string\" && ( value = value.trim());\n        if ( type == \"global\" ) {\n            props == \"Layout\" && ( value = `${ 100 - value }%` );\n            ss[props]( value );\n            storage.read[props.toLowerCase()] = value;\n        } else if ( props ) {\n            cur_custom[type][props] = value;\n            props == \"textShadow\" && ( cur_custom[type][\"textShadow\"]  = value ? \"0 1px rgba(0,0,0,0.3)\" : \"\" );\n            ss.Custom( type, cur_custom[type] );\n        } else {\n            cur_custom[type] = value;\n            ss.CSS( type, value );\n        }\n    },\n    click      = type => {\n        if ( type == \"save\" ) {\n            save();\n        } else {\n            new Notify().Render( \"snackbar\", \"是否初始化 自定义样式 的全部数据？\", \"同意\", () => {\n                Object.keys( cur_custom ).forEach( v => {\n                    v != \"css\" ? Object.keys( cur_custom[v] ).forEach( item => {\n                        cur_custom[v][item] = \"\";\n                    }) : cur_custom[v] = \"\";\n                });\n                save( \"clear\" );\n            });\n        }\n    },\n    save       = state => {\n        storage.Write( () => {\n            watch.SendMessage( \"option\", true );\n            state == \"clear\" ? \n                new Notify().Render( \"snackbar\", \"清除成功，此页面需刷新后才能生效！\", \"刷新 \", ()=>{\n                    location.href = location.origin + location.pathname;\n                })\n            : new Notify().Render( 0, \"保存成功，页面刷新后生效！\" );\n        });\n    };\n    const doms = <div>\n                    <group className=\"lab\">\n                        <h1>帮助</h1>\n                        <group>\n                            <p>如何自定义样式，详细 <a href=\"http://ksria.com/simpread/docs/#/自定义样式\" target=\"_blank\">请看这里</a><br/>\n                            独乐乐不如众乐乐！ <a href=\"https://github.com/Kenshin/simpread/issues/71\" target=\"_blank\">分享你的主题</a></p>\n                        </group>\n                    </group>\n\n                    <group className=\"lab\">\n                        <h1>全局</h1>\n                        <group>\n                            <SelField waves=\"md-waves-effect\"\n                                floatingtext=\"主题\"\n                                name={ conf.readLabels[ th.names.indexOf( storage.read.theme ) ] }\n                                items={ getThemes( conf.readLabels, th.colors )}\n                                onChange={ (v,n)=>changeTheme(v,n) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体样式\"\n                                placeholder=\"支持 CSS3 font-family 值\"\n                                value = { storage.read.fontfamily }\n                                onChange={ (evt)=>change( \"global\", \"FontFamily\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"版面布局\"\n                                placeholder=\"仅支持百分比的数值，取值范围 70 ~ 100\"\n                                value = { 100 - parseInt( storage.read.layout == \"\" ? 20 : storage.read.layout ) }\n                                onChange={ (evt)=>change( \"global\", \"Layout\", evt.target.value ) }\n                            />\n                        </group>\n                    </group>\n\n                    <group className=\"lab\">\n                        <h1>标题与描述</h1>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"标题字体样式\"\n                                placeholder=\"支持 CSS3 font-family 值\"\n                                value = { cur_custom.title.fontFamily }\n                                onChange={ (evt)=>change( \"title\", \"fontFamily\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"标题字体大小\"\n                                placeholder=\"支持 CSS3 font-size 值\"\n                                value = { cur_custom.title.fontSize }\n                                onChange={ (evt)=>change( \"title\", \"fontSize\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"标题颜色\"\n                                placeholder=\"支持 CSS3 color 颜色值\"\n                                value = { cur_custom.title.color }\n                                onChange={ (evt)=>change( \"title\", \"color\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"描述字体样式\"\n                                placeholder=\"支持 CSS3 font-family 值\"\n                                value = { cur_custom.desc.fontFamily }\n                                onChange={ (evt)=>change( \"desc\", \"fontFamily\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"描述字体大小\"\n                                placeholder=\"支持 CSS3 font-size 值\"\n                                value = { cur_custom.desc.fontSize }\n                                onChange={ (evt)=>change( \"desc\", \"fontSize\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"描述颜色\"\n                                placeholder=\"支持 CSS3 color 颜色值\"\n                                value = { cur_custom.desc.color }\n                                onChange={ (evt)=>change( \"desc\", \"color\", evt.target.value ) }\n                            />\n                        </group>\n                    </group>\n\n                    <group className=\"lab\">\n                        <h1>正文</h1>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体样式\"\n                                placeholder=\"支持 CSS3 font-family 值\"\n                                value = { cur_custom.art.fontFamily }\n                                onChange={ (evt)=>change( \"art\", \"fontFamily\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体大小\"\n                                placeholder=\"支持 CSS3 font-size 值\"\n                                value = { cur_custom.art.fontSize }\n                                onChange={ (evt)=>change( \"art\", \"fontSize\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体颜色\"\n                                placeholder=\"支持 CSS3 color 颜色值\"\n                                value = { cur_custom.art.color }\n                                onChange={ (evt)=>change( \"art\", \"color\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字重\"\n                                placeholder=\"支持 CSS3 font-weight 值\"\n                                value = { cur_custom.art.fontWeight }\n                                onChange={ (evt)=>change( \"art\", \"fontWeight\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"单词间距\"\n                                placeholder=\"支持 CSS3 word-spacing 值\"\n                                value = { cur_custom.art.wordSpacing }\n                                onChange={ (evt)=>change( \"art\", \"wordSpacing\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字间距\"\n                                placeholder=\"支持 CSS3 letter-spacing 值\"\n                                value = { cur_custom.art.letterSpacing }\n                                onChange={ (evt)=>change( \"art\", \"letterSpacing\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"行间距\"\n                                placeholder=\"支持 CSS3 line-height 值\"\n                                value = { cur_custom.art.lineHeight }\n                                onChange={ (evt)=>change( \"art\", \"lineHeight\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"首行缩进\"\n                                placeholder=\"支持 CSS3 text-indent 值\"\n                                value = { cur_custom.art.textIndent }\n                                onChange={ (evt)=>change( \"art\", \"textIndent\", evt.target.value ) }\n                            />\n                        </group>\n                    </group>\n\n                    <group className=\"lab\">\n                        <h1>代码段</h1>\n                        <group>\n                            <Switch width=\"100%\" checked={ false }\n                                thumbedColor=\"#3F51B5\" trackedColor=\"#7986CB\" waves=\"md-waves-effect\"\n                                label=\"是否启用阴影\"\n                                checked = { cur_custom.pre.textShadow ? true : false }\n                                onChange={ (s)=>change( \"pre\", \"textShadow\", s ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体样式\"\n                                placeholder=\"支持 CSS3 font-family 值\"\n                                value = { cur_custom.code.fontFamily }\n                                onChange={ (evt)=>change( \"code\", \"fontFamily\", evt.target.value ) }\n                            />\n                        </group>\n                        <group>\n                            <TextField \n                                multi={ false }\n                                floatingtext=\"字体大小\"\n                                placeholder=\"支持 CSS3 font-size 值\"\n                                value = { cur_custom.code.fontSize }\n                                onChange={ (evt)=>change( \"code\", \"fontSize\", evt.target.value ) }\n                            />\n                        </group>\n                    </group>\n\n                    <group className=\"lab\">\n                        <h1>自定义 CSS</h1>\n                        <group>\n                            <TextField \n                                multi ={ true } rows ={ 10 }\n                                value = { cur_custom.css }\n                                onChange={ (evt)=>change( \"css\", \"\", evt.target.value ) }\n                            />\n                        </group>\n                    </group>\n\n                    <group>\n                        <group>\n                            <Button type=\"raised\" text=\"保存\" width=\"100%\"\n                                style={{ \"margin\": \"0\" }}\n                                icon={ ss.IconPath( \"save_icon\" ) }\n                                color=\"#fff\" backgroundColor=\"#3f51b5\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>click( \"save\" ) }\n                            />\n                        </group>\n\n                        <group>\n                            <Button type=\"raised\" text=\"清空并初始化\" width=\"100%\"\n                                style={{ \"margin\": \"0\" }}\n                                icon={ ss.IconPath( \"clear_icon\" ) }\n                                tooltip={{ text: \"不包含：主题、字体类型、字体大小、版面布局等；\" }}\n                                color=\"#fff\" backgroundColor=\"#FF5252\"\n                                waves=\"md-waves-effect md-waves-button\"\n                                onClick={ ()=>click( \"clear\" ) }\n                            />\n                        </group>\n                    </group>\n\n                </div>;\n    ReactDOM.render( doms, $( \".custom .property\" )[0] );\n}"
  },
  {
    "path": "src/options/notice.html",
    "content": "<html>\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>消息中心 - 简悦 · 选项页</title>\n</head>\n<body style=\"opacity: 0;\">\n    <div class=\"header\"><div class=\"nav\"></div><div class=\"title\">消息中心</div></div>\n    <div class=\"notice\"></div>\n    <div class=\"bottom\">\n        <span>简悦 SimpRead - 为你提供「如杂志般沉浸式阅读体验」的扩展</span> <span>&nbsp;©&nbsp;2017&nbsp;-&nbsp;2020&nbsp;<a href=\"http://ksria.com/simpread\">ksria.com</a> by <a href=\"http://kenshin.wang\" target=\"_blank\">Kenshin Wang</a></span>\n    </div>\n    <script src=\"../ga.js\"></script>\n    <script src=\"../bundle/common.js\"></script>\n    <script src=\"../bundle/vendors.js\"></script>\n    <script src=\"../bundle/notice.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "src/options/notice.js",
    "content": "console.log( \"==== simpread options page: notice load ====\" )\n\nimport '../assets/css/simpread.css';\nimport '../assets/css/options_page.css';\nimport '../assets/css/options_notice.css';\nimport 'notify_css';\n\nimport Velocity   from 'velocity';\nimport Button     from 'button';\nimport * as waves from 'waves';\nimport * as tt    from 'tooltip';\n\nimport Notice     from 'notice';\n\nimport {storage}  from 'storage';\nimport * as ss    from 'stylesheet';\n\n/**\n * Entry\n */\nstorage.Read( () => {\n    console.log( \"simpread storage get success!\", storage );\n    navRender();\n    noticeRender();\n    tt.Render( \"body\" );\n    waves.Render({ root: \"body\" });\n    $( \"body\" ).velocity({ opacity: 1 }, { duration: 1000, complete: ()=> {\n        $( \"body\" ).removeAttr( \"style\" );\n    }});\n});\n\n/**\n * navigation Render\n */\nfunction navRender() {\n    const navClick = () => {\n        location.href = location.origin + \"/options/options.html\";\n    };\n    const button = <Button waves=\"md-waves-effect md-waves-circle\" hoverColor=\"transparent\" icon={ ss.IconPath( \"gohome_icon\" ) } onClick={ ()=>navClick() } />;\n    ReactDOM.render( button, $( \".header .nav\" )[0] );\n}\n\n/**\n * notice Render\n */\nfunction noticeRender() {\n    let is_update = location.search == \"?is_update=true\" ? true : false;\n    ReactDOM.render( <Notice is_update={ is_update } />, $( \".notice\" )[0] );\n    history.pushState( \"\", \"\", \"/options/notice.html\" );\n}"
  },
  {
    "path": "src/options/options.html",
    "content": "<html>\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n    <title>选项页 - 简悦 SimpRead</title>\r\n    <style>\r\n        .loadingbar{position:fixed;top:0;left:0;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;-ms-flex-align:center;align-items:center;height:100%;width:100%;background-color:#fafafa;z-index:200}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"loadingbar\"><img class=\"heartBeat animated\" src=\"/assets/images/icon128.png\" width=\"80px\"></div>\r\n    <div class=\"sidebar\"></div>\r\n    <div class=\"topnav nav\"></div>\r\n    <div class=\"header\"><div class=\"nav\"></div><div class=\"title\"></div></div>\r\n    <div class=\"top\"></div>\r\n    <div class=\"main\">\r\n        <div class=\"tabscontainer\"></div>\r\n    </div>\r\n    <div class=\"bottom\" style=\"opacity:0;\">\r\n        <span>简悦 SimpRead - 为你提供「如杂志般沉浸式阅读体验」的扩展</span> <span>&nbsp;©&nbsp;2017&nbsp;-&nbsp;2020&nbsp;<a href=\"http://ksria.com/simpread\">ksria.com</a> by <a href=\"http://kenshin.wang\" target=\"_blank\">Kenshin Wang</a></span>\r\n    </div>\r\n    <script src=\"../ga.js\"></script>\r\n    <script src=\"../bundle/common.js\"></script>\r\n    <script src=\"../bundle/vendors.js\"></script>\r\n    <script src=\"../bundle/options.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "src/options/options.js",
    "content": "console.log( \"==== simpread options page load ====\" )\r\n\r\nimport '../assets/css/options_page.css';\r\nimport '../assets/css/setting.css';\r\nimport 'mduikit_css';\r\nimport 'notify_css';\r\nimport 'intro_css';\r\n\r\nimport Velocity   from 'velocity';\r\nimport Notify     from 'notify';\r\n\r\nimport Tabs       from 'tabs';\r\nimport * as waves from 'waves';\r\nimport * as tt    from 'tooltip';\r\nimport Button     from 'button';\r\nimport * as side  from 'sidebar';\r\n\r\nimport { storage, STORAGE_MODE as mode } from 'storage';\r\nimport local      from 'local';\r\nimport * as ss    from 'stylesheet';\r\nimport * as conf  from 'config';\r\nimport * as ver   from 'version';\r\nimport * as watch from 'watch';\r\nimport {browser,br}from 'browser';\r\nimport * as msg   from 'message';\r\nimport * as exp   from 'export';\r\n\r\nimport FocusOpt   from 'focusopt';\r\nimport ReadOpt    from 'readopt';\r\nimport CommonOpt  from 'commonopt';\r\nimport LabsOpt    from 'labsopt';\r\nimport PluginsOpt from 'pluginsopt';\r\nimport SitesOpts  from 'sitesopt';\r\nimport AccountOps from 'accountopt';\r\nimport About      from 'about';\r\nimport Unrdist    from 'unrdist';\r\nimport * as welc  from 'welcome';\r\nimport * as guide from 'guide';\r\nimport * as fb    from 'feedback';\r\n\r\nimport PureRead   from 'puread';\r\n\r\nlet tabsItemID   = 0,\r\n    loadState    = { first: false, update: false },\r\n    website_sync = false; // when first and update checked versions.json\r\n\r\n/**\r\n * Add parallax scroll\r\n */\r\n$( window ).scroll( (event) => {\r\n    const $target = $( event.target ),\r\n          scroll  = $target.scrollTop(),\r\n          offset  = 0 - scroll;\r\n    scroll >  200 && ( $( \".header\" ).css({ opacity: 1, visibility: \"visible\" }) );\r\n    scroll <= 200 && ( $( \".header\" ).css({ opacity: 0, visibility: \"hidden\"  }) );\r\n    $( \".top\" ).css( \"transform\", `translate3d(0px, ${offset}px, 0px)` );\r\n});\r\n\r\n/**\r\n * Add event listenr\r\n */\r\nwindow.addEventListener( msg.MESSAGE_ACTION.turn_tab, event => {\r\n    const idx = event.detail.page;\r\n    tabChange( idx );\r\n});\r\n\r\nwindow.addEventListener( msg.MESSAGE_ACTION.welcome_close, event => {\r\n    const { first, version } = event.detail;\r\n    !first && new Notify().Render({ content: \"是否查看新版本的入门指引？\", action: \"确认\", cancel: \"取消\", callback: type => {\r\n        type == \"action\" && guide.Start( version );\r\n    }});\r\n});\r\n\r\n/**\r\n * Get tabsItemID from window.location.hash exist\r\n */\r\nwindow.location.hash && ( tabsItemID = conf.tabsItem.findIndex( item => window.location.hash.startsWith(item.route)));\r\ntabsItemID == -1 || tabsItemID == 0 ? tabsItemID = 0 : conf.tabsItem.forEach( ( item, index ) => item.active = tabsItemID == index ? true : false );\r\n\r\n/**\r\n * Listen runtime message\r\n */\r\nbrowser.runtime.onMessage.addListener( function( request, sender, sendResponse ) {\r\n    if ( request.type == msg.MESSAGE_ACTION.redirect_uri ) {\r\n        const { id, uri } = request.value;\r\n        if ([ \"pocket\", \"dropbox\", \"evernote\", \"gdrive\" ].includes( id )) {\r\n            exp[id].Accesstoken( uri );\r\n        } else if ( id == \"yinxiang\" ) {\r\n            exp.evernote.Accesstoken( uri );\r\n        } else if ( uri.indexOf( \"state=yuque_authorize\" ) > 0 ) {\r\n            exp.yuque.Accesstoken( uri );\r\n        } else {\r\n            id.startsWith( \"http://ksria.com/simpread/auth.html?\" ) &&\r\n            exp.onenote.Accesstoken( uri );\r\n        }\r\n    }\r\n});\r\n\r\n/**\r\n * Change tab\r\n * \r\n * @param {number} tab index\r\n */\r\nfunction tabChange( idx ) {\r\n    if ( idx == -1 ) return;\r\n    conf.tabsItem.forEach( ( item, index ) => item.active = idx == index ? true : false );\r\n    mainRender( idx );\r\n}\r\n\r\n/**\r\n * Entry\r\n */\r\nstorage.Read( first => {\r\n    console.log( \"simpread storage get success!\", storage.focus, storage.read, first );\r\n    loadingRender();\r\n    pRead();\r\n    hashnotify();\r\n    firstLoad( first );\r\n    sidebarRender();\r\n    navRender();\r\n    vernotify( first );\r\n    //welcomeRender( false, \"1.1.4\" );\r\n    mainRender( tabsItemID );\r\n    setTimeout(() => noticeRender(), 500 );\r\n    helpRender();\r\n    feedbackRender();\r\n    tt.Render( \"body\" );\r\n    waves.Render({ root: \"body\" });\r\n});\r\n\r\n/**\r\n * Loading Render\r\n */\r\nfunction loadingRender() {\r\n    setTimeout( () => {\r\n        $( '.loadingbar' ).animate({\r\n            opacity: 0,\r\n        }, () => {\r\n            $( '.loadingbar' ).remove();\r\n            $( \".bottom\" ).removeAttr( \"style\" );\r\n        });\r\n    }, 1000 );\r\n}\r\n\r\n/**\r\n * Pure Read\r\n*/\r\nfunction pRead() {\r\n    storage.puread     = new PureRead( storage.sites );\r\n    storage.pr.origins = storage.option.origins;\r\n    console.log( \"current puread object is   \", storage.pr )\r\n}\r\n\r\n/**\r\n * Incompatible and update \r\n */\r\nfunction updateData() {\r\n    ver.Incompatible( storage.version, storage.simpread ) && storage.Write( () => {\r\n        console.log( \"current simpread is update \", storage.simpread )\r\n        new Notify().Render({ type: 2, content: `检测到你曾经修改过第三方适配源，<b>务必刷新后重新导入</b>！<a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点适配源?id=第三方适配源\">详细说明</a>`, state: \"holdon\" });\r\n        watch.SendMessage( \"option\", true );\r\n    }, storage.simpread );\r\n    ver.VerifyPlugins( storage.option ) && new Notify().Render({ type: 2, content: `有需要清理的已失效插件，详细请看 <a href=\"http://ksria.com/simpread/welcome/version_${storage.version}.html#badplugins\" target=\"_blank\">失效插件</a>`, state: \"holdon\" });\r\n}\r\n\r\n/**\r\n * Hash notify\r\n */\r\nfunction hashnotify() {\r\n    const search = location.search,\r\n          prefix = \"?simpread_mode=\";\r\n    if ( search.startsWith( prefix ) ) {\r\n        switch ( search.replace( prefix, \"\" ) ) {\r\n            case \"reload\":\r\n                new Notify().Render( 0, \"数据导入成功！\" );\r\n                break;\r\n            case \"clear\":\r\n                new Notify().Render( 0, \"数据清除成功！\" );\r\n                break;\r\n            case \"sync\":\r\n                new Notify().Render( 0, \"数据同步成功！\" );\r\n                break;\r\n            default:\r\n                // TO-DO\r\n        }\r\n        history.pushState( \"\", \"\", \"/options/options.html\" );\r\n    }\r\n}\r\n\r\n/**\r\n * Version update notify\r\n * \r\n * @param {boolean} is first load\r\n */\r\nfunction vernotify( first ) {\r\n    const hash = location.hash;\r\n    if ( hash.startsWith( \"#firstload?ver=\" ) || hash.startsWith( \"#update?ver=\" ) ) {\r\n        const prefix  = hash.match( /\\w+/      )[0],\r\n              version = hash.match( /[0-9\\.]+/ )[0],\r\n              message = ver.Notify( first, prefix, version );\r\n\r\n        new Notify().Render( \"简悦 版本提示\", message );\r\n\r\n        loadState = { first: true };\r\n        if ( hash.startsWith( \"#update?ver=\" )) {\r\n            watch.SendMessage( \"version\", true );\r\n            loadState = { first: true, update: true };\r\n            welcomeRender( false, version );\r\n            updateData();\r\n        }\r\n        // website_sync = true; when version is 1.1.3 website_list is newer\r\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"install\", eventAction: hash.startsWith( \"#firstload?ver=\" ) ? \"install\" : \"update\", eventValue: hash.startsWith( \"#firstload?ver=\" ) ? \"install\" : \"update\" }) );\r\n        history.pushState( \"\", \"\", \"/options/options.html\" );\r\n    } else if ( hash.startsWith( \"#update?patch=\" ) ) {\r\n        const patch = hash.match( /[0-9\\.]+/ )[0];\r\n        patch == \"5005\" && welcomeRender( false, patch );\r\n        history.pushState( \"\", \"\", \"/options/options.html\" );\r\n    }\r\n    // silent update\r\n    if ( local.Patch( \"get\" ) ) {\r\n        new Notify().Render( \"简悦 版本提示\", ver.SilentUpdate() );\r\n        local.Patch( \"remove\" );\r\n    }\r\n}\r\n\r\n/**\r\n * First load\r\n *\r\n * @param {bool} is first load\r\n */\r\nfunction firstLoad( first ) {\r\n    first && storage.GetRemote( \"local\", ( result, error ) => {\r\n        if ( !error ) {\r\n            storage.pr.Addsites( result );\r\n            storage.Writesite( storage.pr.sites, () => storage.Statistics( \"create\" ) );\r\n        } else new Notify().Render( 0, \"本地更新出现错误，请选择手动点击 同步配置列表\" );\r\n    });\r\n    window.location.hash && window.location.hash.startsWith( \"#firstload\" ) && first && welcomeRender( true, \"all\" );\r\n}\r\n\r\n/**\r\n * Welcome page render()\r\n * \r\n * exclude: 1.0.4\r\n * \r\n * @param {boolean} true: first load\r\n * @param {string} version\r\n */\r\nfunction welcomeRender( first, version ) {\r\n    welc.Render( \"body\", first, version );\r\n}\r\n\r\n/**\r\n * Set options page style and tabs.Render()\r\n *\r\n * @param {number} conf.headerColors index\r\n */\r\nfunction mainRender( idx ) {\r\n    $( \".top\" ).css( \"background-color\", conf.topColors[idx] );\r\n    $( \".header\" ).css( \"background-color\", conf.topColors[idx] ).find( \".title\" ).text( conf.tabsItem[idx].name );\r\n    ( idx == 1 || idx == 2 || idx == 3 || idx == 4 || idx == 6 || idx == 7 ) ? $( '.main' ).addClass( \"main_labs\" ) : $( '.main' ).removeClass( \"main_labs\" );\r\n    tabsRender( conf.headerColors[ idx ] );\r\n}\r\n\r\n/**\r\n * Tabs render\r\n *\r\n * @param {string} header background color\r\n */\r\nfunction tabsRender( color ) {\r\n    const tabs = <Tabs waves=\"md-waves-effect md-waves-light\"\r\n                    headerStyle={{ transition: 'all 1000ms cubic-bezier(0.23, 1, 0.32, 1) 0ms' }}\r\n                    bgColor={ color }\r\n                    items={ conf.tabsItem }\r\n                    onChange={ ( $p, $t, evt )=>tabsOnChange( $p, $t, evt ) }>\r\n                    <section>\r\n                        <CommonOpt website_sync={website_sync} backgroundColor={ conf.topColors[0] } sync={ ()=> refresh() } />\r\n                    </section>\r\n                    <section style={{ 'padding': '0;' }}>\r\n                        <div id=\"labs\" style={{ width: '100%' }}>\r\n                            <div className=\"version-tips\" data-hits=\"focusmode\">\r\n                            <div className=\"label\">聚焦模式</div>\r\n                            <div className=\"lab\" style={{ 'padding': '30px 30px 10px 10px' }}>\r\n                                <FocusOpt option={ storage.focus } />\r\n                                <Button type=\"raised\" width=\"100%\" text=\"保 存\"\r\n                                        color=\"#fff\" backgroundColor={ conf.topColors[1] }\r\n                                        icon={ ss.IconPath( \"save_icon\" ) }\r\n                                        waves=\"md-waves-effect md-waves-button\"\r\n                                        onClick={ ()=>save( true ) } />\r\n                            </div>\r\n                            </div>\r\n                            <div className=\"version-tips\" data-hits=\"readmode\">\r\n                            <div className=\"label\">阅读模式</div>\r\n                            <div className=\"lab\" style={{ 'padding': '30px 30px 10px 10px' }}>\r\n                                <ReadOpt option={ storage.read } />\r\n                                <Button type=\"raised\" width=\"100%\" text=\"保 存\"\r\n                                        color=\"#fff\" backgroundColor={ conf.topColors[1] }\r\n                                        icon={ ss.IconPath( \"save_icon\" ) }\r\n                                        waves=\"md-waves-effect md-waves-button\"\r\n                                        onClick={ ()=>save( true ) } />\r\n                            </div>\r\n                            </div>\r\n                        </div>\r\n                    </section>\r\n                    <section style={{ 'padding': '0;' }}>\r\n                        <LabsOpt option={ storage.option } read={ storage.read } focus={ storage.focus } onChange={ (s)=>save(s) } />\r\n                    </section>\r\n                    <section style={{ 'padding': '0;', 'overflow-x': 'hidden' }}>\r\n                        <SitesOpts option={ storage.option } onChange={ (s)=>save(s) } />\r\n                    </section>\r\n                    <section style={{ 'padding': '0;' }}>\r\n                        <PluginsOpt />\r\n                    </section>\r\n                    <section><Unrdist list={ storage.unrdist.map( item => { return { ...item }} ) } onLoadMoreClick={ ()=> setTimeout( ()=> tt.Render( \"list\" ), 200 ) } /></section>\r\n                    <section style={{ 'padding': '0;' }}>\r\n                        <AccountOps user={ storage.user } load={ loadState } />\r\n                    </section>\r\n                    <section style={{ 'padding': '0;' }}><About option={ storage.option } site={ storage.simpread.sites.length } statistics={ storage.statistics } onClick={t=>welcomeRender(true,\"all\")}/></section>\r\n                </Tabs>,\r\n          tabsOnChange = ( $prev, $target, event ) => {\r\n                const idx = $target.attr( \"id\" );\r\n                tabChange( idx );\r\n          },\r\n          refresh = () => {\r\n                tt.Render( \"body\" );\r\n          },\r\n          save = state => {\r\n                storage.Write( ()=> {\r\n                    watch.SendMessage( \"option\", true );\r\n                    state && new Notify().Render( 0, \"保存成功，页面刷新后生效！\" );\r\n                });\r\n          };\r\n    ReactDOM.render( tabs, $( \".tabscontainer\" )[0] );\r\n}\r\n\r\n/**\r\n * navigation Render\r\n */\r\nfunction navRender() {\r\n    const navClick = () => {\r\n        side.Open();\r\n    };\r\n    const button = <Button waves=\"md-waves-effect md-waves-circle\" hoverColor=\"transparent\" icon={ ss.IconPath( \"sidebar_icon\" ) } onClick={ ()=>navClick() } />;\r\n    ReactDOM.render( button, $( \".header .nav\" )[0] );\r\n    ReactDOM.render( button, $( \".topnav\" )[0] );\r\n}\r\n\r\n/**\r\n * sidebar Render\r\n */\r\nfunction sidebarRender() {\r\n    const sidebarClick = ( $target, items ) => {\r\n        const idx = conf.tabsItem.findIndex( item => item.value == items.value );\r\n        tabChange( idx );\r\n    }, newItems = [\r\n        {\r\n            name: \"帮助中心\",\r\n            value: \"help\",\r\n            fontIcon: \"<i class=\\\"fas fa-question-circle\\\"></i>\",\r\n            route: \"http://ksria.com/simpread/docs/\",\r\n        },\r\n        {\r\n            name: \"开源列表\",\r\n            value: \"license\",\r\n            fontIcon: \"<i class=\\\"fas fa-keyboard\\\"></i>\",\r\n            route: \"http://ksria.com/simpread/docs/#/开源列表\",\r\n        },\r\n    ];\r\n    conf.menuItem = conf.menuItem.concat( newItems );\r\n    const sidebar = <side.Sidebar items={ conf.menuItem }\r\n                             waves=\"md-waves-effect\" autoClose={false} showClose={ true }\r\n                             header=\"设定\" footer=\" 简悦 © 2017 ~ 2020\" onClick={ ($t,o)=>sidebarClick($t,o) } />;\r\n    ReactDOM.render( sidebar, $( \".sidebar\" )[0] );\r\n}\r\n\r\n/*\r\n * Notice bubbles\r\n */\r\nfunction noticeRender() {\r\n    sessionStorage.setItem( \"is_update\", false );\r\n    const tmpl = `\r\n        <div class=\"md-waves-effect bubbles notice effect\" aria-label=\"消息中心\" data-balloon-pos=\"up\">\r\n            <i><svg viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"2555\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"24\" height=\"24\"><defs><style type=\"text/css\"></style></defs><path d=\"M787.908422 563.765991 787.908422 349.052814c0-152.726403-96.294137-222.682685-223.373417-236.678444 0.031722-0.931209 0.278339-1.811252 0.278339-2.76702 0-27.231201-22.429849-49.288566-50.031487-49.288566-27.662013 0-50.058093 22.057365-50.058093 49.288566 0 0.937348 0.23843 1.804089 0.295735 2.677992-127.636982 13.70207-224.524636 83.607186-224.524636 236.767472l0 214.713176c0 172.349323-442.565605 257.698177 265.890766 257.698177C1214.842001 821.464167 787.908422 736.115314 787.908422 563.765991L787.908422 563.765991zM514.782881 960.670649c52.405557 0 94.916766-41.893132 94.916766-93.54042L419.849742 867.13023C419.849742 918.777517 462.347648 960.670649 514.782881 960.670649L514.782881 960.670649zM514.782881 960.670649\" p-id=\"2556\" fill=\"#ffffff\"></path></svg></i>\r\n            <em class=\"init\">...</em>\r\n        </div>`;\r\n    storage.Notice( result => {\r\n        if ( $.isEmptyObject( result ) ) {\r\n            storage.notice.latest = 0;\r\n        }\r\n        $.get( storage.notice_service.latest + \"?\" + Math.round(+new Date()), result => {\r\n            console.log( \"notice latest id \", result )\r\n            if ( storage.notice.latest == 0 ) {\r\n                $( \"body\" ).append( tmpl );\r\n                sessionStorage.setItem( \"is_update\", true );\r\n            } else if ( storage.notice.latest < result ) {\r\n                $( \"body\" ).append( tmpl );\r\n                $( \".bubbles em\" ).removeClass( \"init\" ).text( result - storage.notice.read.length );\r\n                sessionStorage.setItem( \"is_update\", true );\r\n            } else if ( storage.notice.latest > storage.notice.read.length ) {\r\n                $( \"body\" ).append( tmpl );\r\n                $( \".bubbles em\" ).removeClass( \"init\" ).text( storage.notice.latest - storage.notice.read.length );\r\n            } else if ( storage.notice.latest == storage.notice.read.length && storage.option.notice ) {\r\n                $( \"body\" ).append( tmpl );\r\n                $( \".bubbles em\" ).remove();\r\n            }\r\n        });\r\n    });\r\n    $( \"body\" ).on( \"click\", \".notice\", event => {\r\n        location.href = location.origin + \"/options/notice.html?is_update=\" + sessionStorage.getItem( \"is_update\" );\r\n    });\r\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"help\", eventAction: \"help\", eventValue: \"notice\" }) );\r\n}\r\n\r\n/*\r\n * Help bubbles\r\n */\r\nfunction helpRender() {\r\n    const help_icon  = '<svg viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"24\" height=\"24\"><defs><style type=\"text/css\"></style></defs><path d=\"M512 704c-187.733333 0-341.333333-153.6-341.333333-341.333333s153.6-341.333333 341.333333-341.333334 341.333333 153.6 341.333333 341.333334-153.6 341.333333-341.333333 341.333333z m0-597.333333c-140.8 0-256 115.2-256 256s115.2 256 256 256 256-115.2 256-256-115.2-256-256-256z\" p-id=\"4789\" fill=\"#ffffff\"></path><path d=\"M512 576c119.466667 0 213.333333-93.866667 213.333333-213.333333s-93.866667-213.333333-213.333333-213.333334-213.333333 93.866667-213.333333 213.333334 93.866667 213.333333 213.333333 213.333333z\" fill=\"#ffffff\"></path><path d=\"M384 776.533333c0-25.6 21.333333-42.666667 42.666667-42.666666h170.666666c21.333333 0 42.666667 17.066667 42.666667 42.666666s-21.333333 42.666667-42.666667 42.666667h-170.666666c-25.6 0-42.666667-17.066667-42.666667-42.666667z m42.666667 110.933334c0-25.6 17.066667-42.666667 42.666666-42.666667h85.333334c25.6 0 42.666667 17.066667 42.666666 42.666667s-17.066667 42.666667-42.666666 42.666666h-85.333334c-21.333333 0-42.666667-21.333333-42.666666-42.666666z m42.666666 85.333333c0-12.8 8.533333-21.333333 21.333334-21.333333h42.666666c12.8 0 21.333333 8.533333 21.333334 21.333333s-8.533333 21.333333-21.333334 21.333333h-42.666666c-12.8-4.266667-21.333333-12.8-21.333334-21.333333z\" p-id=\"4791\" fill=\"#ffffff\"></path></svg>',\r\n          close_icon = '<svg viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"24\" height=\"24\"><defs><style type=\"text/css\"></style></defs><path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\" fill=\"#ffffff\"></path></svg>',\r\n          tmpl       = `\r\n                <div class=\"md-waves-effect bubbles help effect\" aria-label=\"帮助中心\" data-balloon-pos=\"up\">\r\n                    <i>${help_icon}</i>\r\n                </div>`,\r\n    exit = () => {\r\n        ReactDOM.unmountComponentAtNode( $( \".guide-bg\" )[0] );\r\n        $( \".help i\" ).html( help_icon ).css({ \"animation\": \".1s reverse fadein,235ms cubic-bezier(.4,0,.2,1) popdown\" });\r\n        $( \".guide-bg\" ).remove();\r\n    };\r\n    $( \"body\" ).append( tmpl );\r\n    $( \"body\" ).on( \"click\", \".help\", event => {\r\n        if ( $(\".guide-bg\").length == 0 ) {\r\n            $( \"body\" ).append( `<div class=\"guide-bg\"></div>` );\r\n            ReactDOM.render( <guide.Guide onExit={ ()=> exit() } />, $( \".guide-bg\" )[0] );\r\n            $( \".help i\" ).html( close_icon ).css({ \"animation\": \".1s reverse fadein,235ms cubic-bezier(.4,0,.2,1) popup\" });\r\n        } else {\r\n            exit();\r\n        }\r\n    });\r\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"help\", eventAction: \"help\", eventValue: \"help\" }) );\r\n}\r\n\r\n/*\r\n * Feedback bubbles\r\n */\r\nfunction feedbackRender() {\r\n    const tmpl = `\r\n        <div class=\"md-waves-effect bubbles feedback effect\" aria-label=\"给我反馈\" data-balloon-pos=\"up\">\r\n            <i><svg viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\"><path d=\"M896 981.333333l-213.333333-128H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333V128c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333v853.333333zM298.666667 512c-8.533333 0-12.8 0-21.333334 4.266667s-17.066667 17.066667-21.333333 25.6c0 12.8 0 25.6 4.266667 34.133333 55.466667 93.866667 153.6 149.333333 260.266666 149.333333 106.666667 0 204.8-55.466667 260.266667-149.333333 4.266667-8.533333 8.533333-21.333333 4.266667-34.133333-4.266667-12.8-8.533333-21.333333-21.333334-25.6-8.533333-4.266667-12.8-4.266667-21.333333-4.266667-17.066667 0-29.866667 8.533333-38.4 21.333333C665.6 597.333333 597.333333 640 520.533333 640c-76.8 0-145.066667-42.666667-183.466666-106.666667-8.533333-12.8-21.333333-21.333333-38.4-21.333333z\" fill=\"#ffffff\"></path></svg></i>\r\n        </div>`;\r\n    $( \"body\" ).append( tmpl );\r\n    $( \"body\" ).on( \"click\", \".feedback\", event => {\r\n        fb.Render( storage.version, storage.user );\r\n        setTimeout( () => tt.Render( \".simpread-feedback\" ), 200 );\r\n    });\r\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"help\", eventAction: \"help\", eventValue: \"feedback\" }) );\r\n}"
  },
  {
    "path": "src/options/sitemgr.html",
    "content": "<html>\r\n    <head>\r\n        <meta charset=\"UTF-8\">\r\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n        <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\r\n        <title>站点管理器 - 简悦 · 选项页</title>\r\n    </head>\r\n    <body style=\"opacity: 0;\">\r\n        <div class=\"header\"><div class=\"nav\"></div><div class=\"title\">站点管理器</div></div>\r\n        <div class=\"custom\">\r\n            <div class=\"property\">Property</div>\r\n            <div class=\"editor\">\r\n                <div class=\"userinfo box hide\"></div>\r\n                <div class=\"siteinfo box hide\"></div>\r\n                <div class=\"preview\">\r\n                    <div class=\"empty\">\r\n                        <span class=\"icon\"></span>\r\n                        <span>当前未选择任何适配站点</span>\r\n                    </div>\r\n                </div>\r\n            </div>\r\n        </div>\r\n        <div class=\"bottom\">\r\n            <span>简悦 SimpRead - 为你提供「如杂志般沉浸式阅读体验」的扩展</span> <span>&nbsp;©&nbsp;2017&nbsp;-&nbsp;2020&nbsp;<a href=\"http://ksria.com/simpread\">ksria.com</a> by <a href=\"http://kenshin.wang\" target=\"_blank\">Kenshin Wang</a></span>\r\n        </div>\r\n        <script src=\"../ga.js\"></script>\r\n        <script src=\"../bundle/common.js\"></script>\r\n        <script src=\"../bundle/vendors.js\"></script>\r\n        <script src=\"../bundle/sitemgr.js\"></script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "src/options/sitemgr.js",
    "content": "console.log( \"==== simpread options page: sitemanager load ====\" )\r\n\r\nimport '../assets/css/simpread.css';\r\nimport '../assets/css/setting.css';\r\nimport '../assets/css/options_page.css';\r\nimport '../assets/css/options_custom.css';\r\nimport '../assets/css/options_sitemgr.css';\r\nimport 'notify_css';\r\n\r\nimport Velocity   from 'velocity';\r\nimport Notify     from 'notify';\r\n\r\nimport TextField  from 'textfield';\r\nimport Button     from 'button';\r\nimport AC         from 'ac';\r\n\r\nimport * as waves from 'waves';\r\nimport * as tt    from 'tooltip';\r\n\r\nimport Editor     from 'editor';\r\nimport Import     from 'enhancesite';\r\n\r\nimport {storage}  from 'storage';\r\nimport * as ss    from 'stylesheet';\r\nimport * as watch from 'watch';\r\n\r\nimport PureRead   from 'puread';\r\n\r\nlet cur_site, org_site, pr,\r\n    state = { name: 0, url: 0, title: 0, desc: 0, include: 0, exclude: 0, avatar:{ name: 0, url: 0 }, paging: { prev:0, next: 0} }; // 0: success -1: faield -2: not empty0\r\n\r\n/**\r\n * Entry:\r\n * - storage get data form chrome storage\r\n * - waves.Render()\r\n * - tooltip.Render()\r\n */\r\nstorage.Read( () => {\r\n    console.log( \"simpread storage get success!\", storage.sites );\r\n    pr = new PureRead( storage.sites );\r\n    navRender();\r\n    controlbarRender();\r\n    tt.Render( \"body\" );\r\n    waves.Render({ root: \"body\" });\r\n    $( \"body\" ).velocity({ opacity: 1 }, { duration: 1000, complete: ()=> {\r\n        $( \"body\" ).removeAttr( \"style\" );\r\n    }});\r\n    console.log( \"current puread object is   \", pr )\r\n});\r\n\r\n// hack code\r\nwindow.addEventListener( \"sitechanged\", event => {\r\n    const [ url, site, type, info ] = [ event.data.site.url, event.data.site, event.data.site.target, event.data.info ];\r\n    org_site = [ url, site ];\r\n    siteeditorRender( url, site, type, info );\r\n});\r\n\r\n// hack code\r\nfunction changeSiteinfo( info ) {\r\n    const evt  = document.createEvent(\"Event\");\r\n    evt.data   = { info };\r\n    evt.initEvent( \"siteinfochanged\", true, false );\r\n    window.dispatchEvent( evt );\r\n}\r\n\r\n/**\r\n * navigation Render\r\n */\r\nfunction navRender() {\r\n    const navClick = () => {\r\n        location.href = location.origin + \"/options/options.html#sites\";\r\n    };\r\n    const button = <Button waves=\"md-waves-effect md-waves-circle\" hoverColor=\"transparent\" icon={ ss.IconPath( \"gohome_icon\" ) } onClick={ ()=>navClick() } />;\r\n    ReactDOM.render( button, $( \".header .nav\" )[0] );\r\n}\r\n\r\n/**\r\n * controlbar Render\r\n */\r\nfunction controlbarRender() {\r\n    const getCursite = ( type, value ) => {\r\n            if ( value == \"\" ) return;\r\n            const site = pr.Getsite( type, value );\r\n            org_site   = [ site[0], site[1] ];\r\n            site.length > 0 && siteeditorRender( site[0], site[1], type );\r\n            site.length > 0 && changeSiteinfo( site[1].info );\r\n        },\r\n        add   = () => {\r\n            const msg = cur_site ? \"是否覆盖当前站点并新建立一个？\" : \"是否建立一个新站？\";\r\n            new Notify().Render( \"snackbar\", msg, \"新建\", () => {\r\n                org_site = [ \"\", \"\" ];\r\n                siteeditorRender( \"\", { name: \"\", title: \"<title>\", desc: \"\", include: \"\", exclude: [] }, \"local\" );\r\n            });\r\n        },\r\n        remove = () => {\r\n            new Notify().Render( \"snackbar\", \"是否删除当前站点？\", \"删除\", () => save( \"delete\" ));\r\n        },\r\n        remote = ( type ) => {\r\n            save( type, \"remote\" );\r\n        },\r\n        save = ( type, state ) => {\r\n            if ( !cur_site ) {\r\n                new Notify().Render( 2, \"请选择一个站点源。\" );\r\n                return;\r\n            }\r\n            if ( type != \"delete\" && !verify() ) return;\r\n\r\n            const key = cur_site.target,\r\n                  url = cur_site.url,\r\n                  site= pr.Cleansite({ ...cur_site });\r\n            let flag  = -1;\r\n\r\n            if ( site.info && state != \"remote\" && cur_site.target == \"person\" &&( type == \"update\" || type == \"delete\" )) {\r\n                if ( site.info.id.substr(0,8) == storage.user.uid.substr(0,8) ) {\r\n                    setTimeout( ()=>new Notify().Render( 2, \"\u001d当前站有远程数据，请保持同步更新。\" ), 500 );\r\n                }\r\n            }\r\n\r\n            if ( type == \"update\" && state == \"remote\" ) {\r\n                site.info = { ...storage.remote.info };\r\n                if ( cur_site.target == \"global\" ) {\r\n                    pr.Updatesite( \"person\", org_site[0], [ url, pr.Cleansite(site) ] );\r\n                } else if ( cur_site.target == \"local\" || cur_site.target == \"custom\" ) {\r\n                    pr.Deletesite( cur_site.target, org_site[0], result => {\r\n                        pr.Updatesite( \"person\", org_site[0], [ url, pr.Cleansite(site) ] );\r\n                    });\r\n                }\r\n                org_site = [ url, site ];\r\n                flag = 0;\r\n                setTimeout( () => new Notify().Render( 2, \"当前站提交时会自动增加到「站点集市适配源」！\" ), 500 );\r\n            } else if ( type == \"delete\" && state == \"remote\" ) {\r\n                pr.Deletesite( \"person\", org_site[0], result => {\r\n                    result != -1 ? flag = 1 : new Notify().Render( \"当前站点已删除，请勿重复提交。\" );\r\n                });\r\n            } else if ( type == \"update\" ) {\r\n                pr.Updatesite( key, org_site[0], [ url, pr.Cleansite(site) ] );\r\n                org_site = [ url, site ];\r\n                flag = 0;\r\n            /*\r\n            } else if ( type == \"safe\" ) {\r\n                delete site.info;\r\n                pr.Updatesite( key, org_site[0], [ url, pr.Cleansite(site) ] );\r\n                org_site = [ url, site ];\r\n                flag = 0;\r\n            }\r\n            */\r\n            } else {\r\n               pr.Deletesite( key, org_site[0], result => {\r\n                   result != -1 ? flag = 1 : new Notify().Render( \"当前站点已删除，请勿重复提交。\" );\r\n               });\r\n            }\r\n            flag != -1 && storage.Writesite( pr.sites, ()=> {\r\n                console.log( \"current site is \", cur_site, org_site )\r\n                watch.SendMessage( \"site\", true );\r\n                new Notify().Render( `当前站点${ flag == 0 ? \"更新\" : \"删除\" }成功，请刷新本页。` );\r\n            });\r\n        };\r\n    const doms = <div>\r\n                    <group className=\"lab\">\r\n                        <group><AC placeholder={ `官方适配源（${storage.sites.global.length} 条）`} items={ formatsites( storage.sites.global  )} onChange={ v=>getCursite( \"global\",  v) } /></group>\r\n                        <group><AC placeholder={ `站点集市适配源（${storage.sites.person.length} 条）` } items={ formatsites( storage.sites.person  )} onChange={ v=>getCursite( \"person\",  v) } /></group>\r\n                        <group><AC placeholder={ `第三方适配源（${storage.sites.custom.length} 条）` } items={ formatsites( storage.sites.custom  )} onChange={ v=>getCursite( \"custom\",  v) } /></group>\r\n                        <group><AC placeholder={ `自定义适配源（${storage.sites.local.length} 条）` } items={ formatsites( storage.sites.local  )} onChange={ v=>getCursite( \"local\",  v) } /></group>\r\n                    </group>\r\n                    <group className=\"lab\">\r\n                        <group>\r\n                            <Button type=\"raised\" text=\"新建一个站\"\r\n                                    style={{ \"margin\": \"0\" }} width=\"100%\"\r\n                                    color=\"#fff\" backgroundColor=\"#4CAF50\"\r\n                                    waves=\"md-waves-effect md-waves-button\"\r\n                                    onClick={ ()=>add() } />\r\n                        </group>\r\n                        <group>\r\n                            <Button type=\"raised\" text=\"保存当前站\"\r\n                                    style={{ \"margin\": \"0\" }} width=\"100%\"\r\n                                    color=\"#fff\" backgroundColor=\"#3F51B5\"\r\n                                    waves=\"md-waves-effect md-waves-button\"\r\n                                    onClick={ ()=>save( \"update\" ) } />\r\n                        </group>\r\n                        <group>\r\n                            <Button type=\"raised\" text=\"删除当前站\"\r\n                                    style={{ \"margin\": \"0\" }} width=\"100%\"\r\n                                    color=\"#fff\" backgroundColor=\"#FF5252\"\r\n                                    waves=\"md-waves-effect md-waves-button\"\r\n                                    onClick={ ()=>remove() } />\r\n                        </group>\r\n                    </group>\r\n                    <group className=\"lab\" style={{display:\"none\"}}>\r\n                        <div className=\"sites\"></div>\r\n                    </group>\r\n                    <group className=\"lab\">\r\n                        <Import uid={ storage.user.uid } onUpdate={ t=>remote(t)} />\r\n                    </group>\r\n                 </div>;\r\n    ReactDOM.render( doms, $( \".custom .property\" )[0] );\r\n}\r\n\r\n/**\r\n * siteeditor Render\r\n */\r\nfunction siteeditorRender( url, site, type, info ) {\r\n    $( \"sr-opt-read\" ).length > 0 &&\r\n        $( \".custom .preview\" ).empty();\r\n    cur_site     = pr.Safesite( site, type, url );\r\n\r\n    // set remote site( include info )\r\n    //storage.remote = JSON.parse(JSON.stringify( cur_site ));\r\n    storage.remote = cur_site;\r\n    info && ( storage.remote.info = info );\r\n    changeSiteinfo( storage.remote.info );\r\n\r\n    const doms   = <Editor site={ cur_site } state={ state } />;\r\n    ReactDOM.render( doms, $( \".custom .preview\" )[0] );\r\n    console.log( \"current site is \", cur_site )\r\n}\r\n\r\n/**\r\n * Format storage sites\r\n * \r\n * @param {array} sites\r\n */\r\nfunction formatsites( sites ) {\r\n    return sites.map( item => {\r\n        return { value: item[0], name: item[0] }\r\n    });\r\n}\r\n\r\n/**\r\n * Verify site editor site\r\n * \r\n * @return {boolean}\r\n */\r\nfunction verify() {\r\n    let flag = false;\r\n    if ( [ \"url\", \"name\", \"title\", \"include\" ].findIndex( key => cur_site[key] == \"\" ) != -1 ) {\r\n        new Notify().Render( 3, \"【标识、域名、标题、高亮】不能为空。\" );\r\n    }\r\n    else if ( Object.values( state ).findIndex( key => typeof key == \"number\" && key != 0 ) != -1 ||\r\n           ( state.avatar.name != 0 || state.avatar.url  != 0 ) ||\r\n           ( state.paging.prev != 0 || state.paging.next != 0 )\r\n    ) {\r\n        new Notify().Render( 3, \"请正确填写【标识、域名、标题、高亮】后再提交。\" );\r\n    } else if (( cur_site.avatar[0].name != \"\" && cur_site.avatar[1].url == \"\" ) || ( cur_site.avatar[0].name == \"\" && cur_site.avatar[1].url != \"\" )) {\r\n        new Notify().Render( 3, \"【头像的名称与地址】必须同时设定。\" );\r\n    } else if (( cur_site.paging[0].prev != \"\" && cur_site.paging[1].next == \"\" ) || ( cur_site.paging[0].prev == \"\" && cur_site.paging[1].next != \"\" )) {\r\n        new Notify().Render( 3, \"【前一页与后一页】必须同时设定。\" );\r\n    } else if ( cur_site.name.startsWith( \"tempread::\" ) ) {\r\n        new Notify().Render( 2, \"标识不能包含 tempread:: 请删除。\" );\r\n    } else if ( cur_site.include.trim() == \"\" ) {\r\n        new Notify().Render( 2, \"高亮区域不能为空。\" );\r\n    } else {\r\n        flag = true;\r\n    }\r\n    return flag;\r\n}"
  },
  {
    "path": "src/read/controlbar.jsx",
    "content": "console.log( \"=== simpread read controlbar load ===\" )\n\nimport * as ss     from 'stylesheet';\nimport {browser,br}from 'browser';\nimport * as msg    from 'message';\nimport th          from 'theme';\nimport * as conf   from 'config';\nimport * as output from 'output';\nimport * as watch  from 'watch';\nimport * as kbd    from 'keyboard';\nimport { storage } from 'storage';\nimport * as run    from 'runtime';\n\nimport ReadOpt     from 'readopt';\nimport Actionbar   from 'actionbar';\nimport Pluginbar   from 'pluginbar';\nimport Sitebar     from 'sitebar';\n\nimport Fab         from 'fab';\nimport Fap         from 'fap'\nimport * as ttips  from 'tooltip';\n\nlet notify, readItems;\nconst tooltip_options = {\n    target   : \"name\",\n    position : \"bottom\",\n    delay    : 50,\n};\n\n/**\n * Read controlbar\n * \n * @class\n */\nexport default class ReadCtlbar extends React.Component {\n\n    static propTypes = {\n        onAction: React.PropTypes.func,\n    }\n\n    verify( type ) {\n        if ( ss.VerifyCustom( type, storage.current.custom ) ) {\n            !notify && ( notify = new Notify().Render({ state: \"holdon\", content: '由于已使用自定义样式，设定 <b style=\"color: #fff;\">有可能无效</b>，详细说明 <a href=\"http://ksria.com/simpread/docs/#/自定义样式\" target=\"_blank\">请看这里</a>', callback:()=>notify=undefined }));\n        }\n    }\n\n    componentDidMount() {\n        browser.runtime.onMessage.addListener( ( request, sender, sendResponse ) => {\n            if ( request.type == msg.MESSAGE_ACTION.export ) {\n                console.log( \"controlbar runtime Listener\", request );\n                new Notify().Render( \"已重新授权成功！\" );\n                br.isFirefox() ? new Notify().Render( \"请刷新本页才能生效。\" ) : this.onAction( undefined, request.value.type );\n            }\n        });\n        kbd.Listen( combo => {\n            this.onAction( undefined, combo )\n        });\n        run.Controlbar( undefined, event => {\n            this.onAction( undefined, event.detail.type );\n        });\n    }\n\n    onAction( event, type ) {\n        console.log( \"fab type is =\", type )\n\n        this.verify( type.split( \"_\" )[0] );\n\n        run.Event( \"export\", type );\n\n        const action = ( event, type ) => {\n            this.props.multi && \n            [ \"markdown\", \"dropbox\", \"yinxiang\",\"evernote\", \"onenote\", \"gdrive\" ].includes( type ) &&\n            new Notify().Render( \"当前为论坛类页面，不建议使用导出服务，有可能出现未知错误。\" );\n\n            switch ( true ) {\n                case [ \"exit\", \"setting\", \"siteeditor\", \"remove\", \"highlight\" ].includes( type ):\n                    this.props.onAction( type );\n                    break;\n                case type.indexOf( \"_\" ) > 0 && ( type.startsWith( \"fontfamily\" ) || type.startsWith( \"fontsize\" ) || type.startsWith( \"layout\" )):\n                    const [ key, value ] = [ type.split( \"_\" )[0], type.split( \"_\" )[1] ];\n                    Object.keys( ss ).forEach( (name)=>name.toLowerCase() == key && ss[name]( value ));\n                    this.props.onAction && this.props.onAction( key, value );\n                    break;\n                case type.indexOf( \"_\" ) > 0 && type.startsWith( \"theme\" ):\n                    let i = th.names.indexOf( th.theme );\n                    i = type.endsWith( \"prev\" ) ? --i : ++i;\n                    i >= th.names.length && ( i = 0 );\n                    i < 0 && ( i = th.names.length - 1 );\n                    th.Change( th.names[i] );\n                    this.props.onAction && this.props.onAction( type.split( \"_\" )[0], th.theme );\n                    break;\n                case type.startsWith( \"dyslexia\" ):\n                    output.Action( type, $( \"sr-rd-title\" ).text(), \"\", $( \"sr-rd-content\" ).text() );\n                    break;\n                case type == \"tempread\":\n                    if ( storage.pr.state != \"temp\" ) {\n                        new Notify().Render( \"此功能只限临时阅读模式时使用。\" );\n                        return;\n                    }\n                    // hack code\n                    const news = { ...storage.pr.current.site };\n                    storage.pr.dom && ( news.include = storage.pr.dom.outerHTML.replace( storage.pr.dom.innerHTML, \"\" ).replace( /<\\/\\S+>$/i, \"\" ));\n                    delete news.html;\n                    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.temp_site, { url: location.href, site: news, uid: storage.user.uid, type: \"temp\" }));\n                    break;\n                case type.startsWith( \"webdav_\" ) :\n                        const [ title, desc, content ] = [ $( \"sr-rd-title\" ).text().trim(), $( \"sr-rd-desc\" ).text().trim(), $( \"sr-rd-content\" ).html().trim() ];\n                        output.Action( type, title, desc, content );\n                    break;\n                default:\n                    if ( type.indexOf( \"_\" ) > 0 && type.startsWith( \"share\" ) || \n                        [ \"fullscreen\", \"save\", \"markdown\", \"offlinemarkdown\", \"png\", \"epub\", \"pdf\", \"kindle\", \"temp\", \"bear\", \"ulysses\", \"html\", \"offlinehtml\", \"snapshot\", \"dropbox\", \"pocket\", \"instapaper\", \"linnk\", \"yinxiang\", \"evernote\", \"onenote\", \"gdrive\", \"jianguo\", \"yuque\", \"notion\", \"youdao\", \"weizhi\" ].includes( type )) {\n                        const [ title, desc, content ] = [ $( \"sr-rd-title\" ).text().trim(), $( \"sr-rd-desc\" ).text().trim(), $( \"sr-rd-content\" ).html().trim() ];\n                        output.Action( type, title, desc, content );\n                    }\n            }\n        };\n\n        type != \"exit\" ? watch.Verify( ( state, result ) => {\n            if ( state ) {\n                console.log( \"watch.Lock()\", result );\n                new Notify().Render( \"配置文件已更新，刷新当前页面后才能生效。\", \"刷新\", ()=>location.href = location.href );\n            } else action( event, type );\n        }) : action( event, type );\n\n    }\n\n    onChange( type, custom ) {\n        const [ key, value ] = [ type.split( \"_\" )[0], type.split( \"_\" )[1] ];\n        run.Event( \"read_ui\", { key, value, custom });\n        this.props.onAction && this.props.onAction( key, value, custom );\n        this.verify( key );\n    }\n\n    onPop( type ) {\n        type == \"open\" ? ttips.Render( \".simpread-read-root\", \"panel\" ) : ttips.Exit( \".simpread-read-root\", \"panel\" );\n    }\n\n    componentWillMount() {\n        readItems = $.extend( true, {}, conf.readItems );\n        try {\n            if ( storage.current.fap ) {\n                delete readItems.exit;\n                delete readItems.option.items.setting;\n                delete readItems.fontfamily;\n                delete readItems.fontsize;\n                delete readItems.layout;\n                delete readItems.theme;\n            } else {\n                delete readItems.trigger;\n            }\n            if ( this.props.type.startsWith( \"txtread::\" ) && this.props.type.endsWith( \"::local\" )) {\n                delete readItems.download;\n                delete readItems.readlater;\n                delete readItems.send;\n                delete readItems.share;\n                delete readItems.option;\n            }\n            if ( this.props.type.startsWith( \"metaread::\" ) || this.props.type.startsWith( \"txtread::\" ) ) {\n                delete readItems.option;\n            }\n            if ( !/macintosh|mac os x/i.test(navigator.userAgent) ) {\n                delete readItems.send.items.bear;\n                delete readItems.send.items.ulysses;\n            }\n            readItems.send && storage.Safe( () => {\n                storage.secret.webdav.forEach( item => {\n                    item = JSON.parse( item );\n                    readItems.send.items[ \"webdav_\" + item.name ] = {\n                        name: item.name,\n                        icon: ss.IconPath(\"webdav_icon\"),\n                        \"color\": \"#00BCD4\",\n                    };\n                });\n            })\n            // Add test source\n            storage.current.fap && storage.Plugins( () => {\n                !$.isEmptyObject( storage.plugins ) && storage.option.plugins.forEach( id => {\n                    const plugin = storage.plugins[id];\n                    // Add test source\n                    if ( plugin.enable != false && ( plugin.trigger == true || plugin.trigger == \"true\" )) {\n                    //if ( plugin.id == \"Y7JxbP7B4H\" ) {\n                        readItems.trigger.items[\"plugin_\" + plugin.id] = {\n                            \"name\"     : plugin.name,\n                            \"fontIcon\" : plugin.icon.type,\n                            \"color\"    : plugin.icon.bgColor,\n                        };\n                    }\n                });\n                if ( readItems.trigger && $.isEmptyObject( readItems.trigger.items )) {\n                    delete readItems.trigger;\n                }\n            });\n        } catch ( err ) {\n            // TO-DO\n        }\n        // hack code\n        !/chrome/ig.test( navigator.userAgent ) && ( delete readItems.dyslexia );\n    }\n\n    constructor( props ) {\n        super( props );\n    }\n\n    render() {\n        const Controlbar = storage.current.fap ? \n            <Fap items={ [ \"样式\", \"动作\", \"站点\", \"插件\" ] } autoHide={ false }\n                waves=\"md-waves-effect md-waves-circle md-waves-float\" \n                onOpen={ ()=> this.onPop( \"open\" ) } onClose={ ()=> this.onPop( \"close\" ) }\n                onAction={ (event, type)=>this.onAction(event, type ) }>\n                <ReadOpt option={ storage.current } onChange={ (t,c)=>this.onChange(t,c)}/>\n                <Actionbar items={ readItems } onAction={ (type)=>this.onAction(undefined, type ) }/>\n                <Sitebar />\n                <Pluginbar />\n            </Fap>\n            :\n            <Fab items={ readItems } tooltip={ tooltip_options } waves=\"md-waves-effect md-waves-circle md-waves-float\" onAction={ (event, type)=>this.onAction(event, type ) } />\n        return (\n            <sr-rd-crlbar class={ this.props.show ? \"\" : \"controlbar\" } style={{ \"zIndex\": \"2\" }}>\n                { Controlbar }\n            </sr-rd-crlbar>\n        )\n    }\n}\n"
  },
  {
    "path": "src/read/progressbar.jsx",
    "content": "console.log( \"==== simpread read component: ProcessBar ====\" )\n\nexport default class ProcessBar extends React.Component {\n\n    static defaultProps = {\n        show  : true,\n        offset: document.documentElement.scrollTop / ( document.documentElement.scrollHeight - document.documentElement.clientHeight ) || 0\n    }\n\n    state = {\n        progress: this.props.offset\n    }\n\n    componentDidMount() {\n        setTimeout( ()=>{\n            $( document ).on( \"scroll\", ()=>this.scrollEventHandle() );\n        }, 1000 );\n    }\n\n    componentWillUnmount() {\n        $( document ).off( \"scroll\", this.scrollEventHandle() );\n    }\n\n    scrollEventHandle() {\n        const offset = document.documentElement.scrollTop / ( document.documentElement.scrollHeight - document.documentElement.clientHeight );\n        this.setState({ progress: offset });\n    }\n\n    render() {\n        const progress = this.state.progress * 100;\n        return (\n            this.props.show && <read-process style={{ \"width\": `${progress}%` }}></read-process>\n        )\n    }\n\n}\n"
  },
  {
    "path": "src/read/read.jsx",
    "content": "console.log( \"=== simpread read load ===\" )\n\nimport ProgressBar        from 'schedule';\nimport * as spec          from 'special';\nimport ReadCtlbar         from 'readctlbar';\nimport * as toc           from 'toc';\nimport * as setting       from 'setting';\nimport * as se            from 'siteeditor';\nimport * as kbd           from 'keyboard';\nimport * as fb            from 'feedback';\n\nimport { storage, Clone } from 'storage';\nimport th                 from 'theme';\nimport * as ss            from 'stylesheet';\nimport {browser}          from 'browser';\nimport * as msg           from 'message';\nimport * as highlight     from 'highlight';\nimport * as run           from 'runtime';\nimport * as tips          from 'tips';\n\nimport * as tooltip       from 'tooltip';\nimport * as waves         from 'waves';\n\nconst rdcls   = \"simpread-read-root\",\n      bgtmpl  = `<div class=\"${rdcls}\"></div>`,\n      rdclsjq = \".\" + rdcls,\n      $root   = $( \"html\" ),\n      theme   = \"simpread-theme-root\";\n\n// load count,.0: call Readability. 1: call highlight 2: all failed\nlet   load_count = 0;\n\nconst Footer = () => {\n    const good_icon = '<svg t=\"1556354786433\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"33\" height=\"33\"><defs><style type=\"text/css\"></style></defs><path d=\"M859.8 191.2c-80.8-84.2-212-84.2-292.8 0L512 248.2l-55-57.2c-81-84.2-212-84.2-292.8 0-91 94.6-91 248.2 0 342.8L512 896l347.8-362C950.8 439.4 950.8 285.8 859.8 191.2z\" p-id=\"6225\" fill=\"#8C8C8C\"></path></svg>',\n          bad_icon  = '<svg t=\"1556354650943\" viewBox=\"0 0 1024 1024\" version=\"1.1\" p-id=\"5899\" width=\"33\" height=\"33\"><defs><style type=\"text/css\"></style></defs><path d=\"M458 576c2-36 0-76 16-110 4-10 2-20 2-30-8-42-28-80-30-120 0-2.78 2.008-9.542 2.01-12.314-6.432 4.468-15.214 8.048-22.01 10.314-40 12-35.02 5.146-69.02 27.146l-23.866 14.456c32.686-35.878 77.056-49.562 113.05-77.428 0.388-30.876 1.716-61.354 6.274-91.68C371.22 106.992 243.57 108.536 164.246 191.14c-90.994 94.688-90.994 248.202 0 342.89l305.698 318.192c-0.17-21.312-0.886-42.352-3.944-62.222C454 718 458 648 458 576z\" p-id=\"5900\" fill=\"#8C8C8C\"></path><path d=\"M644 602c-22-52-66-88-126-100-1.7 0-3.758-1.086-5.872-2.638-0.046 0.214-0.082 0.426-0.128 0.638-22 96-46 188-42 284 0 24.454 7.966 50.234 7.666 76.262L512 896l208-216.5C690.306 658.542 660.856 637.242 644 602z\" p-id=\"5901\" fill=\"#8C8C8C\"></path><path d=\"M859.748 191.14c-80.852-84.188-211.978-84.188-292.816 0L528 230.806c0.15 26.35 0.426 52.404-6 77.194-4 20-38 38-32 62 6.006 26.426 16.332 51.41 21.464 77.118C542.028 464.168 569.542 485.792 594 512c45.602 53.532 75.494 114.918 130.566 162.742l135.182-140.71C950.75 439.342 950.75 285.828 859.748 191.14z\" p-id=\"5902\" fill=\"#8C8C8C\"></path></svg>',\n          onClick   = ( rate = false ) => {\n            fb.Render( storage.version, storage.user, rate );\n                setTimeout( () => tooltip.Render( \".simpread-feedback\" ), 200 );\n            };\n    return (\n        <sr-rd-footer>\n            <sr-rd-footer-group>\n                <sr-rd-footer-line></sr-rd-footer-line>\n                <sr-rd-footer-text>全文完</sr-rd-footer-text>\n                <sr-rd-footer-line></sr-rd-footer-line>\n            </sr-rd-footer-group>\n            <sr-rd-footer-copywrite>\n                <div>本文由 <a href=\"http://ksria.com/simpread\" target=\"_blank\">简悦 SimpRead</a> 优化，用以提升阅读体验</div>\n                <div className=\"second\">使用了 <abbr>全新的简悦词法分析引擎<sup>beta</sup></abbr>，<a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\">点击查看</a>详细说明</div>\n                <div className=\"third\">\n                    <a className=\"sr-icon good sr-top\" aria-label=\"觉得不错？请帮忙投票 😄\" data-balloon-pos=\"up\" target=\"_blank\" onClick={ ()=>onClick( true ) } dangerouslySetInnerHTML={{__html: good_icon }} ></a>\n                    <a className=\"sr-icon bad sr-top\"  aria-label=\"有待改进，请帮忙吐槽 😄\" data-balloon-pos=\"up\" target=\"_blank\" onClick={ ()=>onClick() } dangerouslySetInnerHTML={{__html: bad_icon  }} ></a>\n                </div>\n            </sr-rd-footer-copywrite>\n        </sr-rd-footer>\n    )\n}\n\nclass Read extends React.Component {\n\n    verifyContent() {\n        if ( $(\"sr-rd-content\").text().length < 100 ) {\n            if ( load_count == 0 ) {\n                new Notify().Render({ content: \"检测到正文获取异常，是否重新获取？\", action: \"是的\", cancel: \"取消\", callback: type => {\n                    if ( type == \"cancel\" ) return;\n                    load_count++;\n                    this.componentWillUnmount();\n                    storage.pr.Readability();\n                    Render();\n                }});\n            } else if ( load_count == 1 ) {\n                this.componentWillUnmount();\n                new Notify().Render({ content: '获取正文失败，是否使用 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选\">手动框选</a> 高亮的方式获取？', action: \"是的\", cancel: \"取消\", callback: type => {\n                    if ( type == \"cancel\" ) return;\n                    setTimeout( () => {\n                        Highlight().done( dom => {\n                            const rerender = element => {\n                                load_count++;\n                                storage.pr.TempMode( \"read\", element );\n                                Render();\n                            };\n                            storage.current.highlight ? \n                                highlight.Control( dom ).done( newDom => {\n                                    rerender( newDom );\n                                }) : rerender( dom );\n                        });\n                    }, 200 );\n                }});\n            } else if ( load_count >= 2 ) {\n                this.componentWillUnmount();\n                new Notify().Render({ content: \"高亮无法仍无法适配此页面，是否提交？\", action: \"是的\", cancel: \"取消\", callback: type => {\n                    if ( type == \"cancel\" ) return;\n                    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.save_site, { url: location.href, site: {}, uid: storage.user.uid, type: \"failed\" }));\n                }});\n                load_count = 0;\n            }\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    componentWillMount() {\n        $( \"body\" ).addClass( \"simpread-hidden\" );\n        th.Change( this.props.read.theme );\n        if ( storage.current.fap ) {\n            $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/solid.min.css\" />' );\n            $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/brands.min.css\" />' );\n            $( \"head\" ).append( '<link rel=\"stylesheet\" class=\"simpread-fs-style\" href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/fontawesome.min.css\" />' );\n        }\n    }\n\n    async componentDidMount() {\n        if ( load_count > 0 && !this.verifyContent() ) {\n            return;\n        }\n\n        $root\n            .addClass( \"simpread-font\" )\n            .addClass( theme )\n            .find( rdclsjq )\n                .addClass( theme )\n                .sreffect( { opacity: 1 }, { delay: 100 })\n                .addClass( \"simpread-read-root-show\" );\n\n        this.props.read.fontfamily && ss.FontFamily( this.props.read.fontfamily );\n        this.props.read.fontsize   && ss.FontSize( this.props.read.fontsize );\n        this.props.read.layout     && ss.Layout( this.props.read.layout );\n        this.props.read.site.css   && this.props.read.site.css.length > 0\n            && ss.SiteCSS( this.props.read.site.css );\n        ss.Preview( this.props.read.custom );\n\n        storage.pr.state == \"txt\"             && !location.href.endsWith( \".md\" ) && $( \"sr-rd-content\" ).css({ \"word-wrap\": \"break-word\", \"white-space\": \"pre-wrap\" });\n        $( \"sr-rd-desc\" ).text().trim() == \"\" && $( \"sr-rd-desc\" ).addClass( \"simpread-hidden\" );\n\n        excludes( $(\"sr-rd-content\"), this.props.wrapper.exclude );\n        storage.pr.Beautify( $( \"sr-rd-content\" ) );\n        storage.pr.Format( rdcls );\n\n        kbd.Render( $( \"sr-rd-content\" ));\n        tooltip.Render( rdclsjq );\n        waves.Render({ root: rdclsjq });\n        storage.Statistics( \"read\" );\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"mode\", eventAction: \"readmode\", eventValue: \"readmode\" }) );\n\n        !this.props.wrapper.avatar && this.props.read.toc \n            && toc.Render( \"sr-read\", $( \"sr-rd-content\" ), this.props.read.theme, this.props.read.toc_hide );\n\n        this.props.wrapper.avatar && $( \".simpread-read-root\" ).addClass( \"simpread-multi-root\" );\n\n        loadPlugins( \"read_complete\" );\n\n        setTimeout( ()=>{\n            this.verifyContent();\n            tips.Render( storage.option.plugins );\n            tips.Help( storage.statistics );\n        }, 50 );\n    }\n\n    componentWillUnmount() {\n        run.Event( \"read_end\" );\n        loadPlugins( \"read_end\" );\n        ss.FontSize( \"\" );\n        $root.removeClass( theme )\n             .removeClass( \"simpread-font\" );\n        $root.attr(\"style\") && $root.attr( \"style\", $root.attr(\"style\").replace( \"font-size: 62.5%!important\", \"\" ));\n        ss.SiteCSS();\n        $( \"body\" ).removeClass( \"simpread-hidden\" );\n        $( rdclsjq ).remove();\n        tooltip.Exit( rdclsjq );\n    }\n\n    /**\n     * Controlbar action event\n     * @param {string} type, include: exit, setting, save, scroll, option\n     * @param {string} value \n     * @param {string} custom value, storage.current.custom.art.xxx \n     */\n    onAction( type, value, custom ) {\n        switch ( type ) {\n            case \"exit\":\n                this.exit();\n                break;\n            case \"setting\":\n                setting.Render( ()=>setTimeout( ()=>se.Render(), 500 ));\n                break;\n            case \"siteeditor\":\n                $( \"panel-bg\" ).length > 0 && $( \"panel-bg\" )[0].click();\n                setTimeout( ()=>se.Render(), 500 );\n                break;\n            case \"fontfamily\":\n            case \"fontsize\":\n            case \"layout\":\n            case \"theme\":\n            case \"shortcuts\":\n            case \"custom\":\n                type != \"custom\" ? storage.current[type]=value : storage.current.custom.art[custom]=value;\n                storage.Setcur( storage.current.mode );\n                break;\n            case \"remove\":\n                $( \"panel-bg\" ).length > 0 && $( \"panel-bg\" ).trigger( \"click\" );\n                new Notify().Render({ content: \"移动鼠标选择不想显示的内容，可多次选择，使用 ESC 退出。\", delay: 5000 });\n                highlight.Multi( dom => {\n                    const path = storage.pr.Utils().dom2Xpath( dom ),\n                          site = { ...storage.pr.current.site };\n                    site.exclude.push( `[[\\`${path}\\`]]` );\n                    if ( storage.pr.state == \"temp\" ) {\n                        const include = storage.pr.Utils().dom2Xpath( storage.pr.dom );\n                        site.include  = `[[\\`${include}\\`]]`;\n                        site.name     = site.name.replace( \"tempread::\", \"\" );\n                    }\n                    storage.pr.Updatesite( 'local', storage.current.url, [ site.url, storage.pr.Cleansite(site) ]);\n                    storage.Writesite( storage.pr.sites, () => {\n                        storage.pr.current.site.name    = site.name;\n                        storage.pr.current.site.include = site.include;\n                    });\n                    $(dom).remove();\n                });\n                break;\n            case \"highlight\":\n                new Notify().Render( `移动鼠标选择高亮区域，以便生成阅读模式，此模式将会在页面刷新后失效，详细说明请看 <a href=\"http://ksria.com/simpread/docs/#/重新高亮\" target=\"_blank\">重新高亮</a>` );\n                this.exit();\n                Highlight().done( dom => {\n                    const rerender = element => {\n                        storage.pr.TempMode( \"read\", element );\n                        Render();\n                    };\n                    storage.current.highlight ? \n                        highlight.Control( dom ).done( newDom => {\n                            rerender( newDom );\n                        }) : rerender( dom );\n                });\n                break;\n            /*\n            case \"scroll\":\n                $( \"sr-read\" ).velocity( \"scroll\", { offset: $( \"body\" ).scrollTop() + value });\n                break;\n            */\n        }\n    }\n\n   // exit read mode\n   exit() {\n        Exit();\n    }\n\n    render() {\n        const Article = this.props.wrapper.avatar && this.props.wrapper.avatar.length > 0 ? \n                        <spec.Multiple include={ this.props.wrapper.include } avatar={ this.props.wrapper.avatar } /> :\n                        <sr-rd-content dangerouslySetInnerHTML={{__html: this.props.wrapper.include }} ></sr-rd-content>;\n\n        const Page    = this.props.wrapper.paging && this.props.wrapper.paging.length > 0 && \n                        <spec.Paging paging={ this.props.wrapper.paging } />;\n        return (\n            <sr-read>\n                <ProgressBar show={ this.props.read.progress } />\n                <sr-rd-title>{ this.props.wrapper.title }</sr-rd-title>\n                <sr-rd-desc>{ this.props.wrapper.desc }</sr-rd-desc>\n                { Article }\n                { Page    }\n                <Footer />\n                <ReadCtlbar show={ this.props.read.controlbar } \n                            multi={ this.props.wrapper.avatar ? true : false }\n                            type={ this.props.wrapper.name }\n                            site={{ title: this.props.wrapper.title, url: window.location.href }} \n                            custom={ this.props.read.custom } onAction={ (t,v,c)=>this.onAction( t,v,c ) }/>\n            </sr-read>\n        )\n    }\n\n}\n\n/**\n * Render entry\n * \n * @param {boolean} true: call mathJaxMode(); false: @see mathJaxMode\n */\nfunction Render( callMathjax = true ) {\n    loadPlugins( \"read_start\" );\n    callMathjax && mathJaxMode();\n    storage.pr.ReadMode();\n    if ( typeof storage.pr.html.include == \"string\" && storage.pr.html.include.startsWith( \"<sr-rd-content-error>\" ) ) {\n        console.warn( '=== Adapter failed call Readability View ===' )\n        storage.pr.Readability();\n        storage.pr.ReadMode();\n    } else console.warn( '=== Normal Read mode ===' )\n    console.warn( \"=== Current PuRead object is ===\", storage.pr )\n    ReactDOM.render( <Read read={ storage.current } wrapper={ storage.pr.html } />, getReadRoot() );\n}\n\n/**\n * High light current page to read mode( read only )\n */\nfunction Highlight() {\n    const dtd = $.Deferred();\n    highlight.Start().done( dom => {\n        dtd.resolve( dom );\n    });\n    return dtd;\n}\n\n/**\n * Verify simpread-read-root tag exit\n * \n * @param  {boolean}\n * @return {boolean}\n */\nfunction Exist( action ) {\n    if ( $root.find( rdclsjq ).length > 0 ) {\n        action && setting.Render( ()=>setTimeout( ()=>se.Render(), 500 ));\n        return true;\n    } else {\n        return false;\n    }\n}\n\n/**\n * Exit\n */\nfunction Exit() {\n    $( rdclsjq ).sreffect( { opacity: 0 }, {\n        delay: 100,\n        complete: ( elements ) => {\n            ReactDOM.unmountComponentAtNode( getReadRoot() );\n        }\n    }).addClass( \"simpread-read-root-hide\" );\n}\n\n/**\n * MathJax Mode\n */\nfunction mathJaxMode() {\n    if ( storage.pr.isMathJax() && storage.pr.state == \"temp\" ) {\n        console.warn( '=== MathJax Mode ===' )\n        const dom = storage.pr.MathJaxMode();\n        console.log( 'current get dom is ', dom )\n        if ( typeof dom == \"undefined\" ) {\n            new Notify().Render( \"<a href='http://ksria.com/simpread/docs/#/词法分析引擎?id=智能感知' target='_blank' >智能感知</a> 失败，请移动鼠标框选。\" );\n            Highlight().done( dom => {\n                const rerender = element => {\n                    storage.pr.TempMode( \"read\", element );\n                    Render( false );\n                };\n                storage.current.highlight ? \n                    highlight.Control( dom ).done( newDom => {\n                        rerender( newDom );\n                    }) : rerender( dom );\n            });\n        } else if ( typeof dom == \"string\" ) {\n            const html = storage.pr.GetDom( dom, \"html\" );\n            storage.pr.Newsite( \"read\", html );\n        } else {\n            storage.pr.TempMode( \"read\", dom[0] );\n        }\n    }\n}\n\n/**\n * Get read root\n * \n * @return {jquery} read root jquery object\n */\nfunction getReadRoot() {\n    if ( $root.find( rdclsjq ).length == 0 ) {\n        $root.append( bgtmpl );\n    }\n    return $( rdclsjq )[0];\n}\n\n/**\n * Set exclude style\n * \n * @param {jquery} jquery object\n * @param {array}  hidden html\n */\nfunction excludes( $target, exclude ) {\n    const tags = storage.pr.Exclude( $target );\n    $target.find( tags ).remove();\n}\n\n/**\n * Load plugins from storage and exec\n * \n * @param {string} state include: plugin.run_at\n */\nfunction loadPlugins( state ) {\n    storage.Plugins( () => {\n        storage.option.plugins.forEach( id => {\n            storage.plugins[id] && run.Exec( state, storage.current.site.name, storage.plugins[id] );\n        });\n    });\n}\n\nexport { Render, Exist, Exit, Highlight };\n"
  },
  {
    "path": "src/read/special.jsx",
    "content": "console.log( \"=== simpread read: special load ===\" )\r\n\r\nimport Button    from 'button';\r\n\r\nimport * as kbd  from 'keyboard';\r\n\r\nconst default_src = \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAOVBMVEX///+AgICfn5+Hh4f39/fPz8+vr6+QkJDg4ODv7+/n5+fAwMCoqKiYmJjHx8fX19e4uLjQ0NDIyMhhw/XSAAACBUlEQVRYw6VX2ZKEIAw0ATnk8Pj/j90q3V13SSOxpl+mHEknaUKCUw9xTZnpBLu02ukV4lGoAR9Rbb4Xgii7ztxTF35/ay4p1kdzm2mI/KDn4kkB0w3iICVmbO9IDae31zMkoo8YZkJgl5IrGh1WpHaN34VdDUks//YfrNjsn/cO8NtnAedhivl+G6D9kCH8Buhl/JptLv0dLMIcuqnTBY8TGCdhelvohfGjI3mEEybYZKGdZTKokcfNipCXpg4IysgoMAwDl5KegFGwy2cEYZpfEBQkAjpnehFpQ2HRCwKeDKHdhQiQgGBcEE5NgEWwhgAm/eSo2BchmAXMPUMwBSaEYlE/0RPIGWxLb8BkwiiLam6n/kgzx+3+IOoSBGSbr5+0nN43cz0SnE9Wpr9O0/qz2psf0jCtTrqSR8zFK2lu1D5FjZtpnLX1wfGu/Pxn3e8QillUXGxa3A07J8fimjqb9tixrD8JXJJ8UQp7NcN6n7LrScvQTLAqxtEItTm0/r5wKOH/j9AgutAIc5MzE4VXBLa56UUjOsAwB2+bhuteprCIf9wr+yp7tl5H7C0TzW/tZQwaJQ+i2mX2w92M/BBpMEMhkiGzPtOXhyBCGaYZPFHBH6l2z90cRcfh3QJr8lWjsq1nn2Xe0hHCEvaUmL/btRaxyo/vFKdXiCFlPmk8u9rvdl/WWxJO0oeE7gAAAABJRU5ErkJggg==\";\r\n\r\nexport class Multiple extends React.Component {\r\n\r\n    static defaultProps = {\r\n        include: React.PropTypes.any,\r\n        avatar : React.PropTypes.array,\r\n    }\r\n\r\n    render() {\r\n        const contents = [],\r\n              names    = this.props.avatar[ 0 ].name,\r\n              urls     = this.props.avatar[ 1 ].url;\r\n        this.props.include.each( ( idx, item ) => {\r\n            const art = {};\r\n            art.name    = $( names[idx] ).text();\r\n            art.url     = $( urls[idx]  ).attr( \"src\" );\r\n            art.content = $( item       ).html();\r\n            !art.url && ( art.url = default_src );\r\n            contents.push( art );\r\n        });\r\n        const child = contents.map( item => {\r\n            return <sr-rd-mult>\r\n                        <sr-rd-mult-avatar>\r\n                            <img src={ item.url } />\r\n                            <span>{ item.name }</span>\r\n                        </sr-rd-mult-avatar>\r\n                        <sr-rd-mult-content dangerouslySetInnerHTML={{__html: item.content }} ></sr-rd-mult-content>\r\n                   </sr-rd-mult>\r\n        });\r\n        return (\r\n            <sr-rd-content>\r\n                { child }\r\n            </sr-rd-content>\r\n        )\r\n    }\r\n}\r\n\r\nexport class Paging extends React.Component {\r\n\r\n    static defaultProps = {\r\n        paging: React.PropTypes.array,\r\n    }\r\n\r\n    state = {\r\n        prev : this.props.paging[0].prev,\r\n        next : this.props.paging[1].next,\r\n    }\r\n\r\n    onClick( state ) {\r\n        location.href = this.state[state];\r\n    }\r\n\r\n    componentDidMount() {\r\n        kbd.Bind( [ \"left\", \"right\" ], ( event, combo ) => {\r\n            this.onClick( combo == \"left\" ? \"prev\" : \"next\" );\r\n        });\r\n    }\r\n\r\n    render() {\r\n        return (\r\n            !( !this.state.prev && !this.state.next ) && <sr-page>\r\n                <Button type=\"raised\" text=\"← 前一页\"\r\n                        disable={ !this.state.prev }\r\n                        style={{ \"margin-left\": \"0\" }}\r\n                        color=\"#fff\" backgroundColor=\"#1976D2\"\r\n                        waves=\"md-waves-effect md-waves-button\"\r\n                        onClick={ ()=>this.onClick( \"prev\" ) } />\r\n                <Button type=\"raised\" text=\"后一页 →\"\r\n                        disable={ !this.state.next }\r\n                        style={{ \"margin-right\": \"0\" }}\r\n                        color=\"#fff\" backgroundColor=\"#1976D2\"\r\n                        waves=\"md-waves-effect md-waves-button\"\r\n                        onClick={ ()=>this.onClick( \"next\" ) } />\r\n            </sr-page>\r\n        )\r\n    }\r\n}"
  },
  {
    "path": "src/read/toc.jsx",
    "content": "console.log( \"=== simpread read toc load ===\" )\n\nlet is_click = false;\n\nclass TOC extends React.Component {\n\n    onClick( event ) {\n        try {\n            is_click    = true;\n            let $target = $( event.target ).parent();\n\n            while ( $target.is( \"a\" ) ) { $target = $target.parent(); }\n            if ( $target.is( \"toc\" ) ){\n                return;\n            }\n\n            $target.parent().find( \"active\" ).removeClass( \"toc-outline-active\" );\n            $target.find( \"active\" ).addClass( \"toc-outline-active\" );\n\n            const href      = $target.find( \"a\" ).attr( \"href\" ),\n                  offsetTop = href === \"#\" ? 0 : $(href).offset().top - 5;\n            $( \"html\" ).stop().animate({\n                scrollTop: offsetTop\n            }, 300, () => {\n                setTimeout( ()=>is_click = false, 500 );\n            });\n            event.preventDefault();\n        } catch ( error ) {\n            console.error( \"toc error \", error )\n        }\n    }\n\n    componentDidMount() {\n        let lastId;\n        const topMenu       = $( \"toc\" ),\n              topMenuHeight = topMenu.outerHeight(),\n              menuItems     = topMenu.find( \"a\" ),\n              scrollItems   = menuItems.map( function() {\n                const item  = $( $(this).attr(\"href\") );\n                    if ( item.length ) { return item; }\n              }),\n              setActive = function() {\n                if ( is_click ) return;\n                const fromTop = $(window).scrollTop() + topMenuHeight;\n                let cur = scrollItems.map( function() {\n                if ($(this).offset().top < fromTop)\n                    return this;\n                });\n                cur = cur[cur.length - 1];\n                let id = cur && cur.length ? cur[0].id : \"\";\n    \n                if ( lastId !== id ) {\n                    lastId = id;\n                    id == \"\" && ( id = \"sr-toc-0\" );\n                    menuItems.parent().find( \"active\" ).removeClass( \"toc-outline-active\" );\n                    menuItems.filter(\"[href='#\"+id+\"']\").parent().find( \"active\" ).addClass( \"toc-outline-active\" );\n                }\n            };\n\n        $( window ).scroll( setActive );\n\n        $( \"outline\" ).map( ( idx, item ) => {\n            $(item).width( 180 - parseInt( $(item).css(\"padding-left\") ) );\n        })\n\n        this.props.hidden == false && $( \"toc\" ).css({ \"width\" : \"initial\" });\n\n        setActive();\n    }\n\n    render() {\n        const outline = this.props.table.map( item => {\n            return (\n                <outline className={ item.level } onClick={ evt=>this.onClick(evt) }>\n                    <active></active>\n                    <a className={ \"toc-outline-theme-\" + this.props.theme } href={ \"#\" + item.id}><span>{ item.value }</span></a>\n                </outline>\n            )\n        });\n        return (\n            <toc className=\"simpread-font simpread-theme-root\">\n                { outline }\n            </toc>\n        )\n    }\n\n}\n\n/**\n * Render\n * \n * @param {string} selector name\n * @param {jquery} jquery object\n * @param {string} theme\n * @param {boolen} hidden\n */\nfunction Render( root, $target, theme, hidden ) {\n\n    // hack code\n    //if ( location.host.includes( \"blog.csdn.net\"  )) return;\n    //if ( location.host.includes( \"post.smzdm.com\" )) return;\n\n    const table = [],\n          cls   = hidden ? \"toc-bg-hidden\" : \"\";\n    $target.find( \"h1, h2, h3, h4\" ).map( ( idx, item) => {\n        const $item = $( item ),\n              tag   = $item[0].tagName.toLowerCase(),\n              value = $item.text();\n        let   id    = $item.attr( \"id\" );\n        id          = id == undefined ? `sr-toc-${idx}` : `${id}-${idx}`\n        $item.attr( \"id\", id );\n        value && table.push({\n            level: `toc-level-${tag}`,\n            id,\n            value,\n        });\n    });\n    console.log( \"current toc is \", table )\n    $( root ).append( `<toc-bg class=${cls}></tocbg>` );\n    table.length > 1 && ReactDOM.render( <TOC hidden={ hidden } table={ table } theme={ theme } />, $( \"toc-bg\" )[0] );\n}\n\nexport {\n    Render\n}"
  },
  {
    "path": "src/service/browser.js",
    "content": "console.log( \"=== simpread browser load ===\" )\r\n\r\nconst mode = {\r\n    chrome : \"chrome\",\r\n    sogou  : \"sogouExplorer\",\r\n    firefox: \"firefox\",\r\n}, userAgent = () => {\r\n    if ( window.navigator.userAgent.toLowerCase().includes( \"firefox\" ) ) {\r\n        return \"firefox\";\r\n    } else {\r\n        return \"chrome\"\r\n    }\r\n};\r\nlet browser_type;\r\n\r\n/**\r\n * Chromium browser api adapter\r\n * \r\n * @class\r\n */\r\nclass Browser {\r\n\r\n    /**\r\n     * Browser adapter[read]\r\n     * \r\n     * @return {object} current chromium\r\n     */\r\n    get adapter() {\r\n        switch ( browser_type ) {\r\n            case mode.chrome:\r\n                return chrome;\r\n            case mode.sogou:\r\n                return sogouExplorer;\r\n            case mode.firefox:\r\n                return browser;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Browser type[read]\r\n     * \r\n     * @return {string} browser type @see mode\r\n     */\r\n    get type() {\r\n        return browser_type;\r\n    }\r\n\r\n    constructor( type ) {\r\n        browser_type = type;\r\n    }\r\n\r\n    /**\r\n     * Is firefox\r\n     * \r\n     * @return {boolean} true or false\r\n     */\r\n    isFirefox() {\r\n        return browser_type == mode.firefox;\r\n    }\r\n\r\n}\r\n\r\nconst br      = new Browser( userAgent() ),\r\n      adapter = br.adapter;\r\n\r\nexport {\r\n    br,\r\n    adapter as browser\r\n};"
  },
  {
    "path": "src/service/config.js",
    "content": "console.log( \"=== simpread config load ===\" )\n\nimport * as ss from 'stylesheet';\n\nconst keyboard = {\n    \"控制栏 - 导出\" : {\n        md: {\n            \"kbd\"  : \"md\",\n            \"type\" : \"markdown\",\n            \"desc\" : \"导出为 Markdown\",\n        },\n        om: {\n            \"kbd\"  : \"om\",\n            \"type\" : \"offlinemarkdown\",\n            \"desc\" : \"导出为 离线 Markdown\",\n        },\n        pg: {\n            \"kbd\"  : \"pg\",\n            \"type\" : \"png\",\n            \"desc\" : \"导出为 PNG\",\n        },\n        pf: {\n            \"kbd\"  : \"pf\",\n            \"type\" : \"pdf\",\n            \"desc\" : \"导出为 PDF\",\n        },\n        ep: {\n            \"kbd\"  : \"ep\",\n            \"type\" : \"epub\",\n            \"desc\" : \"导出为 epub\",\n        },\n        hm: {\n            \"kbd\"  : \"hm\",\n            \"type\" : \"html\",\n            \"desc\" : \"导出为 HTML\",\n        },\n        oh: {\n            \"kbd\"  : \"oh\",\n            \"type\" : \"offlinehtml\",\n            \"desc\" : \"导出为 离线 HTML\",\n        },\n        tm: {\n            \"kbd\"  : \"tm\",\n            \"type\" : \"temp\",\n            \"desc\" : \"导出为 临时页面\",\n        },\n        cp: {\n            \"kbd\"  : \"cp\",\n            \"type\" : \"snapshot\",\n            \"desc\" : \"截图\",\n        },\n    },\n    \"控制栏 - 其它\" : {\n        ff: {\n            \"kbd\"  : \"ff\",\n            \"type\" : \"fontfamily_\",\n            \"desc\" : \"改变字体样式，取值 1 ~ 5\",\n        },\n        fs: {\n            \"kbd\"  : \"fs\",\n            \"type\" : \"fontsize_\",\n            \"desc\" : \"改变字体大小，取值 1 ~ 3\",\n        },\n        la: {\n            \"kbd\"  : \"la\",\n            \"type\" : \"layout_\",\n            \"desc\" : \"改变版面布局，取值 1 ~ 3\",\n        },\n        \"th →\": {\n            \"kbd\"  : \"th →\",\n            \"type\" : \"theme_next\",\n            \"desc\" : \"更换为后一个主题\",\n        },\n        \"th ←\": {\n            \"kbd\"  : \"th ←\",\n            \"type\" : \"theme_prev\",\n            \"desc\" : \"更换为前一个主题\",\n        },\n    },\n    \"控制栏 - 生产力工具\" : {\n        yx: {\n            \"kbd\"  : \"yx\",\n            \"type\" : \"yinxiang\",\n            \"desc\" : \"保存到 印象笔记\",\n        },\n        er: {\n            \"kbd\"  : \"er\",\n            \"type\" : \"evernote\",\n            \"desc\" : \"保存到 Evernote\",\n        },\n        db: {\n            \"kbd\"  : \"db\",\n            \"type\" : \"dropbox\",\n            \"desc\" : \"保存到 Dropbox\",\n        },\n        on: {\n            \"kbd\"  : \"on\",\n            \"type\" : \"onenote\",\n            \"desc\" : \"保存到 Onenote\",\n        },\n        gr: {\n            \"kbd\"  : \"gr\",\n            \"type\" : \"gdrive\",\n            \"desc\" : \"保存到 Google 云端硬盘\",\n        },\n        nt: {\n            \"kbd\"  : \"nt\",\n            \"type\" : \"notion\",\n            \"desc\" : \"保存到 Notion\",\n        },\n        jg: {\n            \"kbd\"  : \"jg\",\n            \"type\" : \"jianguo\",\n            \"desc\" : \"保存到 坚果云\",\n        },\n        yq: {\n            \"kbd\"  : \"yq\",\n            \"type\" : \"yuque\",\n            \"desc\" : \"保存到 语雀\",\n        },\n        wz: {\n            \"kbd\"  : \"wz\",\n            \"type\" : \"weizhi\",\n            \"desc\" : \"保存到 为知笔记\",\n        },\n        kd: {\n            \"kbd\"  : \"kd\",\n            \"type\" : \"kindle\",\n            \"desc\" : \"保存到 Kindle\",\n        },\n        ln: {\n            \"kbd\"  : \"ln\",\n            \"type\" : \"linnk\",\n            \"desc\" : \"保存到 Linnk\",\n        },\n        pt: {\n            \"kbd\"  : \"pt\",\n            \"type\" : \"pocket\",\n            \"desc\" : \"保存到 Pocket\",\n        },\n        ip: {\n            \"kbd\"  : \"ip\",\n            \"type\" : \"instapaper\",\n            \"desc\" : \"保存到 Instapaper\",\n        },\n        rl: {\n            \"kbd\"  : \"rl\",\n            \"type\" : \"save\",\n            \"desc\" : \"保存到 稍后读\",\n        },\n        br: {\n            \"kbd\"  : \"br\",\n            \"type\" : \"bear\",\n            \"desc\" : \"保存到 Bear\",\n        },\n        ul: {\n            \"kbd\"  : \"ul\",\n            \"type\" : \"ulysses\",\n            \"desc\" : \"保存到 Ulysses\",\n        },\n    },\n    \"控制栏 - 无障碍\" : {\n        ts: {\n            \"kbd\"  : \"ts\",\n            \"type\" : \"dyslexia_speak\",\n            \"desc\" : \"开始朗读\",\n        },\n        tt: {\n            \"kbd\"  : \"tt\",\n            \"type\" : \"dyslexia_speak_stop\",\n            \"desc\" : \"停止朗读\",\n        },\n    },\n    \"控制栏 - 设定\" : {\n        fu: {\n            \"kbd\"  : \"fu\",\n            \"type\" : \"fullscreen\",\n            \"desc\" : \"全屏\",\n        },\n        st: {\n            \"kbd\"  : \"st\",\n            \"type\" : \"setting\",\n            \"desc\" : \"设定对话框\",\n        },\n        se: {\n            \"kbd\"  : \"se\",\n            \"type\" : \"siteeditor\",\n            \"desc\" : \"站点编辑器\",\n        },\n        hl: {\n            \"kbd\"  : \"hl\",\n            \"type\" : \"highlight\",\n            \"desc\" : \"重新选择高亮\",\n        },\n        cl: {\n            \"kbd\"  : \"cl\",\n            \"type\" : \"remove\",\n            \"desc\" : \"隐藏任意元素\",\n        },\n        tr: {\n            \"kbd\"  : \"tr\",\n            \"type\" : \"tempread\",\n            \"desc\" : \"提交临时阅读模式\",\n        },\n    },\n};\n\n/**\n * Shortcust\n */\nconst shortcuts = {\n    \"fontfamily\": {\n        key     : \"F\",\n        value   : [ \"1\", \"2\", \"3\", \"4\", \"5\" ],\n        name    : [ \"default\", \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft Yahei\", \"Source Han Sans CN\" ]\n    },\n    \"fontsize\"  : {\n        key     : \"S\",\n        value   : [ \"1\", \"2\", \"3\" ],\n        name    : [ \"58%\", \"62.5%\", \"70%\" ],\n    },\n    \"layout\"    : {\n        key     : \"W\",\n        value   : [ \"1\", \"2\", \"3\" ],\n        name    : [ \"25%\", \"20%\", \"15%\" ],\n    }\n};\n\n/**\n * Read controlbar items\n */\nconst readItems = {\n    \"exit\" : {\n        \"name\" : \"关闭\",\n        \"icon\" : ss.IconPath(\"exit_icon\"),\n    },\n    \"option\" : {\n        \"name\" : \"设定\",\n        \"icon\" : ss.IconPath(\"option_icon\"),\n        \"color\": \"#03A9F4\",\n        \"items\": {\n            \"fullscreen\" : {\n                \"name\" : \"全屏\",\n                \"icon\" : ss.IconPath(\"fullscreen_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n            \"siteeditor\" : {\n                \"name\" : \"站点编辑器\",\n                \"icon\" : ss.IconPath(\"siteeditor_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n            \"setting\" : {\n                \"name\" : \"设定\",\n                \"icon\" : ss.IconPath(\"setting_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n            \"highlight\" : {\n                \"name\" : \"重新选项高亮区域\",\n                \"icon\" : ss.IconPath(\"highlight_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n            \"remove\" : {\n                \"name\" : \"隐藏任意元素\",\n                \"icon\" : ss.IconPath(\"remove_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n            \"tempread\": {\n                \"name\" : \"提交临时阅读模式\",\n                \"icon\" : ss.IconPath(\"tempread_icon\"),\n                \"color\": \"#03A9F4\",\n            },\n        },\n    },\n    \"readlater\" : {\n        \"name\" : \"暂存\",\n        \"icon\" : ss.IconPath(\"save_icon\"),\n        \"color\": \"#FF5722\",\n        \"items\": {\n            \"linnk\" : {\n                \"name\" : \"保存到 Linnk\",\n                \"icon\" : ss.IconPath(\"linnk_icon\"),\n                \"color\": \"#FF5722\",\n            },\n            \"instapaper\" : {\n                \"name\" : \"保存到 Instapaper\",\n                \"icon\" : ss.IconPath(\"instapaper_icon\"),\n                \"color\": \"#FF5722\",\n            },\n            \"pocket\" : {\n                \"name\" : \"保存到 Pocket\",\n                \"icon\" : ss.IconPath(\"pocket_icon\"),\n                \"color\": \"#FF5722\",\n            },\n            \"save\" : {\n                \"name\" : \"保存到 稍后读\",\n                \"icon\" : ss.IconPath(\"readlater_icon\"),\n                \"color\": \"#FF5722\",\n            },\n        },\n    },\n    \"download\" : {\n        \"name\" : \"导出\",\n        \"icon\" : ss.IconPath(\"download_icon\"),\n        \"color\": \"#D4237A\",\n        \"items\": {\n            \"epub\" : {\n                \"name\" : \"导出为 epub\",\n                \"icon\" : ss.IconPath(\"epub_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"pdf\" : {\n                \"icon\" : ss.IconPath(\"pdf_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"png\" : {\n                \"icon\" : ss.IconPath(\"png_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"markdown\" : {\n                \"name\" : \"导出为 MD\",\n                \"icon\" : ss.IconPath(\"markdown_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"offlinemarkdown\" : {\n                \"name\" : \"导出为 离线 MD\",\n                \"icon\" : ss.IconPath(\"offline_markdown_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"html\" : {\n                \"name\" : \"导出为 HTML\",\n                \"icon\" : ss.IconPath(\"html_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"offlinehtml\" : {\n                \"name\" : \"导出为 离线 HTML\",\n                \"icon\" : ss.IconPath(\"offline_html_icon\"),\n                \"color\": \"#D4237A\",\n            },\n            \"snapshot\" : {\n                \"name\" : \"截图\",\n                \"icon\" : ss.IconPath(\"snapshot_icon\"),\n                \"color\": \"#D4237A\",\n            },\n        },\n    },\n    \"send\" : {\n        \"name\" : \"保存\",\n        \"icon\" : ss.IconPath(\"send_icon\"),\n        \"color\": \"#00BCD4\",\n        \"items\": {\n            \"yinxiang\" : {\n                \"name\" : \"保存到 印象笔记\",\n                \"icon\" : ss.IconPath(\"yinxiang_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"evernote\" : {\n                \"name\" : \"保存到 Evernote\",\n                \"icon\" : ss.IconPath(\"evernote_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"dropbox\" : {\n                \"name\" : \"保存到 Dropbox\",\n                \"icon\" : ss.IconPath(\"dropbox_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"onenote\" : {\n                \"name\" : \"保存到 Onenote\",\n                \"icon\" : ss.IconPath(\"onenote_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"gdrive\" : {\n                \"name\" : \"保存到 Google 云端硬盘\",\n                \"icon\" : ss.IconPath(\"gdrive_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"jianguo\" : {\n                \"name\" : \"保存到 坚果云\",\n                \"icon\" : ss.IconPath(\"jianguo_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"yuque\" : {\n                \"name\" : \"保存到 语雀\",\n                \"icon\" : ss.IconPath(\"yuque_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"notion\" : {\n                \"name\" : \"保存到 Notion\",\n                \"icon\" : ss.IconPath(\"notion_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"youdao\" : {\n                \"name\" : \"保存到 有道云笔记\",\n                \"icon\" : ss.IconPath(\"youdao_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"weizhi\" : {\n                \"name\" : \"保存到 为知笔记\",\n                \"icon\" : ss.IconPath(\"wiz_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"kindle\" : {\n                \"name\" : \"保存到 Kindle\",\n                \"icon\" : ss.IconPath(\"kindle_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"temp\" : {\n                \"name\" : \"生成临时页面\",\n                \"icon\" : ss.IconPath(\"temp_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"bear\" : {\n                \"name\" : \"保存到 Bear\",\n                \"icon\" : ss.IconPath(\"bear_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n            \"ulysses\" : {\n                \"name\" : \"保存到 Ulysses\",\n                \"icon\" : ss.IconPath(\"ulysses_icon\"),\n                \"color\": \"#00BCD4\",\n            },\n        },\n    },\n    \"dyslexia\" : {\n        \"name\" : \"无障碍\",\n        \"icon\" : ss.IconPath(\"dyslexia_icon\"),\n        \"color\": \"#90ee02\",\n        \"items\": {\n            \"dyslexia_speak\" : {\n                \"name\" : \"播放声音\",\n                \"icon\" : ss.IconPath(\"speak_icon\"),\n                \"color\": \"#90ee02\",\n            },\n            \"dyslexia_speak_stop\" : {\n                \"name\" : \"停止播放\",\n                \"icon\" : ss.IconPath(\"speak_stop_icon\"),\n                \"color\": \"#90ee02\",\n            },\n        },\n    },\n    \"share\" : {\n        \"name\" : \"共享\",\n        \"icon\" : ss.IconPath(\"share_icon\"),\n        \"color\": \"#3f51b5\",\n        \"items\": {\n            \"share_gplus\" : {\n                \"name\" : \"Google G+\",\n                \"icon\" : ss.IconPath(\"share_gplus_icon\"),\n                \"color\": \"#DD4B39\",\n            },\n            \"share_facebook\" : {\n                \"name\" : \"Facebook\",\n                \"icon\" : ss.IconPath(\"share_facebook_icon\"),\n                \"color\": \"#3B5998\",\n            },\n            \"share_telegram\" : {\n                \"name\" : \"Telegram\",\n                \"icon\" : ss.IconPath(\"share_telegram_icon\"),\n                \"color\": \"#0088CC\",\n            },\n            \"share_twitter\" : {\n                \"name\" : \"Twitter\",\n                \"icon\" : ss.IconPath(\"share_twitter_icon\"),\n                \"color\": \"#1DA1F2\",\n            },\n            \"share_weibo\" : {\n                \"name\" : \"新浪微博\",\n                \"icon\" : ss.IconPath(\"share_weibo_icon\"),\n                \"color\": \"#E6162D\",\n            },\n            \"share_card\" : {\n                \"name\" : \"分享卡\",\n                \"icon\" : ss.IconPath(\"share_card_icon\"),\n                \"color\": \"#0f4137\",\n            },\n        },\n    },\n    \"trigger\" : {\n        \"name\" : \"插件触发器\",\n        \"icon\" : ss.IconPath(\"plugin_icon\"),\n        \"color\": \"#00bcd4\",\n        \"items\": {}\n    },\n    /*\n    \"down\" : {\n        \"name\" : \"向下滚动\",\n        \"icon\" : ss.IconPath(\"down_icon\"),\n        \"color\": \"#00BCD4\",\n    },\n    \"up\" : {\n        \"name\" : \"向上滚动\",\n        \"icon\" : ss.IconPath(\"up_icon\"),\n        \"color\": \"#00BCD4\",\n    },\n    */\n    \"fontfamily\" : {\n        \"name\" : \"字体样式\",\n        \"icon\" : ss.IconPath(\"fontfamily_icon\"),\n        \"color\": \"#9C27B0\",\n        \"items\": {\n            \"fontfamily_default\" : {\n                \"name\" : \"系统默认\",\n                \"icon\" : ss.IconPath(\"fontfamily_default_icon\"),\n                \"color\": \"#9C27B0\",\n            },\n            \"fontfamily_PingFang SC\" : {\n                \"name\" : \"苹方\",\n                \"icon\" : ss.IconPath(\"fontfamily_pingfang_icon\"),\n                \"color\": \"#9C27B0\",\n            },\n            \"fontfamily_Hiragino Sans GB\" : {\n                \"name\" : \"冬青黑体\",\n                \"icon\" : ss.IconPath(\"fontfamily_hiragino_icon\"),\n                \"color\": \"#9C27B0\",\n            },\n            \"fontfamily_Microsoft Yahei\" : {\n                \"name\" : \"微软雅黑\",\n                \"icon\" : ss.IconPath(\"fontfamily_yahei_icon\"),\n                \"color\": \"#9C27B0\",\n            },\n            \"fontfamily_Source Han Sans CN\" : {\n                \"name\" : \"思源黑体\",\n                \"icon\" : ss.IconPath(\"fontfamily_hansans_icon\"),\n                \"color\": \"#9C27B0\",\n            },\n        },\n    },\n    \"fontsize\" : {\n        \"name\" : \"字体大小\",\n        \"icon\" : ss.IconPath(\"fontsize_icon\"),\n        \"color\": \"#9E9E9E\",\n        \"items\": {\n            \"fontsize_70%\" : {\n                \"name\" : \"增大\",\n                \"icon\" : ss.IconPath(\"fontsize_large_icon\"),\n                \"color\": \"#9E9E9E\",\n            },\n            \"fontsize_62.5%\" : {\n                \"name\" : \"正常\",\n                \"icon\" : ss.IconPath(\"fontsize_normal_icon\"),\n                \"color\": \"#9E9E9E\",\n            },\n            \"fontsize_58%\" : {\n                \"name\" : \"减小\",\n                \"icon\" : ss.IconPath(\"fontsize_small_icon\"),\n                \"color\": \"#9E9E9E\",\n            },\n        },\n    },\n    \"layout\" : {\n        \"name\" : \"版面布局\",\n        \"icon\" : ss.IconPath(\"layout_icon\"),\n        \"color\": \"#FFEB3B\",\n        \"items\": {\n            \"layout_15%\" : {\n                \"name\" : \"宽栏\",\n                \"icon\" : ss.IconPath(\"layout_large_icon\"),\n                \"color\": \"#FFEB3B\",\n            },\n            \"layout_20%\" : {\n                \"name\" : \"正常\",\n                \"icon\" : ss.IconPath(\"layout_normal_icon\"),\n                \"color\": \"#FFEB3B\",\n            },\n            \"layout_25%\" : {\n                \"name\" : \"窄栏\",\n                \"icon\" : ss.IconPath(\"layout_small_icon\"),\n                \"color\": \"#FFEB3B\",\n            },\n        },\n    },\n    \"theme\" : {\n        \"name\" : \"主题\",\n        \"icon\" : ss.IconPath(\"theme_icon\"),\n        \"color\": \"#FB8C00\",\n        \"items\": {\n            \"theme_prev\" : {\n                \"name\" : \"前一个主题\",\n                \"icon\" : ss.IconPath(\"theme_prev_icon\"),\n                \"color\": \"#FB8C00\",\n            },\n            \"theme_next\" : {\n                \"name\" : \"后一个主题\",\n                \"icon\" : ss.IconPath(\"theme_next_icon\"),\n                \"color\": \"#FB8C00\",\n            },\n        },\n    },\n}\n\n/**\n * Read options\n */\nconst fontfamily = [{\n        value : \"default\",\n        name  : \"系统默认\",\n    },{\n        value : \"PingFang SC\",\n        name  : \"苹方字体\",\n        style : {\n            text: { fontFamily: \"PingFang SC\" }\n        }\n    },{\n        value : \"Hiragino Sans GB\",\n        name  : \"冬青黑体\",\n        style : {\n            text: { fontFamily: \"Hiragino Sans GB\" }\n        }\n    },{\n        value : \"Microsoft Yahei\",\n        name  : \"微软雅黑\",\n        style : {\n            text: { fontFamily: \"Microsoft Yahei\" }\n        }\n    },{\n        value : \"Source Han Sans CN\",\n        name  : \"思源黑体\",\n        style : {\n            text: { fontFamily: \"Source Han Sans CN\" }\n        }\n    },{\n        value : \"Noto Serif CJK SC, Source Han Serif SC, Source Han Serif CN\",\n        name  : \"思源宋体\",\n        style : {\n            text: { fontFamily: \"Noto Serif CJK SC, Source Han Serif SC, Source Han Serif CN\" }\n        }\n    },{\n        value : \"\",\n        name  : \"自定义字体\",\n        style : {\n            text: { fontFamily: \"default\" }\n        }\n    }],\n    fontsize = [{\n        value : \"62.5%\",\n        name  : \"正常\",\n    },{\n        value : \"70%\",\n        name  : \"大号\",\n    },{\n        value : \"58%\",\n        name  : \"小号\",\n    }],\n    layout = [{\n        value : \"20%\",\n        name  : \"正常\",\n    },{\n        value : \"15%\",\n        name  : \"宽栏\",\n    },{\n        value : \"25%\",\n        name  : \"窄栏\",\n}],\nreadLabels = [ \"白练\", \"白磁\", \"卯之花色\", \"丁子色\", \"娟鼠\", \"月白\", \"百合\", \"紺鼠\", \"黒鸢\" ];\n\n/**\n * Focus controlbar items\n */\nconst focusItems = ( items => {\n    const news = $.extend( true, {}, items ),\n          dels = [ \"theme\", \"fontfamily\", \"fontsize\", \"layout\", \"dyslexia\", \"trigger\" ];\n    dels.forEach( del => delete news[ del ] );\n    delete news.option.items.fullscreen;\n    delete news.option.items.tempread;\n    delete news.download.items.snapshot;\n    delete news.download.items.offlinehtml;\n    delete news.download.items.offlinemarkdown;\n    news.top = {\n        \"name\" : \"返回顶部\",\n        \"icon\" : ss.IconPath(\"top_icon\"),\n        \"color\": \"#009688\",\n    };\n    return news;\n})( readItems );\n\n/**\n * Focus options\n */\nconst focusThemes = [\n    \"235, 235, 235, 0.9\",\n    \"216, 216, 216, 0.9\",\n    \"229, 221, 208, 0.9\",\n    \"243, 234, 203, 0.9\",\n    \"176, 192, 182, 0.9\",\n    \"28, 31, 43, 0.9\",\n    \"61, 66, 70, 0.9\",\n    \"17, 18, 20, 0.9\"\n],\nfocusLabels = [ \"白练\", \"灰青\", \"素色\", \"鸟之子色\", \"青磁鼠\", \"焦茶\", \"御纳戸色\", \"黒鸢\" ];\n\n/**\n * Options page\n */\nconst tabsItem = [{\n        name: \"共通\",\n        value: \"common\",\n        active : true,\n        route: \"#common\",\n    },{\n        name: \"基础设定\",\n        value: \"simple\",\n        route: \"#simple\",\n    },{\n        name: \"高级设定\",\n        value: \"labs\",\n        route: \"#labs\",\n    },{\n        name: \"站点管理\",\n        value: \"sites\",\n        route: \"#sites\",\n    },{\n        name: \"插件管理\",\n        value: \"plugins\",\n        route: \"#plugins\",\n    },{\n        name: \"稍后读\",\n        value: \"later\",\n        route: \"#later\",\n    },{\n        name: \"账户\",\n        value: \"account\",\n        route: \"#account\",\n    },{\n        name: \"关于\",\n        value: \"about\",\n        route: \"#about\",\n}],\n    headerColors  = [ \"#64B5F6\", \"#81C784\", \"#7986CB\", \"#9575CD\", \"#4DD0E1\", \"#BA68C8\", \"#989fb5\", \"#4DB6AC\" ],\n    topColors     = [ \"#2196F3\", \"#4CAF50\", \"#3F51B5\", \"#673AB7\", \"#00BCD4\", \"#9C27B0\", \"#6f7a9b\", \"#009688\" ],\n    menuItem      = tabsItem.map( ( item, idx ) => {\n       const menu = { ...item };\n       switch ( idx ) {\n            case 0:\n                delete menu.active;\n                menu.fontIcon = '<i class=\"fas fa-sync-alt\"></i>';\n                break;\n            case 1:\n                menu.fontIcon = '<i class=\"fas fa-wrench\"></i>';\n                break;\n            case 2:\n                menu.fontIcon = '<i class=\"fas fa-tools\"></i>';\n                break;\n            case 3:\n                menu.fontIcon = '<i class=\"fas fa-sitemap\"></i>';\n                break;\n            case 4:\n                menu.fontIcon = '<i class=\"fas fa-plug\"></i>';\n                break;\n            case 5:\n                menu.fontIcon = '<i class=\"fas fa-inbox\"></i>';\n                break;\n            case 6:\n                menu.fontIcon = '<i class=\"fas fa-user\"></i>';\n                break;\n            case 7:\n                menu.fontIcon = '<i class=\"fas fa-info-circle\"></i>';\n                break;\n       }\n       return menu;\n});\n\n/**\n * Unread list\n */\nconst actionItems = [\n    {\n        id: \"pocket\",\n        title: \"发送到 Pocket\",\n    },{\n        id: \"instapaper\",\n        title: \"发送到 Instapaper\",\n    },{\n        id: \"linnk\",\n        title: \"发送到 Linnk\",\n    },{\n        id: \"remove\",\n        title: \"删除\",\n    }\n];\n\nexport {\n    shortcuts,\n    keyboard,\n\n    focusItems,\n    focusThemes,\n    focusLabels,\n\n    readItems,\n    fontfamily,\n    fontsize,\n    layout,\n    readLabels,\n\n    tabsItem,\n    headerColors,\n    topColors,\n    menuItem,\n\n    actionItems,\n}\n"
  },
  {
    "path": "src/service/export.js",
    "content": "console.log( \"=== simpread export load ===\" )\n\nimport domtoimage from 'dom2image';\nimport FileSaver  from 'filesaver';\nimport Turndown   from 'markdown';\nimport mdgfm      from 'mdgfm';\nimport EpubPress  from 'epubpress';\nimport Instapaper from 'instapaper';\n\nimport * as msg   from 'message';\nimport {browser}  from 'browser';\nimport * as puplugin from 'puplugin';\nimport * as wiz   from 'wiz';\n\n/**\n * Create PNG\n * \n * @param {html}     html element\n * @param {string}   name\n * @param {function} callback\n */\nfunction png( element, name, callback ) {\n    domtoimage.toBlob( element )\n    .then( blob => {\n        blob && FileSaver.saveAs( blob, name );\n        callback( !!blob );\n    }).catch( error => {\n        console.error( \"export png failed\", error )\n        callback( undefined );\n    });\n}\n\n/**\n * Create PDF file\n */\nfunction pdf() {\n    window.print();\n}\n\n/**\n * Create Markdown file\n * \n * @param {string} data\n * @param {string} name\n * @param {function} 0: base64; 1: error\n */\nfunction markdown( data, name, callback ) {\n    try {\n        const turndownService = new Turndown(),\n              gfm             = mdgfm.gfm,\n              tables          = mdgfm.tables,\n              strikethrough   = mdgfm.strikethrough,\n              codeBlock       = mdgfm.highlightedCodeBlock;\n        turndownService.use([ gfm, tables, strikethrough, codeBlock ]);\n        turndownService.addRule( 'pre', {\n            filter: [ 'pre' ],\n            replacement: content => {\n                return '\\n\\n```\\n' + content + '\\n```\\n\\n'\n            }\n        });\n        const md     = turndownService.turndown( data ),\n              base64 = \"data:text/plain;charset=utf-8,\" + encodeURIComponent( md );\n        name ? download( base64, name ) : callback( md );\n    } catch( error ) {\n        callback( undefined, error );\n    }\n}\n\nfunction epub( data, url, title, desc, callback ) {\n    console.log( data )\n    const ebook = new EpubPress({\n        title,\n        description: desc == \"\" ? title : desc,\n        sections: [{\n            url,\n            html: `<html><body><div>${data}</div></body></html>`,\n        }]\n    });\n    ebook.publish().then( () => {\n        ebook.download();\n    }).then(() => {\n        console.log( \"succcess\" );\n        callback( true );\n    }).catch( error => {\n        console.log( \"publish epub error \", error );\n        callback( false );\n    });\n}\n\n/**\n * Downlaod\n * \n * @param {string} image base64 code\n * @param {string} name\n */\nfunction download( data, name ) {\n    const $a   = $( `<a style=\"display:none\" href=${data} download=\"${name}\"></a>` ).appendTo( \"body\" );\n    $a[0].click();\n    $a.remove();\n}\n\n/**\n * Downlaod\n * \n * @param {string} origin data\n * @param {string} name\n */\nfunction prueDownload( data, name ) {\n    const blob = new Blob([data], {\n        type: \"text/plain;charset=utf-8\"\n    });\n    const url = URL.createObjectURL(blob);\n    browser.downloads.download({\n        url     : url,\n        filename: name,\n    });\n}\n\n/**\n * Dis contented serice\n * \n * @param {string} service id \n */\nfunction unlink( id ) {\n    const content = {\n        \"dropbox\" : \"https://www.dropbox.com/account/connected_apps\",\n        \"pocket\"  : \"https://getpocket.com/connected_applications\",\n        \"instapaper\":\"https://www.instapaper.com/\",\n        \"evernote\": \"https://www.evernote.com/AuthorizedServices.action\",\n        \"yinxiang\": \"https://app.yinxiang.com/AuthorizedServices.action\",\n        \"onenote\" : \"https://account.live.com/consent/Manage\",\n        \"gdrive\"  : \"https://drive.google.com/drive/my-drive\",\n        \"yuque\"   : \"https://www.yuque.com/yuque/developer/delete-oauth-apps\",\n        \"notion\"  : \"http://ksria.com/simpread/docs/#/授权服务?id=取消授权\",\n        \"youdao\"  : \"http://ksria.com/simpread/docs/#/授权服务?id=取消授权\",\n        \"weizhi\"  : \"http://ksria.com/simpread/docs/#/授权服务?id=取消授权\",\n        \"jianguo\" : \"http://help.jianguoyun.com/?p=2064\",\n        \"linnk\"   : \"https://linnk.net/\",\n    }\n    return content[id]\n}\n\n/**\n * Dropbox\n * \n * @class\n */\nclass Dropbox {\n\n    get id()   { return \"dropbox\"; }\n    get name() { return name( this.id ); }\n\n    get client_id() {\n        return \"4cyaw4wqpbg4751\";\n    }\n\n    get redirect_uri() {\n        return \"https://kenshin.github.io/simpread/auth.html?id=dropbox\";\n    }\n\n    get config_name() {\n        return \"simpread_config.json\";\n    }\n\n    New() {\n        this.dtd  = $.Deferred();\n        this.access_token = \"\";\n        return this;\n    }\n\n    Auth() {\n        const url = `https://www.dropbox.com/oauth2/authorize?response_type=token&client_id=${this.client_id}&redirect_uri=${this.redirect_uri}`;\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Accesstoken( url ) {\n        const arr = url.match( /access_token=\\S+&token_type/i );\n        arr && arr.length > 0 &&\n            ( this.access_token = arr[0].replace( /(access_token=)|(&token_type)/ig, \"\" ));\n        this.access_token != \"\" ? this.dtd.resolve() : this.dtd.reject();\n    }\n\n    Exist( name, callback ) {\n        let idx     = -1;\n        const data  = { path : \"\" },\n              token = this.access_token;\n\n        $.ajax({\n            url     : \"https://api.dropboxapi.com/2/files/list_folder\",\n            type    : \"POST\",\n            data    : JSON.stringify( data ),\n            headers : {\n                \"Authorization\"   : `Bearer ${token}`,\n                \"Content-Type\"    : \"application/json\"\n            },\n        }).done( ( data, textStatus, jqXHR ) => {\n            idx = data.entries.findIndex( item => item.name == name && item[\".tag\"] == \"file\" );\n            callback( idx, undefined );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( idx, error );\n        });\n    }\n\n    Read( name, callback ) {\n        const data  = { path : `/${name}` },\n              token = this.access_token;\n\n        $.ajax({\n            url     : \"https://content.dropboxapi.com/2/files/download\",\n            type    : \"POST\",\n            headers : {\n                \"Authorization\"   : `Bearer ${token}`,\n                \"Dropbox-API-Arg\" : JSON.stringify( data ),\n            },\n        }).done( ( data, textStatus, jqXHR ) => {\n            callback( \"read\", data, undefined )\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( \"read\", undefined, error )\n        });\n    }\n\n    Write( name, data, callback, path = \"\" ) {\n        const safename = data => data.replace( /\\//ig, \"\" ),\n              args     = { path: `/${path}${safename(name)}`, mode: \"overwrite\" },\n              token    = this.access_token,\n              safejson = args => {\n                const charsToEncode = /[\\u007f-\\uffff]/g;\n                return JSON.stringify(args).replace( charsToEncode, c => {\n                    return '\\\\u' + ( '000' + c.charCodeAt(0).toString(16)).slice(-4);\n                });\n        };\n\n        $.ajax({\n            url     : \"https://content.dropboxapi.com/2/files/upload\",\n            type    : \"POST\",\n            data    : data,\n            headers : {\n                \"Authorization\"   : `Bearer ${token}`,\n                \"Dropbox-API-Arg\" : safejson( args ),\n                \"Content-Type\"    : \"application/octet-stream\"\n            },\n            processData : false,\n            contentType : false\n        }).done( ( data, textStatus, jqXHR ) => {\n            callback( \"write\", data, undefined );\n        }).fail( ( xhr, textStatus, error ) => {\n            console.error( xhr, status, error )\n            callback( \"write\", undefined, error.toLowerCase().startsWith( \"invalid_access_token\" ) ? `${ this.name } 授权过期，请重新授权。` : \"error\" );\n        });\n\n    }\n}\n\n/**\n * Pocket\n * \n * @class\n */\nclass Pocket {\n\n    get id()   { return \"pocket\"; }\n    get name() { return name( this.id ); }\n\n    get consumer_key() {\n        return \"69741-d75561b7a9a96a511f36552e\";\n    }\n\n    get redirect_uri() {\n        return \"https://kenshin.github.io/simpread/auth.html?id=pocket\";\n    }\n\n    get header() {\n        return {\n            \"content-type\": \"application/x-www-form-urlencoded\",\n            \"X-Accept\"    : \"application/json\"\n        }\n    }\n\n    New() {\n        this.dtd = $.Deferred();\n        this.access_token = \"\";\n        this.code         = \"\";\n        this.tags         = \"\";\n        return this;\n    }\n\n    Request( callback ) {\n        const data = {\n            consumer_key: this.consumer_key,\n            redirect_uri: this.redirect_uri,\n        };\n\n        $.ajax({\n            url     : \"https://getpocket.com/v3/oauth/request\",\n            type    : \"POST\",\n            headers : this.header,\n            data,\n        }).done( ( data, textStatus, jqXHR ) => {\n            callback( data, textStatus == \"success\" ? \"\" : textStatus );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Login( code ) {\n        this.code = code;\n        const url = `https://getpocket.com/auth/authorize?request_token=${code}&redirect_uri=${this.redirect_uri}`;\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Accesstoken( url ) {\n        url ? this.dtd.resolve() : this.reject();\n    }\n\n    Auth( callback ) {\n        const data = {\n            consumer_key: this.consumer_key,\n            code        : this.code,\n            redirect_uri: this.redirect_uri,\n        };\n\n        $.ajax({\n            url     : \"https://getpocket.com/v3/oauth/authorize\",\n            type    : \"POST\",\n            headers : this.header,\n            data,\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result && result.access_token ) {\n                this.access_token = result.access_token;\n                callback( result, undefined );    \n            } else callback( undefined, \"error\" );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Add( url, title, callback ) {\n        const data = {\n            consumer_key: this.consumer_key,\n            access_token: this.access_token,\n            url,\n            title,\n            tags: this.tags ? this.tags : \"simpread\"\n        };\n\n        $.ajax({\n            url     : \"https://getpocket.com/v3/add\",\n            type    : \"POST\",\n            headers : this.header,\n            data,\n        }).done( ( data, textStatus, jqXHR ) => {\n            callback( data, undefined );\n        }).fail( ( xhr, status, error ) => {\n            console.error( xhr, status, error )\n            callback( undefined, error.toLowerCase() == \"unauthorized\" ? `${ this.name } 授权过期，请重新授权。` : \"error\" );\n        });\n    }\n\n}\n\n/**\n * Instapaper\n * \n * @class\n */\nclass Ins {\n\n        get id()   { return \"instapaper\"; }\n        get name() { return name( this.id ); }\n        constructor() {\n            this.access_token = \"\";\n            this.token_secret = \"\";\n            this.ins          = new Instapaper();\n        }\n\n        get consumer_key() {\n            return \"23464e13c91c4cba86f0df8aa87ec15a\";\n        }\n    \n        get consumer_secret() {\n            return \"b71eb22c7def4d19a2d9e7b7208d31c9\";\n        }\n\n        Login( username, password, callback ) {\n            this.ins.consumer_key    = this.consumer_key;\n            this.ins.consumer_secret = this.consumer_secret;\n            this.ins.requestToken( username, password ).done( result => {\n                this.access_token    = this.ins.token;\n                this.token_secret    = this.ins.token_secret;\n                callback( result, undefined );\n            }).fail( ( jqXHR, textStatus, error ) => {\n                console.error( jqXHR, textStatus, error )\n                callback( undefined, textStatus );\n            });\n        }\n\n        Add( url, title, description, callback ) {\n            this.ins.token           = this.access_token;\n            this.ins.token_secret    = this.token_secret;\n            this.ins.consumer_key    = this.consumer_key;\n            this.ins.consumer_secret = this.consumer_secret;\n            const settings           = this.ins.add( url, title, description );\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.CORB, { settings } ), result => {\n                if ( result.done ) { callback( \"success\", undefined ); }\n                else callback( undefined, result.fail );\n            });\n        }\n}\n\n/**\n * Linnk\n * \n * @class\n */\nclass Linnk {\n\n    get id()   { return \"linnk\"; }\n    get name() { return name( this.id ); }\n    constructor() {\n        this.access_token = \"\";\n        this.group_id     = \"\";\n        this.group_name   = \"\";\n    }\n\n    get error_code() {\n        return {\n            \"-1001\": \"密码不正确，请确认。\",\n            \"-1002\": \"⽤户不存在，请确认。\",\n            \"-1004\": \"验证错误，请重新登录。\",\n            \"-1005\": \"登录失效，请重新登录。\",\n            \"-1006\": \"验证失效，请重新登录。\",\n        }\n    }\n\n    get tags() {\n        return \"simpread\";\n    }\n\n    Login( username, password, callback ) {\n        const data = {\n            userName: username,\n            password: password,\n        };\n\n        $.ajax({\n            url     : \"https://linnk.net/a/api/login\",\n            type    : \"POST\",\n            data,\n        }).done( ( result, textStatus, jqXHR ) => {\n            result && result.code == 200 && ( this.access_token = result.token );\n            callback( result, undefined );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Add( url, title, callback ) {\n        const data = {\n            groupId  : this.group_id,\n            targetURL: url,\n            title,\n            tagsStr  : this.tags,\n        },\n        settings = {\n            url     : \"https://linnk.net/a/api/bookmark/new\",\n            type    : \"POST\",\n            headers : { Authorization: this.access_token },\n            data,\n        };\n\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.CORB, { settings } ), result => {\n            if ( result.done ) {\n                const data = JSON.parse( result.done );\n                if ( data && data.code == 200 ) callback( \"success\", undefined );\n                else callback( undefined, \"error\" );\n            } else callback( undefined, result.fail );\n        });\n    }\n\n    Groups( callback ) {\n        const settings = {\n            url     : \"https://linnk.net/a/api/group/my\",\n            type    : \"GET\",\n            headers : { Authorization: this.access_token },\n        };\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.CORB, { settings } ), result => {\n            if ( result.done ) {\n                const data = JSON.parse( result.done );\n                if ( data.code != 200 ) {\n                    callback( undefined, this.error_code[ data.code ] );\n                } else callback( data, undefined );\n            } else callback( undefined, \"error\" );\n        });\n    }\n\n    GetGroup( name, target ) {\n        const group = target.find( obj => obj.groupName == name );\n        group && ( this.group_name = group.groupName );\n        return group;\n    }\n\n    NewGroup( name, callback ) {\n        const settings = {\n            url     : \"https://linnk.net/a/api/group/new\",\n            type    : \"POST\",\n            headers : { Authorization: this.access_token },\n            data    : { groupName: name },\n        };\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.CORB, { settings } ), result => {\n            if ( result.done ) {\n                callback( JSON.parse( result.done ), undefined );\n            } else callback( undefined, result.fail );\n        });\n    }\n\n    GetSafeGroup( name, callback ) {\n        this.Groups( ( result, error ) => {\n            if ( result && result.code == 200 ) {\n                const group = this.GetGroup( name, result.data );\n                !group && this.NewGroup( name, callback );\n                group  && callback({ data: group, code: 200 }, undefined );\n            } else {\n                callback( undefined, error == \"error\" ? \"error\" : error );\n            }\n        })\n    }\n}\n\n/**\n * Evernote\n * \n * @class\n */\nclass Evernote {\n\n    get id()   { return this.env.toLowerCase(); }\n    get name() { return name( this.env ); }\n\n    constructor() {\n        this.token          = \"\";\n        this.token_secret   = \"\"\n        this.oauth_verifier = \"\";\n        this.access_token   = \"\";\n        this.env            = \"\"; // include: \"yinxiang\" \"evernote\"\n        this.sandbox        = true;\n    }\n\n    get host() {\n        if ( this.sandbox ) {\n            return \"sandbox.evernote.com\";\n        } else {\n            return this.env == \"yinxiang\" ? \"app.yinxiang.com\" : \"www.evernote.com\";\n        }\n    }\n\n    get server() {\n        //return this.sandbox ? \"http://localhost:3000/evernote\" : \"https://simpread.herokuapp.com/evernote\";\n        return \"https://simpread.herokuapp.com/evernote\";\n    }\n\n    get china() {\n        return this.env != \"evernote\" ? true : false;\n    }\n\n    get headers() {\n        return {\n            sandbox: this.sandbox,\n            china  : this.china,\n            type   : this.env,\n        }\n    }\n\n    New() {\n        this.dtd            = $.Deferred();\n        this.token          = \"\";\n        this.token_secret   = \"\";\n        this.access_token   = \"\"\n        this.oauth_verifier = \"\";\n        return this;\n    }\n\n    RequestToken( callback ) {\n        $.ajax({\n            url     : `${this.server}/oauth`,\n            type    : \"POST\",\n            headers : this.headers,\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result && result.code == 200 ) {\n                this.token        = result.data.token;\n                this.token_secret = result.data.token_secret;\n                const url = `https://${this.host}/OAuth.action?oauth_token=${this.token}`;\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n                callback( result, undefined );\n            } else {\n                callback( undefined, \"error\" );\n            }\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Accesstoken( url ) {\n        url.split( \"&\" ).forEach( item => {\n            item.startsWith( \"oauth_token=\"    ) && ( this.token = item.replace( \"oauth_token=\", \"\" ));\n            item.startsWith( \"oauth_verifier=\" ) && ( this.oauth_verifier = item.replace( \"oauth_verifier=\", \"\" ));\n        });\n        this.oauth_verifier ? this.dtd.resolve() : this.dtd.reject( \"oauth_verifier is null\" );\n    }\n\n    Auth( callback ) {\n        $.ajax({\n            url     : `${this.server}/token`,\n            type    : \"POST\",\n            headers : this.headers,\n            data    : {\n                token  : this.token,\n                token_secret  : this.token_secret,\n                oauth_verifier: this.oauth_verifier,\n            }\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result && result.code == 200 ) {\n                this.access_token = result.data.token;\n                callback( result, undefined );\n            } else if ( result && result.code == 401 ) {\n                console.log( \"result.code == 401\" )\n            } else {\n                callback( undefined, \"error\" );\n            }\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n        return this;\n    }\n\n    Add( title, content, callback ) {\n        $.ajax({\n            url     : `${this.server}/add`,\n            type    : \"POST\",\n            headers : this.headers,\n            data    : {\n                token  : this.access_token,\n                title,\n                content,\n            }\n        }).done( ( result, textStatus, jqXHR ) => {\n            console.assert( result.code != -1, result )\n            result && result.code == 200 && callback( result, undefined );\n            result && result.code == -1  &&\n                callback( undefined, result.data.message == \"authenticationToken\" ? `${ name(this.env) } 授权错误，请重新授权。` : \"error\" );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n}\n\n/**\n * Onenote\n * \n * @class\n */\nclass Onenote {\n\n    get id()   { return \"onenote\"; }\n    get name() { return name( this.id ); }\n\n    get client_id() {\n        return \"b21d6e4a-30d5-4c39-9ef2-0ac14a01822b\";\n    }\n\n    get client_secret() {\n        return \"q0ewrhaVHidjZragQ0hi1MV\";\n    }\n\n    get redirect_uri() {\n        //return \"https://simpread.herokuapp.com\";\n        return \"https://kenshin.github.io/simpread/auth.html\";\n    }\n\n    get scopes() {\n        return [ \"office.onenote_create\" ];\n    }\n\n    New() {\n        this.dtd  = $.Deferred();\n        this.code = \"\";\n        this.access_token = \"\";\n        return this;\n    }\n\n    Wrapper( url, title, content ) {\n        return `\n        <html xmlns='http://www.w3.org/1999/xhtml' lang='en-us'>\n            <head>\n                <title>${title}</title>\n                <meta name='created' content='${new Date()}'\n            </head>\n            <body>\n                <blockquote>\n                    本文由 <a href=\"http://ksria.com/simpread\" target=\"_blank\">简悦 SimpRead</a> 转码，原文地址 <a href=\"${url}\" target=\"_blank\">${url}</a>\n                </blockquote>\n                <br></br>\n                ${content}\n            </body>\n        </html>\n        `;\n    }\n\n    Login() {\n        let url = \"https://login.live.com/oauth20_authorize.srf?\";\n        const params = {\n            client_id    : this.client_id,\n            redirect_uri : this.redirect_uri,\n            scope        : this.scopes.join( \" \" ),\n            response_type: \"code\",\n        };\n        Object.keys( params ).forEach( key => {\n            url += `${key}=${params[key]}&`;\n        });\n        url = url.substr( 0, url.length )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Accesstoken( url ) {\n        url = url.replace( \"http://ksria.com/simpread/auth.html?\", \"\" );\n        if ( url.startsWith( \"code\" ) ) {\n            this.code = url.replace( \"code=\", \"\" );\n            this.dtd.resolve();\n        } else {\n            this.dtd.reject();\n        }\n    }\n\n    Auth( callback ) {\n        $.ajax({\n            url     : \"https://login.live.com/oauth20_token.srf\",\n            type    : \"POST\",\n            headers : {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            data    : {\n                client_id     : this.client_id,\n                client_secret : this.client_secret,\n                code          : this.code,\n                grant_type    : \"authorization_code\",\n                redirect_uri  : this.redirect_uri,\n            }\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result ) {\n                this.access_token = result.access_token;\n                callback( result, undefined );\n            } else {\n                callback( undefined, \"error\" );\n            }\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Add( html, callback ) {\n        $.ajax({\n            url     : \"https://www.onenote.com/api/v1.0/me/notes/pages\",\n            type    : \"POST\",\n            headers : {\n                \"Content-Type\": \"application/xhtml+xml\",\n                \"Authorization\": `Bearer ${this.access_token}`\n            },\n            data    : html,\n        }).done( ( result, status, xhr ) => {\n            console.log( result, status, xhr )\n            status == \"success\" && callback( result, undefined );\n            status != \"success\" && callback( undefined, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            console.error( xhr, status, error )\n            callback( undefined, xhr.status == 401 ? `${ this.name } 授权过期，请重新授权。` : \"error\" );\n        });\n    }\n}\n\n/**\n * GDrive\n * \n * @class\n */\nclass GDrive {\n\n    get id()   { return \"gdrive\"; }\n    get name() { return name( this.id ); }\n\n    get client_id() {\n        return \"920476054505-fd4192mqtfodackl1vip1c3c0hp6298n.apps.googleusercontent.com\";\n    }\n\n     get redirect_uri() {\n         return \"https://kenshin.github.io/simpread/auth.html?id=gdrive\";\n     }\n\n     get scope() {\n         return \"https://www.googleapis.com/auth/drive.file\";\n     }\n\n     get folder() {\n         return {\n             name : \"简悦\",\n             mimeType: \"application/vnd.google-apps.folder\",\n         }\n     }\n     \n     get header() {\n         return {\n            \"Content-type\" : \"application/json\",\n            \"Authorization\": `Bearer ${this.access_token}`\n         }\n     }\n\n    get errors() {\n        return {\n            401: `${ this.name } 授权过期，请重新授权。`,\n            403: \"调用达到最大值，请重新授权后再使用。\",\n            500: \"Google 服务出现问题，请稍后再使用。\",\n        }\n    }\n\n    get boundary() {\n        return \"-------314159265358979323846\";\n    }\n\n    New() {\n        this.dtd = $.Deferred();\n        this.folder_id    = \"\";\n        this.access_token = \"\";\n        return this;\n    }\n\n    Login() {\n        let url = \"https://accounts.google.com/o/oauth2/v2/auth?\";\n        const params = {\n            client_id    : this.client_id,\n            redirect_uri : this.redirect_uri,\n            scope        : this.scope,\n            response_type: \"token\",\n            include_granted_scopes: true,\n            state        : \"state_parameter_passthrough_value\"\n        };\n        Object.keys( params ).forEach( key => {\n            url += `${key}=${params[key]}&`;\n        });\n        url = url.substr( 0, url.length )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Accesstoken( url ) {\n        url.split( \"&\" ).forEach( item => {\n            item.startsWith( \"access_token=\" ) && ( this.access_token = item.replace( \"access_token=\", \"\" ));\n        })\n        this.access_token != \"\" ? this.dtd.resolve() : this.dtd.reject();\n    }\n\n    Auth( callback ) {\n        $.ajax({\n            url     : `https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${ this.access_token }`,\n            type    : \"GET\",\n        }).done( ( result, textStatus, jqXHR ) => {\n            textStatus == \"success\" && result && result.aud == this.client_id ?\n                this.CreateFolder( callback ) :\n                callback( undefined, \"error\" );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    CreateFolder( callback ) {\n        $.ajax({\n            url     : \"https://www.googleapis.com/drive/v3/files\",\n            type    : \"GET\",\n            headers : this.header,\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( textStatus == \"success\" ) {\n                const folder = result.files.find( file => file.name = this.folder.name && file.mimeType == this.folder.mimeType )\n                if ( folder ) {\n                    this.folder_id = folder.id;\n                    callback( result, undefined );\n                } else this.Add( \"folder\", callback );\n            } else callback( undefined, \"error\" );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    CreateFile( name, content ) {\n        const type  = \"application/json; charset=UTF-8\",\n              meta  = {\n                name,\n                parents: [ this.folder_id ],\n                mimeType: type,\n        },\n        delimiter   = `\\r\\n--${this.boundary}\\r\\n`,\n        close_delim = `\\r\\n--${this.boundary}--`,\n        body        =\n            delimiter +\n            \"Content-Type: application/json\\r\\n\\r\\n\" +\n            JSON.stringify( meta ) +\n            delimiter +\n            `Content-Type: ${type}\\r\\n\\r\\n` +\n            content +\n            close_delim;\n        return body;\n    }\n\n    /**\n     * Add folder / file\n     * @param {string} include: folder file\n     * @param {func} callback\n     * @param {string} content, only type == \"folder\"\n     */\n    Add( type, callback, content ) {\n        $.ajax({\n            url     : type == \"folder\" ? \n                        \"https://www.googleapis.com/drive/v3/files\" : \n                        \"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart\",\n            type    : \"POST\",\n            headers : type == \"folder\" ? \n                        this.header : \n                        { ...this.header, ...{ \"Content-Type\": `multipart/form-data; boundary=\"${this.boundary}\"` }},\n            data    : type == \"folder\" ? \n                        JSON.stringify( this.folder ) : \n                        content\n        }).done( ( result, textStatus, jqXHR ) => {\n            type == \"folder\" && textStatus == \"success\" && ( this.folder_id = result.id );\n            textStatus == \"success\" && callback( result, undefined );\n            textStatus != \"success\" && callback( undefined, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            console.error( xhr, status, error )\n            const msg = xhr && xhr.responseJSON && this.errors[xhr.responseJSON.error.code] ? this.errors[xhr.responseJSON.error.code] : \"error\";\n            callback( undefined, msg );\n        });\n    }\n}\n\n/**\n * Jianguo\n * \n * @class\n */\nclass Jianguo {\n\n    get id()   { return \"jianguo\"; }\n    get name() { return name( this.id ); }\n\n    get url() {\n        return \"https://dav.jianguoyun.com/dav/\";\n    }\n\n    get root() {\n        return \"SimpRead\";\n    }\n\n    get folder() {\n        return \"md\";\n    }\n\n    get config_name() {\n        return \"simpread_config.json\";\n    }\n\n    Auth( user, password, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.jianguo, {\n            url: this.url,\n            user,\n            password,\n            method: {\n                type: \"folder\",\n                root: this.root,\n                folder: this.folder,\n            },\n        }), callback );\n    }\n\n    Add( user, password, path, content, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.jianguo, {\n            url: this.url,\n            user,\n            password,\n            method: {\n                type: \"file\",\n                path,\n                content\n            },\n        }), callback );\n    }\n\n    Read( user, password, name, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.jianguo, {\n            url: this.url,\n            user,\n            password,\n            method: {\n                type : \"read\",\n                path: this.root + \"/\" + name,\n                name,\n            },\n        }), callback );\n    }\n}\n\n/**\n * WebDAV\n * \n * @class\n */\nclass WebDAV {\n\n    get id()   { return \"webdav\"; }\n    get name() { return name( this.id ); }\n\n    get root() {\n        return \"/SimpRead\";\n    }\n\n    Auth( url, user, password, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.WebDAV, {\n            url,\n            user,\n            password,\n            method: {\n                type: \"folder\",\n                root: this.root,\n            },\n        }), callback );\n    }\n\n    Add( url, user, password, name, content, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.WebDAV, {\n            url,\n            user,\n            password,\n            method: {\n                type: \"file\",\n                root: this.root,\n                name,\n                content\n            },\n        }), callback );\n    }\n}\n\n/**\n * Yuque\n * \n * @class\n */\nclass Yuque {\n\n    get id()   { return \"yuque\"; }\n    get name() { return name( this.id ); }\n\n    get client_id() {\n        return \"8p4PvTuP02UN7WhLlQrY\";\n    }\n\n    get client_secret() {\n        return \"kl0LpKR7Tu3EDEJJOjAPEwEAkxeVYrcY5cEEOPeG\";\n    }\n\n    get redirect_uri() {\n        //return \"https://simpread.herokuapp.com\";\n        return \"https://kenshin.github.io/simpread/auth.html\";\n    }\n\n    get scopes() {\n        return \"repo, doc\";\n    }\n\n    New() {\n        this.dtd  = $.Deferred();\n        this.code = \"\";\n        this.access_token = \"\";\n        this.token_type = \"\";\n        this.user_id = \"\";\n        this.repos_id = \"\";\n        return this;\n    }\n\n    Login() {\n        let url = \"https://www.yuque.com/oauth2/authorize?\";\n        const params = {\n            client_id    : this.client_id,\n            redirect_uri : this.redirect_uri,\n            scope        : this.scopes,\n            state        : \"yuque_authorize\",\n            response_type: \"code\",\n        };\n        Object.keys( params ).forEach( key => {\n            url += `${key}=${params[key]}&`;\n        });\n        url = url.substr( 0, url.length )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Accesstoken( url ) {\n        url = url.replace( \"http://ksria.com/simpread/auth.html?\", \"\" );\n        if ( url.startsWith( \"code\" ) ) {\n            this.code = url.replace( \"code=\", \"\" ).replace( \"&state=yuque_authorize\", \"\" );\n            this.dtd.resolve();\n        } else {\n            this.dtd.reject();\n        }\n    }\n\n    Auth( callback ) {\n        $.ajax({\n            url     : \" https://www.yuque.com/oauth2/token\",\n            type    : \"POST\",\n            data    : {\n                client_id     : this.client_id,\n                client_secret : this.client_secret,\n                code          : this.code,\n                grant_type    : \"authorization_code\",\n                redirect_uri  : this.redirect_uri,\n            }\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( result ) {\n                this.access_token = result.access_token;\n                this.token_type   = result.token_type;\n                callback( result, undefined );\n            } else {\n                callback( undefined, \"error\" );\n            }\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    GetUser( callback ) {\n        $.ajax({\n            url     : \"https://www.yuque.com/api/v2/user\",\n            type    : \"GET\",\n            headers : {\n                \"Content-Type\": \"application/json\",\n                \"X-Auth-Token\": this.access_token\n            },\n        }).done( ( result, status, xhr ) => {\n            if ( status == \"success\" ) {\n                this.user_id = result.data.id;\n                callback( result, undefined );\n            } else callback( result, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            callback( undefined, error );\n        });\n    }\n\n    GetRepos( callback ) {\n        $.ajax({\n            url     : `https://www.yuque.com/api/v2/users/${this.user_id}/repos`,\n            type    : \"GET\",\n            headers : {\n                \"Content-Type\": \"application/json\",\n                \"X-Auth-Token\": this.access_token\n            },\n        }).done( ( result, status, xhr ) => {\n            if ( status == \"success\" ) {\n                result.data.forEach( item => {\n                    if ( item.slug == \"simpread\" ) {\n                        this.repos_id = item.id;\n                    }\n                });\n                callback( result, undefined );\n            } else callback( result, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            callback( undefined, xhr );\n        });\n    }\n\n    CreateRepo( callback ) {\n        const data = {\n            name: \"SimpRead\",\n            slug: \"simpread\",\n            description: \"来自简悦的收藏\",\n            public: 0,\n            type: \"Book\",\n        };\n        $.ajax({\n            url     : `https://www.yuque.com/api/v2/users/${this.user_id}/repos`,\n            type    : \"POST\",\n            headers : {\n                \"Content-Type\": \"application/json\",\n                \"X-Auth-Token\": this.access_token\n            },\n            data : JSON.stringify( data )\n        }).done( ( result, status, xhr ) => {\n            if ( status == \"success\" ) {\n                this.repos_id = result.data.id;\n                callback( result, undefined );\n            } else callback( result, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            callback( undefined, xhr );\n        });\n    }\n\n    Add( title, body, callback ) {\n        const data = {\n            title,\n            slug: Math.round(+new Date()),\n            public: 0,\n            body,\n        };\n        $.ajax({\n            url     : `https://www.yuque.com/api/v2/repos/${this.repos_id}/docs`,\n            type    : \"POST\",\n            headers : {\n                \"Content-Type\": \"application/json\",\n                \"X-Auth-Token\": `${this.access_token}`\n            },\n            data : JSON.stringify( data )\n        }).done( ( result, status, xhr ) => {\n            status == \"success\" && callback( result, undefined );\n            status != \"success\" && callback( undefined, \"error\" );\n        }).fail( ( xhr, status, error ) => {\n            console.error( xhr, status, error )\n            callback( undefined, error.toLowerCase() == \"unauthorized\" ? `${ this.name } 授权过期，请重新授权。` : \"error\" );\n        });\n    }\n}\n\n/**\n * Notion\n * \n * @class\n */\nclass Notion {\n\n    get id()   { return \"notion\"; }\n    get name() { return name( this.id ); }\n\n    get url() {\n        return \"https://www.notion.so/\";\n    }\n\n    hasWriteRule(role){\n        return role === 'read_and_write' || role === 'editor'\n    }\n\n    getBlockName(titleArray){\n        if (!titleArray) return 'Undefined'\n        if (Array.isArray(titleArray))\n          return titleArray.map((t) => t[0]).join('')\n        return 'Undefined'\n    }\n\n    UUID() {\n        var __extends=void 0&&(void 0).__extends||function(){var _extendStatics=function extendStatics(d,b){_extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(d,b){d.__proto__=b}||function(d,b){for(var p in b)if(b.hasOwnProperty(p))d[p]=b[p]};return _extendStatics(d,b)};return function(d,b){_extendStatics(d,b);function __(){this.constructor=d}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new __())}}();var ValueUUID=function(){function ValueUUID(_value){this._value=_value;this._value=_value}ValueUUID.prototype.asHex=function(){return this._value};return ValueUUID}();var V4UUID=function(_super){__extends(V4UUID,_super);function V4UUID(){return _super.call(this,[V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-','4',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._oneOf(V4UUID._timeHighBits),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex()].join(''))||this}V4UUID._oneOf=function(array){return array[Math.floor(array.length*Math.random())]};V4UUID._randomHex=function(){return V4UUID._oneOf(V4UUID._chars)};V4UUID._chars=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];V4UUID._timeHighBits=['8','9','a','b'];return V4UUID}(ValueUUID);function generateUuid(){return new V4UUID().asHex()}\n        return generateUuid();\n    }\n\n    getBlocks( result ) {\n        /**\n         * 读取所有空间，并创建映射。\n         */\n        const spaceMaps = {}\n        Object.values( result.recordMap.space ).forEach(({ value: spaceValue, role }) => {\n            if (!this.hasWriteRule(role)) return;\n            spaceMaps[spaceValue.id] = {\n                name  : spaceValue.name,\n                value : spaceValue.id,\n                type  : 'space',\n                blocks: [],\n            }\n        });\n\n        /**\n         * 读取所有收藏空间，并创建映射。\n         */\n        if ( !result.recordMap.collection ) result.recordMap.collection = {};\n        const collectionMaps = {};\n        Object.values(\n            result.recordMap.collection\n        ).forEach(({ value: collectionValue, role }) => {\n            if (!this.hasWriteRule(role)) return;\n            collectionMaps[ collectionValue.parent_id ] = collectionValue;\n            collectionMaps[ collectionValue.id ]        = collectionValue;\n        })\n\n        /**\n         * 遍历所有当前用户能看到的空间。\n         */\n        const processCollection = ( id, spaceBlocks ) => {\n            const collection = collectionMaps[id];\n            if ( !collection ) return;\n\n            let schemaKey;\n            Object.keys( collection.schema ).some( key => {\n                const schema = collection.schema[key];\n                if ( schema.type === 'url' && schema.name === 'URL' ) {\n                    schemaKey = key;\n                    return true;\n                }\n                return false;\n            })\n\n            const block = {\n                name  : '　　' + this.getBlockName( collection.name ),\n                value : collection.id,\n                type  : 'collection',\n            };\n            schemaKey && ( block.schema = schemaKey );\n            spaceBlocks.push( block );\n        }\n        Object.values( result.recordMap.block ).forEach( ({ role, value: blockValue }) => {\n            if ( !this.hasWriteRule( role )) return;\n            const {\n                type,\n                space_id,\n                parent_id,\n                id,\n                collection_id,\n            } = blockValue;\n\n            const _space       = space_id ? spaceMaps[space_id] : spaceMaps[parent_id],\n                    _spaceBlocks = _space   ? _space.blocks       : this.blocks;\n\n            if (type == 'page') {\n                _spaceBlocks.push({\n                    name : '　　' + this.getBlockName( blockValue.properties && blockValue.properties.title || undefined ),\n                    value: id,\n                    type : 'page',\n                });\n            } else if ( type == 'collection_view_page' ) {\n                processCollection( id, _spaceBlocks );\n            } else if ( type == 'collection_view' ) {\n                processCollection( collection_id, _spaceBlocks );\n            }\n        });\n\n        Object.values( spaceMaps ).forEach( space => {\n            const { blocks, ...spaceAttr } = space;\n            if ( blocks && blocks.length > 0 ) {\n                this.blocks.push({ name: spaceAttr.name, type: blocks[0].type, value: blocks[0].value });\n                this.blocks.push( ...blocks );\n            }\n        });\n    }\n\n    getFirstBlock( result ) {\n        const blocks = Object.values( result.recordMap.block );\n        for ( let i = 0; i < blocks.length; i++ ) {\n            const block = blocks[i].value,\n                  role  = blocks[i].role;\n            if ( [ \"page\", \"collection_view\" ].includes( block.type ) && this.hasWriteRule( role )) {\n                const name = block.properties && block.properties.title && block.properties.title[0][0] || \"Undefined\";\n                this.blocks.push({ name, value: block.id, type: block.type });\n                break;\n            }\n        }\n    }\n\n    Auth( callback ) {\n        let warn = \"\";\n        $.ajax({\n            url     : this.url + \"api/v3/loadUserContent\",\n            type    : \"POST\",\n        }).done( ( result, status, xhr ) => {\n            if ( result && status == \"success\" ) {\n                this.access_token = Object.values( result.recordMap.notion_user )[0].value.id;\n                this.blocks       = [];\n\n                try {\n                    this.getBlocks( result );\n                } catch ( error ) {\n                    console.warn( error )\n                    warn = error;\n                    this.getFirstBlock( result );\n                }\n\n                if ( this.blocks.length == 0 ) {\n                    callback( undefined, `Notion.so 并未提供 API 所以会出现授权失败的情况，如发生此问题，请提 <a target=\"_blank\" href=\"https://github.com/Kenshin/simpread/issues/809\"><b>Issues</b></a>` );\n                    return;\n                }\n\n                this.type         = this.blocks[0].type;\n                this.folder_id    = this.blocks[0].value;\n                this.save_image   = false;\n                callback( result, undefined, warn );\n            }\n        }).fail( ( xhr, status, error ) => {\n            console.error( error, status, xhr )\n            callback( undefined, xhr.status == 401 ? `请先 <a target=\"_blank\" href=\"https://www.notion.so/\">登录 Notion</a> ` : \"请稍后再试\" );\n        });\n    }\n\n    MathImages( content ) {\n        const result = content.match( /!\\[.*?\\]\\(http(.*?)\\)/g );\n\n        if( !result ) { return []; }\n\n        const images   = result.map( o => {\n            const temp = /!\\[.*?\\]\\((http.*?)\\)/.exec( o );\n            if ( temp ) {\n                return temp[1];\n            }\n            return '';\n        }).filter( o => o && !~o.indexOf( 'secure.notion-static.com/' ));\n        return images;\n    }\n\n    DonwloadOriginImage( url ) {\n        return new Promise(( resolve, reject ) => {\n          browser.runtime.sendMessage(\n            msg.Add( msg.MESSAGE_ACTION.notion_dl_img, {\n                url,\n                protocol: window.location.protocol\n            }), res => {\n                if ( res.done ) resolve( res.done );\n                else reject( res.fail );\n            });\n        });\n    }\n\n    UploadOriginImage({ type, size, url }) {\n        return new Promise(( resolve, reject ) => {\n            this.GetFileUrl(\n                this.UUID(),\n                urls => {\n                    browser.runtime.sendMessage(\n                        msg.Add( msg.MESSAGE_ACTION.notion_up_img, {\n                        url: url,\n                        upUrl: urls.signedPutUrl,\n                        }), res => {\n                            if ( res.done ) {\n                                resolve({ from: url, to: urls.url });\n                            } else resolve();\n                        }\n                    )\n                },\n                {\n                    bucket: 'secure',\n                    contentType: type,\n                }\n            )\n        })\n    }\n\n    async UploadImages( content ) {\n\n        let completeCount  = 0;\n        const images       = [ ...new Set( this.MathImages( content ))],\n              imagesCount  = images.length,\n              replacements = [],\n              uploadImage  = this.UploadOriginImage.bind( this ),\n              notify       = new Notify().Render({ state: \"loading\", content: `正在上传图片到 Notion ，请稍等` }),\n              updateNotify = () => {\n                notify.update( `正在上传图片到 Notion ，当前进度 ${ completeCount }/${ imagesCount }` );\n              },\n              escapeRegExp = str => {\n                return str.replace( /[.*+\\-?^${}()|[\\]\\\\]/g, '\\\\$&' ); // $& means the whole matched string\n              };\n        replacements.push(\n            await images.reduce(( prevPromise, imageUrl ) => {\n                if ( !imageUrl ) return;\n                return prevPromise.then(( replacement ) => {\n                    if ( replacement ) {\n                        replacements.push( replacement );\n                    }\n                    completeCount ++;\n                    updateNotify();\n                    return this.DonwloadOriginImage( imageUrl ).then(\n                        uploadImage,\n                        err => {\n                            console.error(err)\n                        }\n                    )\n                });\n            }, Promise.resolve())\n        )\n        notify.complete();\n\n        replacements.forEach(( replacement ) => {\n            if ( replacement ) {\n                content = content.replace(\n                    new RegExp(escapeRegExp( replacement.from ), 'g' ),\n                        replacement.to\n                )\n            }\n        })\n\n        return content;\n    }\n\n    async Add( title, content, callback ) {\n        if ( this.save_image ) {\n            content = await this.UploadImages( content );\n        }\n        this.TempFile( this.folder_id, title, ( documentId, error ) => {\n            console.log( 'TempFile: ', documentId )\n            if ( error ) {\n                callback( undefined, error );\n            } else this.GetFileUrl( `${title}.md`, urls => {\n                console.log( 'GetFileUrl: ', urls )\n                this.WriteFile( urls.signedPutUrl, content, result => {\n                    console.log( 'WriteFile: ', result )\n                    this.ImportFile( urls.url, `${title}.md`, documentId, result => {\n                        console.log( 'ImportFile: ', result )\n                        result.done && callback( result, undefined );\n                        result.fail && callback( undefined, \"error\" );\n                    });\n                });\n            });\n        });\n    }\n\n    TempFile( parentId, title, callback ) {\n        const documentId = this.UUID(),\n              userId     = this.access_token,\n              time       = new Date().getDate(),\n              collection = [\n                {\n                  id: documentId,\n                  table: 'block',\n                  path: [],\n                  command: 'set',\n                  args: {\n                    type: 'page',\n                    id: documentId,\n                    version: 1,\n                  },\n                },\n                {\n                  id: documentId,\n                  table: 'block',\n                  path: [],\n                  command: 'update',\n                  args: {\n                    parent_id: parentId,\n                    parent_table: 'collection',\n                    alive: true,\n                  },\n                },\n              ],\n              page = [\n                {\n                    id: documentId,\n                    table: 'block',\n                    path: [],\n                    command: 'set',\n                    args: {\n                        type: 'page',\n                        id: documentId,\n                        version: 1,\n                    },\n                },\n                {\n                    id: documentId,\n                    table: 'block',\n                    path: [],\n                    command: 'update',\n                    args: {\n                        parent_id: parentId,\n                        parent_table: 'block',\n                        alive: true,\n                    },\n                },\n                {\n                    table: 'block',\n                    id: parentId,\n                    path: ['content'],\n                    command: 'listAfter',\n                    args: { id: documentId },\n                },\n                {\n                    id: documentId,\n                    table: 'block',\n                    path: [],\n                    command: 'update',\n                    args: {\n                        created_by: userId,\n                        created_time: time,\n                        last_edited_time: time,\n                        last_edited_by: userId,\n                    },\n                },\n                {\n                    id: parentId,\n                    table: 'block',\n                    path: [],\n                    command: 'update',\n                    args: { last_edited_time: time },\n                },\n                {\n                    id: documentId,\n                    table: 'block',\n                    path: ['properties', 'title'],\n                    command: 'set',\n                    args: [[title]],\n                },\n                {\n                    id: documentId,\n                    table: 'block',\n                    path: [],\n                    command: 'update',\n                    args: { last_edited_time: time },\n                },\n              ],\n              operations = {\n                  operations: this.type == \"collection\" ? collection : page,\n              };\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n            type: \"post\",\n            url: this.url + \"api/v3/submitTransaction\",\n            data: operations\n        }), result => {\n            result.done && callback( documentId, undefined );\n            result.fail && callback( undefined, result.fail.message.includes( '401' ) ? `授权已过期，请重新授权。` : \"请稍后再试\" );\n        });\n    }\n\n    GetFileUrl( name, callback, option = { bucket: 'temporary', contentType: 'text/markdown' } ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n            type: \"post\",\n            url: this.url + \"api/v3/getUploadFileUrl\",\n            data:{\n                name: name,\n                ...option\n            }\n        }), result => {\n            if ( result && result.done ) {\n                callback( result.done.data );\n            }\n        });\n    }\n\n    WriteFile( url, content, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n            type: \"put\",\n            url,\n            content,\n            data: {\n                headers: {\n                    'Content-Type': 'text/markdown'\n                }\n            }\n        }), result => {\n            if ( result && result.done ) {\n                callback( result.done );\n            }\n        });\n    }\n\n    ImportFile( url, name, documentId, callback ) {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n            type: \"post\",\n            url: this.url + \"api/v3/enqueueTask\",\n            data: {\n                task: {\n                    eventName: 'importFile',\n                    request: {\n                        fileURL: url,\n                        fileName: name,\n                        importType: 'ReplaceBlock',\n                        pageId: documentId,\n                    },\n                }\n            }\n        }), result => {\n            if ( this.type == 'collection' ) {\n               /*  result.done && this.CheckQueueTask(result.done.data.taskId, () => {\n                  this.SetProperties(documentId, () => callback(result))\n                }) */\n                const taskId = result.done.data.taskId;\n                if ( result.done ) {\n                    this.CheckQueueTask( taskId, () => {\n                        if ( this.schema ) {\n                            this.SetProperties( documentId, () => callback( result ));\n                        } else {\n                            this.GetCollectionData( collection => {\n                                this.AddCollectionUrlSchema( collection, schemaKey => {\n                                    this.schema = schemaKey;\n                                    this.SetProperties( documentId, () => callback( result ));\n                                });\n                            });\n                        }\n                    })\n                }\n            } else callback( result );\n        });\n    }\n\n    GetCollectionData( callback ) {\n        browser.runtime.sendMessage(\n            msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n                type: 'post',\n                url : this.url + 'api/v3/loadUserContent',\n            }), result => {\n                if (result.done) {\n                    const {\n                        recordMap: { collection },\n                    } = result.done.data,\n                    currentCollection = collection[ this.folder_id ].value;\n                    callback( currentCollection );\n                }\n            }\n        )\n    }\n\n    AddCollectionUrlSchema( collection, callback ) {\n        const { schema }   = collection,\n              schemaKeys   = Object.keys( schema ),\n              genSchemaKey = ( len = 4 ) => {\n                let key    = '';\n                for ( ; key.length < len; ) {\n                    key   += String.fromCharCode( 33 + 94 * Math.random());\n                }\n                return key;\n            };\n        let schemaKey = null;\n\n        schemaKeys.some( key => {\n            const schemaItem = schema[key];\n            if ( schemaItem.type === 'url' && schemaItem.name === 'URL' ) {\n                schemaKey = key;\n                return true;\n            }\n            return false;\n        });\n\n        if ( schemaKey ) {\n            callback( schemaKey );\n            return;\n        }\n\n        const newSchema  = { ...schema };\n        let newSchemaKey = '';\n        while ( !newSchemaKey && schemaKeys.indexOf(newSchemaKey) < 0 ) {\n            newSchemaKey = genSchemaKey();\n        }\n\n        newSchema[ newSchemaKey ] = { type: 'url', name: 'URL' };\n\n        browser.runtime.sendMessage(\n            msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n                type: 'post',\n                url : this.url + 'api/v3/submitTransaction',\n                data: {\n                operations: [{\n                    args: {\n                        schema: newSchema,\n                    },\n                    command: 'update',\n                    id: this.folder_id,\n                    path: [],\n                    table: 'collection',\n                    }],\n                },\n            }), result => {\n                if ( result.done ) {\n                    callback( newSchemaKey );\n                }\n            }\n        )\n    }\n\n    CheckQueueTask( taskId, callback ) {\n        if ( taskId ) {\n            browser.runtime.sendMessage(\n                msg.Add(msg.MESSAGE_ACTION.AXIOS, {\n                    type: 'post',\n                    url : this.url + 'api/v3/getTasks',\n                    data: {\n                        taskIds: [taskId],\n                    },\n                }), result => {\n                    if ( result.done ) {\n                        const { results } = result.done.data;\n                        if ( results[0].state !== 'success' ) {\n                        setTimeout( () => {\n                            this.CheckQueueTask( taskId, callback );\n                        }, 1000 );\n                        } else callback();\n                    }\n                }\n            )\n        }\n    }\n\n    SetProperties( documentId, callback ) {\n        browser.runtime.sendMessage(\n            msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n                type: 'post',\n                url : this.url + 'api/v3/submitTransaction',\n                data: {\n                    operations: [{\n                        id: documentId,\n                        table: 'block',\n                        path: ['properties', this.schema],\n                        command: 'set',\n                        args: [[ window.location.origin, [[ 'a', window.location.href ]]]],\n                    }],\n                },\n            }), result => {\n                result.done && callback( documentId, undefined );\n            }\n        )\n    }\n\n    Save( storage ) {\n        storage.Safe( () => {\n            // TO-DO\n        }, storage.secret );\n    }\n\n}\n\n/**\n * Youdao\n * \n * @class\n */\nclass Youdao {\n\n    get id()   { return \"youdao\"; }\n    get name() { return name( this.id ); }\n\n    get url() {\n        return \"https://note.youdao.com\";\n    }\n\n    get permissions() {\n        return {\n            permissions: [ 'cookies' ],\n            origins: [ 'https://*.youdao.com/' ]\n        };\n    }\n\n    UUID() {\n        var __extends=void 0&&(void 0).__extends||function(){var _extendStatics=function extendStatics(d,b){_extendStatics=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(d,b){d.__proto__=b}||function(d,b){for(var p in b)if(b.hasOwnProperty(p))d[p]=b[p]};return _extendStatics(d,b)};return function(d,b){_extendStatics(d,b);function __(){this.constructor=d}d.prototype=b===null?Object.create(b):(__.prototype=b.prototype,new __())}}();var ValueUUID=function(){function ValueUUID(_value){this._value=_value;this._value=_value}ValueUUID.prototype.asHex=function(){return this._value};return ValueUUID}();var V4UUID=function(_super){__extends(V4UUID,_super);function V4UUID(){return _super.call(this,[V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-','4',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._oneOf(V4UUID._timeHighBits),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),'-',V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex(),V4UUID._randomHex()].join(''))||this}V4UUID._oneOf=function(array){return array[Math.floor(array.length*Math.random())]};V4UUID._randomHex=function(){return V4UUID._oneOf(V4UUID._chars)};V4UUID._chars=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];V4UUID._timeHighBits=['8','9','a','b'];return V4UUID}(ValueUUID);function generateUuid(){return new V4UUID().asHex()}\n        return generateUuid();\n    }\n\n    Cookies( callback ) {\n        browser.cookies.get({\n            url: this.url,\n            name: 'YNOTE_CSTK',\n        }, cookie => {\n            callback( cookie );\n        })\n    }\n\n    Auth( callback ) {\n        this.Cookies( token => {\n            if ( !token ) {\n                callback( undefined, `请先 <a target=\"_blank\" href=\"https://note.youdao.com/web\">登录有道云笔记</a> ` );\n                return;\n            }\n            this.access_token = token.value;\n            const formData = new FormData();\n            formData.append( 'path', '/' );\n            formData.append( 'dirOnly', 'true' );\n            formData.append( 'f', 'true');\n            formData.append( 'cstk', this.access_token );\n            $.ajax({\n                url     : this.url + `/yws/api/personal/file?method=listEntireByParentPath&keyfrom=web&cstk=${this.access_token}`,\n                type    : \"POST\",\n                contentType: false,\n                processData: false,\n                data    : formData\n            }).done( ( result, status, xhr ) => {\n                if ( result && result.length > 0 ) {\n                    this.folder_id = result[0].fileEntry.id;\n                    this.folders   = result.map( item => {\n                        return { name: item.fileEntry.name, value: item.fileEntry.id };\n                    });\n                    callback( result, undefined );\n                }\n            }).fail( ( xhr, status, error ) => {\n                console.error( error, status, xhr )\n                callback( undefined, xhr.status == 500 ? `请先 <a target=\"_blank\" href=\"https://note.youdao.com/web\">登录有道云笔记</a> ` : \"请稍后再试\" );\n            });\n        });\n    }\n\n    Add( title, content, callback ) {\n        const timestamp = String( Math.floor( Date.now() / 1000 )),\n              uuid      = this.UUID().replace( /-/g, '' ),\n              fileId    = `WEB${uuid}`;\n        let formData    = {};\n        formData[ 'fileId'        ] = fileId;\n        formData[ 'parentId'      ] = this.folder_id;\n        formData[ 'name'          ] = `${title}.md`;\n        formData[ 'domain'        ] = `1`;\n        formData[ 'rootVersion'   ] = `-1`;\n        formData[ 'dir'           ] = `false`;\n        formData[ 'sessionId'     ] = '';\n        formData[ 'createTime'    ] = timestamp;\n        formData[ 'modifyTime'    ] = timestamp;\n        formData[ 'transactionId' ] = fileId;\n        formData[ 'bodyString'    ] = content;\n        formData[ 'transactionTime' ] = timestamp;\n        formData[ 'cstk'          ] = this.access_token;\n\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.AXIOS, {\n            type: \"post\",\n            url : this.url + `/yws/api/personal/sync?method=push&keyfrom=web&cstk=${this.access_token}`,\n            form: formData,\n        }), result => {\n            if ( result.fail ) callback( undefined, result.fail.message.includes( '500' ) ? `授权已过期，请重新授权。` : \"请稍后再试\" );\n            else callback( result, undefined );\n        });\n    }\n\n}\n\n/**\n * Wiz\n * \n * @class\n */\nclass Wiz {\n\n    get id()   { return \"weizhi\"; }\n    get name() { return name( this.id ); }\n\n    get tag() {\n        return \"SimpRead\";\n    }\n\n    get category() {\n        return \"/My Notes/\";\n    }\n\n    Auth( user, password, callback ) {\n        const data = {\n            userId   : user,\n            password : password,\n            autoLogin: true,\n        };\n\n        $.ajax({\n            url     : \"https://note.wiz.cn/as/user/login?clientType=webclip_chrome&clientVersion=4.0.10&apiVersion=10&lang=zh-CN\",\n            type    : \"POST\",\n            dataType: \"JSON\",\n            contentType: \"application/json; charset=utf-8\",\n            data    : JSON.stringify(data),\n        }).done( ( result, textStatus, jqXHR ) => {\n            result && result.returnCode == 200 && ( this.access_token = result.result.userGuid );\n            callback( result, undefined );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Add( url, title, content, callback ) {\n        const info = {\n            title,\n            url,\n            category: this.category,\n            cmd     : \"save_content\",\n            comment : \"\",\n            tag     : this.tag,\n            userid  : this.username,\n            params  : wiz.getParams( url, title, content ),\n        };\n        let data = wiz.getInfos( info, this.access_token );\n        data     = JSON.stringify( data );\n        const options = {\n            url     : \"https://kshttps0.wiz.cn/ks/gather?clientType=webclip_chrome&clientVersion=4.0.10&apiVersion=10&lang=zh-CN\",\n            type    : \"POST\",\n            dataType: \"JSON\",\n            contentType: \"application/json; charset=utf-8\",\n            async: true,\n            cache: false,\n            data,\n        };\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.CORB, { settings: options }), result => {\n            if ( result && result.done && result.done.return_code == 200 ) callback( result, undefined );\n            else if ( result && result.done ) callback( undefined, result.done.return_code == 301 ? `授权已过期，请重新授权。` : \"请稍后再试\" );\n            else callback( undefined, result.fail );\n        });\n    }\n\n}\n\n/**\n * Kindle\n * \n * @class\n */\nclass Kindle {\n\n    constructor() {\n        this.id = \"\";\n    }\n\n    get host() {\n        //return \"http://localhost:3000/view\";\n        return \"https://simpread.herokuapp.com/view\";\n    }\n\n    get server() {\n        return \"https://pushtokindle.fivefilters.org/send.php\";\n    }\n\n    Read( url, title, desc, content, style, callback ) {\n        $.ajax({\n            url     : `${this.host}/read`,\n            type    : \"POST\",\n            data    : {\n                url,\n                title,\n                desc,\n                content,\n                style,\n            }\n        }).done( ( result, textStatus, jqXHR ) => {\n            if ( textStatus == \"success\" && result && result.id ) {\n                this.id = result.id;\n                callback( result );\n            } else callback( undefined, \"error\" );\n        }).fail( ( jqXHR, textStatus, error ) => {\n            console.error( jqXHR, textStatus, error )\n            callback( undefined, textStatus );\n        });\n    }\n\n    Temp() {\n        const url = `${this.host}/${this.id}.html`;\n        console.log( url )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n\n    Send() {\n        const url = `${this.server}?url=${this.host}/${this.id}.html`;\n        console.log( url )\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url } ));\n    }\n}\n\n/**\n * Get name\n * \n * @param  {string} service type\n * @return {string} service name\n */\nfunction name( type ) {\n    type = type.toLowerCase();\n    if ( [ \"dropbox\", \"pocket\", \"instapaper\", \"linnk\" , \"evernote\", \"onenote\", \"notion\" ].includes( type ) ) {\n        return type.replace( /\\S/i, $0=>$0.toUpperCase() );\n    } else if ( type == \"yinxiang\" ) {\n        return \"印象笔记\";\n    } else if ( type == \"gdrive\" ) {\n        return \"Google 云端硬盘\";\n    } else if ( type == \"jianguo\" ) {\n        return \"坚果云\";\n    } else if ( type == \"yuque\" ) {\n        return \"语雀\";\n    } else if ( type == \"youdao\" ) {\n        return \"有道云笔记\";\n    } else if ( type == \"weizhi\" ) {\n        return \"为知笔记\";\n    }\nreturn type;\n}\n\n/**\n * markdown wrapper\n * \n * @param  {string} content\n * @param  {string} download file name\n * @param  {object} new Notify()\n * @return {promise} promise\n */\nfunction mdWrapper( content, name, notify ) {\n    const dtd  = $.Deferred();\n    markdown( content, name, ( result, error ) => {\n        error  && notify.Render( 2, \"转换 Markdown 格式失败，这是一个实验性功能，不一定能导出成功。\" );\n        !error && dtd.resolve( result );\n    });\n    return dtd;\n}\n\n/**\n * Markdown to HTML\n * \n * @param {string} content\n */\nfunction md2HTML( content ) {\n    const markdown  = puplugin.Plugin( \"markdown\" ),\n          converter = new markdown.default.Converter();\n    converter.setOption( 'noHeaderId', true );\n    return converter.makeHtml( content );\n}\n\nlet noti; // notify variable\n\n/**\n * Service callback wrapper\n * \n * @param {string} result\n * @param {string} error\n * @param {string} service name, e.g. Google 云端硬盘\n * @param {string} service id, e.g. gdrive\n * @param {object} notify\n */\nfunction serviceCallback( result, error, name, type, notify ) {\n    noti && noti.complete();\n    !error && notify.Render( `已成功保存到 ${name}！` );\n    ![ \"evernote\", \"yinxiang\" ].includes( type ) && error && notify.Render( 2, error == \"error\" ? \"保存失败，请稍后重新再试。\" : error );\n    if ( error && error.includes( \"重新授权\" )) {\n        notify.Clone().Render( \"3 秒钟后将会自动重新授权，请勿关闭此页面...\" );\n        setTimeout( ()=>browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.auth, { name: type } )), 3000 );\n    }\n}\n\n/**\n * Verify service wrapper\n * \n * @param  {object} storage object\n * @param  {object} service object\n * @param  {string} service type\n * @param  {string} service name\n * @param  {object} notify\n * @param  {object} default auto re-auth\n * @return {promise} promise\n */\nfunction verifyService( storage, service, type, name, notify, auto = true ) {\n    const dtd = $.Deferred();\n    storage.Safe( ()=> {\n        if ( storage.secret[type].access_token ) {\n            Object.keys( storage.secret[type] ).forEach( item => service[item] = storage.secret[type][item] );\n            type != \"linnk\" && ( noti = notify.Render({ content: `开始保存到 ${name}，请稍等...`, state: \"loading\" }));\n            dtd.resolve( type );\n        } else {\n            auto ? notify.Render( `请先获取 ${name} 的授权，才能使用此功能！`, \"授权\", ()=>{\n                notify.Clone().Render( [ \"linnk\", \"jianguo\", \"youdao\", \"weizhi\" ].includes( type ) ? `${name} 无法自动授权 3 秒后请自行授权。` : \"3 秒钟后将会自动重新授权，请勿关闭此页面...\" );\n                setTimeout( ()=>browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.auth, { name: type } )), 3000 );\n            }) : notify.Render( `请先获取 ${name} 的授权，才能使用此功能！` );\n            dtd.reject( type );\n        }\n    });\n    return dtd;\n}\n\nconst dropbox  = new Dropbox(),\n      pocket   = new Pocket(),\n      instapaper = new Ins(),\n      linnk    = new Linnk(),\n      evernote = new Evernote(),\n      onenote  = new Onenote(),\n      gdrive   = new GDrive(),\n      yuque    = new Yuque(),\n      jianguo  = new Jianguo(),\n      notion   = new Notion(),\n      youdao   = new Youdao(),\n      webdav   = new WebDAV(),\n      weizhi   = new Wiz(),\n      kindle   = new Kindle();\n\nexport {\n    png      as PNG,\n    pdf      as PDF,\n    epub     as Epub,\n    markdown as Markdown,\n    download as Download,\n    prueDownload as PrDownload,\n    md2HTML  as MD2HTML,\n    unlink   as Unlink,\n    name     as Name,\n    dropbox, pocket, instapaper, linnk, evernote, onenote, gdrive,yuque, jianguo, webdav, notion, youdao, weizhi,\n    kindle,\n    mdWrapper       as MDWrapper,\n    serviceCallback as svcCbWrapper,\n    verifyService   as VerifySvcWrapper,\n} "
  },
  {
    "path": "src/service/highlight.js",
    "content": "console.log( \"=== simpread highlight load ===\" )\r\n\r\nconst highlight_class = \"simpread-highlight-selector\";\r\n\r\n/**\r\n * Highlight\r\n * \r\n * @return {promise} promise\r\n */\r\nfunction start() {\r\n    let $prev;\r\n    const dtd            = $.Deferred(),\r\n          mousemoveEvent = event => {\r\n            if ( !$prev ) {\r\n                $( event.target ).addClass( highlight_class );\r\n            } else {\r\n                $prev.removeClass( highlight_class );\r\n                $( event.target ).addClass( highlight_class );\r\n            }\r\n            $prev = $( event.target );\r\n    };\r\n    $( \"html\" ).one( \"click\", event => {\r\n        if ( !$prev ) return;\r\n        $( event.target ).removeClass( highlight_class );\r\n        $( \"html\"       ).off( \"mousemove\", mousemoveEvent );\r\n        $prev = undefined;\r\n        dtd.resolve( event.target );\r\n        return false;\r\n    });\r\n    $( \"html\" ).one( \"keydown\", event => {\r\n        if ( event.keyCode == 27 && $prev ) {\r\n            $( \"html\" ).find( `.${highlight_class}` ).removeClass( highlight_class );\r\n            $( \"html\" ).off( \"mousemove\", mousemoveEvent );\r\n            $prev = undefined;\r\n            event.preventDefault();\r\n            return false;\r\n        }\r\n    });\r\n    $( \"html\" ).on( \"mousemove\", mousemoveEvent );\r\n    return dtd;\r\n}\r\n\r\n/**\r\n * Multi Highlight\r\n * \r\n * @return {func} callback\r\n */\r\nfunction multi( callback ) {\r\n    let $prev;\r\n    const mousemoveEvent = event => {\r\n            if ( !$prev ) {\r\n                $( event.target ).addClass( highlight_class );\r\n            } else {\r\n                $prev.removeClass( highlight_class );\r\n                $( event.target ).addClass( highlight_class );\r\n            }\r\n            $prev = $( event.target );\r\n    },\r\n    removeDomHander = event => {\r\n        callback( event.target );\r\n        return false;\r\n    };\r\n    $( \"sr-rd-content\" ).on( \"click\", removeDomHander );\r\n    $( \"sr-rd-content\" ).on( \"keydown\", event => {\r\n        if ( event.keyCode == 27 && $prev ) {\r\n            $( \"sr-rd-content\" ).find( `.${highlight_class}` ).removeClass( highlight_class );\r\n            $( \"sr-rd-content\" ).off( \"mousemove\", mousemoveEvent );\r\n            $( \"sr-rd-content\" ).off( \"click\", removeDomHander );\r\n            $prev = undefined;\r\n            event.preventDefault();\r\n            return false;\r\n        }\r\n    });\r\n    $( \"sr-rd-content\" ).on( \"mousemove\", mousemoveEvent );\r\n}\r\n\r\nfunction annotate() {\r\n    const dtd = $.Deferred();\r\n    $( \"html\" ).one( \"mouseup\", event => {\r\n        let userSelection;\r\n        if ( window.getSelection ) {\r\n            userSelection = window.getSelection();\r\n        } else if ( document.selection ) {\r\n            userSelection = document.selection.createRange();\r\n        }\r\n        let selectedText = userSelection;\r\n        if (userSelection.text) selectedText = userSelection.text;\r\n\r\n        if ( selectedText != '' ) {\r\n            selectedText = \"\" + selectedText + \"\";\r\n            window.getSelection().removeAllRanges();\r\n            dtd.resolve( selectedText );\r\n        }\r\n    });\r\n    return dtd;\r\n}\r\n\r\n/**\r\n * Highlight and controlbar\r\n * \r\n * @param {element} dom \r\n */\r\nfunction controlbar( dom ) {\r\n    let $target = $( dom );\r\n    const dtd  = $.Deferred(),\r\n          tmpl = `<simpread-highlight>\r\n                        <sr-highlight-ctl class=\"add\"><svg t=\"1560496884701\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M876.089 439.182h-291.271v-291.271c0-40.268-32.549-72.818-72.818-72.818s-72.818 32.549-72.818 72.818v291.271h-291.271c-40.268 0-72.818 32.549-72.818 72.818s32.549 72.818 72.818 72.818h291.271v291.271c0 40.268 32.549 72.818 72.818 72.818s72.818-32.549 72.818-72.818v-291.271h291.271c40.268 0 72.818-32.549 72.818-72.818s-32.549-72.818-72.818-72.818z\" p-id=\"1998\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\r\n                        <sr-highlight-ctl class=\"sub\"><svg t=\"1560496679351\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M127.289058 490.154459l0 43.770899c0 32.338522 27.009144 57.108672 58.774615 58.734706 0 0 132.448568 13.021571 325.936327 13.021571s325.936327-13.021571 325.936327-13.021571c31.765471-1.626034 58.774615-26.396183 58.774615-58.734706l0-43.770899c0-32.338522-26.51591-57.068763-58.774615-58.734706 0 0-128.005372-12.005428-325.942467-12.005428s-325.930187 12.005428-325.930187 12.005428C153.804968 433.085696 127.289058 457.815937 127.289058 490.154459z\" p-id=\"3204\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\r\n                        <sr-highlight-ctl class=\"done\"><svg t=\"1560496955945\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M416.832 798.08C400.64 798.08 384.512 791.872 372.16 779.52L119.424 525.76C94.784 500.992 94.784 460.8 119.424 436.032 144.128 411.264 184.128 411.264 208.768 436.032L416.832 644.928 814.4 245.76C839.04 220.928 879.04 220.928 903.744 245.76 928.384 270.528 928.384 310.656 903.744 335.424L461.504 779.52C449.152 791.872 432.96 798.08 416.832 798.08Z\" p-id=\"1755\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\r\n                        <sr-highlight-ctl class=\"none\"><svg t=\"1560499513561\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\" p-id=\"1990\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\r\n                        <sr-highlight-ctl class=\"help\"><svg t=\"1560573280563\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M512 958.326255c247.255337 0 447.696462-200.441125 447.696462-447.696462s-200.441125-447.696462-447.696462-447.696462-447.696462 200.441125-447.696462 447.696462S264.74364 958.326255 512 958.326255zM512 217.681788c35.32146 0 63.956637 28.635177 63.956637 63.956637 0 35.323507-28.635177 63.956637-63.956637 63.956637s-63.956637-28.633131-63.956637-63.956637C448.043363 246.316965 476.67854 217.681788 512 217.681788zM448.043363 510.629793c0-35.32146 28.635177-63.956637 63.956637-63.956637s63.956637 28.635177 63.956637 63.956637l0 223.848231c0 35.323507-28.635177 63.956637-63.956637 63.956637s-63.956637-28.633131-63.956637-63.956637L448.043363 510.629793z\" p-id=\"1979\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\r\n                  </simpread-highlight>`;\r\n    $target.addClass( \"simpread-highlight-controlbar\" )\r\n    $(\"html\").append( tmpl );\r\n    $(\"html\").find( \"sr-highlight-ctl\" ).on( \"click\", event => {\r\n        const cls = $( event.currentTarget ).attr( \"class\" );\r\n        if ( cls == \"add\" ) {\r\n            $target.removeClass( \"simpread-highlight-controlbar\" );\r\n            $target = $target.parent();\r\n            $target.addClass( \"simpread-highlight-controlbar\" );\r\n        } else if ( cls == \"sub\" ) {\r\n            $target.removeClass( \"simpread-highlight-controlbar\" );\r\n            $target = $($target.children()[0]);\r\n            $target.addClass( \"simpread-highlight-controlbar\" );\r\n        } else if ( cls == \"none\" ) {\r\n            $target.removeClass( \"simpread-highlight-controlbar\" );\r\n            $( \"simpread-highlight\" ).remove();\r\n        } else if ( cls == \"help\" ) {\r\n            const $a = $( `<a style=\"display:none\" target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选\"></a>` ).appendTo( \"body\" );\r\n            $a[0].click();\r\n            $a.remove();\r\n        } else {\r\n            $target.removeClass( \"simpread-highlight-controlbar\" );\r\n            $( \"simpread-highlight\" ).remove();\r\n            dtd.resolve( $target[0] );\r\n        }\r\n    });\r\n    return dtd;\r\n}\r\n\r\nexport {\r\n    start as Start,\r\n    multi as Multi,\r\n    annotate as Annotate,\r\n    controlbar as Control,\r\n}"
  },
  {
    "path": "src/service/local.js",
    "content": "console.log( \"=== simpread local load ===\" )\n\nconst id    = \"simpread\",\n      NAMES = {\n        VER   : \"version\",\n        COUNT : \"count\",\n        FIRST : \"firstload\",\n        PATCH: \"patch-update\",\n      },\n      MAX_COUNT = 5;\n\nlet curcount;\n\n/**\n * Read and Write Local Storage\n * \n * @class\n */\nclass Local {\n\n    get curcount() {\n        return curcount;\n    }\n\n    /**\n     * @method\n     * \n     * @return {boolean} true: not MAX_COUNT; false: exceed MAX_COUNT, re-count\n     */\n    Count() {\n        const [ cur = 0 ] = [ get(NAMES.COUNT) ];\n        curcount          = cur;\n        set( NAMES.COUNT, ++curcount );\n        if ( curcount > MAX_COUNT ) {\n            set( NAMES.COUNT, 0 );\n            return false;\n        } else {\n            return true;\n        }\n    }\n\n    /**\n     * @method\n     * \n     * @return {boolean} true: first load; false: not first load\n     */\n    Firstload() {\n        let [ firstload = \"true\" ] = [ get(NAMES.FIRST) ];\n        if ( firstload == \"true\" ) {\n            set( NAMES.FIRST, false );\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**\n     * Save manifest.json version to local storage\n     * \n     * @param {string} version\n     */\n    Version( version ) {\n        set( NAMES.VER, version );\n    }\n\n    /**\n     * Save manifest.json version to local storage\n     * \n     * @param {string} version\n     */\n    Patch( state, value ) {\n        if ( state == \"add\" ) set( NAMES.PATCH, value );\n        else if ( state == \"get\" ) return get( NAMES.PATCH );\n        else if ( state == \"remove\" ) remove( NAMES.PATCH );\n    }\n\n}\n\n/**\n * Get localStorage from key\n * \n * @param {string} NAMES.{value}\n */\nfunction get( key ) {\n    return localStorage[ `${id}-${key}` ];\n}\n\n/**\n * Set localStorage\n * \n * @param {string} NAMES.{value}\n * @param {any}  any value\n */\nfunction set( key, value ) {\n    localStorage[ `${id}-${key}` ] = value;\n}\n\n/**\n * Remove localStorage\n * \n * @param {string} NAMES.{value}\n */\nfunction remove( key ) {\n    localStorage.removeItem( `${id}-${key}` );\n}\nconst local = new Local();\nexport default local;"
  },
  {
    "path": "src/service/menu.js",
    "content": "console.log( \"=== simpread menu load ===\" )\n\nimport {storage} from 'storage';\nimport {browser} from 'browser';\nimport * as msg  from 'message';\n\n/**\n * Create context menus\n*/\nconst context = {\n        focus : { id: \"\", menu: {} },\n        read  : { id: \"\", menu: {} },\n        link  : { id: \"\", menu: {} },\n        list  : { id: \"\", menu: {} },\n        whitelist : { id: \"\", menu: {} },\n        exclusion : { id: \"\", menu: {} },\n        blacklist : { id: \"\", menu: {} },\n        unrdist   : { id: \"\", menu: {} },\n        lazyload  : { id: \"\", menu: {} },\n    },\n    menu = {\n        \"type\"     : \"normal\",\n        \"contexts\" :  [ \"all\" ],\n        \"documentUrlPatterns\" : [ \"http://*/*\" , \"https://*/*\" ]\n    };\n\nObject.assign( context.focus.menu,      menu, { id: \"focus\",     \"title\" : \"聚焦模式\", contexts: [ \"page\" ] });\nObject.assign( context.read.menu,       menu, { id: \"read\",      \"title\" : \"阅读模式\", contexts: [ \"page\" ] });\nObject.assign( context.link.menu,       menu, { id: \"link\",      \"title\" : \"使用阅读模式打开此链接\", contexts: [ \"link\" ] });\n\nObject.assign( context.list.menu,       menu, { id: \"list\",      \"title\" : \"打开稍后读\", contexts: [ \"page\" ] });\nObject.assign( context.unrdist.menu,    menu, { id: \"unrdist\",   \"title\" : \"将当前页面加入稍后读\", contexts: [ \"page\" ] });\n\nObject.assign( context.whitelist.menu,  menu, { id: \"whitelist\", \"title\" : \"将当前页面加入到白名单\", contexts: [ \"page\" ] });\nObject.assign( context.exclusion.menu,  menu, { id: \"exclusion\", \"title\" : \"将当前页面加入到排除列表\", contexts: [ \"page\" ] });\nObject.assign( context.blacklist.menu,  menu, { id: \"blacklist\", \"title\" : \"将当前页面加入到黑名单\", contexts: [ \"page\" ] });\nObject.assign( context.lazyload.menu,   menu, { id: \"lazyload\",  \"title\" : \"将当前页面加入到延迟加载\", contexts: [ \"page\" ] });\n\n/**\n * Listen contextMenus message\n */\nfunction onClicked( callback ) {\n    browser.contextMenus.onClicked.addListener( callback );\n}\n\n/**\n * Create all context menu\n */\nfunction createAll() {\n    storage.option.menu.focus &&\n        ( context.focus.id = browser.contextMenus.create( context.focus.menu ));\n\n    storage.option.menu.read &&\n        ( context.read.id  = browser.contextMenus.create( context.read.menu ));\n\n    storage.option.menu.link &&\n        ( context.link.id  = browser.contextMenus.create( context.link.menu ));\n\n    browser.contextMenus.create({ \"type\": \"separator\" });\n\n    storage.option.menu.list &&\n        ( context.list.id     = browser.contextMenus.create( context.list.menu ));\n\n    storage.option.menu.unrdist &&\n        ( context.unrdist.id  = browser.contextMenus.create( context.unrdist.menu ));\n\n    browser.contextMenus.create({ \"type\": \"separator\" });\n\n    storage.option.menu.whitelist &&\n        ( context.whitelist.id  = browser.contextMenus.create( context.whitelist.menu ));\n\n    storage.option.menu.exclusion &&\n        ( context.exclusion.id  = browser.contextMenus.create( context.exclusion.menu ));\n\n    storage.option.menu.blacklist &&\n        ( context.blacklist.id  = browser.contextMenus.create( context.blacklist.menu ));\n\n    storage.option.menu.lazyload &&\n        ( context.lazyload.id   = browser.contextMenus.create( context.lazyload.menu ));\n\n    // all menu is false remove contextMenus\n    Object.values( storage.option.menu ).findIndex( menu => menu == true ) == -1 && browser.contextMenus.removeAll();\n}\n\n/**\n * Create menu from type\n * \n * @param {string} include: foucs read link\n */\nfunction create( type ) {\n    /*\n    if ( !context[type].id ) {\n        delete context[type].menu.generatedId;\n        context[type].id = browser.contextMenus.create( context[type].menu );\n    }\n    */\n   browser.contextMenus.removeAll();\n   createAll();\n}\n\n/**\n * Remove menu from type\n * \n * @param {string} include: foucs read link\n */\nfunction remove( type ) {\n    /*\n    if ( context[type].id ) {\n        browser.contextMenus.remove( context[type].id );\n        context[type].id = undefined;\n    }\n    */\n    browser.contextMenus.removeAll();\n    createAll();\n}\n\n/**\n * Update menu from type\n * \n * @param {string} include: tempread and read\n */\nfunction update( type ) {\n    if ( context.read.id ) {\n        const title = type == \"read\" ? \"阅读模式\" : \"临时阅读模式\";\n        browser.contextMenus.update( context.read.id, { title } );\n    }\n}\n\n/**\n * Refresh menu ( Enforcement fresh )\n * \n * @param {object} new menu object \n */\nfunction refresh( cur ) {\n    Object.keys( cur ).forEach( item => {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.menu, { id: item, value: cur[item] } ));\n    });\n}\n\nexport {\n    createAll as CreateAll,\n    create    as Create,\n    remove    as Remove,\n    update    as Update,\n    refresh   as Refresh,\n    onClicked as OnClicked,\n}"
  },
  {
    "path": "src/service/message.js",
    "content": "console.log( \"=== simpread message load ===\" )\r\n\r\nconst action = {\r\n    focus_mode     : \"focus\",\r\n    read_mode      : \"read\",\r\n    shortcuts      : \"shortcuts\",\r\n    browser_action : \"browser_action\",\r\n    browser_click  : \"browser_click\",\r\n    tab_selected   : \"tab_selected\",\r\n    new_tab        : \"new_tab\",\r\n    close_tab      : \"close_tab\",\r\n    // menu\r\n    menu           : \"menu\",\r\n    menu_whitelist : \"menu_whitelist\",\r\n    menu_exclusion : \"menu_exclusion\",\r\n    menu_blacklist : \"menu_blacklist\",\r\n    menu_lazyload  : \"menu_lazyload\",\r\n    menu_unrdist   : \"menu_unrdist\",\r\n    updated        : \"updated\",\r\n    save_verify    : \"save_verify\",\r\n    storage        : \"storage\",         // only firefox\r\n    // about export and auth\r\n    auth           : \"auth\",\r\n    auth_success   : \"auth_success\",\r\n    export         : \"export\",\r\n    redirect_uri   : \"redirect_uri\",\r\n    // dyslexia\r\n    speak          : \"speak\",\r\n    speak_stop     : \"speak_stop\",\r\n    // track\r\n    track          : \"track\",\r\n    // site\r\n    update_site    : \"update_site\",\r\n    pending_site   : \"pending_site\",\r\n    save_site      : \"save_site\",\r\n    temp_site      : \"temp_site\",\r\n    // corb\r\n    CORB           : \"corb\",\r\n    AXIOS          : \"axios\",\r\n    // webdav\r\n    jianguo        : \"jianguo\",\r\n    WebDAV         : \"webdav\",\r\n    // event\r\n    turn_tab       : \"turn_tab\",\r\n    welcome_close  : \"welcome_close\",\r\n    controlbar     : \"simpread-plugin_controlbar\",\r\n    // offline\r\n    download       : \"download\",\r\n    base64         : \"base64\",\r\n    permission     : \"permission\",\r\n    // snapshot\r\n    snapshot       : \"snapshot\",\r\n    // tips\r\n    tips           : \"tips\",\r\n    tips_norepeat  : \"tips_norepeat\",\r\n    // notion.so\r\n    notion_dl_img  : \"notion_dl_img\",\r\n    notion_up_img  : \"notion_up_img\",\r\n};\r\n\r\n/**\r\n * Add message object\r\n *\r\n * @param {string} @see action\r\n * @param {object} { code,url }\r\n */\r\nfunction add( type, value = {} ) {\r\n    return { type, value };\r\n}\r\n\r\nexport {\r\n    add    as Add,\r\n    action as MESSAGE_ACTION\r\n}"
  },
  {
    "path": "src/service/offline.js",
    "content": "console.log( \"=== simpread offline load ===\" )\n\nimport {browser}   from 'browser';\nimport * as msg    from 'message';\n\nlet currIdx = 0, maxCount = 0, urls = [], images, cb, type = \"html\", markdown;\n\n/**\n * Offline HTML\n * \n * @param {string} title\n * @param {string} desc\n * @param {string} content\n * @param {object} styles, include: simpread(global), common, theme, css\n * \n * @return {string} html\n */\nfunction HTML( title, desc, content, styles ) {\n    const hightlight = () => {\n            if ( content.search( 'pre class=\"hljs' ) > -1 || content.search( 'code class=\"hljs' ) > -1 ) {\n                return `<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/default.min.css\">`\n            } else return '';\n        },\n        html = `\n                <html lang=\"en\" class=\"simpread-font simpread-theme-root\" style='${ $( \"html\" ).attr( \"style\" ) }'>\n                    <head>\n                        <meta charset=\"utf-8\">\n                        <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8;charset=utf-8\">\n                        <meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">\n                        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=1\">\n                        <meta name=\"author\" content=\"Kenshin\"/>\n                        <meta name=\"description\" content=\"简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展\" />\n                        <meta name=\"keywords\" content=\"Chrome extension, Chrome 扩展, 阅读模式, 沉浸式阅读, 简悦, 简阅, read mode, reading mode, reader view, firefox, firefox addon, userscript, safari, opera, tampermonkey\"/>\n                        <meta name=\"thumbnail\" content=\"https://simpread-1254315611.cos.ap-shanghai.myqcloud.com/static/introduce-2.png\"/>\n                        <meta property=\"og:title\" content=\"简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展\"/>\n                        <meta property=\"og:type\" content=\"website\">\n                        <meta property=\"og:local\" content=\"zh_CN\"/>\n                        <meta property=\"og:url\" content=\"http://ksria.com/simpread\"/>\n                        <meta property=\"og:image\" content=\"https://simpread-1254315611.cos.ap-shanghai.myqcloud.com/static/introduce-2.png\"/>\n                        <meta property=\"og:image:type\" content=\"image/png\"/>\n                        <meta property=\"og:image:width\" content=\"960\"/>\n                        <meta property=\"og:image:height\" content=\"355\"/>\n                        <meta property=\"og:site_name\" content=\"http://ksria.com/simpread\"/>\n                        <meta property=\"og:description\" content=\"简悦 SimpRead - 如杂志般沉浸式阅读体验的扩展\"/>\n                        <style type=\"text/css\">${ styles.common }</style>\n                        <style type=\"text/css\">${ styles.theme  }</style>\n                        <style type=\"text/css\">${ styles.global }</style>\n                        <style type=\"text/css\">${ styles.mobile }</style>\n                        <style type=\"text/css\">${ styles.css    }</style>\n                        <style type=\"text/css\">${ styles.special}</style>\n                        ${hightlight()}\n                        <title>简悦 | ${title}</title>\n                    </head>\n                    <body>\n                        <sr-read style='${ $( \"sr-read\" ).attr( \"style\" ) }'>\n                            <sr-rd-title>${title}</sr-rd-title>\n                            <sr-rd-desc ${desc == \"\" ? 'style=\"display: none;\"' : \"\" }>${desc}</sr-rd-desc>\n                            <sr-rd-content>${content}</sr-rd-content>\n                            <sr-rd-footer>\n                                <sr-rd-footer-group>\n                                    <sr-rd-footer-line></sr-rd-footer-line>\n                                    <sr-rd-footer-text>全文完</sr-rd-footer-text>\n                                    <sr-rd-footer-line></sr-rd-footer-line>\n                                </sr-rd-footer-group>\n                                <sr-rd-footer-copywrite>\n                                    <div>本文由 <a href=\"http://ksria.com/simpread\" target=\"_blank\">简悦 SimpRead</a> 转码，用以提升阅读体验，<a href=\"${ location.href }\" target=\"_blank\">原文地址 </a></div>\n                                </sr-rd-footer-copywrite>\n                            </sr-rd-footer>\n                        </sr-read>\n                    </body>\n                </html>`;\n    return html;\n}\n\n/**\n * Markdown offline e.g. ![img](http://xxx.png) → ![img][id] ... [id]:base54\n * \n * @param {string} markdown str\n * @param {func} callback\n */\nfunction Markdown( content, callback ) {\n    type         = \"markdown\";\n    cb           = callback;\n    markdown     = content;\n    images       = new Map();\n    const arr    = markdown.match( /!\\[\\]\\(http\\S+\\)/ig );\n    if ( arr && arr.length > 0 ) {\n        arr.forEach( ( item, idx ) => {\n            markdown = markdown.replace( item, `![][img-${idx}]` );\n            item     = item.replace( /[!\\[\\]\\(]|[\\)]/ig, \"\" );\n            markdown = markdown + `\\r\\n\\r\\n` + `[img-${idx}]:${item}`;\n            images.set( item, `[img-${idx}]:${item}` );\n        });\n        urls     = [...images.keys()];\n        maxCount = urls.length;\n        currIdx  = 0;\n        serialConvert( urls[0] );\n    } else cb( markdown );\n}\n\n/**\n * Get current page( readmode ) all images and convert to base64\n * \n * @param {func} callback \n */\nfunction getImages( callback ) {\n    type   = \"html\";\n    cb     = callback;\n    images = new Map();\n    $( \"sr-rd-content\" ).find( \"img\" ).map( ( idx, img ) => {\n        if ( !images.has( img.src ) ) {\n            images.set( img.src, img );\n        } else {\n            //TO-DO\n        }\n    });\n    urls     = [...images.keys()];\n    maxCount = urls.length;\n    currIdx  = 0;\n    serialConvert( urls[0] );\n}\n\n/**\n * Convert url to base64\n * \n * @param {string} url\n */\nfunction serialConvert( url ) {\n    // call contentscriptsa\n    //toBase64( url, result => {\n    // call background\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.base64, { url }), result => {\n        currIdx++;\n        if ( result && result.done ) {\n            setBase64( result.done.url, result.done.uri );\n        } else {\n            // TO-DO\n        }\n        if ( currIdx < maxCount ) {\n            serialConvert( urls[currIdx] );\n        } else {\n            console.log( \"All images convert done\" )\n            cb && cb( markdown );\n        }\n    });\n}\n\n/**\n * Change img.src to base64\n * \n * @param {string} url\n * @param {string} uri\n */\nfunction setBase64( url, uri ) {\n    const img = images.get( url );\n    if ( type == \"html\" ) {\n        $(img).attr( \"src\", uri ).attr( \"sr-org-src\", url );\n    } else {\n        const str = img.replace( url, uri )\n        markdown = markdown.replace( img, str );\n    }\n}\n\n/**\n * toBase64 usage FileReader\n * \n * @param {string} url\n * @param {func} callback\n */\nfunction toBase64( url, callback ) {\n    fetch( url )\n        .then( response => response.blob() )\n        .then( blob     => new Promise(( resolve, reject ) => {\n            const reader = new FileReader()\n            reader.onloadend = event => {\n                callback({ done: { url, uri: event.target.result }});\n            };\n            reader.onerror = error => {\n                callback({ fail: { error, url } });\n            };\n            reader.readAsDataURL( blob );\n        }))\n        .catch( error => {\n            callback({ fail: { error, url } });\n        });\n}\n\n/**\n * Restore base64 to url\n */\nfunction restoreImg() {\n    $( \"sr-rd-content\" ).find( \"img\" ).map( ( idx, img ) => {\n        const src = $(img).attr( \"sr-org-src\" );\n        $(img).attr( \"src\", src ).removeAttr( \"sr-org-src\" );\n    });\n}\n\nexport {\n    HTML,\n    Markdown,\n    getImages,\n    toBase64,\n    restoreImg,\n}"
  },
  {
    "path": "src/service/output.js",
    "content": "console.log( \"=== simpread output load ===\" )\r\n\r\nimport * as util   from 'util';\r\nimport * as exp    from 'export';\r\nimport { storage } from 'storage';\r\nimport {browser}   from 'browser';\r\nimport * as msg    from 'message';\r\nimport * as highlight from 'highlight';\r\nimport * as share  from 'sharecard';\r\nimport * as offline from 'offline';\r\nimport th           from 'theme';\r\nimport * as ss      from 'stylesheet';\r\nimport * as snap    from 'snapshot';\r\n\r\n/**\r\n * Controlbar common action, include:\r\n * \r\n * - share_xxx\r\n * - save, markdown, png, pdf\r\n * - dropbox, pocket, linnk, evernote, onenote, gdrive\r\n * \r\n * @param {string} type, include above ↑ type \r\n * @param {string} current page: title \r\n * @param {string} current page: desc \r\n * @param {string} current page: content \r\n */\r\nfunction action( type, title, desc, content ) {\r\n\r\n    console.log( \"output: Action is \", type )\r\n\r\n    const styles = callback => {\r\n        ss.SpecialCSS( storage.pr.mathjax, special => {\r\n            th.GetAll();\r\n            const theme  = th.Get( storage.read.theme ),\r\n                  global = th.Get( \"global\" ),\r\n                  common = th.Get( \"common\" ),\r\n                  mobile = th.Get( \"mobile\" ),\r\n                  css    = ss.GetCustomCSS();\r\n            callback({ theme, global, common, css, mobile, special });\r\n      });\r\n    },\r\n    toMarkdown = callback => {\r\n        exp.MDWrapper( util.ClearMD( content ), undefined, new Notify() ).done( result => callback( result ));\r\n    };\r\n\r\n    if ( type.indexOf( \"_\" ) > 0 && type.startsWith( \"share\" ) ) {\r\n        let url = \"\";\r\n        switch ( type.split(\"_\")[1] ) {\r\n            case \"facebook\":\r\n                url = `https://www.facebook.com/dialog/feed?app_id=1528743474024441&link=${ window.location.href }`;\r\n                break;\r\n            case \"twitter\":\r\n                url = `https://twitter.com/intent/tweet?text=${ title } （ 分享自 简悦 ）&url=${ window.location.href }`;\r\n                break;\r\n            case \"gplus\":\r\n                url = `https://plus.google.com/share?url=${ window.location.href }`;\r\n                break;\r\n            case \"weibo\":\r\n                url = `http://service.weibo.com/share/share.php?url=${ window.location.href }&title=${ title } （ 分享自 简悦-SimpRead ）`;\r\n                break;\r\n            case \"telegram\":\r\n                url = `https://t.me/share/url?url=${ window.location.href }`;\r\n                break;\r\n            case \"card\":\r\n                new Notify().Render( \"已启动分享卡标注功能，请在页面标注，生成分享卡。\" );\r\n                $(\"sr-rd-crlbar\").find(\"panel-bg\").length > 0 && $(\"sr-rd-crlbar\").find(\"panel-bg\")[0].click();\r\n                highlight.Annotate().done( txt => {\r\n                    txt != \"\" && share.Render( storage.current.mode == \"focus\" ? \"html\" : \"sr-read\", title, txt );\r\n                });\r\n                break;\r\n        }\r\n        type.split(\"_\")[1] != \"card\" && browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url }));\r\n    } else if ( [ \"save\", \"markdown\", \"offlinemarkdown\", \"png\", \"kindle\", \"pdf\", \"epub\", \"temp\", \"html\", \"offlinehtml\", \"snapshot\", \"bear\", \"ulysses\" ].includes( type ) ) {\r\n        storage.Statistics( \"service\", type );\r\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"service\", eventAction: \"service\", eventValue: type }) );\r\n        switch ( type ) {\r\n            case \"save\":\r\n                const url = window.location.href.replace( /(\\?|&)simpread_mode=read/, \"\" );\r\n                storage.UnRead( \"add\", util.GetPageInfo(), success => {\r\n                    success  && new Notify().Render( 0, \"成功加入未读列表。\" );\r\n                    !success && new Notify().Render( 0, \"已加入未读列表，请勿重新加入。\" );\r\n                });\r\n                break;\r\n            case \"markdown\":\r\n                const md = \"simpread-\" + title + \".md\";\r\n                storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                exp.MDWrapper( util.ClearMD( content ), md, new Notify() );\r\n                break;\r\n            case \"offlinemarkdown\":\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.permission ), result => {\r\n                    if ( !result.done ) {\r\n                        new Notify().Render( 2, `离线下载的文件体积较大，所以需要使用 Chrome 下载方案，请授权。` );\r\n                        return;\r\n                    } else {\r\n                        const notify2 = new Notify().Render({ content: \"图片转换中吗，请稍等...\", state: \"loading\" });\r\n                        const md = \"simpread-\" + title + \".md\";\r\n                        storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                        toMarkdown( result => {\r\n                            offline.Markdown( result, str => {\r\n                                notify2.complete();\r\n                                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.download, { data: str, name: md }), result => {\r\n                                    console.log( \"Current download result: \", result )\r\n                                });\r\n                            });\r\n                        });\r\n                    }\r\n                });\r\n                break;\r\n            case \"png\":\r\n                try {\r\n                    new Notify().Render( \"下载已开始，请稍等...\" );\r\n                    const $target = storage.current.mode == \"read\" ? $( \".simpread-read-root\" ) : $( \".simpread-focus-highlight\" );\r\n                    $( \"sr-rd-crlbar\" ).css({ \"opacity\": 0 });\r\n                    setTimeout( () => {\r\n                        exp.PNG( $target[0] , `simpread-${ title }.png`, result => {\r\n                            $( \"sr-rd-crlbar\" ).removeAttr( \"style\" );\r\n                            !result && new Notify().Render( 2, \"转换 PNG 格式失败，这是一个实验性功能，不一定能导出成功。\" );\r\n                        });\r\n                    }, 1000 );\r\n                } catch ( e ) {\r\n                    new Notify().Render( 1, \"转换 PNG 格式失败，请注意，这是一个实验性功能，不一定能导出成功。\" );\r\n                }\r\n                break;\r\n            case \"epub\":\r\n                new Notify().Render( `当前使用了第三方 <a href=\"http://ksria.com/simpread/docs/#/发送到-Epub\" target=\"_blank\">epub.press</a> 服务，开始转码生成 epub 请稍等...` );\r\n                exp.Epub( content, window.location.href, title, desc, success => {\r\n                    success  && new Notify().Render( 0, \"转换成功，马上开始下载，请稍等。\" );\r\n                    !success && new Notify().Render( 2, `转换失败，这是一个实验性功能，不一定能导出成功，详细请看 <a href=\"http://ksria.com/simpread/docs/#/发送到-Epub\" target=\"_blank\">epub.press</a>` );\r\n                });\r\n                break;\r\n            case \"offlinehtml\":\r\n                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.permission ), result => {\r\n                    if ( !result.done ) {\r\n                        new Notify().Render( 2, `离线下载的文件体积较大，所以需要使用 Chrome 下载方案，请授权。` );\r\n                        return;\r\n                    } else {\r\n                        const notify2 = new Notify().Render({ content: \"图片转换中吗，请稍等...\", state: \"loading\" });\r\n                        offline.getImages( () => {\r\n                            notify2.complete();\r\n                            new Notify().Render( 0, \"全部图片已经转换完毕，马上开始下载，请稍等。\" );\r\n                            styles( csses => {\r\n                                const html = offline.HTML( title, desc, $( \"sr-rd-content\" ).html(), csses );\r\n                                offline.restoreImg();\r\n                                browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.download, { data: html, name: `simpread-${title}.html` }), result => {\r\n                                    console.log( \"Current download result: \", result )\r\n                                });\r\n                            });\r\n                        });\r\n                    }\r\n                });\r\n                break;\r\n            case \"html\":\r\n                styles( csses => {\r\n                    const html = offline.HTML( title, desc, content, csses );\r\n                    exp.Download( \"data:text/plain;charset=utf-8,\" + encodeURIComponent(html), `simpread-${title}.html` );\r\n                });\r\n                break;\r\n            case \"snapshot\":\r\n                new Notify().Render( \"请移动鼠标，按住鼠标左键框选，框选后可再次框选。\" );\r\n                $(\"panel-bg\").click();\r\n                setTimeout( () => {\r\n                    snap.Start().done( result => {\r\n                        snap.End();\r\n                        setTimeout(() => {\r\n                            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.snapshot, result ), result => {\r\n                                exp.Download( result.done, `simpread-${title}.png` );\r\n                            });\r\n                        }, 100 );\r\n                    });\r\n                }, 500 );\r\n                break;\r\n            case \"bear\":\r\n                storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                toMarkdown( result => {\r\n                    location.href = `bear://x-callback-url/create?title=${title}&text=${encodeURIComponent(result)}&tags=simpread`;\r\n                });\r\n                break;\r\n            case \"ulysses\":\r\n                storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                toMarkdown( result => {\r\n                    location.href = `ulysses://x-callback-url/new-sheet?text=${encodeURIComponent(result)}`;\r\n                });\r\n                break;\r\n            case \"temp\":\r\n            case \"kindle\":\r\n                const notify = new Notify().Render({ state: \"loading\", content: \"开始转码阅读模式并上传到服务器，请稍后。\" });\r\n                const style = {\r\n                    theme     : storage.read.theme,\r\n                    fontsize  : storage.read.fontsize,\r\n                    fontfamily: storage.read.fontfamily,\r\n                    layout    : storage.read.layout,\r\n                    custom    : storage.read.custom,\r\n                }\r\n                exp.kindle.Read( window.location.href, title, desc, content, style, ( result, error ) => {\r\n                    notify.complete();\r\n                    if ( error ) {\r\n                        new Notify().Render( 2, \"保存失败，请稍候再试！\" );\r\n                    } else {\r\n                        switch ( type ) {\r\n                            case \"kindle\":\r\n                                new Notify().Render( \"保存成功，3 秒钟后将跳转到发送页面。\" );\r\n                                setTimeout( ()=>{ exp.kindle.Send(); }, 3000 );\r\n                                break;\r\n                            case \"temp\":\r\n                                new Notify().Render( \"保存成功，3 秒钟后将跳转到临时页面。\" );\r\n                                setTimeout( ()=>{ exp.kindle.Temp(); }, 3000 );\r\n                                break;\r\n                        }\r\n                    }\r\n                });\r\n                break;\r\n            case \"pdf\":\r\n                if ( storage.current.mode == \"read\" ) {\r\n                    $( \"sr-rd-crlbar\" ).css({ \"opacity\": 0 });\r\n                    setTimeout( () => {\r\n                        exp.PDF();\r\n                        $( \"sr-rd-crlbar\" ).removeAttr( \"style\" );\r\n                    }, 500 );\r\n                } else {\r\n                    new Notify().Render( 2, \"当前模式不支持导出到 PDF，请使用阅读模式。\" );\r\n                }\r\n                break;\r\n        }\r\n    } else if ( [ \"dropbox\", \"pocket\", \"instapaper\", \"linnk\", \"yinxiang\",\"evernote\", \"onenote\", \"gdrive\", \"jianguo\", \"yuque\", \"notion\", \"youdao\", \"weizhi\" ].includes( type ) ) {\r\n        const { dropbox, pocket, instapaper, linnk, evernote, onenote, gdrive, jianguo, yuque, notion, youdao, weizhi } = exp,\r\n              id      = type == \"yinxiang\" ? \"evernote\" : type;\r\n        storage.Statistics( \"service\", type );\r\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.track, { eventCategory: \"service\", eventAction: \"service\", eventValue: type }) );\r\n        const service = type => {\r\n            switch( type ) {\r\n                case \"dropbox\":\r\n                    storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                    toMarkdown( result => {\r\n                        dropbox.Write( `${ title }.md`, result, ( _, result, error ) => exp.svcCbWrapper( result, error, dropbox.name, type, new Notify() ), \"md/\" );\r\n                    });\r\n                    break;\r\n                case \"pocket\":\r\n                    pocket.Add( window.location.href, title, ( result, error ) => exp.svcCbWrapper( result, error, pocket.name, type, new Notify() ));\r\n                    break;\r\n                case \"instapaper\":\r\n                    instapaper.Add( window.location.href, title, desc, ( result, error ) => exp.svcCbWrapper( result, error, instapaper.name, type, new Notify() ));\r\n                    break;\r\n                case \"linnk\":\r\n                    const notify = new Notify().Render({ content: `开始保存到 Linnk，请稍等...`, state: \"loading\" });\r\n                    linnk.access_token = storage.secret.linnk.access_token;\r\n                    linnk.GetSafeGroup( linnk.group_name, ( result, error ) => {\r\n                        notify.complete();\r\n                        if ( !error ) {\r\n                            linnk.group_id = result.data.groupId;\r\n                            linnk.Add( window.location.href, title, ( result, error ) => exp.svcCbWrapper( result, error, linnk.name, type, new Notify() ));\r\n                        } else new Notify().Render( 2, error == \"error\" ? `${ linnk.name } 保存失败，请稍后重新再试。` : error );\r\n                    });\r\n                    break;\r\n                case \"evernote\":\r\n                case \"yinxiang\":\r\n                    evernote.env     = type;\r\n                    evernote.sandbox = false;\r\n                    storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                    evernote.Add( title, util.HTML2ENML( content, window.location.href ), ( result, error ) => {\r\n                        exp.svcCbWrapper( result, error, evernote.name, type, new Notify() );\r\n                        if ( error == \"error\" ) {\r\n                            new Notify().Render( \"保存失败，正在尝试优化结构再次保存，请稍等...\" );\r\n                            exp.MDWrapper( util.ClearMD( content, false ), undefined, new Notify() ).done( result => {\r\n                                const md   = util.MD2ENML( result ),\r\n                                      tmpl = util.ClearHTML( exp.MD2HTML( result ));\r\n                                evernote.Add( title, tmpl, ( result, error ) => {\r\n                                    exp.svcCbWrapper( result, error, evernote.name, type, new Notify() );\r\n                                    if ( error == \"error\" ) {\r\n                                        new Notify().Render({ content: \"导出失败，是否以 Markdown 格式保存？\", action: \"是的\", cancel: \"取消\", callback: action => {\r\n                                            if ( action == \"cancel\" ) return;\r\n                                            new Notify().Render({ content: \"转换为 Markdown 并保存中，请稍等...\", delay: 2000 } );\r\n                                            evernote.Add( title, util.HTML2ENML( md, window.location.href ), ( result, error ) => {\r\n                                                exp.svcCbWrapper( result, error, evernote.name, type, new Notify() );\r\n                                                if ( error == \"error\" ) {\r\n                                                    new Notify().Render({ content: `转换后保存失败，是否提交当前站点？`, action: \"是的\", cancel: \"取消\", callback: type => {\r\n                                                        if ( type == \"cancel\" ) return;\r\n                                                        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.save_site, { url: location.href, site: {}, uid: storage.user.uid, type: \"evernote\" }));\r\n                                                    }});\r\n                                                }\r\n                                            });\r\n                                        }});\r\n                                    }\r\n                                });\r\n                            });\r\n                        }\r\n                    });\r\n                    break;\r\n                case \"onenote\":\r\n                    onenote.Add( onenote.Wrapper( window.location.href, title, content ), ( result, error ) => exp.svcCbWrapper( result, error, onenote.name, type, new Notify() ));\r\n                    break;\r\n                case \"gdrive\":\r\n                    storage.pr.current.site.avatar[0].name != \"\" && ( content = util.MULTI2ENML( content ) );\r\n                    toMarkdown( result => {\r\n                        gdrive.Add( \"file\",( result, error ) => exp.svcCbWrapper( result, error, gdrive.name, type, new Notify() ), gdrive.CreateFile( `${title}.md`, result ));\r\n                    });\r\n                    break;\r\n                case \"jianguo\":\r\n                    toMarkdown( markdown => {\r\n                        title = title.replace( /[|@!#$%^&*()<>/,.+=\\\\]/ig, \"-\" );\r\n                        jianguo.Add( storage.secret.jianguo.username, storage.secret.jianguo.password, `${jianguo.root}/${jianguo.folder}/${title}.md`, markdown, result => {\r\n                            let error = undefined;\r\n                            if ( result && ( result.status != 201 && result.status != 204 )) {\r\n                                error = \"导出到坚果云失败，请稍后再试。\";\r\n                            }\r\n                            exp.svcCbWrapper( result, error, jianguo.name, type, new Notify() );\r\n                        });\r\n                    });\r\n                    break;\r\n                case \"yuque\":\r\n                    toMarkdown( result => {\r\n                        yuque.Add( title, result,( result, error ) => exp.svcCbWrapper( result, error, yuque.name, type, new Notify() ));\r\n                    });\r\n                    break;\r\n                case \"notion\":\r\n                    toMarkdown( result => {\r\n                        corbLoader( \"load\", () => {\r\n                            notion.access_token = storage.secret.notion.access_token;\r\n                            notion.folder_id    = storage.secret.notion.folder_id;\r\n                            notion.save_image   = storage.secret.notion.save_image;\r\n                            notion.schema       = storage.secret.notion.schema;\r\n                            notion.type         = storage.secret.notion.type;\r\n                            notion.Add( title, result.replace( /.(png|jpe?g)!\\d+/ig, '.$1' ).replace( /， 原文地址 \\S+\\)/i, '\\n' ), ( result, error ) => {\r\n                                // hack code\r\n                                if ( notion.type == \"collection\" && notion.schema != storage.secret.notion.schema ) {\r\n                                    storage.secret.notion.schema = notion.schema;\r\n                                    notion.Save( storage );\r\n                                }\r\n                                exp.svcCbWrapper( result, error, notion.name, type, new Notify() )\r\n                            });\r\n                        }, 500 );\r\n                    });\r\n                    break;\r\n                case \"youdao\":\r\n                    toMarkdown( result => {\r\n                        corbLoader( \"load\", () => {\r\n                            youdao.access_token = storage.secret.youdao.access_token;\r\n                            youdao.folder_id    = storage.secret.youdao.folder_id;\r\n                            youdao.Add( title, result, ( result, error ) => {\r\n                                exp.svcCbWrapper( result, error, youdao.name, type, new Notify() )\r\n                            });\r\n                        });\r\n                    });\r\n                    break;\r\n                case \"weizhi\":\r\n                    styles( csses => {\r\n                        const html          = offline.HTML( title, desc, content, csses );\r\n                        weizhi.username     = storage.secret.weizhi.username;\r\n                        weizhi.access_token = storage.secret.weizhi.access_token;\r\n                        weizhi.Add( window.location.href, title, html, ( result, error ) => {\r\n                            exp.svcCbWrapper( result, error, weizhi.name, type, new Notify() )\r\n                        });\r\n                    });\r\n                    break;\r\n            }\r\n        };\r\n\r\n        exp.VerifySvcWrapper( storage, exp[id], type, exp.Name( type ), new Notify() )\r\n        .done( result => service( result ));\r\n\r\n    } else if ( type.startsWith( \"dyslexia\" ) ) {\r\n        if ( type.endsWith( \"speak\" ) ) {\r\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.speak, { content: `标题 ${title} 正文 ${content}` } ));\r\n        } else {\r\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.speak_stop ));\r\n        }\r\n    } else if ( type.startsWith( \"fullscreen\" ) ) {\r\n        document.documentElement.requestFullscreen();\r\n    } else if ( type.startsWith( \"webdav_\" ) ) {\r\n        const id      = type.replace( \"webdav_\", \"\" ),\r\n              covernt = ( type, callback ) => {\r\n                if ( type == \"html\" ) {\r\n                    styles( csses => {\r\n                        const html = offline.HTML( title, desc, content, csses );\r\n                        callback( html );\r\n                    });\r\n                } else if ( type == \"ofhtml\" ) {\r\n                    const notify2 = new Notify().Render({ content: \"图片转换中吗，请稍等...\", state: \"loading\" });\r\n                    offline.getImages( () => {\r\n                        notify2.complete();\r\n                        new Notify().Render( 0, \"全部图片已经转换完毕，开始发送，请稍等。\" );\r\n                        styles( csses => {\r\n                            const html = offline.HTML( title, desc, $( \"sr-rd-content\" ).html(), csses );\r\n                            offline.restoreImg();\r\n                            callback( html );\r\n                        });\r\n                    });\r\n                } else {\r\n                    toMarkdown( markdown => {\r\n                        callback( markdown );\r\n                    });\r\n                }\r\n              };\r\n        storage.Safe( () => {\r\n            storage.secret.webdav.forEach( item => {\r\n                item = JSON.parse( item );\r\n                item.format == undefined && ( item.format = \"md\" );\r\n                if ( item.name == id ) {\r\n                    covernt( item.format, str => {\r\n                        title        = title.replace( /[|@!#$%^&*()<>/,.+=\\\\]/ig, \"-\" );\r\n                        const suffix = item.format.endsWith( \"html\" ) ? \".html\" : \".md\";\r\n                        new Notify().Render( `开始保存到 ${ item.name}，请稍等...` );\r\n                        exp.webdav.Add( item.url, item.user, item.password, `${title}${suffix}`, str, result => {\r\n                            let error = undefined;\r\n                            if ( result && ( result.status != 201 && result.status != 204 )) {\r\n                                error = `导出到 ${item.name} 失败，请稍后再试。`;\r\n                            }\r\n                            exp.svcCbWrapper( result, error, item.name, type, new Notify() );\r\n                        });\r\n                    });\r\n                }\r\n            });\r\n        })\r\n    }\r\n    else {\r\n        new Notify().Render( 2, \"当前模式下，不支持此功能。\" );\r\n    }\r\n}\r\n\r\n/**\r\n * Open and Remove CORB iframe\r\n * \r\n * @param {string} include: load & remove\r\n */\r\nfunction corbLoader( state, callback ) {\r\n    if ( state == \"load\" ) {\r\n        if ( $( '#sr-corb' ).length == 0 ) {\r\n            $( 'html' ).append( `<iframe id=\"sr-corb\" src=\"${browser.runtime.getURL('options/corb.html')}\" width=\"0\" height=\"0\" frameborder=\"0\"></iframe>` );\r\n            $( '#sr-corb' ).on( \"load\", event => callback());\r\n        } else callback();\r\n    } else $( '#sr-corb' ).remove();\r\n}\r\n\r\nexport {\r\n    action as Action,\r\n}"
  },
  {
    "path": "src/service/permission.js",
    "content": "\nimport {browser} from 'browser';\n\nfunction Get( permissions, callback ) {\n    browser.permissions.contains({ permissions: permissions.permissions }, result => {\n        result == false ? chrome.permissions.request( permissions, granted => {\n            callback( granted );\n        }): callback( result );\n    });\n}\n\nfunction Remove( permissions, callback ) {\n    browser.permissions.remove( { permissions: permissions.permissions }, result => {\n        callback( result )\n    })\n}\n\nexport {\n    Get,\n    Remove\n}"
  },
  {
    "path": "src/service/runtime.js",
    "content": "console.log( \"=== simpread runtime load ===\" )\n\nimport nanoid           from 'nanoid';\n\nimport {browser}        from 'browser';\nimport {storage, Clone} from 'storage';\nimport * as highlight   from 'highlight';\nimport * as watch       from 'watch';\n\nlet is_firstload = true;\n\n/**\n * Generate ID\n * \n * @param {string} generate id, include: user id( uuid v4 ), plugin id( like t.co/cKPFh3Qsh4 )\n */\nfunction generateID( type ) {\n    if ( type == \"user\" ) {\n        const random = \"0123456789abcdefghijklmnopqrstuvwxyz\",\n              first  = nanoid( random, 8 ),\n              second = nanoid( random, 4 ),\n              third  = nanoid( random, 4 ),\n              fourth = nanoid( random, 4 ),\n              fifth  = nanoid( random, 12 );\n        return `${first}-${second}-${third}-${fourth}-${fifth}`;\n    } else if ( type == \"plugin\" ) {\n        return nanoid( \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\", 10 );\n    } else if ( type == \"site\" ) {\n        return nanoid( \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\", 10 );\n    }\n}\n\n/**\n * Install plugin\n * \n * @param {string} id  e.g. kw36BtjGu0\n * @param {string} url\n */\nfunction install( id, url, callback ) {\n    url = id ? `https://simpread.ksria.cn/plugins/srplug/${id}.srplug` : url;\n    url = url + `?${+new Date()}`;\n    console.log( \"install url is\", url )\n    $.ajax({ url, dataType: \"json\" })\n    .done( result => callback( result, undefined  ))\n    .fail( ()     => callback( undefined, \"error\" ));\n}\n\n/**\n * Dispatch event\n * \n * @param {string} type include: export, read_ui, read_start, read_end\n * @param {string} value\n */\nfunction dispatch( type, value ) {\n    window.dispatchEvent( new CustomEvent( \"simpread-plugin\", { detail: { type, value }}));\n}\n\n/**\n * Execute\n * \n * @param {string} state, include: read_start, read_loading, read_complete, read_end\n * @param {string} site name, inlcude: \"\", \"xxx\", \"xxx, yyy\"\n * @param {object} plugin object\n */\nfunction exec( state, site, plugin ) {\n    try {\n        if ( plugin.enable == false ) return;\n        if ( plugin.run_at != state ) return;\n        if ( plugin.site   != \"\" && !plugin.site.split(\",\").includes( site ) ) return;\n        console.log( \"current plugin is running\", plugin )\n        new Function( func( plugin.script ) )();\n        plugin.style != \"\" && addStyle( plugin.style );\n    } catch ( error ) {\n        new Notify().Render( 2, `插件 ${ plugin.name } 运行时出错，可以的话，请 <a href=\"https://github.com/Kenshin/simpread/issues/new\" target=\"_blank\">提交此问题</a> 😁` );\n    }\n}\n\n/**\n * Contact (function(){})() string\n * \n * @param {string} source \n */\nfunction func( source ) {\n    window.Notify  = Notify;\n    window.browser = browser;\n    window.current = Clone( storage.current );\n    window.read    = Clone( storage.read );\n    window.highlight = highlight;\n    window.db      = Storage;\n    window.control = Controlbar;\n    window.SRSave  = Save;\n    return `( function ( $$version, $read, $title, $desc, $content, $footer, $process, $toc, Notify, $$highlight, browser, $$storage, $$current, $$read, $$control, $$save ) {\n        ${ source }\n    })( \"0.0.2\", $( \"sr-read\" ), $( \"sr-rd-title\" ), $( \"sr-rd-desc\" ), $( \"sr-rd-content\" ), $( \"sr-rd-footer\" ), $( \"read-process\" ), $( \"toc\" ), Notify, highlight, browser, db, current, read, control, SRSave );`\n}\n\n/**\n * Getter / Setter plugin config\n * \n * @param {string} plugin id\n * @param {object} data\n * @param {func} callback \n */\nfunction Storage( id, data, callback ) {\n    if ( data ) {\n        browser.storage.local.set( { [\"plugin-\"+id] : data }, () => {\n            callback && callback();\n        });\n    } else {\n        browser.storage.local.get( [\"plugin-\"+id], result => {\n            callback && callback( result );\n        });\n    }\n}\n\n/**\n * Contorlbar setting\n * \n * @param {string} controlbar type include: markdown \n * @param {func} callback\n */\nfunction Controlbar( type, callback ) {\n    if ( callback ) {\n        is_firstload && window.addEventListener( \"simpread-plugin_controlbar\", event => {\n            callback( event );\n        });\n    } else window.dispatchEvent( new CustomEvent( \"simpread-plugin_controlbar\", { detail: { type }}));\n    is_firstload = false;\n}\n\n/**\n * Save adapter storage.Write\n * \n * @param {object} simpread data structure\n * @param {func}   callback\n */\nfunction Save( data, callback ) {\n    if ( data ) {\n        storage.Write( ()=> {\n            watch.SendMessage( \"option\", true );\n            callback();\n        }, storage.simpread );\n    } else {\n        return {\n            focus  : storage.focus,\n            read   : storage.read,\n            option : storage.option,\n            version: storage.version\n        }\n    }\n}\n\n/**\n * Add style\n * \n * @param {string} add css to head\n */\nfunction addStyle( str ) {\n    $( \"head\" ).append(`<style type=\"text/css\">${str}</style>`);\n}\n\n/**\n * Add trigger\n * \n * @param {string} add trigger to fap controlbar\n */\nfunction addTrigger( str ) {\n    let is_found = false;\n    $( \"fap action-bar\" ).find( \"sr-opt-label\" ).map( ( idx, item ) => {\n        if ( $(item).text() == \"插件触发器\" ) {\n            is_found = true;\n            $(item).next().append( str );\n        }\n    });\n    if ( is_found == false ) {\n        const html = `<sr-opt-gp>\n                        <sr-opt-label>插件触发器</sr-opt-label>\n                        <actions style=\"display:flex;margin:10px 0;flex-wrap:wrap;\">\n                            ${str}\n                        </actions>\n                      </sr-opt-gp>`;\n        $( \"fap action-bar\" ).append( html );\n    }\n}\n\n/**\n * Test Plugin\n * \n * @param {func} style func\n * @param {func} plugin func\n * @param {func} trigger func\n */\nfunction testPlugin( style, plugin, trigger ) {\n    style  && addStyle( style() );\n    plugin && plugin( \"0.0.2\",\n                      $( \"sr-read\" ), $( \"sr-rd-title\" ), $( \"sr-rd-desc\" ), $( \"sr-rd-content\" ), $( \"sr-rd-footer\" ), $( \"read-process\" ), $( \"toc\" ),\n                      Notify, highlight,\n                      browser, Storage,\n                      storage.current, storage.read, Controlbar, Save );\n    trigger && addTrigger( trigger() );\n}\n\nwindow.simpread = { testPlugin };\n\nexport {\n    install as Install,\n    exec    as Exec,\n    generateID as ID,\n    dispatch as Event,\n    Controlbar,\n}"
  },
  {
    "path": "src/service/snapshot.js",
    "content": "console.log( \"=== simpread snapshot load ===\" )\n\nlet dtd, startPos, endPos, dragStart = false, position;\n\nfunction start() {\n    dtd       = $.Deferred();\n    dragStart = false;\n    $( \".simpread-read-root\" ).append( `<simpread-snapshot><sr-mask></sr-mask></simpread-snapshot>` );\n    $( \"simpread-snapshot\" )\n        .on( \"mousemove\", event => {\n            if ( dragStart == false ) return;\n            endPos       = { left: event.clientX, top: event.clientY };\n            const width  = endPos.left - startPos.left,\n                  height = endPos.top  - startPos.top;\n            position     = {\n                left  : width  >= 0 ? startPos.left : endPos.left,\n                top   : height >= 0 ? startPos.top  : endPos.top,\n                width : Math.abs( width ),\n                height: Math.abs( height ),\n            }\n            $( event.currentTarget ).find( \"sr-mask\" ).css( position );\n        })\n        .on( \"mousedown\", event => {\n            startPos  = { left: event.clientX, top: event.clientY };\n            dragStart = true;\n        })\n        .on( \"mouseup\", event =>{\n            dragStart = false;\n            controlbar();\n        });\n    return dtd;\n}\n\nfunction remove() {\n    $( \".simpread-read-root\" ).find( \"simpread-snapshot\" ).remove();\n}\n\nfunction controlbar() {\n    if ( $( \"simpread-snapshot sr-highlight-ctl\" ).length > 0 ) return;\n    const html = `<sr-snapshot-ctlbar>\n                    <sr-highlight-ctl class=\"done\"><svg t=\"1560496955945\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M416.832 798.08C400.64 798.08 384.512 791.872 372.16 779.52L119.424 525.76C94.784 500.992 94.784 460.8 119.424 436.032 144.128 411.264 184.128 411.264 208.768 436.032L416.832 644.928 814.4 245.76C839.04 220.928 879.04 220.928 903.744 245.76 928.384 270.528 928.384 310.656 903.744 335.424L461.504 779.52C449.152 791.872 432.96 798.08 416.832 798.08Z\" p-id=\"1755\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\n                    <sr-highlight-ctl class=\"none\"><svg t=\"1560499513561\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M649.179 512l212.839-212.84c37.881-37.881 37.881-99.298 0-137.179s-99.298-37.881-137.179 0L512 374.821l-212.839-212.84c-37.881-37.881-99.298-37.881-137.179 0s-37.881 99.298 0 137.179L374.821 512 161.982 724.84c-37.881 37.881-37.881 99.297 0 137.179 18.94 18.94 43.765 28.41 68.589 28.41 24.825 0 49.649-9.47 68.589-28.41L512 649.179l212.839 212.84c18.94 18.94 43.765 28.41 68.589 28.41s49.649-9.47 68.59-28.41c37.881-37.882 37.881-99.298 0-137.179L649.179 512z\" p-id=\"1990\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\n                    <sr-highlight-ctl class=\"help\"><svg t=\"1560573280563\" viewBox=\"0 0 1024 1024\" version=\"1.1\" width=\"15\" height=\"15\"><defs><style type=\"text/css\"></style></defs><path d=\"M512 958.326255c247.255337 0 447.696462-200.441125 447.696462-447.696462s-200.441125-447.696462-447.696462-447.696462-447.696462 200.441125-447.696462 447.696462S264.74364 958.326255 512 958.326255zM512 217.681788c35.32146 0 63.956637 28.635177 63.956637 63.956637 0 35.323507-28.635177 63.956637-63.956637 63.956637s-63.956637-28.633131-63.956637-63.956637C448.043363 246.316965 476.67854 217.681788 512 217.681788zM448.043363 510.629793c0-35.32146 28.635177-63.956637 63.956637-63.956637s63.956637 28.635177 63.956637 63.956637l0 223.848231c0 35.323507-28.635177 63.956637-63.956637 63.956637s-63.956637-28.633131-63.956637-63.956637L448.043363 510.629793z\" p-id=\"1979\" fill=\"#ffffff\"></path></svg></sr-highlight-ctl>\n                  </sr-snapshot-ctlbar>`;\n    $( \"simpread-snapshot\" ).append( html );\n    $( \"simpread-snapshot\" ).on( \"click\", \"sr-highlight-ctl\", event => {\n        const cls = $( event.currentTarget ).attr( \"class\" );\n        if ( cls == \"done\" ) {\n            dtd.resolve( position );\n        } else if ( cls == \"none\" ) {\n            remove();\n        } else if ( cls == \"help\" ) {\n            const $a = $( `<a style=\"display:none\" target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/截图\"></a>` ).appendTo( \"body\" );\n            $a[0].click();\n            $a.remove();\n        }\n    });\n}\n\nexport {\n    start  as Start,\n    remove as End,\n}"
  },
  {
    "path": "src/service/storage.js",
    "content": "console.log( \"=== simpread storage load ===\" )\r\n\r\nimport \"babel-polyfill\";\r\n\r\nimport {browser, br}  from 'browser';\r\nimport {version,patch}from 'version';\r\n\r\n/**\r\n * Read and Write Chrome storage\r\n * \r\n * @class\r\n */\r\n\r\nconst name = \"simpread\",\r\n    remote = \"http://sr.ksria.cn/website_list_v4.json\",\r\n    origins= \"http://sr.ksria.cn/website_list_origins.json\",\r\n    versions= \"http://sr.ksria.cn/versions.json\",\r\n    local  = browser.extension.getURL( \"website_list.json\" ),\r\n    help   = browser.extension.getURL( \"help_tips.json\" ),\r\n    mode   = {\r\n        focus     : \"focus\",\r\n        read      : \"read\",\r\n        option    : \"option\",\r\n        unrdist   : \"unrdist\",\r\n    },\r\n    site   = {\r\n        url       : \"\",\r\n        target    : \"\",\r\n        matching  : [],\r\n        name      : \"\",   // only read mode\r\n        title     : \"\",   // only read mode\r\n        desc      : \"\",   // only read mode\r\n        exclude   : [],\r\n        include   : \"\",\r\n        avatar    : [],\r\n        paging    : [],\r\n    },\r\n    focus  = {\r\n        version   : \"2016-12-29\",\r\n        bgcolor   : \"rgba( 235, 235, 235, 0.9 )\",\r\n        controlbar: true,\r\n        mask      : true,\r\n        highlight : true,\r\n        opacity   : 90,\r\n        shortcuts : \"A S\",\r\n        //sites     : [],    // e.g. [ \"<url>\", site ]\r\n    },\r\n    read   = {\r\n        version   : \"2017-03-16\",\r\n        progress  : false,\r\n        auto      : false,\r\n        controlbar: true,\r\n        fap       : true,\r\n        highlight : true,\r\n        shortcuts : \"A A\",\r\n        toc       : true,\r\n        toc_hide  : true,\r\n        theme     : \"github\",\r\n        fontfamily: \"default\",\r\n        cleanup   : true,\r\n        pure      : true,\r\n        whitelist : [],\r\n        exclusion : [\r\n            \"v2ex.com\",\"issue.github.com\",\"readme.github.com\",\"question.zhihu.com\",\"douban.com\",\"nationalgeographic.com.cn\",\"tech.163.com\",\"docs.microsoft.com\",\"msdn.microsoft.com\",\"baijia.baidu.com\",\"code.oschina.net\",\"http://www.ifanr.com\",\"http://www.ifanr.com/news\",\"http://www.ifanr.com/app\",\"http://www.ifanr.com/minapp\",\"http://www.ifanr.com/dasheng\",\"http://www.ifanr.com/data\",\"https://www.ifanr.com/app\",\"http://www.ifanr.com/weizhizao\",\"http://www.thepaper.cn\",\"http://www.pingwest.com\",\"http://tech2ipo.com\",\"https://www.waerfa.com/social\"\r\n        ],\r\n        fontsize  : \"\",  // default 62.5%\r\n        layout    : \"\",  // default 20%\r\n        //sites     : [],  // e.g. [ \"<url>\", site ]\r\n        custom    : {\r\n            // remove by 1.1.1\r\n            //global: {\r\n            //    fontFamily : \"\",\r\n            //    marginLeft : \"\",\r\n            //    marginRight: \"\",\r\n            //},\r\n            title : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n            },\r\n            desc  : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n            },\r\n            art   : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n                fontWeight : \"\",\r\n                wordSpacing: \"\",\r\n                letterSpacing: \"\",\r\n                lineHeight : \"\",\r\n                textIndent : \"\",\r\n            },\r\n            pre  : {\r\n                textShadow : \"\",\r\n            },\r\n            code  : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n            },\r\n            css   : \"\",\r\n        },\r\n    },\r\n    option = {\r\n        version   : \"2017-04-03\",\r\n        create    : \"\",\r\n        update    : \"\",\r\n        sync      : \"\",\r\n        save_at   : \"dropbox\", // include: dropbox | jianguo\r\n        notice    : true,\r\n        //focus   : 0,\r\n        //read    : 0,\r\n        esc       : true,\r\n        br_exit   : false,\r\n        secret    : false,\r\n        preload   : true,\r\n        lazyload  : [\r\n            \"baidu.com\", \"weibo.com\", \"youtube.com\"\r\n        ],\r\n        uninstall : true,\r\n        menu      : {\r\n            focus : true,\r\n            read  : true,\r\n            link  : true,\r\n            list  : false,\r\n            whitelist: false,\r\n            exclusion: false,\r\n            blacklist: false,\r\n            lazyload: false,\r\n            unrdist: false,\r\n        },\r\n        origins   : [],\r\n        blacklist : [\r\n            \"google.com\",\r\n            \"youtube.com\",\r\n            \"simp.red\",\r\n            \"simpread.herokuapp.com\",\r\n            \"simpread-test.herokuapp.com\",\r\n            \"simpread.ksria.cn\"\r\n        ],\r\n        plugins   : [], // plugin id, e.g. kw36BtjGu0\r\n        urlscheme : true,\r\n    },\r\n    /*statistics = {   remove by 1.1.4\r\n        \"focus\"   : 0,\r\n        \"read\"    : 0,\r\n        \"service\" : {\r\n            \"linnk\"      : 0,\r\n            \"instapaper\" : 0,\r\n            \"pocket\"     : 0,\r\n            \"readlater\"  : 0,\r\n            \"epub\"       : 0,\r\n            \"pdf\"        : 0,\r\n            \"png\"        : 0,\r\n            \"markdown\"   : 0,\r\n            \"html\"       : 0,\r\n            \"evernote\"   : 0,\r\n            \"yinxiang\"   : 0,\r\n            \"dropbox\"    : 0,\r\n            \"onenote\"    : 0,\r\n            \"gdrive\"     : 0,\r\n            \"kindle\"     : 0,\r\n            \"temp\"       : 0,\r\n            \"yuque\"      : 0,\r\n            \"jianguo\"    : 0,\r\n            \"weizhi\"     : 0,\r\n        }\r\n    },*/\r\n    user   = {\r\n        uid       : \"\",\r\n        name      : \"\",\r\n        contact   : \"\",\r\n        email     : \"\",\r\n        avatar    : \"\",\r\n        permission: \"\",\r\n    },\r\n    notice = {\r\n        latest: 0,\r\n        read  : []\r\n    },\r\n    unread = {\r\n        idx       : 0,\r\n        create    : \"\",\r\n        url       : \"\",\r\n        title     : \"\",\r\n        desc      : \"\",\r\n    };\r\n\r\nlet current  = {},\r\n    curori   = {},\r\n    origin   = {},\r\n    sync     = {},\r\n    simpread = {\r\n        version,\r\n        patch,\r\n        option,\r\n        focus,\r\n        read,\r\n        unrdist : [],\r\n        sites   : [],\r\n        websites: {\r\n            person : [],\r\n            custom : [],\r\n            local  : [], // include focus.sites and read.sites\r\n        },\r\n        statistics,\r\n        notice,\r\n        user,\r\n    },\r\n    plugins  = {},\r\n    unrdist  = [],\r\n    statistics= {\r\n        \"focus\"   : 0,\r\n        \"read\"    : 0,\r\n        \"service\" : {},\r\n    },\r\n    secret   = {\r\n        version   : \"2019-12-20\",\r\n        \"dropbox\" : {\r\n            \"access_token\": \"\"\r\n        },\r\n        \"pocket\"  : {\r\n            \"access_token\": \"\",\r\n            \"tags\"        : \"\",\r\n        },\r\n        \"linnk\"   : {\r\n            access_token  : \"\",\r\n            \"group_name\"  : \"\",\r\n        },\r\n        \"instapaper\"   : {\r\n            access_token  : \"\",\r\n            token_secret  : \"\",\r\n        },\r\n        \"yinxiang\" : {\r\n            access_token  : \"\",\r\n        },\r\n        \"evernote\" : {\r\n            access_token  : \"\",\r\n        },\r\n        \"onenote\"  : {\r\n            access_token  : \"\",\r\n        },\r\n        \"gdrive\"   : {\r\n            access_token  : \"\",\r\n            folder_id     : \"\",\r\n        },\r\n        \"yuque\"  : {\r\n            access_token  : \"\",\r\n            repos_id: \"\",\r\n        },\r\n        \"notion\"  : {\r\n            access_token  : \"\",\r\n            type          : \"\",\r\n            save_image    : false,\r\n            folder_id     : \"\",\r\n        },\r\n        \"youdao\"  : {\r\n            access_token  : \"\",\r\n            folder_id     : \"\",\r\n        },\r\n        \"jianguo\"  : {\r\n            username      : \"\",\r\n            password      : \"\",\r\n            access_token  : \"\",\r\n        },\r\n        \"weizhi\"  : {\r\n            username      : \"\",\r\n            password      : \"\",\r\n            access_token  : \"\",\r\n        },\r\n        \"webdav\"  : []\r\n    };\r\n    //stcode = -1;\r\n\r\nclass Storage {\r\n\r\n    /**\r\n     * Get simpread.option data structure\r\n     * \r\n     * @return {object} simpread[\"option\"]\r\n     */\r\n    get option() {\r\n        return simpread[ mode.option ];\r\n    }\r\n\r\n    /**\r\n     * Get simpread.focus data structure\r\n     * \r\n     * @return {object} simpread[\"focus\"]\r\n     */\r\n    get focus() {\r\n        return simpread[ mode.focus ];\r\n    }\r\n\r\n    /**\r\n     * Get simpread.read data structure\r\n     * \r\n     * @return {object} simpread[\"read\"]\r\n     */\r\n    get read() {\r\n        return simpread[ mode.read ];\r\n    }\r\n\r\n    /**\r\n     * Get current data structure\r\n     * \r\n     * @return {object} current\r\n     */\r\n    get current() {\r\n        return current;\r\n    }\r\n\r\n    /**\r\n     * Get read site code, include: simpread.sites and simpread.read.sites\r\n     * \r\n     * @return {int} @see Findsite\r\n     */\r\n    /*\r\n    get stcode() {\r\n        return stcode;\r\n    }\r\n    */\r\n\r\n    /**\r\n     * Get unread list\r\n     * \r\n     * @return {array} unread list\r\n     */\r\n    get unrdist() {\r\n        return unrdist;\r\n    }\r\n\r\n    /**\r\n     * Get notice\r\n     * \r\n     * @return {object} notice\r\n     */\r\n    get notice() {\r\n        return simpread.notice;\r\n    }\r\n\r\n    /**\r\n     * Get version\r\n     * \r\n     * @return {string} version\r\n     */\r\n    get version() {\r\n        return simpread.version;\r\n    }\r\n\r\n    /**\r\n     * Get patch version\r\n     * \r\n     * @return {string} patch version\r\n     */\r\n    get patch() {\r\n        return simpread.patch;\r\n    }\r\n\r\n    /**\r\n     * Get simpread data structure clone\r\n     * \r\n     * @return {string} version\r\n     */\r\n    get simpread() {\r\n        return { ...simpread };\r\n    }\r\n\r\n    /**\r\n     * Get statistics\r\n     * \r\n     * @return {object} statistics object\r\n     */\r\n    get statistics() {\r\n        return statistics;\r\n    }\r\n\r\n    /**\r\n     * Get user info\r\n     * \r\n     * @return {object} user object\r\n     */\r\n    get user() {\r\n        return simpread.user;\r\n    }\r\n\r\n    /**\r\n     * Get secret data structure\r\n     * \r\n     * @return {object} secret object\r\n     */\r\n    get secret() {\r\n        return secret;\r\n    }\r\n\r\n    /**\r\n     * Get plugins data structure\r\n     * \r\n     * @return {object} plugins object\r\n     */\r\n    get plugins() {\r\n        return plugins;\r\n    }\r\n\r\n    /**\r\n     * Get all sites structure\r\n     * \r\n     * @return {object} all sites\r\n     */\r\n    get sites() {\r\n        return {\r\n            global: simpread.sites,\r\n            person: simpread.websites.person,\r\n            custom: simpread.websites.custom,\r\n            local : simpread.websites.local,\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Get simpread.websites data structure\r\n     * \r\n     * @return {object} secret object\r\n     */\r\n    get websites() {\r\n        return simpread.websites;\r\n    }\r\n\r\n    /**\r\n     * Set puread object\r\n     * \r\n     * @param {object} pure read\r\n     */\r\n    set puread( value ) {\r\n        this.pr = value;\r\n    }\r\n\r\n    /**\r\n     * Get puread object\r\n     * \r\n     * @return {object} pure read\r\n     */\r\n    get puread() {\r\n        return this.pr;\r\n    }\r\n\r\n    /**\r\n     * Get service url\r\n     * \r\n     * @return {string} url\r\n     */\r\n    get service() {\r\n        //return \"http://localhost:3000\";\r\n        return \"https://simpread.ksria.cn\";\r\n    }\r\n\r\n    /**\r\n     * Get notice service url\r\n     * \r\n     * @return {string} url\r\n     */\r\n    get notice_service() {\r\n        return {\r\n            latest: \"https://static.simp.red/notice/latest\",\r\n            message: \"https://static.simp.red/notice\",\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Get help service url\r\n     * \r\n     * @return {string} url\r\n     */\r\n    get help_service() {\r\n        return \"http://sr.ksria.cn/help_tips.json\";\r\n    }\r\n\r\n    /**\r\n     * Get simpread object from chrome storage\r\n     * \r\n     * @param {function} callback\r\n     */\r\n    Read( callback ) {\r\n        browser.storage.local.get( [name], result => {\r\n            let firstload = true;\r\n            if ( result && !$.isEmptyObject( result )) {\r\n                simpread  = result[name];\r\n                firstload = false;\r\n            }\r\n            origin = clone( simpread );\r\n            this.Statistics( undefined, undefined, \"read\" );\r\n            this.UnRead( undefined, undefined, () => {\r\n                callback( firstload );\r\n            }, \"read\" );\r\n            console.log( \"chrome storage read success!\", simpread, origin, result );\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Read storage usage aync only firefox\r\n     * \r\n     * @param {func} callback \r\n     */\r\n    ReadAsync( callback ) {\r\n        const db = browser.storage.local.get();\r\n        const safesave = obj => {\r\n            if ( secret && secret.version != obj.version ) {\r\n                obj.version = secret.version;\r\n                Object.keys( secret ).forEach( item => {\r\n                    obj[item] == undefined && ( obj[item] = secret[item] );\r\n                });\r\n            }\r\n            return { ...obj };\r\n        };\r\n        db.then( result => {\r\n            let firstload = true;\r\n            if ( result && !$.isEmptyObject( result )) {\r\n                secret    = result[ \"secret\" ] && safesave( result[ \"secret\" ]);\r\n                simpread  = result[name];\r\n                plugins   = result[\"plugins\"];\r\n                firstload = false;\r\n            }\r\n            origin = clone( simpread );\r\n            callback( simpread, secret, plugins );\r\n            console.log( \"chrome storage read success!\", simpread, origin, result );\r\n        }, error => {\r\n            console.log(`Error: ${error}`);\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Write Object only firefox\r\n     * \r\n     * @param {object} simpread object \r\n     * @param {object} secret object \r\n     * @param {object} plugins object \r\n     */\r\n    WriteAsync( simp, sec, plugs ) {\r\n        simpread  = simp;\r\n        origin    = clone( simpread );\r\n        sec   && ( secret  = sec    );\r\n        plugs && ( plugins = plugs  );\r\n        browser.storage.local.set( { [\"secret\"] : secret }, () => {\r\n            console.log( \"firefox storage secret set success!\", secret );\r\n        });\r\n        browser.storage.local.set( { [\"plugins\"] : plugins }, () => {\r\n            console.log( \"firefox storage plugins set success!\", plugins );\r\n        });\r\n    }\r\n\r\n    /**\r\n     * Set simpread object to chrome storage\r\n     * \r\n     * @param {function} callback\r\n     * @param {object}   new simpread data structure\r\n     */\r\n    Write( callback, new_val = undefined ) {\r\n        new_val && Object.keys( new_val ).forEach( key => simpread[ key ] = new_val[key] );\r\n        save( callback, new_val );\r\n    }\r\n\r\n    /**\r\n     * Set simpread sites to chrome storage\r\n     * \r\n     * @param {object} all sites, @see this.sites\r\n     * @param {function} callback\r\n     */\r\n    Writesite( sites, callback ) {\r\n        simpread.sites           = sites.global;\r\n        simpread.websites.person = sites.person;\r\n        simpread.websites.custom = sites.custom;\r\n        simpread.websites.local  = sites.local;\r\n        callback && save( callback, true );\r\n    }\r\n\r\n    Getcur( key, site ) {\r\n        current      = swap( simpread[key], {} );\r\n        current.url  = site.url;\r\n        current.mode = key;\r\n        current.site = site;\r\n        curori       = { ...current };\r\n        curori.site  = { ...current.site };\r\n        console.log( \"current site object is \", current )\r\n    }\r\n\r\n    /**\r\n     * Set current to simpread[key]\r\n     * \r\n     * @param {string} @see mode\r\n     */\r\n    Setcur( key ) {\r\n        swap( current, simpread[key] );\r\n        save( undefined, true );\r\n    }\r\n\r\n    /**\r\n     * Verity current changed\r\n     * \r\n     * @param {string} @see mode\r\n     */\r\n    VerifyCur( type ) {\r\n        return current.mode != type    ||\r\n               current.url != getURI() ||\r\n               $.isEmptyObject( current );\r\n    }\r\n\r\n    /**\r\n     * Compare focus and read setting is changed\r\n     * \r\n     * @param  {string} inlcude: focus, read\r\n     * @return {object} option: option changed, st: site changed\r\n     */\r\n    Compare( type ) {\r\n        const target = { ...current },\r\n              read   = [ \"theme\", \"shortcuts\", \"fontfamily\", \"fontsize\", \"layout\" ],\r\n              focus  = [ \"bgcolor\", \"opacity\", \"shortcuts\" ],\r\n              site   = [ \"title\", \"include\", \"exclude\", \"desc\" ],\r\n              option = [],\r\n              st     = [],\r\n              source = type == \"read\" ? read : focus;\r\n        source.forEach( item => {\r\n            curori[item] != current[item] && option.push({ item, old: curori[item], newer: current[item] });\r\n        });\r\n        site.forEach( item => {\r\n            curori.site[item] != current.site[item] && st.push({ item, old: curori.site[item], newer: current.site[item] });\r\n        });\r\n        return { option, st };\r\n    }\r\n\r\n    /**\r\n     * Get remote from type\r\n     * \r\n     * @param {string} include: local, remote, origins, versions and <urls>\r\n     * @param {func} callback\r\n     */\r\n    async GetRemote( type, callback ) {\r\n        let url;\r\n        switch ( type ) {\r\n            case \"local\":\r\n                url = local;\r\n                break;\r\n            case \"remote\":\r\n                url = remote;\r\n                break;\r\n            case \"origins\":\r\n                url = origins;\r\n                break;\r\n            case \"versions\":\r\n                url = versions;\r\n                break;\r\n            case \"help_tips\":\r\n                url = help;\r\n                break;\r\n            default:\r\n                url = type;\r\n        }\r\n        try {\r\n            const response = await fetch( url + \"?_=\" + Math.round(+new Date()) ),\r\n                  result   = await response.json();\r\n            result ? callback( result ) : callback( undefined, \"error\" );\r\n        } catch ( error ) {\r\n            callback( undefined, \"error\" );\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Statistics simpread same info\r\n     * \r\n     * @param {string} include: create, focus, read, service\r\n     * @param {string} include: service type, e.g. pdf png onenote\r\n     * @param {boolean} include: read & write\r\n     */\r\n    Statistics( type, service, state = \"write\" ) {\r\n        if ( type == \"create\" ) {\r\n            simpread.option.create = now();\r\n            save( undefined, type == \"create\" );\r\n            return;\r\n        }\r\n\r\n        const write = () => {\r\n                browser.storage.local.set( { [\"statistics\"] : statistics }, () => {\r\n                    console.log( \"chrome storage statistics set success!\", statistics );\r\n                });\r\n            },\r\n            read = cb => {\r\n                browser.storage.local.get( [\"statistics\"], result => {\r\n                    console.log( \"chrome storage statistics get success!\", result );\r\n                    result && !$.isEmptyObject( result ) && ( statistics = result[\"statistics\"] );\r\n                    cb && cb();\r\n                });\r\n            };\r\n\r\n        if ( state == \"read\" ) {\r\n            if ( !$.isEmptyObject( simpread.statistics ) ) {\r\n                statistics = clone( simpread.statistics );\r\n                simpread.statistics = {};\r\n                write();\r\n            } else read();\r\n        } else {\r\n            read( () => {\r\n                if ( type == \"create\" ) {\r\n                    simpread.option.create = now();\r\n                } else {\r\n                    service && statistics.service[ service ] == undefined && ( statistics.service[ service ] = 0 );\r\n                    service ? statistics.service[ service ]++ : statistics[ type ]++;\r\n                }\r\n                console.log( \"current statistics is \",statistics )\r\n                write();\r\n            });\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Unread list\r\n     * \r\n     * @param {type} include: add remove\r\n     * @param {any} include: object( @see unread ) or index\r\n     * @param {function} callback\r\n     * @param {boolean} include: read & write\r\n     */\r\n    UnRead( type, args, callback, state = \"write\" ) {\r\n        let success = true;\r\n        const write = () => {\r\n                browser.storage.local.set( { [\"unrdist\"] : unrdist }, () => {\r\n                    console.log( \"chrome storage unrdist set success!\", unrdist );\r\n                    callback && callback( success );\r\n                });\r\n            },\r\n            read = cb => {\r\n                browser.storage.local.get( [\"unrdist\"], result => {\r\n                    console.log( \"chrome storage unrdist get success!\", result );\r\n                    result && !$.isEmptyObject( result ) && ( unrdist = result[\"unrdist\"] );\r\n                    cb  && cb();\r\n                    !cb && callback && callback( success );\r\n                });\r\n            };\r\n\r\n        if ( state == \"read\" ) {\r\n            if ( simpread.unrdist.length > 0 ) {\r\n                unrdist = $.extend( true, [], simpread.unrdist );\r\n                simpread.unrdist = [];\r\n                write();\r\n            } else read();\r\n        } else {\r\n            read( () => {\r\n                switch ( type ) {\r\n                    case \"add\":\r\n                        const len = unrdist.length;\r\n                        args.create = now();\r\n                        args.idx = len > 0 ? unrdist[0].idx + 1 : 0;\r\n                        unrdist.findIndex( item => item.url == args.url ) == -1 ?\r\n                            unrdist.splice( 0, 0, args ) : success = false;\r\n                        break;\r\n                    case \"remove\":\r\n                        const idx = unrdist.findIndex( item => item.idx == args );\r\n                        idx != -1 && unrdist.splice( idx, 1 );\r\n                        idx == -1 && ( success = false );\r\n                        break;\r\n                }\r\n                write();\r\n            });\r\n        }\r\n   }\r\n\r\n    /**\r\n     * Verify simpread data structure\r\n     * \r\n     * @param  {object} verify simpread data structure, when undefined, verify self\r\n     * @return {object} option: { code: 0|-1|-2, keys: [ \"bgcolor\", \"layout\" ] }\r\n     *         code: 0: valid success; -1: field name failed; -2: site field name failed;\r\n     */\r\n    Verify( data = undefined ) {\r\n        const pendding = data ? { ...data } : { simpread },\r\n              valid    = ( value, source ) => {\r\n            let result = { code: 0, keys: [] }, target = pendding[ value ];\r\n            if ( Object.keys( target ).length !== Object.keys( source ).length ) {\r\n                result.code = -1;\r\n            } else {\r\n                Object.keys( target ).forEach( key => {\r\n                    if ( !Object.keys( source ).includes( key ) ||\r\n                       ( key != \"sites\" && value != \"option\" && typeof target == \"string\" && target[key] == \"\" )) {\r\n                        result.keys.push( key );\r\n                    }\r\n                    if ( key == \"sites\" ) {\r\n                        target.sites.forEach( items => {\r\n                            const site_keys = Object.keys( items[1] );\r\n                            if ( !site_keys.includes( \"avatar\" ) && site_keys.includes( \"paging\" ) ) {\r\n                                if ( site_keys.length != Object.keys( site ).length ) {\r\n                                    result.code = -2;\r\n                                } else {\r\n                                    site_keys.forEach( key => {\r\n                                        ( !Object.keys( site ).includes( key ) ) && result.keys.push( `site::${key}` );\r\n                                    });\r\n                                }\r\n                            }\r\n                        });\r\n                    }\r\n                });\r\n                result.keys.length > 0 && result.code == 0 && ( result.code = -3 );\r\n            }\r\n            return result;\r\n        }\r\n\r\n        let opt  = valid( \"option\", option ),\r\n            focu = valid( \"focus\",  focus ),\r\n            rd   = valid( \"read\",    read ),\r\n            stat = valid( \"statistics\", statistics );\r\n\r\n        console.log( \"storage.Verify() result \", opt, focu, rd, stat )\r\n        return { option: opt, focus: focu, read: rd, stat: stat };\r\n    }\r\n\r\n    /**\r\n     * Safe set/get, secret not import/export\r\n     * \r\n     * @param {object}   secret\r\n     * @param {function} callback\r\n     */\r\n    Safe( callback, data ) {\r\n        const safesave = obj => {\r\n            if ( secret.version != obj.version ) {\r\n                obj.version = secret.version;\r\n                Object.keys( secret ).forEach( item => {\r\n                    obj[item] == undefined && ( obj[item] = secret[item] );\r\n                });\r\n            }\r\n            return obj;\r\n        };\r\n        if ( data ) {\r\n            secret = { ...data };\r\n            browser.storage.local.set( { [\"secret\"] : secret }, () => {\r\n                console.log( \"chrome storage safe set success!\", secret );\r\n                callback && callback();\r\n            });\r\n        } else {\r\n            if ( br.isFirefox() && window.location.protocol != \"moz-extension:\" ) {\r\n                callback && callback();\r\n            } else {\r\n                browser.storage.local.get( [\"secret\"], result => {\r\n                    console.log( \"chrome storage safe get success!\", result );\r\n                    result && !$.isEmptyObject( result ) && ( secret = safesave( result[\"secret\"] ));\r\n                    callback && callback();\r\n                });\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Notice set/get\r\n     * \r\n     * @param {object}   notice\r\n     * @param {function} callback\r\n     */\r\n    Notice( callback, data ) {\r\n        if ( data ) {\r\n            browser.storage.local.set( { [\"notice\"] : data }, () => {\r\n                console.log( \"chrome storage notice set success!\", data );\r\n                callback && callback();\r\n            });\r\n        } else {\r\n            if ( br.isFirefox() && window.location.protocol != \"moz-extension:\" ) {\r\n                callback && callback();\r\n            } else {\r\n                browser.storage.local.get( [\"notice\"], result => {\r\n                    console.log( \"chrome storage notice get success!\", result );\r\n                    callback && callback( result );\r\n                });\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Plugins set/get, plugins not import/export\r\n     * \r\n     * @param {object}   plugins\r\n     * @param {function} callback\r\n     */\r\n    Plugins( callback, data ) {\r\n        if ( data ) {\r\n            plugins = { ...data };\r\n            browser.storage.local.set( { [\"plugins\"] : plugins }, () => {\r\n                console.log( \"chrome storage plugins set success!\", plugins );\r\n                callback && callback();\r\n            });\r\n        } else {\r\n            browser.storage.local.get( [\"plugins\"], result => {\r\n                console.log( \"chrome storage plugins get success!\", result );\r\n                result && !$.isEmptyObject( result ) && ( plugins = result[\"plugins\"] );\r\n                callback && callback();\r\n            });\r\n        }\r\n    }\r\n\r\n    /**\r\n     * Export\r\n     * \r\n     * @return {string} object json stringify\r\n     */\r\n    Export() {\r\n        const download = {\r\n            version : version,\r\n            option  : { ...this.option },\r\n            focus   : { ...this.focus  },\r\n            read    : { ...this.read   },\r\n            websites: { ...this.websites },\r\n            statistics: { ...this.statistics },\r\n            user    : { ...this.user },\r\n            notice  : { ...this.notice },\r\n            unrdist : this.unrdist,\r\n        };\r\n        this.option.secret && ( download.secret = { ...secret });\r\n        return JSON.stringify( download );\r\n    }\r\n\r\n    /**\r\n     * Restore simpread[key]\r\n     * \r\n     * @param {string} @see mode\r\n     */\r\n    Restore( key ) {\r\n        simpread[key] = clone( origin[key] );\r\n        this.Getcur( key, curori.site );\r\n    }\r\n\r\n    /**\r\n     * Clear simpread data structure\r\n     * \r\n     * @param {string}   include: local remote all\r\n     * @param {function} callback\r\n     */\r\n    Clear( state, callback ) {\r\n        browser.storage.local.clear( callback );\r\n    }\r\n\r\n    /**\r\n     * Fix simpread.read.site only old 1.0.0 / 1.0.1 and 1.1.0\r\n     * \r\n     * @param  {array} changed target\r\n     * @param  {string} old version\r\n     * @param  {string} new version\r\n     * @param  {object} simpread.focus.site\r\n     */\r\n    Fix( target, curver, newver, source ) {\r\n        if ( curver == \"1.0.0\" || curver == \"1.0.1\" ) {\r\n            target.forEach( ( site, idx ) => {\r\n                let url      = site[0],\r\n                    { name } = site[1];\r\n                    for ( let item of simpread.sites ) {\r\n                        if ( name == item[1].name ) {\r\n                            item[1].avatar  && ( site[1].avatar  = item[1].avatar  );\r\n                            item[1].paging  && ( site[1].paging  = item[1].paging  );\r\n                            item[1].include && ( site[1].include = item[1].include );\r\n                            target[idx][1] = { ...item[1] };\r\n                            target[idx][0] = item[0];\r\n                            continue;\r\n                        }\r\n                    }\r\n            });\r\n        }\r\n        if ( newver == \"1.1.0\" ) {\r\n            const map = new Map( target );\r\n            source.forEach( site => {\r\n                map.get( site[0] ) &&\r\n                    ( site[0] = site[0].endsWith( \"*\" ) ? site[0] + \"*\" : site[0] + \"**\" );\r\n                site[1].name == \"\" &&\r\n                    ( site[1].name = \"tempfocus\" );\r\n            });\r\n        }\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Swap source and target property\r\n * \r\n * @param {object} source origin\r\n * @param {object} target origin\r\n */\r\nfunction swap( source, target ) {\r\n    for ( const key of Object.keys( source ) ) {\r\n        if ( ![ \"site\", \"sites\", \"version\", \"url\", \"mode\" ].includes( key ) ) {\r\n            target[key] = source[key];\r\n        }\r\n    }\r\n    return target;\r\n}\r\n\r\n/**\r\n * Deep clone object\r\n * \r\n * @param  {object} target object\r\n * @return {object} new target object\r\n */\r\nfunction clone( target ) {\r\n    return $.extend( true, {}, target );\r\n}\r\n\r\n/**\r\n * Call chrome storage set\r\n * \r\n * @param {function} callback\r\n * @param {object}   when exist no_update = false\r\n */\r\nfunction save( callback, no_update ) {\r\n    !no_update && ( simpread.option.update = now());\r\n    browser.storage.local.set( { [name] : simpread }, function() {\r\n        console.log( \"chrome storage save success!\", simpread );\r\n        origin      = clone( simpread );\r\n        curori      = { ...current };\r\n        curori.site = { ...current.site };\r\n        callback && callback();\r\n    });\r\n}\r\n\r\n/**\r\n * Get now time\r\n * \r\n * @return {string} return now, e.g. 2017年04月03日 11:43:53\r\n */\r\nfunction now() {\r\n    const date   = new Date(),\r\n          format = value => value = value < 10 ? \"0\" + value : value;\r\n    return date.getFullYear() + \"年\" + format( date.getUTCMonth() + 1 ) + \"月\" + format( date.getUTCDate() ) + \"日 \" + format( date.getHours() ) + \":\" + format( date.getMinutes() ) + \":\" + format( date.getSeconds() );\r\n}\r\n\r\n/**\r\n * Get URI from puread/util getURI()\r\n * \r\n * @return {string} e.g. current site url is http://www.cnbeta.com/articles/1234.html return http://www.cnbeta.com/articles/\r\n */\r\nfunction getURI() {\r\n    const name = (pathname) => {\r\n        pathname = pathname != \"/\" && pathname.endsWith(\"/\") ? pathname = pathname.replace( /\\/$/, \"\" ) : pathname;\r\n        return pathname.replace( /\\/[%@#.~a-zA-Z0-9_-]+$|^\\/$/g, \"\" );\r\n    },\r\n    path = name( window.location.pathname );\r\n    return `${ window.location.protocol }//${ window.location.hostname }${ path }/`;\r\n}\r\n\r\nconst storage = new Storage();\r\n\r\nexport {\r\n    storage,\r\n    mode  as STORAGE_MODE,\r\n    clone as Clone,\r\n    now   as Now,\r\n};"
  },
  {
    "path": "src/service/stylesheet.js",
    "content": "console.log( \"=== simpread stylesheet load ===\" )\n\n/**\n * Clone from puread/plugin/stylesheet.js except iconPath\n */\n\nimport {browser} from 'browser';\n\nconst [ bgcolorstyl, bgcls ] = [ \"background-color\", \".simpread-focus-root\" ];\nlet origin_read_style = \"\", html_style_bal = \"-1\";\n\n/**\n * Get chrome extension icon path\n * @param {string} icon name\n */\nfunction iconPath( name ) {\n    return browser.extension.getURL( `assets/images/${name}.png` )\n}\n\n/**\n * Get background color value for focus mode\n * \n * @param  {string} background-color, e.g. rgba(235, 235, 235, 0.901961)\n * @return {string} e.g. 235, 235, 235\n */\nfunction getColor( value ) {\n    const arr = value ? value.match( /[0-9]+, /ig ) : [];\n    if ( arr.length > 0 ) {\n        return arr.join( \"\" ).replace( /, $/, \"\" );\n    } else {\n        return null;\n    }\n};\n\n/**\n * Set focus mode background color for focus mode\n * \n * @param  {string} background color\n * @param  {number} background opacity\n * @return {string} new background color\n */\nfunction backgroundColor( bgcolor, opacity ) {\n    const color   = getColor( bgcolor ),\n          newval  = `rgba(${color}, ${opacity / 100})`;\n    $( bgcls ).css( bgcolorstyl, newval );\n    return newval;\n}\n\n/**\n * Set background opacity for focus mode\n * \n * @param  {string} opacity\n * @return {string} new background color or null\n */\nfunction opacity( opacity ) {\n    const bgcolor = $( bgcls ).css( bgcolorstyl ),\n          color   = getColor( bgcolor ),\n          newval  = `rgba(${color}, ${opacity / 100})`;\n    if ( color ) {\n        $( bgcls ).css( bgcolorstyl, newval );\n        return newval;\n    } else {\n        return null;\n    }\n}\n\n/**\n * Set read mode font family for read mode\n * \n * @param {string} font family name e.g. PingFang SC; Microsoft Yahei\n */\nfunction fontFamily( family ) {\n    $( \"sr-read\" ).css( \"font-family\", family == \"default\" ? \"\" : family );\n}\n\n/**\n * Set read mode font size for read mode\n * \n * @param {string} font size, e.g. 70% 62.5%\n */\nfunction fontSize( value ) {\n    if ( html_style_bal == \"-1\" ) {\n        html_style_bal = $( \"html\" ).attr( \"style\" );\n        html_style_bal == undefined && ( html_style_bal = \"\" );\n    }\n    value ? $( \"html\" ).attr( \"style\", `font-size: ${value}!important;${html_style_bal}` ) : $( \"html\" ).attr( \"style\", html_style_bal );\n}\n\n/**\n * Set read mode layout width for read mode\n * \n * @param {string} layout width\n */\nfunction layout( width ) {\n    $( \"sr-read\" ).css( \"margin\", width ? `20px ${width}` : \"\" );\n}\n\n/**\n * Add custom css to <head> for read mode\n * \n * @param {string} read.custom[type]\n * @param {object} read.custom\n */\nfunction custom( type, props ) {\n    const format = ( name ) => {\n        return name.replace( /[A-Z]/, name => { return `-${name.toLowerCase()}` } );\n    },\n    arr = Object.keys( props ).map( v => {\n        return props[v] && `${format( v )}: ${ props[v] };`\n    });\n    let styles = arr.join( \"\" );\n    switch ( type ) {\n        case \"title\":\n            styles = `sr-rd-title {${styles}}`;\n            break;\n        case \"desc\":\n            styles = `sr-rd-desc {${styles}}`;\n            break;\n        case \"art\":\n            styles = `sr-rd-content *, sr-rd-content p, sr-rd-content div {${styles}}`;\n            break;\n        case \"pre\":\n            styles = `sr-rd-content pre {${styles}}`;\n            break;\n        case \"code\":\n            styles = `sr-rd-content pre code, sr-rd-content pre code * {${styles}}`;\n            break;\n    }\n\n    const $target = $( \"head\" ).find( `style#simpread-custom-${type}` );\n    if ( $target.length == 0 ) {\n        $( \"head\" ).append(`<style type=\"text/css\" id=\"simpread-custom-${type}\">${styles}</style>`);\n    } else {\n        $target.html( styles );\n    }\n\n}\n\n/**\n * Add css to <head> for read mode\n * \n * @param {string} read.custom.css\n * @param {object} read.custom.css value\n */\nfunction css( type, styles ) {\n    const $target = $( \"head\" ).find( `style#simpread-custom-${type}` );\n    if ( $target.length == 0 ) {\n        $( \"head\" ).append(`<style type=\"text/css\" id=\"simpread-custom-${type}\">${styles}</style>`);\n    } else {\n        $target.html( styles );\n    }\n}\n\n/**\n * Add/Remove current site styles( string ) to head for read mdoe\n * \n * @param {string} styles \n */\nfunction siteCSS( styles ) {\n    styles ? $( \"head\" ).append(`<style type=\"text/css\" id=\"simpread-site-css\">${styles}</style>`) :\n             $( \"#simpread-site-css\" ).remove();\n}\n\n/**\n * Add custom to .preview tag\n * \n * @param {object} read.custom\n * @param {string} theme backgroud color\n */\nfunction preview( styles ) {\n    Object.keys( styles ).forEach( v => {\n        v != \"css\" && custom( v, styles[v] );\n    });\n    css( \"css\", styles[\"css\"] );\n}\n\n/**\n * Verify custom is exist\n * \n * @param {string} verify type\n * @param {object} read.custom value\n */\nfunction vfyCustom( type, styles ) {\n    switch( type ) {\n        case \"layout\":\n        case \"margin\":\n        case \"fontfamily\":\n        case \"custom\":\n            return styles.css != \"\";\n        case \"fontsize\":\n            return styles.title.fontSize != \"\" ||\n                   styles.desc.fontSize != \"\"  ||\n                   styles.art.fontSize != \"\"   ||\n                   styles.css != \"\";\n        case \"theme\":\n            return styles.css.search( \"simpread-theme-root\" ) != -1;\n    }\n}\n\nfunction getCustomCSS() {\n    let styles = $( \"style#simpread-site-css\" ).text() || \"\";\n    $( \"head\" ).find( \"style\" ).map( (index, item) => {\n        item.id.startsWith( \"simpread-custom-\" ) && ( styles += item.innerHTML );\n    });\n    return styles;\n}\n\n/**\n * Get special style\n * \n * @param {boolean} mathjax\n * @param {func} callback\n */\nfunction specialCSS( mathjax, callback ) {\n    let css = \"\";\n    if ( mathjax && location.hostname == \"blog.csdn.net\" ) {\n        $.get(\"https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css\", result=> {\n            callback( result );\n        })\n    } else if ( mathjax ) {\n        $( \"head\" ).find( \"style\" ).map( (index, item) => {\n            const $target = $(item),\n                  cls     = $target.attr( \"class\" ),\n                  html    = $target.text();\n            if ( cls == \"simpread-offline-special\" || html.search( \".MathJax\" ) > -1 || html.search( \".mathjax\" ) > -1 || html.search( \".MJX\" ) > -1 ) {\n                css += html;\n            }\n        });\n        callback( css );\n    } else callback( css );\n}\n\nexport {\n    iconPath as IconPath,\n    getColor as GetColor,\n    backgroundColor as BgColor,\n    opacity    as Opacity,\n    fontFamily as FontFamily,\n    fontSize   as FontSize,\n    layout     as Layout,\n    siteCSS    as SiteCSS,\n    preview    as Preview,\n    custom     as Custom,\n    css        as CSS,\n    vfyCustom  as VerifyCustom,\n    getCustomCSS as GetCustomCSS,\n    specialCSS as SpecialCSS,\n}"
  },
  {
    "path": "src/service/theme.js",
    "content": "console.log( \"=== simpread theme load ===\" )\r\n\r\nconst names  = [ \"github\", \"newsprint\", \"gothic\", \"engwrite\", \"octopress\", \"pixyii\", \"monospace\", \"night\", \"dark\" ],\r\n      flag   = \"sr-rd-theme-\",\r\n      themes = {},\r\n      colors = [\r\n        \"251, 251, 251, 1\",\r\n        \"243, 242, 238, 1\",\r\n        \"252, 252, 252, 1\",\r\n        \"252, 245, 237, 1\",\r\n        \"248, 248, 248, 1\",\r\n        \"250, 250, 250, 1\",\r\n        \"245, 245, 245, 1\",\r\n        \"54,  59,  64,  1\",\r\n        \"34,  34,  34,  1\"\r\n       ];\r\n\r\nlet curtheme = \"\";\r\n\r\n/**\r\n * Theme class\r\n * \r\n * @class\r\n */\r\n\r\nclass Theme {\r\n\r\n    /**\r\n     * Theme colors[read]\r\n     * \r\n     * @return {array} theme colors\r\n     */\r\n    get colors() {\r\n        return  colors;\r\n    }\r\n\r\n    /**\r\n     * Theme names[read]\r\n     * \r\n     * @return {array} theme name array\r\n     */\r\n    get names() {\r\n        return names;\r\n    }\r\n\r\n    /**\r\n     * Current theme name[read]\r\n     * \r\n     * @return {string} theme name\r\n     */\r\n    get theme() {\r\n        return curtheme;\r\n    }\r\n\r\n    /**\r\n     * Change theme\r\n     * \r\n     * @param {string} theme name\r\n     */\r\n    Change( theme ) {\r\n        curtheme = theme;\r\n        findThemeStyle( function( name, css, $target ) {\r\n            if ( name == theme )  $target.html( themes[theme] );\r\n            else                  $target.html( `${flag}${name}` + \"{}\" );\r\n        });\r\n        tocTheme( theme );\r\n    }\r\n\r\n    Get( theme ) {\r\n        return themes[theme];\r\n    }\r\n\r\n    GetAll() {\r\n        findThemeStyle();\r\n    }\r\n\r\n    constructor() {\r\n        require( `../assets/css/theme_common.css` );\r\n        names.forEach( name => require( `../assets/css/theme_${name}.css` ) );\r\n        require( `../assets/css/theme_mobile.css` );\r\n        findThemeStyle( ( name, content ) => themes[name] = content );\r\n    }\r\n}\r\n\r\n/**\r\n * Find theme style tag\r\n * \r\n * @return {function} param1: theme name; param2: theme css; param3: theme style tag jquery object\r\n */\r\nfunction findThemeStyle( callback ) {\r\n    $( \"head\" ).find( \"style\" ).map( (index, item) => {\r\n        const $target = $(item),\r\n              css     = $target.text();\r\n        if ( css.startsWith( flag ) ) {\r\n            const arr  = css.replace( flag, \"\" ).match( /\\w+/ ),\r\n                  name = arr[ arr.length - 1 ];\r\n            callback && callback( name, css, $target );\r\n        } else if ( css.search( \".simpread-font\" ) > -1 ) {\r\n            !themes[\"global\"] && ( themes[\"global\"] = css );\r\n        } else if ( css.search( \"(pointer:coarse)\" ) == -1 && css.search( \".simpread-theme-root\" ) > -1 ) {\r\n            !themes[\"common\"] && ( themes[\"common\"] = css );\r\n        } else if ( css.search( \"(pointer:coarse)\" ) > -1 && css.search( \"sr-read\" ) > -1 ) {\r\n            !themes[\"mobile\"] && ( themes[\"mobile\"] = css );\r\n        }\r\n    });\r\n}\r\n\r\n/**\r\n * Change toc <a> style( hacked )\r\n * \r\n * @param {string} theme\r\n */\r\nfunction tocTheme( theme ) {\r\n    $( \"toc outline a\" ).removeClass().addClass( \"toc-outline-theme-\" + theme );\r\n}\r\n\r\nconst theme = new Theme();\r\n\r\nexport default theme;"
  },
  {
    "path": "src/service/tips.js",
    "content": "console.log( \"=== simpread tips load ===\" )\n\nimport Notify      from 'notify';\nimport * as msg    from 'message';\nimport {browser}   from 'browser';\n\n/**\n * Verify current page and some plugin exist\n * \n * @param {array} plugins ids\n */\nfunction Render( plugins ) {\n    const notify = code => {\n        new Notify().Render( messages[code], \"不再提示\", () => {\n            browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.tips_norepeat, { code }));\n        });\n    },\n    pushMessage = code => {\n        browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.tips, { code }), result => {\n            if ( result ) {\n                notify( code );\n            }\n        });\n    },\n    messages = {\n        \"klGUASLasg\": '检测到当前环境有代码段，请使用      <a target=\"blank\" href=\"https://simpread.ksria.cn/plugins/details/klGUASLasg\">代码增强插件</a> 辅助阅读',\n        \"VQOZdNET2d\": '检测到当前环境有大量的图片，可以使用 <a target=\"blank\" href=\"https://simpread.ksria.cn/plugins/details/VQOZdNET2d\">点击查看大图</a> 辅助阅读',\n        \"DxlFcL52iy\": '如果你想换个论坛类页面风格，可以使用 <a target=\"blank\" href=\"https://simpread.ksria.cn/plugins/details/DxlFcL52iy\">Materail Design 风格</a> 辅助阅读',\n    }\n    // verify klGUASLasg\n    if ( $( \"sr-read\" ).find( \"pre\" ).length > 0 && plugins.findIndex( item => item == \"klGUASLasg\" ) == -1 ) {\n        pushMessage( \"klGUASLasg\" );\n    }\n    // verify VQOZdNET2d\n    if ( $( \"sr-read\" ).find( \"img\" ).length > 5 && plugins.findIndex( item => item == \"VQOZdNET2d\" ) == -1 ) {\n        pushMessage( \"VQOZdNET2d\" );\n    }\n    // verify DxlFcL52iy\n    if ( ( location.hostname == \"www.zhihu.com\" && location.pathname.startsWith( \"/question/\" ) ) && plugins.findIndex( item => item == \"DxlFcL52iy\" ) == -1 ) {\n        pushMessage( \"DxlFcL52iy\" );\n    }\n}\n\n/**\n * Help\n * \n * @param {object} storage.statistics\n */\nfunction Help( statistics ) {\n    if ( statistics.read == 1 ) {\n        new Notify().Render({ content: \"嗨，很高兴第一次使用简悦的阅读模式，是否看看新手帮助？\", action: \"我要看\", cancel: \"老司机\", callback: type => {\n            type == \"action\" && browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.new_tab, { url: \"http://ksria.com/simpread/guide/#readmode\" }));\n            type == \"cancel\" && browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.tips_norepeat, { code: \"readmode\" }));\n        }});\n    }\n}\n\n/**\n * Background call\n * \n * @param {string} plugin id\n * @param {func} callback true: tips; false: not tip\n */\nfunction Verify( id, callback ) {\n    const tips = JSON.parse( localStorage[ \"simpread-tips\" ] || \"{}\" );\n    if ( !tips[id] ) {\n        callback( true );\n    } else callback( false );\n}\n\n/**\n * Not repeat tips\n * \n * @param {string} tips id\n */\nfunction Done( id ) {\n    const tips = JSON.parse( localStorage[ \"simpread-tips\" ] || \"{}\" );\n    tips[id]   = true;\n    localStorage.setItem( \"simpread-tips\", JSON.stringify( tips ));\n}\n\nexport {\n    Render,\n    Verify,\n    Done,\n    Help,\n}"
  },
  {
    "path": "src/service/util.js",
    "content": "console.log( \"=== simpread util load ===\" )\n\n/**\n * Verify html from puread/util verifyHtml()\n * \n * @param  {string} input include html tag, e.g.:\n    <div class=\"article fmt article__content\">\n *\n * @return {array} 0: int include ( -1: fail； 0: empty html; 1: success; 2: special tag )\n *                 1: result\n */\nfunction verifyHtml( html ) {\n    if ( html == \"\" ) return [ 0, html ];\n    else if ( specTest( html )) return [ 2, html ];\n    const item = html.match( /<\\S+ (class|id)=(\"|')?[\\w-_=;:' ]+(\"|')?>?$|<[^/][-_a-zA-Z0-9]+>?$/ig );\n    if ( item && item.length > 0 ) {\n        return [ 1, item ];\n    } else {\n        return [ -1, undefined ];\n    }\n}\n\n/**\n * Verify special action from puread/util specTest()\n * action include:\n   - [[{juqery code}]] // new Function, e.g. $(\"xxx\").xxx() return string\n   - [['text']]        // remove '<text>'\n   - [[/regexp/]]      // regexp e.g. $(\"sr-rd-content\").find( \"*[src='http://ifanr-cdn.b0.upaiyun.com/wp-content/uploads/2016/09/AppSo-qrcode-signature.jpg']\" )\n   - [[[juqery code]]] // new Function, e.g. $(\"xxx\").find() return jquery object\n   - [[`xpath`]]       // /html[1]/div[1]/sr-read[1]/sr-rd-content[1]/p[1]\n\n * \n * @param  {string} verify content\n * @return {boolen} verify result\n */\nfunction specTest( content ) {\n    return /^(\\[\\[)[\\[{`'/]{1}[ \\S]+[}`'/\\]]\\]\\]{1}($)/g.test( content );\n}\n\n/**\n * Html convert to enml\n * \n * @param  {string} convert string\n * @param  {string} url\n * \n * @return {string} convert string\n */\nfunction html2enml( html, url ) {\n    let $target, str;\n    const bad  = [ \"sup\", \"hr\", \"section\", \"applet\", \"base\", \"basefont\", \"bgsound\", \"blink\", \"body\", \"button\", \"dir\", \"embed\", \"fieldset\", \"form\", \"frame\", \"frameset\", \"head\", \"html\", \"iframe\", \"ilayer\", \"input\", \"isindex\", \"label\", \"layer\", \"legend\", \"link\", \"marquee\", \"menu\", \"meta\", \"noframes\", \"noscript\", \"object\", \"optgroup\", \"option\", \"param\", \"plaintext\", \"script\", \"select\", \"style\", \"textarea\", \"xml\" ],\n          good = [ \"a\", \"abbr\", \"acronym\", \"address\", \"area\", \"b\", \"bdo\", \"big\", \"blockquote\", \"br\", \"caption\", \"center\", \"cite\", \"code\", \"col\", \"colgroup\", \"dd\", \"del\", \"dfn\", \"div\", \"dl\", \"dt\", \"em\", \"font\", \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\", \"hr\", \"i\", \"img\", \"ins\", \"kbd\", \"li\", \"map\", \"ol\", \"p\", \"pre\", \"q\", \"s\", \"samp\", \"small\", \"span\", \"strike\", \"strong\", \"sub\", \"sup\", \"table\", \"tbody\", \"td\", \"tfoot\", \"th\", \"thead\", \"title\", \"tr\", \"tt\", \"u\", \"ul\", \"var\", \"xmp\"];\n\n    $( \"html\" ).append( `<div id=\"simpread-en\" style=\"display: none;\">${html}</div>` );\n    $target = $( \"#simpread-en\" );\n    $target.find( \"img\" ).map( ( index, item ) => {\n        $( '<simpread-img></simpread-img>' ).attr({ src: item.src, style: \"max-width:100%;height:auto;\" }).replaceAll( $(item) );\n    });\n    $target.find( bad.join( \",\" ) ).remove();\n    // remove element all atrr\n    $target.find( \"*\" ).map( ( index, item ) => {\n        const tag = item.tagName.toLowerCase();\n        if ( tag.startsWith( \"sr\" ) && /sr-\\S[^>]+/ig.test( tag )) {\n            $(item).remove();\n        }\n        else if ( item.attributes.length > 0 ) {\n            for ( let i = item.attributes.length - 1; i >= 0; i-- ) {\n                const name = item.attributes[i].name;\n                if ( tag == \"a\" && name == \"href\" ) {\n                    let value = item.attributes[i].value;\n                    value.startsWith( \"//\" ) && ( item.attributes[i].value += location.protocol );\n                    continue;\n                } else if ( tag == \"simpread-img\" ) {\n                    continue;\n                }\n                item.removeAttribute( name )\n            }\n        }\n    });\n    str = $target.html();\n    $target.remove();\n\n    const origin   = document.createElement( 'a' );\n    origin.href    = url;\n\n    try {\n        const href = url.indexOf(\"chksm\") > 0 ? \"\" : `，原文地址 <a href=\"${url}\" target=\"_blank\">${origin.host}</a>`;\n        str = `<blockquote>本文由 <a href=\"http://ksria.com/simpread\" target=\"_blank\">简悦 SimpRead</a> 转码${href}</blockquote><hr></hr><br></br>` + str;\n        str = str.replace( /(id|class|onclick|ondblclick|accesskey|data|dynsrc|tabindex|name)=\"[\\S ][^\"]*\"/ig, \"\" )\n                //.replace( / style=[ \\w=\"-:\\/\\/:#;]+/ig, \"\" )             // style=\"xxxx\"\n                .replace( /label=[\\u4e00-\\u9fa5 \\w=\"-:\\/\\/:#;]+\"/ig, \"\" )  // label=\"xxxx\"\n                .replace( / finallycleanhtml=[\\u4e00-\\u9fa5 \\w=\"-:\\/\\/:#;]+\"/ig, \"\" )  // finallycleanhtml=\"xxxx\"\n                //.replace( /<img[ \\w=\"-:\\/\\/?!]+>/ig, \"\" )                // <img>\n                .replace( /<simpread-img/ig, \"<img\" )                      // <simpread-img>  → <img>\n                .replace( /<\\/simpread-img>/ig, \"</img>\" )                 // </simpread-img> → </img>\n                .replace( /data[-\\w]*=[ \\w=\\-.:\\/\\/?!;+\"]+\"[ ]?/ig, \"\" )   // data=\"xxx\" || data-xxx=\"xxx\"\n                .replace( /href=\"javascript:[\\w()\"]+/ig, \"\" )              // href=\"javascript:xxx\"\n                .replace( /sr-blockquote/ig, \"blockquote\" )                // sr-blockquote to blockquote\n                .replace( /<p[ -\\w*= \\w=\\-.:\\/\\/?!;+\"]*>/ig, \"\" )          // <p> || <p > || <p xxx=\"xxx\">\n                //.replace( /<figcaption[ -\\w*= \\w=\\-.:\\/\\/?!;+\"]*>/ig, \"\" ) // <figcaption >\n                //.replace( /<\\/figcaption>/ig, \"\" )                        // </figcaption>\n                .replace( /<(figcaption|figure)/ig, \"<div\" )               // <figcaption|figure>  → <div>\n                .replace( /<\\/(figcaption|figure)>/ig, \"</div>\" )          // </figcaption|figure> → </div>\n                .replace( /<\\/br>/ig, \"\" )                                 // </br>\n                .replace( /<br>/ig, \"<br></br>\" )\n                .replace( / >/ig, \">\" )\n                .replace( /<\\/p>/ig, \"<br></br>\" );\n\n        return str;\n\n    } catch( error ) {\n        return `<div>转换失败，原文地址 <a href=\"${url}\" target=\"_blank\">${url}</a></div>`\n    }\n}\n\n/**\n * Markdown to ENML\n * \n * @param {string} str\n * @return {string} format str\n */\nfunction md2enml( result ) {\n    result = result.replace( /</ig, \"&lt;\" ).replace( />/ig, \"&gt;\" );\n    let str = \"\";\n    result.split( \"\\n\" ).forEach( item => str += `<div>${item}</div>` );\n    return str;\n}\n\n/**\n * Multi to ENML\n * \n * @param {string} str\n * @return {string} format str\n */\nfunction multi2enml( str ) {\n    return str.replace( / data-\\S+\">/ig, \">\" )\n              .replace( /sr-[\\w-]+/ig, \"div\" )\n              .replace( /dangerouslysetinnerhtml=\"\\[object Object\\]\"/ig, \"\" );\n}\n\n/**\n * Clear Html to MD, erorr <tag>\n * \n * @param {string} convert string\n * @param {boolen} header\n * \n * @return {string} format string\n */\nfunction clearMD( str, header = true ) {\n    const origin    = document.createElement( 'a' );\n    origin.href     = window.location.href;\n    header && ( str = `> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码， 原文地址 [${ origin.host }](${ window.location.href }) \\r\\n\\r\\n ${str}` );\n    str = str.replace( /<\\/?(ins|font|span|div|canvas|noscript|fig\\w+)[ -\\w*= \\w=\\-.:&\\/\\/?!;,%+()#'\"{}\\u4e00-\\u9fa5]*>/ig, \"\" )\n             .replace( /sr-blockquote/ig, \"blockquote\" )\n             .replace( /<\\/?style[ -\\w*= \\w=\\-.:&\\/\\/?!;,+()#\"\\S]*>/ig, \"\" )\n             .replace( /(name|lable)=[\\u4e00-\\u9fa5 \\w=\"-:\\/\\/:#;]+\"/ig, \"\" )\n             return str;\n}\n\n/**\n * Clean HTML\n * \n * @param {string} str\n * @return {string} optimze str\n */\nfunction clearHTML( str ) {\n    const host = url => {\n            const origin    = document.createElement( 'a' );\n            origin.href     = url;\n            return origin.host;\n          },\n          url  = location.href,\n          href = url.indexOf(\"chksm\") > 0 || url.indexOf(\"#\") > 0 ? \"\" : `，原文地址 <a href=\"${url}\" target=\"_blank\">${host( url )}</a>`;\n    str = `<blockquote>本文由 <a href=\"http://ksria.com/simpread\" target=\"_blank\">简悦 SimpRead</a> 转码${href}</blockquote><hr></hr><br></br>` + str;\n    str = str.replace( /(id|class|onclick|ondblclick|accesskey|data|dynsrc|tabindex|name)=\"[\\S ][^\"]*\"/ig, \"\" )\n             .replace( /&/ig, \"&amp;\" )\n    return str;\n}\n\n/**\n * Exclusion\n * \n * @param  {object} minimatch\n * @param  {object} simpread.read\n * @return {boolen} true: exist; false: not exist\n */\nfunction exclusion( minimatch, data ) {\n    const url = window.location.origin + window.location.pathname;\n    return data.exclusion.findIndex( item => {\n        item == null && ( item = \"\" );\n        item = item.trim();\n        if ( item.startsWith( \"[[/\" ) && item.endsWith( \"/]]\" ) ) {\n            return location.href.replace( new RegExp( item.replace( /\\[\\[\\/|\\/\\]\\]/ig, \"\" ), \"g\" ), \"\" ) == \"\" ? true : false;\n        } else return item.startsWith( \"http\" ) ? minimatch( url, item ) : item == data.site.name;\n    }) != -1 ? true : false;\n}\n\n/**\n * Whitelist\n * \n * @param  {object} minimatch\n * @param  {object} simpread.read\n * @return {boolean} \n */\nfunction whitelist( minimatch, data ) {\n    const url = window.location.origin + window.location.pathname;\n    return data.whitelist.findIndex( item => {\n        item == null && ( item = \"\" );\n        item = item.trim();\n        if ( item.startsWith( \"[[/\" ) && item.endsWith( \"/]]\" ) ) {\n            return location.href.replace( new RegExp( item.replace( /\\[\\[\\/|\\/\\]\\]/ig, \"\" ), \"g\" ), \"\" ) == \"\" ? true : false;\n        } else return item.startsWith( \"http\" ) ? minimatch( url, item ) : item == data.site.name;\n    }) != -1 ? true : false;\n}\n\n/**\n * Blacklist\n * \n * @param  {object} minimatch\n * @param  {object} simpread.read\n * @return {boolean} true: is blacklist; false: is't blacklist\n */\nfunction blacklist( minimatch, data ) {\n    return data.blacklist.findIndex( item => {\n       item == null && ( item = \"\" );\n       item = item.trim();\n       if ( item.startsWith( \"[[/\" ) && item.endsWith( \"/]]\" ) ) {\n           return location.href.replace( new RegExp( item.replace( /\\[\\[\\/|\\/\\]\\]/ig, \"\" ), \"g\" ), \"\" ) == \"\" ? true : false;\n       } else return item.startsWith( \"http\" ) ? minimatch( location.href, item ) : location.hostname.includes( item );\n    }) != -1 ? true : false;\n}\n\n/**\n * Lazyload\n * \n * @param  {object} minimatch\n * @param  {object} simpread.read\n * @return {boolean} true: is blacklist; false: is't blacklist\n */\nfunction lazyload( minimatch, data ) {\n    return data.lazyload.findIndex( item => {\n       item == null && ( item = \"\" );\n       item = item.trim();\n       if ( item.startsWith( \"[[/\" ) && item.endsWith( \"/]]\" ) ) {\n           return location.href.replace( new RegExp( item.replace( /\\[\\[\\/|\\/\\]\\]/ig, \"\" ), \"g\" ), \"\" ) == \"\" ? true : false;\n       } else return item.startsWith( \"http\" ) ? minimatch( location.href, item ) : location.hostname.includes( item );\n    }) != -1 ? true : false;\n}\n\n/**\n * Get page info\n * \n * @return {object} include: url, title, favicon, img, desc\n */\nfunction getPageInfo() {\n    const url     = location.href,\n          title   = $( \"sr-read\" ).find( \"sr-rd-title\" ).text() || $( \"head\" ).find( \"title\" ).text() || \"\",\n          favicon = $( `head link[rel~=icon]` ).attr( \"href\" ) || \"\",\n          img     = $( `head meta[property=\"og:image\"]` ).attr( \"content\" ) || $( \"sr-read\" ).find( \"img\" ).attr( \"src\" ) || \"\",\n          desc    = $( \"sr-read\" ).find( \"sr-rd-desc\" ).text() || $( `head meta[property=\"og:description\"]` ).attr( \"content\" ) || $( 'meta[name=description]' ).attr( 'content' ) || \"\";\n    return { url, title: title.trim(), favicon, img, desc: desc.trim() };\n}\n\nexport {\n    verifyHtml     as verifyHtml,\n    html2enml      as HTML2ENML,\n    md2enml        as MD2ENML,\n    multi2enml     as MULTI2ENML,\n    clearMD        as ClearMD,\n    clearHTML      as ClearHTML,\n    exclusion      as Exclusion,\n    whitelist      as Whitelist,\n    blacklist      as Blacklist,\n    lazyload       as Lazyload,\n    getPageInfo    as GetPageInfo,\n}"
  },
  {
    "path": "src/service/version.js",
    "content": "console.log( \"=== simpread version load ===\" )\r\n\r\nimport {browser} from 'browser';\r\n\r\n/**\r\n * Manifest.json version\r\n */\r\nconst version  = browser.runtime.getManifest().version.replace( /.\\d{2,}/, \"\" ),           // get x.x.x,\r\n      sub_ver  = browser.runtime.getManifest().version.replace( /(\\d{1,2}.){2}\\d.?/, \"\" ), // get *.*.*.xxxx\r\n      versions = new Map([\r\n          [ \"1.0.0\", \"Sun Jun 11 2017 12:30:00 GMT+0800 (CST)\" ],\r\n          [ \"1.0.1\", \"Fri Jun 30 2017 09:27:18 GMT+0800 (CST)\" ],\r\n          [ \"1.0.2\", \"Mon Aug 07 2017 19:03:50 GMT+0800 (CST)\" ],\r\n          [ \"1.0.3\", \"Mon Aug 21 2017 04:09:23 GMT+0800 (CST)\" ],\r\n          [ \"1.0.4\", \"Mon Sep 25 2017 14:40:27 GMT+0800 (CST)\" ],\r\n          [ \"1.0.5\", \"Wed Nov 15 2017 11:39:23 GMT+0800 (CST)\" ],\r\n          [ \"1.0.6\", \"Thu Dec 07 2017 14:48:44 GMT+0800 (CST)\" ],\r\n          [ \"1.1.0\", \"Sat Dec 23 2017 15:09:30 GMT+0800 (CST)\" ],\r\n          [ \"1.1.1\", \"Mon Jun 11 2018 15:10:12 GMT+0800 (CST)\" ],\r\n          [ \"1.1.2\", \"Tue Jun 19 2018 14:15:12 GMT+0800 (CST)\" ],\r\n          [ \"1.1.3\", \"Thu Jun 06 2019 15:47:44 GMT+0800 (CST)\" ],\r\n          [ \"1.1.4\", \"Thu Jan 16 2020 14:24:53 GMT+0800 (CST)\" ],\r\n      ]),\r\n      details = new Map([\r\n          [ \"1.0.0\", \"\" ],\r\n          [ \"1.0.1\", \"新增「高级设定」选项页，\" ],\r\n          [ \"1.0.2\", \"新增「自定义样式，论坛类页面与分页功能」，\" ],\r\n          [ \"1.0.3\", \"新增「导出到生产力工具，发送到 Kindle，自定义样式，论坛类页面，分页等」，\" ],\r\n          [ \"1.0.4\", \"新增「高级聚焦模式、主动适配与临时阅读模式」，\" ],\r\n          [ \"1.0.5\", \"新增「导出 epub，TXT 阅读器，阅读模式增加目录功能，白名单等」，\" ],\r\n          [ \"1.0.6\", \"新增「添加新站到阅读模式，导入第三方适配站点等」，\" ],\r\n          [ \"1.1.0\", \"新增「站点编辑器，站点适配源，站点管理器等」，\" ],\r\n          [ \"1.1.1\", \"新增「黑名单，全新的控制栏面板，更丰富的中文定制化，无障碍阅读等」，\" ],\r\n          [ \"1.1.2\", \"新增「插件中心，站点集市等」，\" ],\r\n          [ \"1.1.3\", \"新增「消息中心，帮助中心，入门指引，支持导入语雀 / 坚果云，预加载机制，增强插件 API 等」，\" ],\r\n          [ \"1.1.4\", \"新增「反馈中心，支持导入 Notion, 有道笔记，为知笔记，离线下载，截图 等」，\" ],\r\n    ]),\r\n    patchs = new Map([\r\n        [ \"1.1.4.6016\", \"修复 Notion 相关问题，并支持 Database 导出方案，\" ],\r\n        [ \"1.1.4.6022\", \"修复 Notion 授权问题，并支持 图床 导出方案，\" ],\r\n        [ \"1.1.4.6025\", \"修复 Notion 授权问题，\" ],\r\n  ]),\r\n    tips      = {\r\n        \"root\"  : value => `.version-tips[data-hits='${value}']`,\r\n        \"1.1.4\" : {\r\n            target: 'labs',\r\n            idx: 2,\r\n            items: [\r\n                {\r\n                    id: '',\r\n                    intro: '简悦 1.1.4 功能描述：<br>' + details.get( \"1.1.4\" ) + '详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/welcome/version_1.1.4.html\">请看这里</a> 。' ,\r\n                },\r\n                {\r\n                    id: 'lazyload',\r\n                    intro: '现在可通过右键菜单发送「延迟加载」了',\r\n                },\r\n                {\r\n                    id: 'urlscheme',\r\n                    intro: '【黑名单 · 白名单 · 排除列表 · 延迟加载】加入 <b>正则表达式</b> 的方式，同时也新增加了 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/右键菜单?id=URL编辑器\">URL 编辑器</a>。',\r\n                },\r\n                {\r\n                    id: 'notion',\r\n                    intro: '简悦支持导出 Markdown 格式到 Notion，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/Notion\">请看这里</a>',\r\n                },\r\n                {\r\n                    id: 'youdao',\r\n                    intro: '简悦支持导出 Markdown 格式到 有道云笔记，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/有道云笔记\">请看这里</a>',\r\n                },\r\n                {\r\n                    id: 'weizhi',\r\n                    intro: '简悦支持导出 HTML 格式到 为知笔记，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/为知笔记\">请看这里</a>',\r\n                },\r\n                {\r\n                    id: 'webdav',\r\n                    intro: 'WebDAV 增加了导出格式的定制化，包括 <span>Markdown</span> <span>HTML</span>，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/WebDAV?id=定制\">请看这里</a>',\r\n                }\r\n            ]\r\n        },\r\n        \"1.1.3\" : {\r\n            target: 'labs',\r\n            idx: 2,\r\n            items: [\r\n                {\r\n                    id: '',\r\n                    intro: '简悦 1.1.3 功能描述：<br>' + details.get( \"1.1.3\" ) + '详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/welcome/version_1.1.3.html\">请看这里</a> 。' ,\r\n                },\r\n                {\r\n                    id: 'save_at',\r\n                    intro: '从现在开始可以将配置文件保存到坚果云了，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/坚果云\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'preload',\r\n                    intro: '简悦的词法分析引擎采用了预加载机制，当系统性能吃紧时，可以选择关闭此功能，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=预加载机制\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'lazyload',\r\n                    intro: '此功能适合 <b>经常使用简悦但又性能不够</b> 的用户；需要动态加载的页面；支持 Mathjax 解析的页面等，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=预加载机制\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'jianguo',\r\n                    intro: '你可以在这里输入坚果云的用户名和授权的密码来绑定坚果云，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/坚果云\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'yuque',\r\n                    intro: '连接你的语雀帐号后，就可使用导出到语雀的服务了，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/授权服务\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'webdav',\r\n                    intro: '导出服务 <b>任意支持 WebDAV 协议</b> 了，从现在开始使用你熟悉的网盘吧，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/WebDAV\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'notice',\r\n                    intro: '简悦 1.1.3 版增加了消息中心，为了方便查看简悦的一些最新消息，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/消息中心\">请看这里</a> 。',\r\n                }\r\n            ]\r\n        },\r\n        \"common\" : {\r\n            target: 'common',\r\n            idx: 0,\r\n            items: [\r\n                {\r\n                    id: 'sync',\r\n                    intro: '简悦支持导出配置文件到 Dropbox 或 坚果云，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/同步\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'config',\r\n                    intro: '从 <b>本地导入配置文件</b> 或 <b>导出配置文件到本地</b> 。<br>注意：简悦支持导入任意版本的配置文件，但请尽量上传匹配版本的配置文件。',\r\n                },\r\n                {\r\n                    id: 'oldnewsites',\r\n                    intro: '从 1.1.3 开始，此功能转移到 <b>站点管理</b> 选项卡里面，此功能已废除。',\r\n                },\r\n                {\r\n                    id: 'clear',\r\n                    intro: '清除简悦产生的全部数据，等同于重新安装，慎用！使用前 <b>请先备份</b> 。',\r\n                }\r\n            ]\r\n        },\r\n        \"simple\" : {\r\n            target: 'simple',\r\n            idx: 1,\r\n            items: [\r\n                {\r\n                    id: 'focusmode',\r\n                    intro: '使用 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/聚焦模式\">聚焦模式</a> 时的选项<br>包括：遮罩的主题色，遮罩的透明度，以及进入聚焦模式的快捷键。<br>这些功能也可以在进入此模式后通过右下角控制栏调整。',\r\n                },\r\n                {\r\n                    id: 'readmode',\r\n                    intro: '使用 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/阅读模式\">阅读模式</a> 时的选项<br>包括：主题色，进入阅读模式的快捷键，字体类型，版面布局，甚至正文的字体细调（字间距，行间距等）。<br>这些功能也可以在进入此模式后通过右下角控制栏调整。',\r\n                }\r\n            ]\r\n        },\r\n        \"labs\" : {\r\n            target: 'labs',\r\n            idx: 2,\r\n            items: [\r\n                {\r\n                    id: '',\r\n                    intro: '本页的功能专门针对 <b>不同需求\b、不同使用场景</b> 的精细调整。<br>如果你是初级用户的话，完全可以无视这些调整，简悦支持 <b>开箱即用</b>。<br>如果想让阅读模式更具个性化，建议花 1 ~ 2 分钟来看下这些功能点。 😊 ' ,\r\n                },\r\n                {\r\n                    id: 'esc',\r\n                    intro: '启用此功能后，进入阅读模式 & 聚焦模式，可通过点击 ESC 的方式退出。',\r\n                },\r\n                {\r\n                    id: 'br_exit',\r\n                    intro: '点击浏览器右上角 <b>简悦 icon</b> 后的动作，包括：退出当前模式 & 弹出设置对话框。',\r\n                },\r\n                {\r\n                    id: 'blacklist',\r\n                    intro: '加入到列表中的 URL 对应的页面将不会运行简悦，适合一些完全不需要简悦的场合，如：视频类的网站。<br>支持绝对地址或主域名，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/FAQ?id=黑名单\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'save_at',\r\n                    intro: '从现在开始可以将配置文件保存到坚果云了，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/坚果云\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'menu',\r\n                    intro: '简悦支持右键菜单，如果你是个鼠标党的话，可以好好利用它们，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/右键菜单\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'focusconfig',\r\n                    intro: '与 <b>基础设定</b> 中不同，这里是关于聚焦模式细节的设定，同时这些选项也只能在选项页中修改。',\r\n                },\r\n                {\r\n                    id: 'readconfig',\r\n                    intro: '与 <b>基础设定</b> 中不同，这里是关于阅读模式细节的设定，同时这些选项也只能在选项页中修改。<br><br> <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/阅读模式\">阅读模式</a> 是简悦重要的组成部分，除了常规的阅读模式外，简悦还支持多种类型，包括：<br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/论坛类页面及分页\">论坛类页面及分页</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/主动适配阅读模式\">主动适配</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=智能感知\">智能感知</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选\">手动框选</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/TXT-阅读器\">TXT 阅读器</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=markdown-识别\">Markdown 阅读器</a> <br> - <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=latex-识别\">LaTeX 阅读器</a>',\r\n                },\r\n                {\r\n                    id: 'progress',\r\n                    intro: '进入阅读模式后会在页面上方显示一个阅读进度条，从 1.1.3 版开始 <b>默认为不启用</b>，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/阅读进度\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'readcontrolbar',\r\n                    intro: '进入阅读模式后，会在页面的右下角显示一个 icon 点击可查看阅读模式的一些功能，你可以在这里选择隐藏（鼠标移上时才显示）它。',\r\n                },\r\n                {\r\n                    id: 'fap',\r\n                    intro: '1.1.1 版开始提供 <b>控制栏浮动面板</b> 用来替代原来的 <b>控制栏浮动工具条</b>。<br>如果你并不经常使用简悦的一些高级功能，可以关闭此选项，使用更简洁的 <b>控制栏浮动工具条</b>，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/阅读模式-控制栏\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'highlight',\r\n                    intro: '在 <b>手动框选</b> 方式的基础上增加了 <b>二次确认模式</b>，此模式专门针对页面极其复杂的情况，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/手动框选\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'toc',\r\n                    intro: '进入阅读模式后，会自动生成当前页面的大纲，同时也可选择大纲的显示方式，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/目录\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'readauto',\r\n                    intro: '如果当前 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点适配源\">站点已适配</a> 的话，启用此选项后会自动进入到阅读模式，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/适配站点\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'exclusion',\r\n                    intro: '启用 <b>自动进入阅读模式</b> 后，可将不需要自动进入阅读模式的站加入到这个列表中，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/FAQ?id=排除列表\">请看这里</a> 。<br>关闭 <b>自动进入阅读模式</b> 后，会有 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/FAQ?id=白名单\">白名单</a> 功能，与 <b>排除列表</b> 相反，加入此的站会自动进入阅读模式。',\r\n                },\r\n                {\r\n                    id: 'pured',\r\n                    intro: '简悦从 1.1.2.5005 开始增加了此功能，目前还处于测试版。<br>词法分析引擎会对版面重新设计，包括：去除多余空格、优化版面结构等。<br>注意：经常解析失败时，请关闭此功能，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'puredpure',\r\n                    intro: '包括：字形、颜色、字号、代码段等，如：微信订阅号，CSDN 等。<br>注意：如果经常阅读代码的话，请安装 <a target=\"_blank\" href=\"https://simpread.ksria.cn/plugins/details/klGUASLasg\">代码段增强</a> 插件，功能包括：高亮，去重，支持 CSDN 等特殊情况的代码段。',\r\n                },\r\n                {\r\n                    id: 'preload',\r\n                    intro: '简悦的词法分析引擎采用了预加载机制，当系统性能吃紧时，可以选择关闭此功能，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=预加载机制\">请看这里</a> 。<br>注意：建议无特殊情况下不要关闭此功能，可以 <b>使用下一条的功能</b> 来规避性能问题。',\r\n                },\r\n                {\r\n                    id: 'lazyload',\r\n                    intro: '为了更快的进入到阅读模式，简悦会主动分析每个页面，但加入此列表的 URL 不会被主动分析。<br><br>此功能适合：<br><b> - 经常使用简悦但又性能不够</b> 的用户；<br> - 需要动态加载的页面；<br> - 支持 Mathjax 解析的页面等；<br><br>详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=延迟加载\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'auth',\r\n                    intro: '简悦支持常见的导出服务，你可以授权它们，导出 <b>阅读模式（简悦优化后）的页面</b> 到这些服务，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/授权服务\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'secret',\r\n                    intro: '使用导出服务后，会产生授权码，简悦默认 <b>不会在导出配置时包含它们</b>，如果需要的话，请开启此功能，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/授权服务?id=授权码\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'custom',\r\n                    intro: '简悦可以对 <b>阅读模式生成的页面</b> 更加精细的调整，甚至于 <b>使用 CSS 来深度定制</b>，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/自定义样式\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'notice',\r\n                    intro: '简悦 1.1.3 版增加了消息中心，为了方便查看简悦的一些最新消息，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/消息中心\">请看这里</a> 。',\r\n                }\r\n            ]\r\n        },\r\n        \"sites\" : {\r\n            target: 'sites',\r\n            idx: 2,\r\n            items: [\r\n                {\r\n                    id: 'newsites',\r\n                    intro: '简悦每隔一段时间会自动同步适配列表，你也可以手动同步。<br>什么是适配列表？详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/适配站点\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'customsites',\r\n                    intro: '从 1.1.3 开始，简悦调整了第三方适配的规则：仅针对个人的适配源，关于这部分的详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点适配源?id=第三方适配源\">请看这里</a> 。<br><b>注意：</b> 如果你使用了自己的适配源，请先清除再导入。',\r\n                },\r\n                {\r\n                    id: 'sitemgr',\r\n                    intro: '用来管理全部的站点，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点管理器\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'personsites',\r\n                    intro: '简悦用户自行上传且未收录到 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点适配源?id=官方适配源\">官方适配源</a> 里面的适配站点，可以在这里对这些站点进行安装，删除，更新等操作，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/站点集市\">请看这里</a> 。',\r\n                }\r\n            ]\r\n        },\r\n        \"plugins\" : {\r\n            target: 'plugins',\r\n            idx: 4,\r\n            items: [\r\n                {\r\n                    id: 'pluginsite',\r\n                    intro: '为了让阅读模式更加的丰富，简悦从 1.1.2 版本开始支持插件系统，插件系统 <b>仅支持阅读模式</b>。<br>点击这里打开到插件的官网，关于插件的详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/插件系统\">请看这里</a> 。<br>注意：安装过多的插件会引起性能问题，建议 <b>不要超过 6 个</b> 。',\r\n                },\r\n                {\r\n                    id: 'pluginconfig',\r\n                    intro: '当用户上传了新的配置文件，需要手动从配置文件读取插件。<br>注意：上传配置文件后会清除当前环境的插件，所以请别忘记手动导入。',\r\n                },\r\n                {\r\n                    id: 'pluginupdate',\r\n                    intro: '更新已安装的全部插件到最新版本。',\r\n                },\r\n                {\r\n                    id: 'pluginclear',\r\n                    intro: '清除当前环境的全部插件。<br>注意：此操作并不能清除当前的配置文件，如果要清除配置文件，请前往 <b>共通 → 清除数据</b> 操作。',\r\n                },\r\n                {\r\n                    id: 'pluginmange',\r\n                    intro: '这是用户的已安装的全部插件，在这里进行管理，包括：禁用， 删除，更新，查看 等操作。<br>同样，在这里安装的插件可以在阅读模式下启用禁用操作，位置在 <b>阅读模式 → 右下角控制栏 → 插件（选项卡）</b> 查看。',\r\n                }\r\n            ]\r\n        },\r\n        \"later\" : {\r\n            target: 'later',\r\n            idx: 5,\r\n            items: [\r\n                {\r\n                    id: 'laterlist',\r\n                    intro: '简悦自带了一个未读列表，你可以把任意 URL 通过 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/右键菜单\">右键菜单</a> / <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/阅读模式-控制栏\">控制栏 → 动作</a> 发送到稍后读。<br>稍后读也支持发送这些链接到 Pocket · Instapaper · Linnk 里面，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/稍后读\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'latermore',\r\n                    intro: '加载更多的稍后读。',\r\n                }\r\n            ]\r\n        },\r\n        \"@performance\" : {\r\n            target: 'labs',\r\n            idx: 2,\r\n            items: [\r\n                {\r\n                    id: 'preload',\r\n                    intro: '简悦的词法分析引擎采用了预加载机制，当系统性能吃紧时，可以选择关闭此功能，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=预加载机制\">请看这里</a> 。<br>注意：建议无特殊情况下不要关闭此功能，可以 <b>使用下一条的功能</b> 来规避性能问题。',\r\n                },\r\n                {\r\n                    id: 'lazyload',\r\n                    intro: '为了更快的进入到阅读模式，简悦会主动分析每个页面，但加入此列表的 URL 不会被主动分析。<br><br>此功能适合：<br><b> - 经常使用简悦但又性能不够</b> 的用户；<br> - 需要动态加载的页面；<br> - 支持 Mathjax 解析的页面等；<br><br>详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/词法分析引擎?id=延迟加载\">请看这里</a> 。',\r\n                },\r\n                {\r\n                    id: 'blacklist',\r\n                    intro: '也可以将完全不需要的站点加入到黑名单中，详细说明 <a target=\"_blank\" href=\"http://ksria.com/simpread/docs/#/FAQ?id=黑名单\">请看这里</a> 。',\r\n                }\r\n            ]\r\n        }\r\n    };\r\n\r\n/**\r\n * Verify version\r\n * \r\n * @param {string} local version\r\n * @param {object} simpread data structure\r\n */\r\nfunction Verify( curver, data ) {\r\n\r\n    if ( curver == \"1.0.0\" ) {\r\n        data.option.esc      = true;\r\n        data.option.menu     = { focus: true, read: true, link: true };\r\n        data.focus.controlbar= true;\r\n        data.focus.mask      = true;\r\n        data.read.progress   = true;\r\n        data.read.auto       = false;\r\n        data.read.controlbar = true;\r\n        data.read.exclusion  = [\r\n            \"v2ex.com\",\"issue.github.com\",\"readme.github.com\",\"question.zhihu.com\",\"douban.com\",\"nationalgeographic.com.cn\",\"tech.163.com\",\"docs.microsoft.com\",\"msdn.microsoft.com\",\"baijia.baidu.com\",\"code.oschina.net\",\"http://www.ifanr.com\",\"http://www.ifanr.com/news\",\"http://www.ifanr.com/app\",\"http://www.ifanr.com/minapp\",\"http://www.ifanr.com/dasheng\",\"http://www.ifanr.com/data\",\"https://www.ifanr.com/app\",\"http://www.ifanr.com/weizhizao\",\"http://www.thepaper.cn\",\"http://www.pingwest.com\",\"http://tech2ipo.com\",\"https://www.waerfa.com/social\"\r\n        ];\r\n\r\n        curver = \"1.0.1\";\r\n    }\r\n\r\n    if ( curver == \"1.0.1\" ) {\r\n        data.read.custom     = {\r\n            global: {\r\n                fontFamily : \"\",\r\n                marginLeft : \"\",\r\n                marginRight: \"\",\r\n            },\r\n            title : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n            },\r\n            desc  : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n            },\r\n            art   : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n                color      : \"\",\r\n                fontWeight : \"\",\r\n                wordSpacing: \"\",\r\n                letterSpacing: \"\",\r\n                lineHeight : \"\",\r\n                textIndent : \"\",\r\n            },\r\n            pre  : {\r\n                textShadow : \"\",\r\n            },\r\n            code  : {\r\n                fontFamily : \"\",\r\n                fontSize   : \"\",\r\n            },\r\n            css   : \"\",\r\n        };\r\n        curver = \"1.0.2\";\r\n    }\r\n\r\n    if ( curver == \"1.0.2\" ) {\r\n        data.option.sync = \"\";\r\n        curver = \"1.0.3\";\r\n    }\r\n\r\n    if ( curver == \"1.0.3\" ) {\r\n        data.focus.highlight  = true;\r\n        data.read.highlight   = true;\r\n        data.option.menu.list = false;\r\n        data.option.br_exit   = false;\r\n        data.option.secret    = false;\r\n        curver = \"1.0.4\";\r\n    }\r\n\r\n    if ( curver == \"1.0.4\" ) {\r\n        data.read.toc       = true;\r\n        data.read.toc_hide  = true;\r\n        data.read.whitelist = [];\r\n        curver = \"1.0.5\";\r\n    }\r\n\r\n    if ( curver == \"1.0.5\" ) {\r\n        data.option.origins = [];\r\n        data.websites       = {\r\n            custom : [],\r\n            local  : []\r\n        };\r\n        curver = \"1.0.6\";\r\n    }\r\n\r\n    if ( curver == \"1.0.6\" ) {\r\n        data.websites.local = data.read.sites.concat( data.focus.sites );\r\n        delete data.focus.sites;\r\n        delete data.read.sites;\r\n        curver = \"1.1.0\";\r\n    }\r\n\r\n    if ( curver == \"1.1.0\" ) {\r\n        data.option.blacklist = [ \"google.com\" ];\r\n        data.read.fap         = true;\r\n        data.read.custom.global.fontFamily && ( data.read.fontfamily = data.read.custom.global.fontFamily );\r\n        data.read.custom.global.marginLeft && ( data.read.layout     = data.read.custom.global.marginLeft );\r\n        delete data.read.custom.global;\r\n\r\n        data.statistics = {\"focus\":0,\"read\":0,\"service\":{\"linnk\":0,\"instapaper\":0,\"pocket\":0,\"readlater\":0,\"epub\":0,\"pdf\":0,\"png\":0,\"markdown\":0,\"html\":0,\"evernote\":0,\"yinxiang\":0,\"dropbox\":0,\"onenote\":0,\"gdrive\":0,\"kindle\":0,\"temp\":0}}\r\n        data.statistics.focus = data.option.focus;\r\n        data.statistics.read  = data.option.read;\r\n        delete data.option.focus;\r\n        delete data.option.read;\r\n        curver = \"1.1.1\";\r\n    }\r\n\r\n    if ( curver == \"1.1.1\" ) {\r\n        data.user = { \"uid\": \"\",\"name\": \"\",\"contact\": \"\",\"email\": \"\",\"avatar\": \"\",\"permission\": \"\" };\r\n        data.option.plugins = [];\r\n        data.websites.person = [];\r\n        curver = \"1.1.2\";\r\n    }\r\n\r\n    if ( curver == \"1.1.2\" ) {\r\n        data.patch != sub_ver && FixSubver( sub_ver, data );\r\n    }\r\n\r\n    if ( curver == \"1.1.2\" ) {\r\n        data.option.save_at   = \"dropbox\";\r\n        data.option.notice    = true;\r\n        data.option.preload   = true;\r\n        data.option.lazyload  = [\r\n            \"baidu.com\", \"weibo.com\", \"youtube.com\"\r\n        ];\r\n        data.option.uninstall = true;\r\n\r\n        data.statistics.service.yuque   = 0;\r\n        data.statistics.service.jianguo = 0;\r\n\r\n        data.notice = { \"latest\": 0, \"read\": [] };\r\n\r\n        data.option.blacklist.findIndex( item => item.toLowerCase() == \"youtube.com\" ) < 0 && data.option.blacklist.push( \"youtube.com\" );\r\n\r\n        data.patch = 0;\r\n        curver = \"1.1.3\";\r\n    }\r\n\r\n    if ( curver == \"1.1.3\" ) {\r\n        data.option.urlscheme     = true;\r\n        data.option.menu.lazyload = false;\r\n        curver = \"1.1.4\";\r\n    }\r\n\r\n    /*\r\n    if ( curver == \"1.0.1\" ) {\r\n        data.option.pocket = { \"consumer\": \"\", \"access\": \"\" };\r\n        data.read.custom = \"\";\r\n        curver = \"2.0.0\";\r\n    }\r\n    */\r\n\r\n    data.version = version;\r\n    return data;\r\n}\r\n\r\n/**\r\n * Fix Incompatible simpread data structure\r\n * \r\n * @param  {string} version\r\n * @param  {object} simpread data structure\r\n * @return {boolean} true: changed false: not changed\r\n */\r\nfunction Incompatible( ver, data ) {\r\n    let is_changed = false;\r\n    if ( ver == \"1.1.4\" ) {\r\n        data.option.origins = data.option.origins.filter( item => item != \"http://sr.ksria.cn/origins/website_list_en.json\" && item != \"http://sr.ksria.cn/origins/website_list_tw.json\" ) \r\n        if ( data.option.origins.length > 0 ) {\r\n            is_changed = true;\r\n            new Notify(); // hack code\r\n        }\r\n    }\r\n    return is_changed;\r\n}\r\n\r\n/**\r\n * Notify with type and version\r\n * 1.0.4 before usage http://ksria.com/simpread/changelog.html#{ver}\r\n * 1.0.4 after  usage http://ksria.com/simpread/version_${ver}.html\r\n * \r\n * @param {boolean} is first load\r\n * @param {string} type, include: firstload, update\r\n * @param {string} ver, e.g. 1.0.0, 1.0.1\r\n */\r\nfunction Notify2( first, type, ver ) {\r\n    const str    = type == \"firstload\" ? \"安装\" : \"更新\",\r\n          detail = type == \"firstload\" ? \"\" : details.get(ver),\r\n          link   = first ? `${detail}如何使用请看 <a href=\"http://ksria.com/simpread/guide/\" target=\"_blank\">新手入门</a> 及 <a href=\"http://ksria.com/simpread/docs/#/\" target=\"_blank\">文档中心</a>` : `${detail}请看 <a href=\"http://ksria.com/simpread/welcome/version_${ver}.html\" target=\"_blank\">更新说明</a>`;\r\n    return `${str} 到最新版本 ${ver} ，${ link }`;\r\n}\r\n\r\n/**\r\n * Silent update\r\n */\r\nfunction SilentUpdate() {\r\n    const ver = `${ version }.${ sub_ver }`;\r\n    return `更新到最新版本 ${ ver } ，${ patchs.get( `${ ver }` ) }更多细节请看 <a href=\"http://ksria.com/simpread/changelog.html#${ver}\" target=\"_blank\">更新说明</a>`;\r\n}\r\n\r\n/**\r\n * Compare current version and target version\r\n * \r\n * @param  {string} target version\r\n * @return {number} -2: not exist; -1: old version; 1: new version; 0: current version\r\n */\r\nfunction Compare( target ) {\r\n    let result = -2;\r\n    if ( versions.has( target ) ) {\r\n        result = Date.parse( versions.get( version )) - Date.parse( versions.get( target ));\r\n        if ( result > 0 ) {\r\n            result = 1;\r\n        } else if ( result < 0 ) {\r\n            result = -1;\r\n        }\r\n    }\r\n    return result;\r\n}\r\n\r\n/**\r\n * Fix subver config\r\n * \r\n * @param {string} patch version e.g. 1025 / 5005\r\n * @param {object} @see simpread\r\n */\r\nfunction FixSubver( patch, target ) {\r\n    if ( patch == \"5005\" ) {\r\n        target.read.cleanup == undefined && ( target.read.cleanup = true );\r\n        target.read.pure    == undefined && ( target.read.pure    = true );\r\n        target.option.menu.whitelist == undefined && ( target.option.menu.whitelist = false );\r\n        target.option.menu.exclusion == undefined && ( target.option.menu.exclusion = false );\r\n        target.option.menu.blacklist == undefined && ( target.option.menu.blacklist = false );\r\n        target.option.menu.unrdist   == undefined && ( target.option.menu.unrdist   = false );\r\n    }\r\n    target.patch = patch;\r\n    return target;\r\n}\r\n\r\n/**\r\n * Verify current version plugins\r\n * \r\n * @param {object} option\r\n * @return {boolean}\r\n */\r\nfunction VerifyPlugins( option ) {\r\n    try {\r\n        if ( option.plugins.length == 0 ) return false;\r\n        const str = option.plugins.join( \",\" );\r\n        const newStr = str.replace( /(E0j1nYBmDD,?|SumEaxStWE,?|EHLtCwBy6c,?|UsayAKSuwe,?)/g, \"\" );\r\n        if ( str != newStr ) {\r\n            option.plugins = newStr.replace( /,$/, \"\" ).split( \",\" );\r\n            return true;\r\n        }\r\n    } catch( error ) {\r\n        console.error( \"version::VerifyPlugin catch\", error )\r\n        return false;\r\n    }\r\n}\r\n\r\nexport {\r\n    version,\r\n    tips,\r\n    sub_ver as patch,\r\n    Verify,\r\n    Notify2 as Notify,\r\n    Compare,\r\n    FixSubver,\r\n    VerifyPlugins,\r\n    Incompatible,\r\n    SilentUpdate,\r\n}"
  },
  {
    "path": "src/service/watch.js",
    "content": "console.log( \"=== simpread watch load ===\" )\n\nimport * as msg    from 'message';\nimport {br,browser}from 'browser';\n\nconst watcher = {\n        site   : new Map(),\n        import : new Map(),\n        version: new Map(),\n        option : new Map(),\n    };\n\n/**\n * Message watcher push\n * \n * @param {string} type watcher object, incude: site\n * @param {string} value watcher object state\n */\nfunction message( type, value ) {\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.updated, { type, value } ));\n}\n\n/**\n * Push watcher target\n * \n * @param {string} type watcher object, incude: site\n * @param {string} value watcher object state\n */\nfunction push( type, value ) {\n    getCurAllTabs( type );\n}\n\n/**\n * Pull( remove ) watcher by tabid\n * \n * @param {string} tab id\n */\nfunction pull( tabid ) {\n    Object.values( watcher ).forEach( item => item.delete( tabid ));\n}\n\n/**\n * Lock\n * \n * @param  {string} url\n * @return {object} return wacher item, when url exist tabs status is lock( true ), else is unlock( false )\n */\nfunction lock( url ) {\n    try {\n        return {\n            site   : [ ...watcher.site.values()   ].includes( url ),\n            import : [ ...watcher.import.values() ].includes( url ),\n            version: [ ...watcher.version.values()].includes( url ),\n            option : [ ...watcher.option.values() ].includes( url ),\n        };\n    } catch( error ) {\n        console.error( \"watch.Lock has same failed, \", error );\n        return { site: false, import: false };\n    }\n}\n\n/**\n * Verify\n * \n * @param {fucntion} callback watch.Lock() state, result\n */\nfunction verify( callback ) {\n    !br.isFirefox() ?\n    browser.runtime.sendMessage( msg.Add( msg.MESSAGE_ACTION.save_verify, { url: window.location.href }), result => {\n        callback( result.site || result.import || result.version || result.option, result );\n    }) : callback( false );\n}\n\n/**\n * Get current all tabs\n * \n * @param {string} @see wathc.Push()\n */\nfunction getCurAllTabs( type ) {\n    browser.tabs.query( {}, result => {\n        result.forEach( tab => watcher[type].set( tab.id, tab.url ));\n    });\n}\n\nexport {\n    message as SendMessage,\n    push    as Push,\n    pull    as Pull,\n    verify  as Verify,\n    lock    as Lock,\n}"
  },
  {
    "path": "src/vender/carousel/carousel.css",
    "content": "/*\n * Carousel\n * http://materializecss.com/carousel.html\n * \n * https://github.com/Dogfalo/materialize/blob/master/LICENSE\n */\n\n.carousel {\n    position: relative;\n\n    width: 100%;\n    height: 400px;\n\n    -webkit-perspective: 500px;\n    perspective: 500px;\n\n    -webkit-transform-style: preserve-3d;\n    transform-style: preserve-3d;\n    -webkit-transform-origin: 0% 50%;\n    transform-origin: 0% 50%;\n\n    overflow: hidden;\n}\n\n.carousel.carousel-slider {\n    top: 0;\n    left: 0;\n\n    height: 100%\n}\n\n.carousel.carousel-slider .carousel-item {\n    position: absolute;\n\n    top: 0;\n    left: 0;\n\n    width: 100%;\n    height: 100%;\n    min-height: 400px;\n}\n\n.carousel .carousel-item {\n    display: none;\n    position: absolute;\n\n    top: 0;\n    left: 0;\n\n    width: 200px;\n    height: 200px;\n}\n\n.carousel .carousel-item>img {\n    width: 100%\n}\n\n.carousel .indicators {\n    position: absolute;\n\n    margin: 0;\n    padding: 0;\n\n    left: 0;\n    right: 0;\n    bottom: 0;\n\n    text-align: center;\n}\n\n.carousel .indicators .indicator-item {\n    display: inline-block;\n    position: relative;\n\n    margin: 14px 4px;\n\n    height: 10px;\n    width: 10px;\n\n    background-color: #E0E0E0;\n\n    transition: background-color .3s;\n    border-radius: 50%;\n\n    cursor: pointer;\n}\n\n.carousel .indicators .indicator-item.active {\n    background-color: #4CAF50\n}\n\n.carousel.scrolling .carousel-item .materialboxed,.carousel .carousel-item:not(.active) .materialboxed {\n    pointer-events: none\n}\n"
  },
  {
    "path": "src/vender/carousel/carousel.js",
    "content": "/*\n * Carousel\n * http://materializecss.com/carousel.html\n * \n * https://github.com/Dogfalo/materialize/blob/master/LICENSE\n */\n\n(function ($) {\n\n  var methods = {\n\n    init : function(options) {\n      var defaults = {\n        duration  : 200,    // ms\n        dist      : -100,   // zoom scale TODO: make this more intuitive as an option\n        shift     : 0,      // spacing for center image\n        padding   : 0,      // Padding between non center items\n        fullWidth : false,  // Change to full width styles\n        indicators: false,  // Toggle indicators\n        noWrap    : false,  // Don't wrap around and cycle through items.\n        onCycleTo : null,   // Callback for when a new slide is cycled to.\n        onActived : null,   // Callback for when a new slide actived.\n      };\n\n      options = $.extend(defaults, options);\n\n      return this.each(function(i) {\n\n        var images, item_width, item_height, offset, center, pressed, dim, count,\n            reference, referenceY, amplitude, target, velocity, scrolling,\n            xform, frame, timestamp, ticker, dragged, vertical_dragged;\n        var $indicators = $('<ul class=\"indicators\"></ul>');\n        var scrollingTimeout = null;\n        var tweenedOpacity, zTranslation;\n\n        // Initialize\n        var view = $(this);\n        var showIndicators = view.attr('data-indicators') || options.indicators;\n\n\n        // Options\n        var setCarouselHeight = function() {\n          var firstImage = view.find('.carousel-item img').first();\n          if (firstImage.length) {\n            if (firstImage.prop('complete')) {\n              view.css('height', firstImage.height());\n            } else {\n              firstImage.on('load', function(){\n                view.css('height', $(this).height());\n              });\n            }\n          } else {\n            var imageHeight = view.find('.carousel-item').first().height();\n            view.css('height', imageHeight);\n          }\n        };\n\n        if (options.fullWidth) {\n          options.dist = 0;\n          //setCarouselHeight();\n\n          // Offset fixed items when indicators.\n          if (showIndicators) {\n            view.find('.carousel-fixed-item').addClass('with-indicators');\n          }\n        }\n\n        // Don't double initialize.\n        if (view.hasClass('initialized')) {\n          // Recalculate variables\n          $(window).trigger('resize');\n\n          // Redraw carousel.\n          $(this).trigger('carouselNext', [0.000001]);\n          return true;\n        }\n\n        view.addClass('initialized');\n        pressed = false;\n        offset = target = 0;\n        images = [];\n        item_width = view.find('.carousel-item').first().innerWidth();\n        item_height = view.find('.carousel-item').first().innerHeight();\n        dim = item_width * 2 + options.padding;\n\n        view.find('.carousel-item').each(function (i) {\n          images.push($(this)[0]);\n          if (showIndicators) {\n            var $indicator = $('<li class=\"indicator-item\"></li>');\n\n            // Add active to first by default.\n            if (i === 0) {\n              $indicator.addClass('active');\n            }\n\n            // Handle clicks on indicators.\n            $indicator.click(function (e) {\n              e.stopPropagation();\n\n              var index = $(this).index();\n              cycleTo(index);\n            });\n            $indicators.append($indicator);\n          }\n        });\n\n        if (showIndicators) {\n          view.append($indicators);\n        }\n        count = images.length;\n\n\n        function setupEvents() {\n          if (typeof window.ontouchstart !== 'undefined') {\n            view[0].addEventListener('touchstart', tap);\n            view[0].addEventListener('touchmove', drag);\n            view[0].addEventListener('touchend', release);\n          }\n          view[0].addEventListener('mousedown', tap);\n          view[0].addEventListener('mousemove', drag);\n          view[0].addEventListener('mouseup', release);\n          view[0].addEventListener('mouseleave', release);\n          view[0].addEventListener('click', click);\n        }\n\n        function xpos(e) {\n          // touch event\n          if (e.targetTouches && (e.targetTouches.length >= 1)) {\n            return e.targetTouches[0].clientX;\n          }\n\n          // mouse event\n          return e.clientX;\n        }\n\n        function ypos(e) {\n          // touch event\n          if (e.targetTouches && (e.targetTouches.length >= 1)) {\n            return e.targetTouches[0].clientY;\n          }\n\n          // mouse event\n          return e.clientY;\n        }\n\n        function wrap(x) {\n          return (x >= count) ? (x % count) : (x < 0) ? wrap(count + (x % count)) : x;\n        }\n\n        function scroll(x) {\n          // Track scrolling state\n          scrolling = true;\n          if (!view.hasClass('scrolling')) {\n            view.addClass('scrolling');\n          }\n          if (scrollingTimeout != null) {\n            window.clearTimeout(scrollingTimeout);\n          }\n          scrollingTimeout = window.setTimeout(function() {\n            scrolling = false;\n            view.removeClass('scrolling');\n            var $curr_item = view.find('.carousel-item').eq(wrap(center));\n            scrollingTimeout != 1 && options.onActived && options.onActived.call(this, $(\".carousel .indicators\").find( \".active\").index() );\n          }, options.duration);\n\n          // Start actual scroll\n          var i, half, delta, dir, tween, el, alignment, xTranslation;\n          var lastCenter = center;\n\n          offset = (typeof x === 'number') ? x : offset;\n          center = Math.floor((offset + dim / 2) / dim);\n          delta = offset - center * dim;\n          dir = (delta < 0) ? 1 : -1;\n          tween = -dir * delta * 2 / dim;\n          half = count >> 1;\n\n          if (!options.fullWidth) {\n            alignment = 'translateX(' + (view[0].clientWidth - item_width) / 2 + 'px) ';\n            alignment += 'translateY(' + (view[0].clientHeight - item_height) / 2 + 'px)';\n          } else {\n            alignment = 'translateX(0)';\n          }\n\n          // Set indicator active\n          if (showIndicators) {\n            var diff = (center % count);\n            var activeIndicator = $indicators.find('.indicator-item.active');\n            if (activeIndicator.index() !== diff) {\n              activeIndicator.removeClass('active');\n              $indicators.find('.indicator-item').eq(diff).addClass('active');\n            }\n          }\n\n          // center\n          // Don't show wrapped items.\n          if (!options.noWrap || (center >= 0 && center < count)) {\n            el = images[wrap(center)];\n\n            // Add active class to center item.\n            if (!$(el).hasClass('active')) {\n              view.find('.carousel-item').removeClass('active');\n              $(el).addClass('active');\n            }\n            el.style[xform] = alignment +\n              ' translateX(' + (-delta / 2) + 'px)' +\n              ' translateX(' + (dir * options.shift * tween * i) + 'px)' +\n              ' translateZ(' + (options.dist * tween) + 'px)';\n            el.style.zIndex = 0;\n            if (options.fullWidth) { tweenedOpacity = 1; }\n            else { tweenedOpacity = 1 - 0.2 * tween; }\n            el.style.opacity = tweenedOpacity;\n            el.style.display = 'block';\n          }\n\n          for (i = 1; i <= half; ++i) {\n            // right side\n            if (options.fullWidth) {\n              zTranslation = options.dist;\n              tweenedOpacity = (i === half && delta < 0) ? 1 - tween : 1;\n            } else {\n              zTranslation = options.dist * (i * 2 + tween * dir);\n              tweenedOpacity = 1 - 0.2 * (i * 2 + tween * dir);\n            }\n            // Don't show wrapped items.\n            if (!options.noWrap || center + i < count) {\n              el = images[wrap(center + i)];\n              el.style[xform] = alignment +\n                ' translateX(' + (options.shift + (dim * i - delta) / 2) + 'px)' +\n                ' translateZ(' + zTranslation + 'px)';\n              el.style.zIndex = -i;\n              el.style.opacity = tweenedOpacity;\n              el.style.display = 'block';\n            }\n\n\n            // left side\n            if (options.fullWidth) {\n              zTranslation = options.dist;\n              tweenedOpacity = (i === half && delta > 0) ? 1 - tween : 1;\n            } else {\n              zTranslation = options.dist * (i * 2 - tween * dir);\n              tweenedOpacity = 1 - 0.2 * (i * 2 - tween * dir);\n            }\n            // Don't show wrapped items.\n            if (!options.noWrap || center - i >= 0) {\n              el = images[wrap(center - i)];\n              el.style[xform] = alignment +\n                ' translateX(' + (-options.shift + (-dim * i - delta) / 2) + 'px)' +\n                ' translateZ(' + zTranslation + 'px)';\n              el.style.zIndex = -i;\n              el.style.opacity = tweenedOpacity;\n              el.style.display = 'block';\n            }\n          }\n\n          // center\n          // Don't show wrapped items.\n          if (!options.noWrap || (center >= 0 && center < count)) {\n            el = images[wrap(center)];\n            el.style[xform] = alignment +\n              ' translateX(' + (-delta / 2) + 'px)' +\n              ' translateX(' + (dir * options.shift * tween) + 'px)' +\n              ' translateZ(' + (options.dist * tween) + 'px)';\n            el.style.zIndex = 0;\n            if (options.fullWidth) { tweenedOpacity = 1; }\n            else { tweenedOpacity = 1 - 0.2 * tween; }\n            el.style.opacity = tweenedOpacity;\n            el.style.display = 'block';\n          }\n\n          // onCycleTo callback\n          if (lastCenter !== center &&\n              typeof(options.onCycleTo) === \"function\") {\n            var $curr_item = view.find('.carousel-item').eq(wrap(center));\n            options.onCycleTo.call(this, $(\".carousel .indicators\").find( \".active\").index(), $curr_item, dragged);\n          }\n        }\n\n        function track() {\n          var now, elapsed, delta, v;\n\n          now = Date.now();\n          elapsed = now - timestamp;\n          timestamp = now;\n          delta = offset - frame;\n          frame = offset;\n\n          v = 1000 * delta / (1 + elapsed);\n          velocity = 0.8 * v + 0.2 * velocity;\n        }\n\n        function autoScroll() {\n          var elapsed, delta;\n\n          if (amplitude) {\n            elapsed = Date.now() - timestamp;\n            delta = amplitude * Math.exp(-elapsed / options.duration);\n            if (delta > 2 || delta < -2) {\n                scroll(target - delta);\n                requestAnimationFrame(autoScroll);\n            } else {\n                scroll(target);\n            }\n          }\n        }\n\n        function click(e) {\n          // Disable clicks if carousel was dragged.\n          if (dragged) {\n            e.preventDefault();\n            e.stopPropagation();\n            return false;\n\n          } else if (!options.fullWidth) {\n            var clickedIndex = $(e.target).closest('.carousel-item').index();\n            var diff = (center % count) - clickedIndex;\n\n            // Disable clicks if carousel was shifted by click\n            if (diff !== 0) {\n              e.preventDefault();\n              e.stopPropagation();\n            }\n            cycleTo(clickedIndex);\n          }\n        }\n\n        function cycleTo(n) {\n          var diff = (center % count) - n;\n\n          // Account for wraparound.\n          if (!options.noWrap) {\n            if (diff < 0) {\n              if (Math.abs(diff + count) < Math.abs(diff)) { diff += count; }\n\n            } else if (diff > 0) {\n              if (Math.abs(diff - count) < diff) { diff -= count; }\n            }\n          }\n\n          // Call prev or next accordingly.\n          if (diff < 0) {\n            view.trigger('carouselNext', [Math.abs(diff)]);\n\n          } else if (diff > 0) {\n            view.trigger('carouselPrev', [diff]);\n          }\n        }\n\n        function tap(e) {\n          e.preventDefault();\n          pressed = true;\n          dragged = false;\n          vertical_dragged = false;\n          reference = xpos(e);\n          referenceY = ypos(e);\n\n          velocity = amplitude = 0;\n          frame = offset;\n          timestamp = Date.now();\n          clearInterval(ticker);\n          ticker = setInterval(track, 100);\n        }\n\n        function drag(e) {\n          var x, y, delta, deltaY;\n          if (pressed) {\n            x = xpos(e);\n            y = ypos(e);\n            delta = reference - x;\n            deltaY = Math.abs(referenceY - y);\n            if (deltaY < 30 && !vertical_dragged) {\n              // If vertical scrolling don't allow dragging.\n              if (delta > 2 || delta < -2) {\n                dragged = true;\n                reference = x;\n                scroll(offset + delta);\n              }\n\n            } else if (dragged) {\n              // If dragging don't allow vertical scroll.\n              e.preventDefault();\n              e.stopPropagation();\n              return false;\n\n            } else {\n              // Vertical scrolling.\n              vertical_dragged = true;\n            }\n          }\n\n          if (dragged) {\n            // If dragging don't allow vertical scroll.\n            e.preventDefault();\n            e.stopPropagation();\n            return false;\n          }\n        }\n\n        function release(e) {\n          if (pressed) {\n            pressed = false;\n          } else {\n            return;\n          }\n\n          clearInterval(ticker);\n          target = offset;\n          if (velocity > 10 || velocity < -10) {\n            amplitude = 0.9 * velocity;\n            target = offset + amplitude;\n          }\n          target = Math.round(target / dim) * dim;\n\n          // No wrap of items.\n          if (options.noWrap) {\n            if (target >= dim * (count - 1)) {\n              target = dim * (count - 1);\n            } else if (target < 0) {\n              target = 0;\n            }\n          }\n          amplitude = target - offset;\n          timestamp = Date.now();\n          requestAnimationFrame(autoScroll);\n\n          if (dragged) {\n            e.preventDefault();\n            e.stopPropagation();\n          }\n          return false;\n        }\n\n        xform = 'transform';\n        ['webkit', 'Moz', 'O', 'ms'].every(function (prefix) {\n          var e = prefix + 'Transform';\n          if (typeof document.body.style[e] !== 'undefined') {\n            xform = e;\n            return false;\n          }\n          return true;\n        });\n\n        setupEvents();\n        scroll(offset);\n\n        $(this).on('carouselNext', function(e, n) {\n          if (n === undefined) {\n            n = 1;\n          }\n          target = (dim * Math.round(offset / dim)) + (dim * n);\n          if (offset !== target) {\n            amplitude = target - offset;\n            timestamp = Date.now();\n            requestAnimationFrame(autoScroll);\n          }\n        });\n\n        $(this).on('carouselPrev', function(e, n) {\n          if (n === undefined) {\n            n = 1;\n          }\n          target = (dim * Math.round(offset / dim)) - (dim * n);\n          if (offset !== target) {\n            amplitude = target - offset;\n            timestamp = Date.now();\n            requestAnimationFrame(autoScroll);\n          }\n        });\n\n        $(this).on('carouselSet', function(e, n) {\n          if (n === undefined) {\n            n = 0;\n          }\n          cycleTo(n);\n        });\n\n      });\n\n    },\n\n    next : function(n) {\n      $(this).trigger('carouselNext', [n]);\n    },\n\n    prev : function(n) {\n      $(this).trigger('carouselPrev', [n]);\n    },\n\n    set : function(n) {\n      $(this).trigger('carouselSet', [n]);\n    }\n\n  };\n\n  $.fn.carousel = function(methodOrOptions) {\n    if ( methods[methodOrOptions] ) {\n      return methods[ methodOrOptions ].apply( this, Array.prototype.slice.call( arguments, 1 ));\n    } else if ( typeof methodOrOptions === 'object' || ! methodOrOptions ) {\n      // Default to \"init\"\n      return methods.init.apply( this, arguments );\n    } else {\n      $.error( 'Method ' +  methodOrOptions + ' does not exist on jQuery.carousel' );\n    }\n  };\n\n}( jQuery ));"
  },
  {
    "path": "src/vender/instapaper.js",
    "content": "// Instapaper xAuth API for client side JS. Depends on cross-domain requests.\n// Reference: <http://www.instapaper.com/api/full>\n// xAuth documentation from Twitter: <https://dev.twitter.com/docs/oauth/xauth>\n// With help from <https://gist.github.com/447636>\n\nvar jsSHA = require(\"./sha1\");\nvar Instapaper, fixedEncodeURIComponent, qline2object;\n\nInstapaper = (function() {\n  class Instapaper {\n    // ## Class Methods\n    // ### Creates Nonce\n    // > \"The oauth_nonce parameter is a unique token your application should\n    // generate for each unique request. Twitter will use this value to determine\n    // whether a request has been submitted multiple times.\"\n    // > <https://dev.twitter.com/docs/auth/authorizing-request>\n    generateNonce() {\n      var length, nonce;\n      nonce = [];\n      length = 5; // arbitrary - looks like a good length\n      while (length > 0) {\n        nonce.push((((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1));\n        length--;\n      }\n      return nonce.join(\"\");\n    }\n\n    // UTC timestamp.\n    getUTCtimestamp() {\n      return (new Date((new Date).toUTCString())).getTime() / 1000;\n    }\n\n    // ### Creates 'header string' for 'Authorization' in HTTP header\n    // cf. <https://dev.twitter.com/docs/auth/authorizing-request>\n    authTemplate(req) {\n      var auth;\n      auth = `OAuth oauth_consumer_key=\"${fixedEncodeURIComponent(req.consumer_key)}\", `;\n      if (req.token != null) {\n        auth += `oauth_token=\"${fixedEncodeURIComponent(req.token)}\", `;\n      }\n      auth += `oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"${fixedEncodeURIComponent(req.signature)}\", oauth_timestamp=\"${fixedEncodeURIComponent(req.timestamp)}\", oauth_nonce=\"${fixedEncodeURIComponent(req.nonce)}\", oauth_version=\"1.0\"`.trim();\n      return auth;\n    }\n\n    // ### Creates 'Signature base string'\n    // cf. <https://dev.twitter.com/docs/auth/creating-signature>\n    sigBaseTemplate(req) {\n      var i, j, len, param_helper, param_string, params, ref, sig;\n      params = {\n        oauth_consumer_key: req.consumer_key,\n        oauth_nonce: req.nonce,\n        oauth_signature_method: 'HMAC-SHA1',\n        oauth_timestamp: req.timestamp,\n        oauth_version: '1.0'\n      };\n      if (req.token != null) {\n        params.oauth_token = req.token;\n      }\n      if (req.data != null) {\n        params = $.extend(params, req.data);\n      }\n      // Params string: sort object by key, then make querystring\n      param_helper = [];\n      ref = Object.keys(params).sort();\n      for (j = 0, len = ref.length; j < len; j++) {\n        i = ref[j];\n        param_helper.push(`${fixedEncodeURIComponent(i)}=${fixedEncodeURIComponent(params[i])}`);\n      }\n      param_string = param_helper.join('&');\n      sig = `POST&${fixedEncodeURIComponent(this.baseUrl + req.url)}&${fixedEncodeURIComponent(param_string)}`;\n      return sig;\n    }\n\n    // ## General Methods\n    makeSigningKey() {\n      var key;\n      key = this.consumer_secret + '&';\n      if (this.token_secret != null) {\n        key += this.token_secret;\n      }\n      return key;\n    }\n\n    // ### Create Signature for `authTemplate()`\n    // Depends on HMAC-SHA1 from <http://caligatio.github.com/jsSHA/>\n    makeSignature(req) {\n      var hmacGen;\n      hmacGen = new jsSHA(this.sigBaseTemplate(req), \"TEXT\");\n      var hmac = hmacGen.getHMAC(this.makeSigningKey(), \"TEXT\", \"SHA-1\", \"B64\")\n      return hmac;\n    }\n\n    // ### Creates `req`, an object with data specific for each request\n    makeRequestObject(options) {\n      var req;\n      req = $.extend({\n        consumer_key: this.consumer_key,\n        consumer_secret: this.consumer_secret,\n        nonce: this.generateNonce(),\n        timestamp: this.getUTCtimestamp(),\n        token: this.token,\n        token_secret: this.token_secret,\n        method: 'POST'\n      }, options);\n      // Add signature, depends on req data so far\n      req.signature = this.makeSignature(req);\n      return req;\n    }\n\n    // Creates new Ajax request\n    // Always uses POST\n    request(options) {\n      var auth, req, settings;\n      req = options.req || (options.req = this.makeRequestObject({\n        url: options.url,\n        data: options.data\n      }));\n      auth = this.authTemplate(options.req);\n      settings = {\n        url: `${this.baseUrl}${options.url}`,\n        dataType: (function() {\n          return options.dataType || \"json\";\n        })(),\n        type: 'POST',\n        data: options.data,\n        headers: {\n          Authorization: auth\n        }\n      };\n      if ( options.url == \"bookmarks/add\" ) {\n        return settings;\n      }\n      return $.ajax(settings);\n    }\n\n    // ## Specific API Methods\n    // ### Gets an OAuth access token for a user.\n    // * Requires username and password\n    // * Also needs HTTPS\n    requestToken(user, password) {\n      var data, tokening, url;\n      if (!((user != null) && (password != null))) {\n        throw 'Please provide username and password.';\n      }\n      this.user = user;\n      url = \"oauth/access_token\";\n      data = {\n        x_auth_username: user,\n        x_auth_password: password,\n        x_auth_mode: \"client_auth\"\n      };\n      // Make Ajax request\n      tokening = this.request({\n        url: url,\n        req: this.makeRequestObject({\n          url: url,\n          data: data\n        }),\n        data: data,\n        dataType: 'text'\n      });\n      // Sucessful response looks like:\n      // `oauth_token=aabbccdd&oauth_token_secret=efgh1234`\n      tokening.done((response) => {\n        // Uses `jline2object` (see below) to retrieve data from query string\n        data = qline2object(response);\n        // Save oauth tokens to instance variables\n        this.token = data.oauth_token;\n        return this.token_secret = data.oauth_token_secret;\n      });\n      // Please can for a failure case yourself!\n      // `tokening.fail (jqXHR, textStatus, errorThrown) => ...`\n      return tokening;\n    }\n\n    // ### Returns the currently logged in user.\n    // `[{\"type\":\"user\",\"user_id\":54321,\"username\":\"TestUserOMGLOL\"}]`\n    verifyCredentials() {\n      return this.request({\n        url: \"account/verify_credentials\"\n      });\n    }\n\n    // Example method\n    // I won't add all the API stuff here, just do something like:\n    //     insta = new Instapaper()\n    //     insta.requestToken(username, password)\n    //     insta.request(url: \"bookmarks/list\")\n    bookmarkList() {\n      return this.request({\n        url: \"bookmarks/list\"\n      });\n    }\n\n    add( url, title, description ) {\n      return this.request({\n        url: \"bookmarks/add\",\n        data: {\n          url,\n          title,\n          description\n        }\n      });\n    }\n\n  };\n\n  // Always uses HTTPS\n  Instapaper.prototype.baseUrl = \"https://www.instapaper.com/api/1/\";\n\n  // Application keys for this application\n  Instapaper.prototype.consumer_key = 'SECRET';\n\n  Instapaper.prototype.consumer_secret = 'TOPSECRET';\n\n  return Instapaper;\n\n})();\n\nif (typeof module !== \"undefined\" && module !== null) {\n  module.exports = Instapaper;\n}\n\n// Helper function to transform querystring/qline to JS object\n// Insanely fast: <http://jsperf.com/query-str-parsing-regex-vs-split/5>\nqline2object = function(query = \"\") {\n  var item, j, len, parts, result;\n  result = {};\n  parts = query.split(\"&\");\n  for (j = 0, len = parts.length; j < len; j++) {\n    item = parts[j];\n    item = item.split(\"=\");\n    result[item[0]] = item[1];\n  }\n  return result;\n};\n\n// native encodeURIComponent isn't sufficient here\n// from <https://gist.github.com/447636>\nfixedEncodeURIComponent = function(str) {\n  return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\\(/g, '%28').replace(/\\)/g, '%29').replace(/\\*/g, '%2A');\n};\n"
  },
  {
    "path": "src/vender/mduikit/autocomplete.jsx",
    "content": "/*!\n * React Material Design: AutoComplete\n * \n * @version : 0.0.1\n * @update  : 2018/04/23\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: AutoComplete ====\" )\n\nconst cssinjs = () => {\n\n    const focus_color = 'rgba(0, 137, 123, .8)',\n          border_color= 'rgba(224, 224, 224, 1)',\n          margin      = '8px 0 0 0',\n          display     = 'block',\n          medium      = '14px',\n          large       = '16px',\n          lineHeight  = 1.5,\n          fontWeight  = 'bold',\n          width       = '100%',\n          styles      = {\n            root: {\n                display,\n                position: 'relative',\n                margin: 0,\n                padding: 0,\n\n                width,\n                lineHeight: 1,\n            },\n\n            input: {\n                backgroundColor: 'transparent',\n\n                width,\n                height: '20px',\n\n                margin,\n                padding: 0,\n\n                fontFamily: 'sans-serif',\n                fontSize: medium,\n\n                border: 'none',\n                outline: 'none',\n\n                boxShadow: 'none',\n                boxSizing: 'content-box',\n                transition: 'all 0.3s',\n            },\n\n            border : {\n                display,\n\n                width,\n                margin,\n\n                borderTop: `none ${border_color}`,\n                borderLeft: `none ${border_color}`,\n                borderRight: `none ${border_color}`,\n                borderBottom: `1px solid ${border_color}`,\n                boxSizing: 'content-box',\n            },\n\n            float : {\n                display,\n                position: 'absolute',\n\n                margin,\n\n                color: 'rgb(117, 117, 117)',\n\n                fontSize: medium,\n                fontWeight: 'initial',\n\n                userSelect: 'none',\n                pointerEvents: 'none',\n\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n                transform: 'scale(1) translate( 0px, 0px )',\n                transformOrigin: 'left top 0px',\n            },\n\n            float_focus : {\n                color: focus_color,\n\n                margin: `-${margin}`,\n\n                fontSize: medium,\n                fontWeight,\n\n                transform: 'scale(0.75) translate( 0px, -8px )',\n            },\n\n            state : {\n                display,\n                position: 'absolute',\n\n                width,\n                margin: '-1px 0 0 0',\n\n                borderTop: `none ${focus_color}`,\n                borderLeft: `none ${focus_color}`,\n                borderRight: `none ${focus_color}`,\n                borderBottom: `2px solid ${focus_color}`,\n                boxSizing: 'content-box',\n\n                transform: 'scaleX(0)',\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n            },\n\n            state_focus : {\n                transform: 'scaleX(1)',\n            },\n\n            icon: {\n                display: 'block',\n                position: 'absolute',\n\n                width: '24px',\n                height: '24px',\n\n                top: '1px',\n                right: 0,\n\n                border: 'none',\n                backgroundPosition: 'center',\n                backgroundRepeat: 'no-repeat',\n                backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABqSURBVEiJ7dQxCsAgDIXhZ8ktgmetVw31GIF06lI0yeIWJyH4f4hgMzOcXNfRegEFFAAAoGA+ROR2A0STmftu7t5ARAYRTS+uqtt4CACAqvYVkomngBWSjQPxG/yR59tnz7X6rgso4DzwAnJQKlbAmFdgAAAAAElFTkSuQmCC)',\n\n                cursor: 'pointer',\n            },\n\n        };\n\n    return styles;\n};\n\nconst cssinjs_list = () => {\n\n    const styles = {\n            root : {\n                display: 'block',\n                position: 'absolute',\n\n                top: '40px',\n                left: 0,\n\n                margin: 0,\n                padding: 0,\n\n                width: '100%',\n                maxHeight: '400px',\n\n                color: 'rgba(51, 51, 51, .87)',\n                backgroundColor: 'rgba(255, 255, 255, 1)',\n\n                boxSizing: 'border-box',\n                boxShadow: '0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2)',\n                borderRadius: '2px',\n\n                zIndex: 2100,\n\n                overflowY: 'auto',\n\n                opacity: 0,\n                transform: 'scaleY(0)',\n                transformOrigin: 'left top 0px',\n                transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n\n                overflowX: 'hidden',\n            },\n\n            open: {\n                opacity: 1,\n                transform: 'scaleY(1)',\n            },\n\n            list_filed: {\n                display: 'flex',\n                alignItems: 'center',\n\n                padding: '8px 24px 8px 16px',\n\n                height: '36px',\n                width: '100%',\n\n                textAlign: 'left',\n\n                boxSizing: 'border-box',\n                transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n\n                cursor: 'pointer',\n            },\n\n            list_filed_value: {\n                display: 'inline',\n                width: '100%',\n                fontSize: 'inherit',\n            },\n\n    }\n\n    return styles;\n};\n\nclass ListView extends React.Component {\n\n    static defaultProps = {\n        waves           : \"\",\n        items           : [],\n        active          : \"\",\n    };\n\n    static propTypes = {\n        waves           : React.PropTypes.string,\n        items           : React.PropTypes.array,\n        active          : React.PropTypes.string,\n        onChange        : React.PropTypes.func,\n    };\n\n    style = cssinjs_list()\n\n    onMouseOver( event ) {\n        const $target = $( event.target );\n        if ( $target.is( \"list-field\" ) ) {\n            $( \"list-field[active=true]\" ).css( \"background-color\", \"transparent\" ).attr( \"active\", false );\n            $target.attr( \"active\", true ).css( \"background-color\", this.props.hoverColor );\n        }\n    }\n\n    onClick( event ) {\n        this.props.onChange && this.props.onChange( $( event.target ).text(), $(event.target).attr(\"value\") );\n    }\n\n    render() {\n        const style = { ...this.style };\n        style.root  = this.props.items.length > 0 ? { ...style.root, ...style.open } : { ...style.root };\n        const list  = this.props.items.map( ( item, idx ) => {\n            let name_style = { ...style.list_filed_value };\n            item.value == this.props.active && ( name_style.color = this.props.activeColor );\n            item.style && item.style.root   && ( style.list_filed = { ...style.list_filed, ...item.style.root });\n            item.style && item.style.text   && ( name_style       = { ...name_style, ...item.style.text });\n            return (\n                <list-field class={ this.props.waves } style={ style.list_filed } value={ item.value } onMouseOver={ (e)=>this.onMouseOver(e) } onClick={ (e)=>this.onClick(e) }>\n                    <list-field-name style={ name_style } value={ item.value }>{ item.name }</list-field-name>\n                </list-field>\n            )\n        });\n\n        return (\n            <list-view style={ style.root }>\n                { list }\n            </list-view>\n        )\n    }\n}\n\n/**\n * Custom component: AutoComplete\n * \n    <auto-complete>\n        <icon></icon>\n        <text-field-float></text-field-float>\n        <input/>\n        <group>\n            <text-field-border/>\n            <text-field-state/>\n        </group>\n        <list-view>\n            <list-field>\n                <list-field-name></list-field-name>\n            </list-field>\n        </list-view>\n    </auto-complete>\n * \n * Reference:\n * - https://material.io/guidelines/components/text-fields.html#text-fields-layout\n * - http://materializecss.com/autocomplete.html\n * \n * @class\n */\nexport default class AC extends React.Component {\n\n    static defaultProps = {\n        // input\n        color       : \"rgba(51, 51, 51, .87)\",\n        value       : \"\",\n        placeholder : \"\",\n        floating    : undefined,\n        // dropdown\n        items       : [],\n        activeColor : \"rgba(255, 64, 129, 1)\",\n        hoverColor  : \"rgba(238, 238, 238, 1)\",\n        borderColor : undefined,\n        focusColor  : undefined,\n\n        tooltip     : {},\n    };\n\n    static propTypes = {\n        // input\n        color       : React.PropTypes.string,\n        value       : React.PropTypes.string,\n        placeholder : React.PropTypes.string,\n        floating    : React.PropTypes.string,\n        items       : React.PropTypes.array,\n        // dropdown\n        activeColor : React.PropTypes.string,\n        hoverColor  : React.PropTypes.string,\n        borderColor : undefined,\n        focusColor  : undefined,\n\n        tooltip     : React.PropTypes.object,\n        // event\n        onChange    : React.PropTypes.func,\n    }\n\n    style = cssinjs()\n\n    state = {\n        name  : \"\",\n        items : [],\n    }\n\n    // usage hack code, not usage react\n    onTextKeyDown( event ) {\n        const $target = $( \"list-view\" );\n        if ( $target.children().length == 0 && event.keyCode == 40 ) {\n            this.setState({ name : this.refs.input.value, items: this.props.items });\n            this.refs.dropdown.dataset.state = \"open\";\n            return;\n        }\n        const $sub = $target.find( \"list-field[active=true]\" );\n        if ( event.keyCode == 9 || event.keyCode == 27 ) {\n            this.refs.dropdown.dataset.state = \"close\";\n            this.setState({ name : \"\", items: [] });\n        }\n        else if ( event.keyCode == 40 ) {\n            if ( $sub.length == 0 ) {\n                $target.children().first().attr( \"active\", true ).css( \"background-color\", this.props.hoverColor );\n            } else {\n                $sub.css( \"background-color\", \"transparent\" ).attr( \"active\", false );\n                $sub.next().attr( \"active\", true ).css( \"background-color\", this.props.hoverColor );\n            }\n        } else if ( event.keyCode == 38 ) {\n            if ( $sub.length == 0 ) {\n                $target.children().last().attr( \"active\", true ).css( \"background-color\", this.props.hoverColor );\n            } else {\n                $sub.css( \"background-color\", \"transparent\" ).attr( \"active\", false );\n                $sub.prev().attr( \"active\", true ).css( \"background-color\", this.props.hoverColor );\n            }\n        } else if ( event.keyCode == 13 ) {\n            this.onDropdownChange( $sub.text(), $sub.attr(\"value\") );\n        }\n    }\n\n    onTextChangeFocus( event ) {\n        const style   = { ...this.style },\n              $target = $( this.refs.input ),\n              $state  = $target.next().find( \"text-field-state\" ),\n              $float  = $target.prev();\n        $state.css({ ...style.state, ...style.state_focus });\n        this.props.floating != \"\" && $float.css({ ...style.float, ...style.float_focus });\n    }\n\n    onTextChangeBlur( event ) {\n        const style   = { ...this.style },\n              $target = $( event.target ),\n              $state  = $target.next().find( \"text-field-state\" ),\n              $float  = $target.prev();\n        $state.css({ ...style.state });\n        if ( this.props.floating != \"\" && event.target.value == \"\" ) $float.css({ ...style.float });\n    }\n\n    onTextChange( event ) {\n        if ( event.target.value == \"\" ) {\n            this.setState({ name : \"\", items: [] });\n            this.refs.dropdown.dataset.state = \"close\";\n        } else {\n            this.setState({name : event.target.value, items: this.filter( event.target.value ) });\n            this.refs.dropdown.dataset.state = \"open\";\n        }\n        this.props.onChange && this.props.onChange( name, event.target.value );\n    }\n\n    onDropdownClick( event ) {\n        if ( event.target.dataset.state == \"close\" ) {\n            this.setState({ name : this.refs.input.value, items: this.props.items });\n            event.target.dataset.state = \"open\";\n        } else {\n            this.setState({ name : \"\", items: [] });\n            event.target.dataset.state = \"close\";\n        }\n    }\n\n    onDropdownChange( name, value ) {\n        this.refs.input.value = value == undefined ? \"\" : value;\n        this.refs.dropdown.dataset.state = \"close\";\n        this.setState({ name : \"\", items: [] });\n        this.props.onChange && this.props.onChange( name, value );\n    }\n\n    filter( value ) {\n        return this.props.items.filter( obj => {\n            return obj.name.includes( value );\n        });\n    }\n\n    componentDidMount() {\n        this.refs.input.value = this.props.value;\n    }\n\n    render() {\n        const style       = { ...this.style };\n        style.input.color = this.props.color;\n        style.float       = this.props.placeholder == \"\" && this.props.value == \"\" ? style.float : { ...style.float, ...style.float_focus };\n        if ( this.props.borderColor ) {\n            style.border.borderTop    = `none ${this.props.borderColor}`;\n            style.border.borderLeft   = `none ${this.props.borderColor}`;\n            style.border.borderRight  = `none ${this.props.borderColor}`;\n            style.border.borderBottom = `1px solid ${this.props.borderColor}`;\n        }\n        if ( this.props.focusColor ) {\n            style.float_focus.color  = this.props.focusColor;\n            style.state.borderTop    = `none ${this.props.focusColor}`;\n            style.state.borderLeft   = `none ${this.props.focusColor}`;\n            style.state.borderRight  = `none ${this.props.focusColor}`;\n            style.state.borderBottom = `2px solid ${this.props.focusColor}`;\n        }\n\n        const props = {\n            placeholder :this.props.placeholder,\n            onFocus  : (e)=>this.onTextChangeFocus(e),\n            onBlur   : (e)=>this.onTextChangeBlur(e),\n            onChange : (e)=>this.onTextChange(e),\n            onKeyDown: (e)=>this.onTextKeyDown(e),\n        },\n        tooltip = this.props.tooltip;\n\n        return (\n            <auto-complete style={ style.root }\n                data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }>\n                <icon ref=\"dropdown\" style={ style.icon } data-state=\"close\" onClick={evt=>this.onDropdownClick(evt)}></icon>\n                <text-field-float style={ style.float }>{this.props.floating}</text-field-float>\n                <input ref=\"input\" style={ style.input } { ...props }/>\n                <ac-group>\n                    <text-field-border style={ style.border }/>\n                    <text-field-state style={ style.state }/>\n                </ac-group>\n                <ListView waves={ this.props.waves } onChange={ (n,v)=>this.onDropdownChange(n,v) }\n                    activeColor={ this.props.activeColor } hoverColor={ this.props.hoverColor }\n                    active={ this.state.name } items={ this.state.items } />\n            </auto-complete>\n        )\n    }\n\n}\n"
  },
  {
    "path": "src/vender/mduikit/button.jsx",
    "content": "/*!\n * React Material Design: Button\n * \n * @version : 0.0.4.1231\n * @update  : 2019/12/31\n * @homepage: https://github.com/kenshin/mduikit-ui\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Button ====\" )\n\nconst raisedstyle = {\n        color           : \"rgba(255, 255, 255, .7)\",\n        backgroundColor : \"rgba(0, 137, 123, 1)\",\n        hoverColor      : \"rgba( 255, 255, 255, .4)\",\n    },\n    flatstyle = {\n        color           : \"rgba(0, 137, 123, .8)\",\n        backgroundColor : \"transparent\",\n        hoverColor      : \"rgba( 153, 153, 153, .4)\"\n    },\n    secondary = {\n        flat: {\n            opacity: 0.6,\n        },\n        raised: {\n            backgroundColor: \"rgba( 153, 153, 153, .2)\",\n        }\n    },\n    disable = {\n        flat: {\n            cursor: \"no-drop\",\n            color: \"rgba(0, 0, 0, 0.298039)\",\n        },\n        raised: {\n            cursor: \"no-drop\",\n            color: \"rgba(0, 0, 0, 0.298039)\",\n            backgroundColor: \"rgb(229, 229, 229)\",\n            boxShadow: \"none\",\n        }\n    }\n\nconst cssinjs = () => {\n    const styles = {\n\n        root: {},\n        normal_root : {\n            display: 'block',\n\n            minWidth: '88px',\n            height: '36px',\n\n            margin: '6px',\n            padding: 0,\n\n            fontFamily: 'sans-serif',\n            textDecoration: 'none',\n\n            cursor: 'pointer',\n\n            border: 'none',\n            borderRadius: '2px',\n        },\n\n        mask: {\n            display: '-webkit-flex',\n            justifyContent: 'center',\n            alignItems: 'center',\n\n            width: '100%',\n            height: '100%',\n\n            margin: 0,\n            padding: '0 8px',\n\n            border: 'none',\n            borderRadius: '2px',\n            boxSizing: 'border-box',\n            transition: 'all .5s ease-in-out',\n\n            backgroundColor: 'transparent',\n        },\n\n        raised: {\n            boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)',\n        },\n\n        flat: {\n            fontWeight: 400,\n        },\n\n        span : {\n            display: 'flex',\n            alignItems: 'center',\n\n            userSelect: 'none',\n        },\n\n        text: {\n            padding: '0 8px 0',\n \n            textDecoration: 'none',\n            textAlign: 'center',\n            letterSpacing: '.5px',\n\n            fontSize: '15px',\n            lineHeight: '1',\n        },\n\n        icon: {\n            order: -1,\n            display: 'block',\n\n            width: '24px',\n            height: '24px',\n\n            border: 'none',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        font_icon: {\n            display: 'flex',\n            justifyContent: 'center',\n            alignItems: 'center',\n\n            fontSize: '18px',\n            color: '#fff',\n\n            order: -1,\n            display: 'block',\n\n            width: '24px',\n            height: '24px',\n\n            border: 'none',\n        },\n\n        circle: {\n            borderRadius: '50%',\n        },\n\n    }\n\n    return  styles;\n}\n\n/**\n * Custom <a> tag component: Button\n * \n * Reference:\n * - https://material.io/guidelines/components/buttons.html\n * - https://material.io/guidelines/components/buttons-floating-action-button.html\n * - http://www.material-ui.com/#/components/flat-button\n * - http://www.material-ui.com/#/components/raised-button\n * - http://www.material-ui.com/#/components/icon-button\n * \n * @class\n */\nexport default class Button extends React.Component {\n\n    static defaultProps = {\n        href    : \"javascript:;\",\n        target  : \"_self\",\n        text    : \"\",\n        disable : false,\n        icon    : \"\",\n        fontIcon: \"\",\n        order   : \"before\",\n        type    : \"flat\",\n        mode    : \"primary\",\n        shape   : \"rect\",\n        style   : undefined,\n        color   : \"\",\n        width   : undefined,\n        backgroundColor : \"\",\n        hoverColor : \"\",\n        tooltip : {},\n        waves   : undefined,\n    }\n\n    static propTypes = {\n        href    : React.PropTypes.string,\n        target  : React.PropTypes.string,\n        text    : React.PropTypes.string,\n        disable : React.PropTypes.bool,\n        icon    : React.PropTypes.string,\n        fontIcon: React.PropTypes.string,\n        order   : React.PropTypes.oneOf([ \"before\", \"after\" ]),\n        type    : React.PropTypes.oneOf([ \"flat\", \"raised\" ]),\n        mode    : React.PropTypes.oneOf([ \"primary\", \"secondary\" ]),\n        shape   : React.PropTypes.oneOf([ \"rect\", \"circle\" ]),\n        style   : React.PropTypes.object,\n        width   : React.PropTypes.string,\n        color   : React.PropTypes.string,\n        backgroundColor : React.PropTypes.string,\n        hoverColor : React.PropTypes.string,\n        tooltip : React.PropTypes.object,\n        waves   : React.PropTypes.string,\n        onClick : React.PropTypes.func,\n    }\n\n    state = {\n        color          : ((bool)=>bool ? flatstyle.color : raisedstyle.color)( this.props.type != \"raised\" ),\n        backgroundColor: ((bool)=>bool ? flatstyle.backgroundColor : raisedstyle.backgroundColor)( this.props.type != \"raised\" ),\n        hoverColor     : ((bool)=>bool ? flatstyle.hoverColor : raisedstyle.hoverColor)( this.props.type != \"raised\" ),\n    }\n\n    style = cssinjs()\n\n    onMouseOver() {\n        const [ style, $mask ] = [ { ...this.style }, $( this.refs.mask ) ];\n        $mask.css( \"background-color\", this.state.hoverColor );\n    }\n\n    onMouseOut() {\n        const [ style, $mask ] = [ { ...this.style }, $( this.refs.mask ) ];\n        this.props.shape == \"circle\" && ( style.mask.borderRadius = \"50%\" );\n        $mask.css({ ...style.mask });\n    }\n\n    onClick( event ) {\n        this.props.onClick && this.props.onClick( event );\n    }\n\n    componentWillMount() {\n        this.props.color != \"\"           && this.setState({ color: this.props.color });\n        this.props.backgroundColor != \"\" && this.setState({ backgroundColor: this.props.backgroundColor });\n        this.props.hoverColor != \"\"      && this.setState({ hoverColor: this.props.hoverColor });\n    }\n\n    render() {\n        const style   = $.extend( true, {}, this.style );\n        let   current = this.props.type == \"raised\" ? { ...style.raised } : { ...style.flat };\n        current.color = this.state.color;\n        current.backgroundColor = this.state.backgroundColor;\n\n        if ( this.props.text == \"\" && ( this.props.icon != \"\" || this.props.fontIcon != \"\" )) {\n            delete style.normal_root.minWidth;\n            delete style.normal_root.borderRadius;\n            style.normal_root.width = style.normal_root.height;\n        }\n\n        this.props.shape == \"circle\" &&\n            ( current = { ...current, ...style.circle } );\n\n        this.props.shape == \"circle\" && this.props.width &&\n            ( current.height = this.props.width );\n\n        this.props.mode == \"secondary\" &&\n            Object.keys( secondary[ this.props.type ] ).forEach( key => style.mask[ key ] = secondary[ this.props.type ][ key ] );\n\n        this.props.disable &&\n            Object.keys( disable[ this.props.type ] ).forEach( key => current[ key ] = disable[ this.props.type ][ key ] );\n\n        style.root = { ...style.normal_root, ...current };\n\n        this.props.style &&\n            ( style.root = { ...style.root, ...this.props.style } );\n\n        this.props.text  == \"\" && ( style.text.display = \"none\" );\n\n        if ( this.props.fontIcon != \"\" ) {\n            style.icon         = { ...style.font_icon };\n            style.icon.display = \"flex\";\n            style.icon.color   = style.color;\n        } else this.props.icon  != \"\" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = \"none\" );\n\n        this.props.order == \"after\" && ( style.icon.order = 1 );\n        this.props.width && ( style.root.width = this.props.width );\n\n        const events = this.props.disable ? {} : {\n                onMouseOver : ()=>this.onMouseOver(),\n                onMouseOut  : ()=>this.onMouseOut(),\n                onClick     : (e)=>this.onClick(e),\n        },\n        tooltip = this.props.tooltip;\n\n        this.props.shape == \"circle\" && ( style.mask.borderRadius = \"50%\" );\n\n        return (\n            <a  style={ style.root }\n                href={ this.props.href } target={ this.props.target }\n                type={ this.props.type } mode={ this.props.mode }\n                data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }\n                { ...events }>\n                <button-mask ref=\"mask\" style={ style.mask } class={ this.props.waves }>\n                    <button-span style={ style.span }>\n                        <button-icon style={ style.icon } dangerouslySetInnerHTML={{__html: this.props.fontIcon }} ></button-icon>\n                        <button-text style={ style.text }>{ this.props.text }</button-text>\n                    </button-span>\n                </button-mask>\n            </a>\n        )\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/dialog.jsx",
    "content": "/*!\r\n * React Material Design: Dialog\r\n * \r\n * @version : 0.0.3\r\n * @update  : 2018/03/15\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: Dialog ====\" )\r\n\r\nlet   root, rootjq;\r\nconst style = {\r\n\r\n    bg: {\r\n        display: '-webkit-flex',\r\n        justifyContent: 'center',\r\n        alignItems: 'center',\r\n\r\n        position: 'fixed',\r\n\r\n        top: '-100px',\r\n        left: 0,\r\n        width: '100%',\r\n        height: '100%',\r\n\r\n        color: '#fff',\r\n\r\n        textShadow: '0 1px rgba(0,0,0,0.3)',\r\n\r\n        opacity: 0,\r\n        transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\r\n\r\n        zIndex: 2147483646,\r\n    },\r\n\r\n    root: {\r\n        display: '-webkit-flex',\r\n        flexDirection: 'column',\r\n\r\n        minWidth: '480px',\r\n        minHeight: '300px',\r\n\r\n        margin: 0,\r\n        padding: 0,\r\n\r\n        color: 'rgba(0, 0, 0, 0.870588)',\r\n        backgroundColor: 'rgb(255, 255, 255)',\r\n\r\n        borderRadius: '3px',\r\n\r\n        boxSizing: 'border-box',\r\n        boxShadow: 'rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px',\r\n    },\r\n\r\n    content: {\r\n        display: 'block',\r\n\r\n        width: '100%',\r\n\r\n        minHeight: '244px',\r\n\r\n        padding: '39px 24px 0',\r\n\r\n        overflowY: 'auto',\r\n        boxSizing: 'border-box',\r\n    },\r\n\r\n    footer: {\r\n        display: '-webkit-flex',\r\n        flexFlow: 'row nowrap',\r\n        justifyContent: 'flex-end',\r\n\r\n        width: '100%',\r\n\r\n        boxSizing: 'border-box',\r\n    },\r\n\r\n};\r\n\r\n/**\r\n * Custom component: Dialog\r\n * \r\n * Reference:\r\n * - https://material.io/guidelines/components/dialogs.html\r\n * - http://www.material-ui.com/#/components/dialog\r\n * \r\n * @class\r\n */\r\nclass Dialog extends React.Component {\r\n\r\n    static defaultProps = {\r\n        onOpen : undefined,\r\n        onClose: undefined,\r\n    }\r\n\r\n    static propTypes = {\r\n        onOpen : React.PropTypes.func,\r\n        onClose: React.PropTypes.func,\r\n    }\r\n\r\n    componentDidMount() {\r\n        $( \"dialog-content\" ).height() < 585 && $( \"dialog-footer\" ).css( \"border-top\", \"none\" );\r\n        $( rootjq ).height() - $( \"dialog-content\" ).height() <= 200 &&\r\n            $( \"dialog-content\" ).height( $( rootjq ).height() - 200 );\r\n        $( rootjq ).css({ opacity: 1, top: 0 });\r\n        this.props.onOpen && this.props.onOpen();\r\n    }\r\n\r\n    componentWillUnmount() {\r\n        this.props.onClose && this.props.onClose();\r\n    }\r\n\r\n    render() {\r\n\r\n        let content, footer;\r\n\r\n        if ( this.props.children && !$.isArray( this.props.children )) {\r\n            content = this.props.children;\r\n        } else if ( this.props.children && $.isArray( this.props.children )) {\r\n            content = this.props.children[0];\r\n            footer  = this.props.children[1];\r\n        }\r\n\r\n        return (\r\n            <dialog-gp style={ style.root }>\r\n                { content }\r\n                { footer }\r\n            </dialog-gp>\r\n        )\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * React stateless component\r\n * \r\n * @param {object} props, include: children\r\n */\r\nconst Content = props => <dialog-content style={ style.content }>{ props.children }</dialog-content>,\r\n      Footer  = props => <dialog-footer  style={ style.footer  }>{ props.children }</dialog-footer>;\r\n\r\n/**\r\n * get Dialog root html element\r\n * \r\n * @param {jquery} jquery query selector\r\n * @param {string} class name\r\n * \r\n * @return {elem} html element\r\n */\r\nfunction getRoot( $target, cls ) {\r\n    [ root, rootjq ] = [ cls, `.${cls}` ];\r\n    $target.find( rootjq ).length == 0 && $target.append( `<div class=\"${ root }\"></div>` );\r\n    Object.keys( style.bg ).forEach( key => $( rootjq )[0].style[ key ] = style.bg[ key ] );\r\n    return $( rootjq )[0];\r\n}\r\n\r\n/**\r\n * Open Dialog\r\n * \r\n * @param {react}  react dom\r\n * @param {string} dialog background class name\r\n * @param {jquery} jquery query selector\r\n */\r\nfunction Open( reactDom, cls, $target = $(\"html\") ) {\r\n    ReactDOM.render( reactDom, getRoot( $target, cls ) );\r\n}\r\n\r\n/**\r\n * Close Dialog\r\n */\r\nfunction Close() {\r\n    $.fn.sreffect = $.fn.velocity == undefined ? $.fn.animate : $.fn.velocity; // hack code for firefox\r\n    $( rootjq )\r\n        .css({ top: \"-100px\" })\r\n        .sreffect({ opacity: 0 }, { complete: ()=>{\r\n            ReactDOM.unmountComponentAtNode( $( rootjq )[0] );\r\n            $( rootjq ).remove();\r\n        }});\r\n}\r\n\r\n/**\r\n * Verify dialog is popup\r\n * \r\n * @param  {string} jquery selector\r\n * @return {boolean}\r\n */\r\nfunction Popup( clsjq ) {\r\n    return $( clsjq ).children().length == 0 ? false : true;\r\n}\r\n\r\nexport {\r\n    Dialog,\r\n    Content,\r\n    Footer,\r\n    Open,\r\n    Close,\r\n    Popup,\r\n}"
  },
  {
    "path": "src/vender/mduikit/dropdown.jsx",
    "content": "/*!\n * React Material Design: Dropdown\n * \n * @version : 0.0.3\n * @update  : 2018/06/27\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2018\n */\n\nconsole.log( \"==== simpread component: Dropdown ====\" )\n\nconst color            = 'rgba(51, 51, 51, .87)',\n      secondary_color  = 'rgba(204, 204, 204, 1)',\n\n      focus_color      = 'rgba(0, 137, 123, .8)',\n      border_color     = 'rgba(224, 224, 224, 1)',\n      error_color      = 'rgba(244, 67, 54, 1)',\n\n      selected_color   = 'rgba(255, 64, 129, 1)',\n      hover_color      = 'rgba(238, 238, 238, 1)',\n      background_color = 'rgba(255, 255, 255, 1)';\n\nconst cssinjs = () => {\n\n    const display     = 'block',\n          width       = '100px',\n          margin      = '8px 0 0 0',\n          medium      = '14px',\n          large       = '16px',\n          lineHeight  = 1.5,\n          fontWeight  = 'bold',\n          styles      = {\n            hidden : 'none',\n            root: {},\n            root_normal: {\n                position: 'relative',\n\n                display: 'flex',\n                alignItems: 'center',\n\n                margin: 0,\n                padding: 0,\n\n                height: '36px',\n                width,\n                lineHeight: 1,\n\n                cursor: 'pointer',\n                userSelect: 'none',\n            },\n\n            disable: {\n                color: secondary_color,\n                cursor: 'not-allowed',\n            },\n\n            text: {\n                display,\n\n                margin: 0,\n                padding: \"8px 24px 8px 0\",\n\n                width: '100%',\n                textAlign: 'left',\n            },\n\n            icon: {\n                display: 'block',\n                position: 'absolute',\n\n                width: '24px',\n                height: '24px',\n\n                top: '6px',\n                right: 0,\n\n                border: 'none',\n                backgroundPosition: 'center',\n                backgroundRepeat: 'no-repeat',\n                backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABqSURBVEiJ7dQxCsAgDIXhZ8ktgmetVw31GIF06lI0yeIWJyH4f4hgMzOcXNfRegEFFAAAoGA+ROR2A0STmftu7t5ARAYRTS+uqtt4CACAqvYVkomngBWSjQPxG/yR59tnz7X6rgso4DzwAnJQKlbAmFdgAAAAAElFTkSuQmCC)',\n            },\n\n            bg: {\n                display: 'none',\n                position: 'fixed',\n\n                top: 0,\n                left: 0,\n\n                width: '100%',\n                height: '100%',\n            },\n\n        };\n\n    return styles;\n}\n\nconst cssinjs_list = () => {\n\n    const styles = {\n        hidden : 'none',\n        root : {},\n        root_normal : {\n            display: 'block',\n            position: 'absolute',\n\n            top: 0,\n            left: 0,\n\n            margin: 0,\n            padding: 0,\n\n            width: '100%',\n            maxHeight: '400px',\n\n            color,\n            backgroundColor: background_color,\n\n            boxSizing: 'border-box',\n            boxShadow: '0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2)',\n            borderRadius: '2px',\n\n            zIndex: 2100,\n\n            overflowY: 'auto',\n\n            opacity: 0,\n            transform: 'scaleY(0)',\n            transformOrigin: 'left top 0px',\n            transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        open: {\n            opacity: 1,\n            transform: 'scaleY(1)',\n        },\n\n        list_filed: {\n            display: 'flex',\n            alignItems: 'center',\n\n            padding: '8px 24px 8px 16px',\n\n            height: '36px',\n            width: '100%',\n\n            textAlign: 'left',\n\n            boxSizing: 'border-box',\n            transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        list_filed_icon: {\n            display: 'block',\n\n            width: '18px',\n            height: '18px',\n\n            margin: '0 10px 0 0',\n            padding: '10px',\n\n            border: 'none',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        list_filed_value: {\n            display: 'inline',\n            width: '100%',\n            fontSize: 'inherit',\n        },\n\n        list_filed_info: {\n            display: 'inline',\n            padding: '0 0 0 16px',\n\n            fontSize: '13px',\n            textAlign: 'right',\n\n            minWidth: '100px',\n        },\n\n    }\n\n    return styles;\n}\n\nclass ListView extends React.Component {\n\n    static defaultProps = {\n        waves           : \"\",\n        items           : [],\n        active          : \"\",\n    };\n\n    static propTypes = {\n        waves           : React.PropTypes.string,\n        items           : React.PropTypes.array,\n        active          : React.PropTypes.string,\n        onChange        : React.PropTypes.func,\n    };\n\n    style = cssinjs_list();\n\n    onMouseOver( event ) {\n        const $target = $( event.target );\n        if ( $target.is( \"list-field\" ) ) {\n            $( \"list-field[active=true]\" ).css( \"background-color\", \"transparent\" ).attr( \"active\", false );\n            $target.attr( \"active\", true ).css( \"background-color\", hover_color );\n        }\n    }\n\n    onClick( event ) {\n        let $target = $( event.target );\n        while ( !$target.is( \"list-field\" )) { $target = $target.parent(); }\n        setTimeout( ()=>this.props.onChange && this.props.onChange( $target.find( \"list-field-name\" ).attr( \"value\" ), $target.find( \"list-field-name\" ).text() ), 130 );\n    }\n\n    render() {\n        const style = { ...this.style };\n        style.root  = this.props.items.length > 0 ? { ...style.root_normal, ...style.open } : { ...style.root_normal };\n\n        const list = this.props.items.map( ( item, idx ) => {\n            let [ name_style, icon_style ] =[ { ...style.list_filed_value }, { ...style.list_filed_icon }];\n            if ( item.icon && item.icon != \"\" ) {\n                icon_style.backgroundImage = `url(${ item.icon })`;\n            } else {\n                icon_style.display = style.hidden;\n            }\n            item.name == this.props.active && ( name_style.color = selected_color );\n            item.style && item.style.root  && ( style.list_filed = { ...style.list_filed, ...item.style.root });\n            item.style && item.style.icon  && ( icon_style       = { ...icon_style, ...item.style.icon });\n            item.style && item.style.text  && ( name_style       = { ...name_style, ...item.style.text });\n            return (\n                <list-field class={ this.props.waves } style={ style.list_filed } onMouseOver={ (e)=>this.onMouseOver(e) } onClick={ (e)=>this.onClick(e) }>\n                    <i style={ icon_style }></i>\n                    <list-field-name style={ name_style } value={ item.value }>{ item.name }</list-field-name>\n                </list-field>\n            )\n        });\n\n        return (\n            <list-view style={ style.root }>\n                { list }\n            </list-view>\n        )\n    }\n}\n\n/**\n * Custom component: Dropdown\n * \n * Reference: \n * - https://aboutme.google.com/u/0/?referer=gplus\n * - https://plus.google.com/settings\n * \n * @class\n */\nexport default class Dropdown extends React.Component {\n\n    static defaultProps = {\n        name        : \"\",\n        disable      : false,\n        width        : undefined,\n        items        : [],\n        waves        : \"\",\n    };\n\n    static propTypes = {\n        name         : React.PropTypes.string,\n        disable      : React.PropTypes.bool,\n        width        : React.PropTypes.string,\n        items        : React.PropTypes.array,\n        waves        : React.PropTypes.string,\n        onChange     : React.PropTypes.func,\n    };\n\n    state = {\n        name : this.props.name,\n    };\n\n    style = cssinjs();\n\n    onClick() {\n        !this.props.disable && this.props.items.length > 0 && this.setState({ items: this.props.items });\n        !this.props.disable && this.props.items.length > 0 && $( this.refs.bg ).css( \"display\", \"block\" );\n    }\n\n    bgOnClick() {\n        $( this.refs.bg ).css( \"display\", \"none\" );\n        this.setState({ items : [] });\n    }\n\n    onChange( value, name ) {\n        this.props.onChange && this.props.onChange( value, name );\n        this.setState({\n            items : [],\n            name,\n        });\n        $( this.refs.bg ).css( \"display\", \"none\" );\n    }\n\n    render() {\n        const maxwidth = items => {\n            items.sort( ( a, b ) => b.name.length - a.name.length );\n            return items[0].name.length * 12;\n        },\n        style = { ...this.style };\n\n        style.root = this.props.disable ? { ...style.root_normal, ...style.disable } : { ...style.root_normal };\n        this.props.disable && ( style.border = { ...style.border, ...style.border_disable });\n\n        if ( !this.props.width ) {\n            const max = maxwidth( this.props.items ) + 40;\n            max >= 100 && ( style.root.width = `${max}px` );\n        } else style.root.width = this.props.width;\n\n        return (\n            <dropdown style={ style.root }>\n                <span style={ style.text } onClick={ ()=> this.onClick() }>\n                    { this.state.name == \"\" ? this.props.name : this.state.name }\n                </span>\n                <icon style={ style.icon }></icon>\n                <ListView waves={ this.props.waves } active={ this.state.name } items={ this.state.items } onChange={ (v,n)=>this.onChange(v,n) } />\n                <list-bg ref=\"bg\" style={ style.bg } onClick={ ()=>this.bgOnClick() }></list-bg>\n            </dropdown>\n        )\n\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/fab.jsx",
    "content": "/*!\n * React Material Design: FAB( Floating Action Button )\n * \n * @version : 0.0.3.0109\n * @update  : 2020/01/09\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Floating Action Button ====\" )\n\nconst cssinjs = () => {\n    const spec_color = 'rgba(244, 67, 54, 1)',\n          normal_color= 'rgba(245, 82, 70, .8)',\n          focus_color = 'rgba(243, 52, 38, .9)',\n          styles      = {\n\n              root : {\n                display: '-webkit-box',\n                WebkitBoxAlign: 'center',\n                WebkitBoxOrient: 'vertical',\n                WebkitBoxDirection: 'reverse',\n                position: 'fixed',\n\n                bottom: '45px',\n                right: '24px',\n\n                width: 'auto',\n                height: 'auto',\n              },\n\n              bg: {\n                position: 'fixed',\n\n                bottom: '45px',\n                right: 0,\n\n                width: '100px',\n                height: '100%',\n                zIndex: 2147483647,\n              },\n\n              origin : {\n                display: 'block',\n                position: 'relative',\n\n                margin: '0 0 15px',\n                padding: 0,\n\n                width: '40px',\n                height: '40px',\n                lineHeight: '40px',\n\n                color: '#fff',\n                backgroundColor: normal_color,\n\n                borderRadius: '50%',\n\n                cursor: 'pointer',\n                boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)',\n              },\n\n              large : {\n                width: '56px',\n                height: '56px',\n                lineHeight: '56px',\n              },\n\n              spec: {},\n              normal: {},\n\n              spec_item : {\n                backgroundColor: spec_color,\n                transform: 'rotate(0deg)',\n              },\n\n              spec_focus : {\n                  backgroundColor: focus_color,\n                  transition: 'all 450ms 0ms',\n                  transform: 'rotate(45deg)',\n              },\n\n              normal_focus : {\n                  transition: 'all 450ms 0ms',\n                  boxShadow: '0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15)',\n              },\n\n              spec_icon: {},\n              normal_icon: {},\n\n              icon : {\n                  display: 'block',\n                  width: '100%',\n                  height: '100%',\n                  border: 'none',\n                  backgroundPosition: 'center',\n                  backgroundRepeat: 'no-repeat',\n              },\n\n              ul : {\n                display: '-webkit-flex',\n                position: 'initial',\n                flexFlow: 'column nowrap',\n\n                listStyle: 'none',\n\n                padding: 0,\n                margin: 0,\n\n                opacity: 0,\n                visibility: 'hidden',\n                transition: 'opacity 1s ease',\n              },\n\n              li : {\n                  margin: 0,\n              },\n\n              ul_hori: {\n                display: '-webkit-flex',\n                position: 'absolute',\n\n                flexFlow: 'row nowrap',\n                listStyle: 'none',\n\n                right: '48px',\n              },\n\n              li_hori : {\n                  margin: '0 15px 0 0',\n              },\n\n          };\n    return styles;\n};\n\n/**\n * Button react stateless component\n * \n * @param {object} react props, include:\n *   - id          : [PropTypes.string] identify\n *   - type        : [PropTypes.string] type, include: sepc, anchor, normal\n *   - style       : [PropTypes.object] <a> style\n *   - name        : [PropTypes.string] name\n *   - color       : [PropTypes.string] background color\n *   - icon        : [PropTypes.object] { style, path }\n *   - tooltip     : [PropTypes.object] tooltip \n *   - waves       : [PropTypes.string] waves \n *   - onClick     : [PropTypes.func]   click event handler\n *   - onMouseOver : [PropTypes.func]   mouse over event handler\n *   - onMouseOut  : [PropTypes.func]   mouse out event handler\n */\nconst Button = ( props ) => {\n    props.icon.style.backgroundImage = `url(${props.icon.path})`;\n    if ( props.color ) {\n        props.style.backgroundColor = props.color;\n    } else {\n        props.color = props.style.backgroundColor;\n    }\n    const tooltip =  props.type == \"anchor\" ? \"\" : ( props.tooltip.text ? props.tooltip.text : props[ props.tooltip.target ] );\n    return (\n        <a style={ props.style } className={  props.waves }\n           data-tooltip={ tooltip } data-tooltip-position={ props.tooltip.position } data-tooltip-delay={ props.tooltip.delay } >\n            <i \n                id={ props.id }\n                type={ props.type }\n                name={ props.name }\n                color={ props.color }\n                style={ props.icon.style }\n                onClick={ evt=>props.onClick(evt) }\n                onMouseOver={ evt=> props.onMouseOver(evt) }\n                onMouseOut={ evt=> props.onMouseOut(evt) }\n            ></i>\n        </a>\n    )\n};\n\n/**\n * ListView react stateless component\n * \n * @param {object} react props, include:\n *   - id           : [PropTypes.string] identify\n *   - style        : [PropTypes.object]  <li> style \n *   - child        : [PropTypes.element] <ul> child\n *   - onMouseLeave : [PropTypes.func]    <li> mouse leave event handler\n *   - btn_props    : [PropTypes.object]  <Button> component props\n */\nconst ListView = ( props ) => {\n    return (\n        <li id={ props.id } style={ props.child ? props.style.li : props.style.li_hori } onMouseLeave={ evt=> props.onMouseLeave(evt) }>\n            <Button { ...props.btn_props } />\n            { props.child && props.child.length > 0 && <ul type=\"hori\" style={{ ...props.style.ul, ...props.style.ul_hori }}>{ props.child }</ul> }\n        </li>\n    )\n};\n\n/**\n * Custom component: Floating action button, component e.g.\n * \n    <Button id={ \"exit\" } name={\"退出\"} icon={ {style.spec_icon, `${path}assets/images/exit_icon.png`} } type={ \"spec\" }   style={ style.spec } />\n    <Button id={ \"more\" } name={\"更多\"} icon={ {style.icon, `${path}assets/images/more_icon.png` } }     type={ \"anchor\" } style={ style.origin } />\n    <ul style={ style.ul }>\n        <li style={ style.li } onMouseLeave={ evt=> this.liMouseLeaveHandler(evt) } >\n            <Button id={ \"fontsize\" } name={\"字体大小\"} icon={ {style.icon, `${path}assets/images/fontsize_icon.png` } } color=\"#9E9E9E\" type={ \"normal\" } style={ style.origin } />\n            <ul style={{ ...style.ul, ...style.ul_hori }}>\n                <li style={ style.li_hori } ><Button id={ \"fontsizedown\"  } name={\"减小\"} icon={ {style.icon, `${path}assets/images/fontsize_small_icon.png`  } } color=\"#9E9E9E\" type={ \"normal\" } style={ style.origin } /></li>\n            </ul>\n        </li>\n        <li style={ style.li } onMouseLeave={ evt=> this.liMouseLeaveHandler(evt) } >\n            <Button id={ \"weight\" } name={\"版面布局\"} icon={ {style.icon, `${path}assets/images/weight_icon.png` } } color=\"#FFEB3B\" type={ \"normal\" } style={ style.origin } />\n        </li>\n    </ul>\n * \n * Reference:\n * - https://material.io/guidelines/components/buttons-floating-action-button.html\n * - http://materializecss.com/buttons.html\n * \n * @class\n */\nexport default class Fab extends React.Component {\n\n    static defaultProps = {\n        items   : {},\n        tooltip : {},\n        waves   : undefined,\n    }\n\n    static propTypes = {\n        items    : React.PropTypes.object,\n        tooltip  : React.PropTypes.object,\n        waves    : React.PropTypes.string,\n        onAction : React.PropTypes.func,\n    }\n\n    state = {\n        keys : [],\n        items: {},\n    }\n\n    style = cssinjs();\n\n    maxWidth = -1;\n\n    btnClickHandler( event ) {\n        const type = $( event.target ).attr( \"id\" );\n        if ( this.props.onAction ) this.props.onAction( event, type );\n    }\n\n    btnMouseOverHandler( event ) {\n        const $target = $( event.target ),\n              type    = $target.attr( \"type\" ),\n              style   = { ...this.style };\n        if ( type == \"spec\" ) {\n            $target.parent().css({ ...style.spec, ...style.spec_focus });\n        } else {\n            $target.parent().css({ ...style.normal, ...style.normal_focus });\n            if ( $target.parent().next() && $target.parent().next().is( \"ul\" ) ) {\n                $( this.refs.root ).find( \"ul[type=hori]\" ).css( \"opacity\", 0 ).css( \"visibility\", \"hidden\" );\n                $target.parent().next().css( \"opacity\", 1 ).css( \"visibility\", \"visible\" );\n            }\n        }\n        $( this.refs.root ).width( this.maxWidth + 100 );\n    }\n\n    btnMouseOutHandler( event ) {\n        const $target = $( event.target ),\n              type    = $target.attr( \"type\" ),\n              style   = { ...this.style };\n        const color = $target.attr( \"color\" );\n        if ( type == \"spec\" ) {\n            $target.parent().css({ ...style.origin, ...style.large, ...style.spec_item });\n        } else {\n            if ( color ) style.origin.backgroundColor = color;\n            $target.parent().css({ ...style.origin });\n        }\n    }\n\n    fabMouseOutHandler( event ) {\n        const style = { ...this.style };\n        $( event.target ).find(\"fab\").find( \"ul\" ).css( \"opacity\", 0 ).css( \"visibility\", \"hidden\" );\n        $( this.refs.root ).css({ ...style.bg });\n    }\n\n    liMouseLeaveHandler( event ) {\n        return;\n        const $target = $( event.target );\n        if ( $target.is( \"i\" ) ) {\n            $target.parent().next().css( \"opacity\", 0 ).css( \"visibility\", \"hidden\" );\n        } else if ( $target.is( \"li\" ) ) {\n            $target.find(\"ul\").css( \"opacity\", 0 ).css( \"visibility\", \"hidden\" );\n        }\n    }\n\n    componentWillMount() {\n        const keys  = Object.keys( this.props.items ),\n              items = { ...this.props.items };\n        if ( keys.length > 1 ) {\n            keys.splice( 1, 0, \"anchor\" );\n            items[ \"anchor\" ] = {\n                \"name\" : \"更多\",\n                \"icon\" : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAANElEQVQ4T+3GMQ0AIAwAMAwSEvwLACai3HtmAHq1te8xpnCM6okAu3rigFU9MWxLr/695AI0E1VgH26hCQAAAABJRU5ErkJggg==',\n            }\n        }\n        this.setState({ keys, items });\n    }\n\n    componentDidMount() {\n        const $root  = $( \"fab\" ),\n              $ul    = $($root.children()[2]);\n        if ( $ul.is(\"ul\") ) {\n            $ul.children().map( ( idx, item )=> {\n                const $ul = $(item).find( \"ul\" );\n                if ( $ul ) {\n                    $ul.width() > this.maxWidth && ( this.maxWidth = $ul.width() );\n                    $ul.css( \"top\", `${idx * $ul.height()}px` );\n                }\n            });\n        }\n    }\n\n    componentWillUnmount() {\n        this.setState({\n            keys : [],\n            items: {}\n        });\n    }\n\n    render() {\n        const style = { ...this.style };\n        let spec, anchor, others = [];\n\n        const keys = this.state.keys,\n              btn_props = ( id, type, style, { name, color, icon }, icon_style, tooltip, waves )=> {\n                return {\n                    id,\n                    type,\n                    style,\n                    name,\n                    color,\n                    tooltip,\n                    waves,\n                    icon       : { style: icon_style, path: icon },\n                    onClick    : evt=>this.btnClickHandler(evt),\n                    onMouseOver: evt=>this.btnMouseOverHandler(evt),\n                    onMouseOut : evt=>this.btnMouseOutHandler(evt),\n                };\n            },\n            list = ( items, key, style, child, tooltip, waves ) => {\n                const props = btn_props( key, \"normal\", style.origin, items, style.icon, tooltip, waves );\n                return <ListView id={ key } child={ child } style={ style } btn_props={ props } onMouseLeave={ evt=> this.liMouseLeaveHandler(evt) } />\n        };\n\n        if ( keys.length > 0 ) {\n            style.spec = { ...style.origin, ...style.large, ...style.spec_item };\n            spec = <Button { ...btn_props( keys[0], \"spec\", style.spec, this.state.items[keys[0]], style.icon, this.props.tooltip, this.props.waves ) } />;\n        }\n\n        keys.length > 1 && ( anchor = <Button { ...btn_props( keys[1], \"anchor\", style.origin, this.state.items[keys[1]], style.icon, this.props.tooltip, this.props.waves ) } /> );\n\n        for( let idx = keys.length - 1; idx >= 2; idx-- ) {\n            const child   = [],\n                  items   = this.state.items[ keys[idx] ].items;\n            if ( items ) {\n                const subkeys = Object.keys( items );\n                for ( let j = 0; j < subkeys.length; j++ ) {\n                    child.push( list( items[subkeys[j]], subkeys[j], style, undefined, this.props.tooltip, this.props.waves ) )\n                }\n            }\n            others.push( list( this.state.items[keys[idx]], keys[idx], style, child, this.props.tooltip, this.props.waves ));\n        }\n        others.length > 0 && ( others = ( <ul style={ style.ul }>{ others }</ul> ) );\n\n        return (\n            <fab-bg ref=\"root\" style={ style.bg } onMouseLeave={ evt=>this.fabMouseOutHandler(evt) }>\n                <fab style={ style.root }>\n                    { spec   }\n                    { anchor }\n                    { others }\n                </fab>\n            </fab-bg>\n        )\n    }\n\n}"
  },
  {
    "path": "src/vender/mduikit/fap.jsx",
    "content": "/*!\n * React Material Design: FAP( Floating Action Panel )\n * \n * @version : 0.0.1.1231\n * @update  : 2018/04/19\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Floating Action Panel ====\" )\n\nconst cssinjs = () => {\n    const spec_color = 'rgba(244, 67, 54, 1)',\n          normal_color= 'rgba(245, 82, 70, .8)',\n          focus_color = 'rgba(243, 52, 38, .9)',\n          styles      = {\n\n              root : {\n                display: '-webkit-box',\n                WebkitBoxAlign: 'center',\n                WebkitBoxOrient: 'vertical',\n                WebkitBoxDirection: 'reverse',\n                position: 'fixed',\n\n                bottom: '45px',\n                right: '24px',\n\n                width: 'auto',\n                height: 'auto',\n              },\n\n              origin : {\n                display: 'block',\n                position: 'relative',\n\n                margin: '0 0 15px',\n                padding: 0,\n\n                width: '40px',\n                height: '40px',\n                lineHeight: '40px',\n\n                color: '#fff',\n                backgroundColor: normal_color,\n\n                borderRadius: '50%',\n\n                cursor: 'pointer',\n                boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)',\n              },\n\n              large : {\n                width: '56px',\n                height: '56px',\n                lineHeight: '56px',\n                zIndex: 2,\n              },\n\n              spec: {},\n              normal: {},\n\n              spec_item : {\n                backgroundColor: spec_color,\n                transform: 'rotate(0deg)',\n              },\n\n              spec_focus : {\n                  backgroundColor: focus_color,\n                  transition: 'all 450ms 0ms',\n                  transform: 'rotate(45deg)',\n              },\n\n              normal_focus : {\n                  transition: 'all 450ms 0ms',\n                  boxShadow: '0 5px 11px 0 rgba(0,0,0,0.18), 0 4px 15px 0 rgba(0,0,0,0.15)',\n              },\n\n              spec_icon: {},\n              normal_icon: {},\n\n              icon : {\n                  display: 'block',\n                  width: '100%',\n                  height: '100%',\n                  border: 'none',\n                  backgroundPosition: 'center',\n                  backgroundRepeat: 'no-repeat',\n              },\n\n              panel_bg :{\n                display: 'none',\n                position: 'fixed',\n\n                top: 0,\n                left: 0,\n\n                width: '100%',\n                height: '100%',\n              },\n\n          };\n    return styles;\n};\n\nconst cssinjs_panel = () => {\n\n    const styles = {\n          activeColor: 'rgba(0, 137, 123, .8)',\n          root : {\n              display: '-webkit-flex',\n              flexDirection: 'column',\n\n              position: 'absolute',\n              right: '32px',\n              bottom: '185px',\n\n              minWidth: '380px',\n              minHeight: '300px',\n              maxWidth: '500px',\n              width: '100%',\n\n              margin: 0,\n              padding: '39px 24px 0',\n\n              color: 'rgba(0, 0, 0, 0.870588)',\n              backgroundColor: 'rgb(255, 255, 255)',\n\n              borderRadius: '3px',\n\n              boxSizing: 'border-box',\n              boxShadow: '0 0 2px rgba(0,0,0,0.12), 0 2px 2px rgba(0,0,0,0.26)',\n\n              opacity: 0,\n              visibility: 'hidden',\n              transition: 'opacity 300ms ease',\n\n              zIndex: 2147483647,\n          },\n\n          panel_tabs: {\n            display: 'flex',\n            flexDirection: 'row',\n            justifyContent: 'space-around',\n\n            fontSize: '14px',\n\n            borderBottom: '2px solid #E0E0E0',\n          },\n\n          panel_tab: {\n            position: 'relative',\n            padding: '0px 24px 5px 24px',\n            width: '100%',\n\n            cursor: 'pointer',\n          },\n\n          panel_tab_label: {\n            display: '-webkit-box',\n            flexShrink: 1,\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n          },\n\n          panel_border: {\n            position: 'absolute',\n            bottom: 0,\n            left: 0,\n            right: 0,\n\n            marginBottom: '-2px',\n\n            fontWeight: 500,\n            color: '#3273dc',\n\n            borderBottom: '2px solid ',\n\n            transform: 'scaleX(0)',\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n          },\n\n          panel_border_active: {\n            transform: 'scaleX(1)',\n          },\n\n          groups: {\n            display: 'block',\n            width: '100%',\n            overflowX: 'hidden',\n            overflowY: 'auto',\n          },\n\n          group: {\n            display: 'none',\n            padding: '39px 0px 10px',\n          },\n\n          group_active: {\n            display: 'block',\n            opacity: 1,\n          },\n    }\n    return styles;\n}\n\nclass Panel extends React.Component {\n\n    static defaultProps = {\n        items      : [],\n        autoHeight : false,\n        activeColor: undefined,\n    };\n\n    static propTypes = {\n        item       : React.PropTypes.array,\n        autoHeight : React.PropTypes.bool,\n        activeColor: React.PropTypes.string,\n\n        onOpen     : React.PropTypes.func,\n        onClose    : React.PropTypes.func,\n    };\n\n    style = cssinjs_panel()\n\n    onTabChange( event ) {\n        const style   = { ...this.style };\n        let   $target = $( event.target ),\n              active  = $target.attr( \"active\" ),\n              idx     = $target.attr( \"idx\" ),\n              tag     = event.target.tagName.toLowerCase();\n        while ( tag  != \"panel-tab\" ) {\n            $target   = $target.parent();\n            active    = $target.attr( \"active\" );\n            idx       = $target.attr( \"idx\" );\n            tag       = $target[0].tagName.toLowerCase();\n        }\n        if ( active == \"false\" ) {\n            const $others = $( \"panel-tab[active=true]\" );\n            $target.attr( \"active\", true );\n            $target.find( \"panel-border\" ).css({ ...style.panel_border, ...style.panel_border_active });\n\n            $others.attr( \"active\", false );\n            $others.find( \"panel-border\" ).css({ ...style.panel_border });\n\n            $( `panel-group[idx=${idx}]`  ).attr( \"active\", true ).css({ ...style.group, ...style.group_active });\n            $( `panel-group[idx!=${idx}]` ).attr( \"active\", false ).css({ ...style.group });\n        }\n    }\n\n    autoHeight() {\n        let maxHeight = 0;\n        $( \"panel-groups\" ).children().map( ( idx, item ) => {\n            if ( maxHeight == 0 ) {\n                maxHeight = $(item).height();\n            } else if ( $(item).height() > maxHeight ) {\n                maxHeight = $(item).height();\n            }\n        });\n        $( \"panel-groups\" ).height( maxHeight );\n    }\n\n    offsetHeight() {\n        const $target = $( this.refs.panel ),\n              offset  = $target.position().top,\n              $groups = $target.find( \"panel-groups\" ),\n              height  = $groups.height();\n        if ( offset < 0 ) {\n            $groups.height( height + offset - 32 ).css({ \"padding-right\": \"20px\" });\n        }\n    }\n\n    componentDidMount() {\n        $( this.refs.panel ).css( \"opacity\", 1 ).css( \"visibility\", \"visible\" );\n        this.props.autoHeight == false && this.autoHeight();\n        this.offsetHeight();\n        this.props.onOpen();\n    }\n\n    componentWillUnmount() {\n        this.props.onClose();\n    }\n\n    render() {\n        const style = { ...this.style };\n\n        style.panel_border.borderBottom = style.panel_border.borderBottom + ( this.props.activeColor ? this.props.activeColor : style.activeColor );\n\n        const active_border = { ...style.panel_border, ...style.panel_border_active },\n              active_group  = { ...style.group, ...style.group_active },\n              events        = this.props.tabAutoSel ? { onMouseEnter: evt => this.onTabChange(evt) } : { onClick: evt => this.onTabChange(evt) },\n              tabs          = this.props.items.map( ( item, idx ) => {\n                return <panel-tab style={ style.panel_tab } active={ idx == 0 ? \"true\" : \"false\" } idx={ idx } { ...events } >\n                        <span style={ style.panel_tab_label }>{item}</span>\n                        <panel-border style={ idx == 0 ? active_border : { ...style.panel_border } }></panel-border>\n                    </panel-tab>;\n             }),\n             groups         = this.props.children.map( ( child, idx ) => {\n                return <panel-group style={ idx == 0 ? active_group : { ...style.group } } active={ idx == 0 ? \"true\" : \"false\" } idx={ idx }>{child}</panel-group>;\n             });\n\n        return (\n            <panel ref=\"panel\" style={ style.root }>\n                <panel-tabs style={ style.panel_tabs }>\n                    { tabs }\n                </panel-tabs>\n                <panel-groups style={ style.groups }>\n                    { groups }\n                </panel-groups>\n            </panel>\n        )\n    }\n}\n\n/**\n * Button react stateless component\n * \n * @param {object} react props, include:\n *   - id          : [PropTypes.string] identify\n *   - type        : [PropTypes.string] type, include: sepc, anchor, normal\n *   - style       : [PropTypes.object] <a> style\n *   - name        : [PropTypes.string] name\n *   - color       : [PropTypes.string] background color\n *   - icon        : [PropTypes.object] { style, path }\n *   - tooltip     : [PropTypes.object] tooltip \n *   - waves       : [PropTypes.string] waves \n *   - onClick     : [PropTypes.func]   click event handler\n *   - onMouseOver : [PropTypes.func]   mouse over event handler\n *   - onMouseOut  : [PropTypes.func]   mouse out event handler\n */\nconst Button = ( props ) => {\n    props.icon.style.backgroundImage = `url(${props.icon.path})`;\n    if ( props.color ) {\n        props.style.backgroundColor = props.color;\n    } else {\n        props.color = props.style.backgroundColor;\n    }\n    const tooltip =  props.type == \"anchor\" ? \"\" : ( props.tooltip.text ? props.tooltip.text : props[ props.tooltip.target ] );\n    return (\n        <a style={ props.style } className={  props.waves }\n           data-tooltip={ tooltip } data-tooltip-position={ props.tooltip.position } data-tooltip-delay={ props.tooltip.delay } >\n            <i \n                id={ props.id }\n                type={ props.type }\n                name={ props.name }\n                color={ props.color }\n                style={ props.icon.style }\n                onClick={ evt=>props.onClick(evt) }\n                onMouseOver={ evt=> props.onMouseOver(evt) }\n                onMouseOut={ evt=> props.onMouseOut(evt) }\n            ></i>\n        </a>\n    )\n};\n\n/**\n * Custom component: Floating Action Panel, component e.g.\n * \n    <Button id={ \"exit\" } name={\"退出\"} icon={ {style.spec_icon, `${path}assets/images/exit_icon.png`} } type={ \"spec\" }   style={ style.spec } />\n    <Button id={ \"more\" } name={\"更多\"} icon={ {style.icon, `${path}assets/images/more_icon.png` } }     type={ \"anchor\" } style={ style.origin } />\n    <panel-bg>\n        <panel>\n            <panel-tabs>\n                <panel-tab active=\"true\">\n                    <span>设定</span>\n                    <panel-border></panel-border>\n                </panel-tab>\n                <panel-tab>\n                    <span>动作</span>\n                    <panel-border></panel-border>\n                </panel-tab>\n                <panel-tab>\n                    <span>脚本</span>\n                    <panel-border></panel-border>\n                </panel-tab>\n        </panel-tabs>\n            <panel-groups>\n                <panel-group>设定</panel-group>\n                <panel-group>动作</panel-group>\n                <panel-group>脚本</panel-group>\n            </panel-groups>\n        </panel>\n    </panel-bg>\n * \n * Reference:\n * - https://material.io/guidelines/components/tabs.html\n * - https://bulma.io/documentation/components/tabs/\n * \n * @class\n */\nexport default class Fap extends React.Component {\n\n    static defaultProps = {\n        items       : [],\n        autoHeight  : false,\n        activeColor : undefined,\n        autoHide    : true,\n        tabAutoSel  : true,\n\n        triggerItems: {\n            \"exit\"  : {\n                \"name\": \"关闭\",\n                \"icon\": \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAdklEQVQ4y+WTuQ3AIBAEaQKK8NN/BEUArmccgGyj43MMIZo5TqtFqbUPJxYtbg2OvS44IJQKhguwdUETSiXjXr77KhGICRjihWKm8Dw3KXP4Z5VZ/Lfw7B5kyD1cy5C7uAx5iJcht6vhRTUi4OrC0Szftvi/vAFNdbZ2Dp661QAAAABJRU5ErkJggg==\",\n            },\n            \"anchor\": {\n                \"name\" : \"更多\",\n                \"icon\" : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAANElEQVQ4T+3GMQ0AIAwAMAwSEvwLACai3HtmAHq1te8xpnCM6okAu3rigFU9MWxLr/695AI0E1VgH26hCQAAAABJRU5ErkJggg==',\n            }\n        },\n        waves       : undefined,\n    };\n\n    static propTypes = {\n        item        : React.PropTypes.array,   // panel props\n        autoHeight  : React.PropTypes.bool,    // panel props\n        activeColor : React.PropTypes.string,  // panel props\n        autoHide    : React.PropTypes.bool,    // panel props\n        tabAutoSel  : React.PropTypes.bool,    // panel props\n\n        triggerItems: React.PropTypes.object,\n        waves       : React.PropTypes.string,\n\n        onOpen      : React.PropTypes.func,\n        onClose     : React.PropTypes.func,\n        onAction    : React.PropTypes.func,\n    }\n\n    style = cssinjs()\n\n    btnClickHandler( event ) {\n        const type = $( event.target ).attr( \"id\" );\n        if ( this.props.onAction ) this.props.onAction( event, type );\n    }\n\n    btnMouseOverHandler( event ) {\n        const style   = { ...this.style },\n              $target = $( event.target ),\n              type    = $target.attr( \"type\" ),\n              active  = $target.attr( \"active\" );\n        if ( type == \"spec\" ) {\n            $target.parent().css({ ...style.spec, ...style.spec_focus });\n        } else {\n            this.panelRender( $( this.refs.bg ) );\n        }\n    }\n\n    btnMouseOutHandler( event ) {\n        const style   = { ...this.style },\n              $target = $( event.target ),\n              type    = $target.attr( \"type\" ),\n              color   = $target.attr( \"color\" );\n        if ( type == \"spec\" ) {\n            $target.parent().css({ ...style.origin, ...style.large, ...style.spec_item });\n        }\n    }\n\n    panelBgEventHandler( event ) {\n        if ( event.target.tagName.toLowerCase() == \"panel-bg\" ) {\n            const $panelbg = $( this.refs.bg ),\n                  style    = { ...this.style };\n            $panelbg.animate({ opacity: 0 },{\n                complete: ()=> {\n                    ReactDOM.unmountComponentAtNode( $panelbg[0] );\n                    $panelbg.css({ ...style.panel_bg });\n                }\n            });\n        }\n    }\n\n    panelRender( $panelbg ) {\n        if ( $panelbg.length > 0 ) {\n            const style = { ...this.style };\n            $panelbg.css({ ...style.panel_bg, ...{ \"display\": \"block\" , opacity: 1 }});\n            ReactDOM.render( <Panel { ...this.props } onOpen={ ()=>this.onPop( \"onOpen\" ) } onClose={ ()=>this.onPop( \"onClose\" ) }/>, $panelbg[0] );\n        }\n    }\n\n    onPop( type ) {\n        this.props[type] && this.props[type]();\n    }\n\n    render() {\n        const style = { ...this.style };\n        style.spec  = { ...style.origin, ...style.large, ...style.spec_item };\n\n        const evt   = this.props.autoHide ? {\n            onMouseOver: (e)=> this.panelBgEventHandler(e)\n        } : {\n            onClick    : (e)=> this.panelBgEventHandler(e)\n        };\n\n        const btn_props = ( id, type, style, { name, color, icon }, icon_style, tooltip, waves )=> {\n                return {\n                    id,\n                    type,\n                    style,\n                    name,\n                    color,\n                    tooltip,\n                    waves,\n                    icon       : { style: icon_style, path: icon },\n                    onClick    : evt=>this.btnClickHandler(evt),\n                    onMouseOver: evt=>this.btnMouseOverHandler(evt),\n                    onMouseOut : evt=>this.btnMouseOutHandler(evt),\n                };\n        },\n        spec   = <Button { ...btn_props( \"exit\", \"spec\",     style.spec,   this.props.triggerItems.exit,   style.icon, {target: \"name\", delay: 50}, this.props.waves ) } />,\n        anchor = <Button { ...btn_props( \"anchor\", \"anchor\", style.origin, this.props.triggerItems.anchor, style.icon, {target: \"name\", delay: 50}, this.props.waves ) } />,\n        panel  = <panel-bg ref=\"bg\"></panel-bg>;\n\n        return (\n            <fap style={ style.root } { ...evt } >\n                { spec   }\n                { anchor }\n                { panel  }\n            </fap>\n        )\n    }\n\n}"
  },
  {
    "path": "src/vender/mduikit/list.jsx",
    "content": "/*!\n * React Material Design: List\n * \n * @version : 0.0.3.0116\n * @update  : 2020/01/16\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: List ====\" )\n\nconst color            = \"rgba( 51, 51, 51, .87 )\",\n      secondary_color  = \"rgba( 51, 51, 51, .54 )\",\n      disable_color    = \"rgba( 51, 51, 51, .38 )\",\n      hover_color      = \"rgba( 238, 238, 238, 1 )\",\n      border_color     = \"rgba( 224, 224, 224, 1 )\",\n      transparent_color= \"transparent\",\n      background_color = \"rgba( 255, 255, 255, 1 )\";\n\nconst cssinjs = () => {\n    const styles = {\n\n        root: {\n            display: 'flex',\n            flexDirection: 'column',\n        },\n\n        header: {\n            display: 'block',\n\n            paddingLeft: '72px',\n\n            textAlign: 'left',\n\n            color,\n            fontSize: '1.5rem',\n            fontWeight: 700,\n        },\n\n        list_item: {\n            display: 'flex',\n            justifyContent: 'center',\n            alignItems: 'center',\n\n            height: '56px',\n        },\n\n        pri_item: {},\n\n        pri_item_text: {\n            display: 'block',\n\n            minWidth: '40px',\n            minHeight: '40px',\n\n            margin: '0 16px',\n            padding: 0,\n\n            lineHeight: '40px',\n            fontWeight: 600,\n\n            borderRadius: '50%',\n\n            color,\n            backgroundColor: transparent_color,\n        },\n\n        sec_item: {},\n\n        sec_item_text: {\n            display: '-webkit-box',\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            margin: '0 16px',\n\n            minWidth: '50px',\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n\n            color,\n            backgroundColor: transparent_color,\n        },\n\n        state_none: {\n            width: 0,\n            opacity: 0,\n            visibility: 'hidden',\n            paddingLeft: '72px',\n        },\n\n        state_avatar: {\n            display: 'block',\n\n            minWidth: '40px',\n            minHeight: '40px',\n\n            margin: '0 16px',\n            padding: 0,\n\n            lineHeight: '40px',\n\n            borderRadius: '50%',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        state_icon: {\n            display: 'block',\n\n            minWidth: '24px',\n            minHeight: '24px',\n\n            margin: '8px 24px',\n            padding: 0,\n\n            lineHeight: '24px',\n\n            borderRadius: '50%',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        state_action: {},\n\n        content: {\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'flex-start',\n            width: '100%',\n        },\n\n        link: {\n            display: '-webkit-box',\n            flexShrink: 1,\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n            textAlign: 'left',\n\n            fontSize: '1.6rem',\n            color,\n        },\n\n        subtitle: {\n            display: '-webkit-box',\n            flexShrink: 2,\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n            textAlign: 'left',\n\n            color: secondary_color,\n        },\n\n        action: {\n            display: 'block',\n            position: 'relative',\n\n            margin: '0 16px 0 0',\n            padding: 0,\n        },\n\n        action_icon: {\n            display: 'block',\n\n            width: '27px',\n            height: '27px',\n\n            lineHeight: '27px',\n\n            borderRadius: '50%',\n\n            cursor: 'pointer',\n\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n            backgroundImage: 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAHdElNRQfhBAwKJDqU6cY9AAAAgElEQVQ4y+3PsQnCYBRF4U+xcQYRHEcdwLhASOE8avEjSsApBIOWOoFYSRZxgBh8WueU73Lu49Lxnd7ncxrKvPKqmfRbip72zmkZF0ZgGhd2YN0MBi3CykWd33/ZUKpSFhfGYBYXDmAT31A4qfNb/MPD0TUt4sIEzONCCbY6/uIN+soX7VMadxMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMDQtMTJUMTA6MzY6NTgrMDg6MDBBhh2RAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTA0LTEyVDEwOjM2OjU4KzA4OjAwMNulLQAAAABJRU5ErkJggg==)',\n        },\n\n        action_items: {\n            display: 'block',\n            position: 'absolute',\n\n            top: 0,\n            right: 0,\n\n            margin: 0,\n            padding: '8px 0',\n\n            minWidth: '150px',\n\n            backgroundColor: background_color,\n\n            borderRadius: '2px',\n            boxShadow: 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px',\n\n            opacity: 0,\n            transformOrigin: 'left top 0px',\n            transform: 'scaleY(0)',\n            transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n\n            zIndex: 0,\n        },\n\n        action_items_active: {\n            opacity: 1,\n            transform: 'scaleY(1)',\n\n            zIndex: 100,\n        },\n\n        action_items_over: {\n            backgroundColor: hover_color,\n            transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        action_bg: {\n            display: 'none',\n            position: 'fixed',\n\n            top: 0,\n            left: 0,\n\n            width: '100%',\n            height: '100%',\n        },\n\n        action_group: {},\n\n        action_item: {\n            display: '-webkit-box',\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            margin: 0,\n            padding: '0 24px',\n\n            height: '32px',\n\n            color,\n            fontSize: '1.2rem',\n            fontWeight: 400,\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n            textAlign: 'left',\n            lineHeight: '32px',\n\n            cursor: 'pointer',\n\n            transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        hr: {\n            margin: '8px 0',\n            height: '1px',\n            border: 'none',\n            backgroundColor: border_color,\n        },\n\n        disable: {\n            color: disable_color,\n            cursor: 'not-allowed',\n        },\n\n    };\n    return styles;\n}\n\n/**\n * ListItem react stateless component\n * \n * @param {object} props, include:\n *   - idx             : [PropTypes.number] index\n *   - url             : [PropTypes.string] url\n *   - title           : [PropTypes.string] title\n *   - desc            : [PropTypes.string] subtitle\n * \n *   - style           : [PropTypes.object] style\n * \n *   - priType         : [PropTypes.string] primary   type, include: text, icon, avatar, action, none\n *   - secType         : [PropTypes.string] Secondary type, include: text, icon, avatar, action, none\n *   - priValue        : [PropTypes.any]    primary   value, value tyupe include: string, any\n *   - secValue        : [PropTypes.any]    Secondary value, value tyupe include: string, any\n * \n *   - events          : [PropTypes.object] events, include:\n *     - priOnClick    : [PropTypes.func]   list item primary onClick event\n *     - secOnClick    : [PropTypes.func]   list item secondary onClick event\n * \n *     - iconOnClick   : [PropTypes.func]   action icon onClick event\n *     - bgOnClick     : [PropTypes.func]   action bg   onClick event\n *     - itemOnClick   : [PropTypes.func]   action item onClick event\n *     - itemMouseOver : [PropTypes.func]   action item mouse over event\n * \n *   - action          : [PropTypes.array]  include: id, title, icon, disable, hr\n */\nconst ListItem = props => {\n    const { idx, url, title, desc, style,\n            acIconWaves, acItemWaves,\n            priType, priValue, secType, secValue,\n            action, events } = props,\n        content_style = props.contentStyle ? { ...props.contentStyle } : { ...style.content };\n\n    let pri_style = priType == \"text\" ? { ...style[ `pri_item_${ priType }` ] } : { ...style[ `state_${ priType }` ] },\n        sec_style = secType == \"text\" ? { ...style[ `sec_item_${ secType }` ] } : { ...style[ `state_${ secType }` ] },\n        pri_value = [ \"avatar\", \"icon\" ].includes( priType ) ? \"\" : priValue,\n        sec_value = [ \"avatar\", \"icon\" ].includes( secType ) ? \"\" : secValue;\n\n    props.priStyle && ( pri_style = { ...props.priStyle } );\n    props.secStyle && ( sec_style = { ...props.secStyle } );\n\n    [ \"avatar\", \"icon\" ].includes( priType ) && ( pri_style.backgroundImage = `url(${priValue})` );\n    [ \"avatar\", \"icon\" ].includes( secType ) && ( sec_style.backgroundImage = `url(${secValue})` );\n    priType == \"none\" && ( pri_style = { ...pri_style, ...style.state_none } );\n    secType == \"none\" && ( sec_style = { ...sec_style, ...style.state_none } );\n\n    props.priBgColor && ( pri_style.backgroundColor = props.priBgColor );\n    props.secBgColor && ( sec_style.backgroundColor = props.secBgColor );\n\n    // hack code by only firefox\n    let true_title = title;\n    if ( window.navigator.userAgent.toLowerCase().includes( \"firefox\" )) {\n        style.subtitle.display = \"none\";\n        true_title.length > 26 && ( true_title = true_title.substring( 0, 26 ) + \"...\" );\n    }\n\n    const actionItems = action ? action.map( item => {\n        const { id, title, disable, hr } = item,\n                root = disable ? { ...style.action_item, ...style.disable } : { ...style.action_item };\n        return <action-group>\n                    <action-item style={ root } id={ id } class={ acItemWaves }\n                                 onClick={ !disable && ( (e)=>events.itemOnClick( e, props )) }\n                                 onMouseOver={ (e)=>events.itemMouseOver(e) }>\n                                 { title }\n                    </action-item>\n                    { hr && <hr style={ style.hr }/> }\n                </action-group>\n        }) : undefined;\n    return (\n        <list-item idx={ idx } style={ style.list_item }>\n            <pri-item style={ pri_style } onClick={ (e)=>events.priOnClick( e, props ) }>{ pri_value }</pri-item>\n            <content style={ content_style } data-tooltip={ true_title } data-tooltip-position=\"up\">\n                <a style={ style.link } href={ url } target=\"_blank\">{ true_title }</a>\n                <subtitle style={ style.subtitle }>{ desc }</subtitle>\n            </content>\n            <sec-item style={ sec_style } onClick={ (e)=>events.secOnClick( e, props ) }>{ sec_value }</sec-item>\n            <action style={ style.action }>\n                <action-icon style={ style.action_icon } class={ acIconWaves } onClick={ (e)=>events.iconOnClick(e) }></action-icon>\n                <action-items style={ style.action_items }>\n                    { actionItems }\n                </action-items>\n                <action-bg style={ style.action_bg } onClick={ (e)=>events.bgOnClick(e) }></action-bg>\n            </action>\n        </list-item>\n    );\n}\n\n/**\n * Custom <a> tag component: List, component e.g.\n * \n    <list>\n        <list-header>未读列表：100 条</list-header>\n        <list-item>\n            <pri-item>换</pri-item>\n            <content>\n                <a href=\"http://www.cnbeta.com/articles/tech/601485.htm\" target=\"_blank\">换壳为本？Nokia 6 银白色版 1499 元起正式开卖</a>\n                <subtitle>4 月 4 日的时候，诺基亚官方宣传，将于在 4 月 11 日正式发售全新配色的 Nokia 6 智能手机，即银白色版本，并且从那时起已经正式提供预约服务4 月 4 日的时候，诺基亚官方宣传，将于在 4 月 11 日正式发售全新配色的 Nokia 6 智能手机，即银白色版本，并且从那时起已经正式提供预约服务</subtitle>\n            </content>\n            <sec-item>2 days</sec-item>\n            <action>\n                <action-icon></action-icon>\n                <action-items>\n                    <action-group>\n                        <action-item>发送到 Pocket</action-item>\n                        <hr>\n                    </action-group>\n                    <action-group>\n                        <action-item>删除</action-item>\n                    </action-group>\n                </action-items>\n                <action-bg></action-bg>\n            </action>\n        </list-item>\n    </list>\n * \n * Reference:\n * - https://material.io/guidelines/components/lists.html\n * - http://www.material-ui.com/#/components/list\n * - chrome://history\n * \n * @class\n */\nexport default class List extends React.Component {\n\n    static defaultProps = {\n        title       : \"\",\n\n        items       : [],\n        actionItems : [],\n\n        acIconWaves : \"\",\n        acItemWaves : \"\",\n\n        priStyle    : undefined,\n        secStyle    : undefined,\n        contentStyle: undefined,\n\n        priBgColor  : \"\",\n        secBgColor  : \"\",\n\n    };\n\n    static PropTypes = {\n       title       : React.PropTypes.string,\n\n       items       : React.PropTypes.array,\n       actionItems : React.PropTypes.array,\n\n       onAction    : React.PropTypes.func,\n\n       acIconWaves : React.PropTypes.string,\n       acItemWaves : React.PropTypes.string,\n\n       priStyle    : React.PropTypes.object,\n       secStyle    : React.PropTypes.object,\n       contentStyle: React.PropTypes.object,\n\n       priBgColor  : React.PropTypes.string,\n       secBgColor  : React.PropTypes.string,\n    };\n\n    style = cssinjs()\n\n    acIconOnClick( event ) {\n        const style = { ...this.style };\n        $( event.target ).next().css({ ...style.action_items, ...style.action_items_active });\n        $( event.target ).parent().find( \"action-bg\" ).css( \"display\", \"block\" );\n    }\n\n    acBgOnClick( event ) {\n        const style = { ...this.style };\n        $( event.target )\n            .css( \"display\", \"none\" )\n            .prev().css({ ...style.action_items });\n    }\n\n    acItemOnClick( event, data ) {\n        const $target = $( event.target ),\n              id      = $target.attr( \"id\" ),\n              title   = $target.text(),\n              style   = { ...this.style };\n        $target.parent().parent()\n            .css({ ...style.action_items })\n            .next().css( \"display\", \"none\" );\n        this.props.onAction && this.props.onAction( event, id, title, data )\n    }\n\n    acItemMouseOver( event ) {\n        const $target = $( event.target );\n        if ( $target.is( \"action-item\" ) ) {\n            $( \"action-item[active=true]\" ).css( \"background-color\", \"transparent\" ).attr( \"active\", false );\n            $target\n                .attr( \"active\", true )\n                .css( \"background-color\", \"rgb(238, 238, 238)\" );\n        }\n    }\n\n    priOnClick( event, data ) {\n        this.props.priOnClick && this.props.priOnClick( event, data );\n    }\n\n    secOnClick( event, data ) {\n        this.props.secOnClick && this.props.secOnClick( event, data );\n    }\n\n    render() {\n        const style = { ...this.style },\n              { items, title, actionItems, ...others } = this.props,\n              list = items.map( item => {\n                const events = {\n                    iconOnClick  : (e) => this.acIconOnClick(e),\n                    bgOnClick    : (e) => this.acBgOnClick(e),\n                    itemOnClick  : ( e, d ) => this.acItemOnClick( e, d ),\n                    itemMouseOver: (e) => this.acItemMouseOver(e),\n                    priOnClick   : ( e, d ) => this.priOnClick( e, d ),\n                    secOnClick   : ( e, d ) => this.secOnClick( e, d ),\n                };\n                return <ListItem { ...item } { ...others } action={ actionItems } events={ events } style={{ ...style }} />\n        });\n        return (\n            <list style={ style.root }>\n                <list-header style={ style.header }>{ title }</list-header>\n                { list }\n            </list>\n        )\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/mduikit.css",
    "content": "/*!\n * React Material Design Style\n * \n * @version : 0.0.1\n * @update  : 2019/12/29\n * @homepage: https://github.com/kenshin/mduikit\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2019\n */\n\n/**\n * Sidebar\n */\n\nsidebar side content a {\n    transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n}\n\nsidebar side content a:hover {\n    background-color: rgba(0,0,0,.04);\n}\n\nsidebar side content a.active {\n    font-weight: bold;\n}\n\nsidebar side toc {\n    position: relative;\n    width: 100%;\n}\n\nsidebar side toc i {\n    position: absolute;\n    left: 35px;\n    top: 0;\n    bottom: 0;\n\n    display: block;\n\n    width: 2px;\n    background: rgba(189,189,189,.4);\n}\n\nsidebar side toc outline {\n    position: relative;\n    display: -webkit-box!important;\n    -webkit-line-clamp: 1;\n    -webkit-box-orient: vertical;\n    overflow: hidden;\n    text-overflow: ellipsis;\n\n    padding: 12px 0 12px 60px;\n    min-height: 21px;\n\n    line-height: 21px;\n    text-align: left;\n\n    cursor: pointer;\n}\n\nsidebar side toc outline:hover {\n    background-color: rgba(0,0,0,.04)!important;\n}\n\nsidebar side .toc-level-h1 {\n    padding-left: 60px;\n}\nsidebar side .toc-level-h2 {\n    padding-left: 70px;\n}\nsidebar side .toc-level-h3 {\n    padding-left: 75px;\n}\nsidebar side .toc-level-h4 {\n    padding-left: 80px;\n}\n\n/**\n * Source from https://jonsuh.com/hamburgers/\n */\n.hamburger {\n    font: inherit;\n    display: inline-block;\n    overflow: visible;\n    margin: 0;\n    padding: 15px;\n    cursor: pointer;\n    transition-timing-function: linear;\n    transition-duration: .15s;\n    transition-property: opacity,filter;\n    text-transform: none;\n    color: inherit;\n    border: 0;\n    background-color: transparent;\n    transform: scale(.5);\n}\n\n.hamburger-box {\n    position: relative;\n    display: inline-block;\n    width: 40px;\n    height: 24px;\n}\n\n.hamburger-inner {\n    top: 50%;\n    display: block;\n    margin-top: -2px;\n}\n\n.hamburger-inner, .hamburger-inner:after, .hamburger-inner:before {\n    position: absolute;\n    width: 40px;\n    height: 4px;\n    transition-timing-function: ease;\n    transition-duration: .15s;\n    transition-property: transform;\n    border-radius: 4px;\n    background-color: #9E9E9E;\n}\n\n.hamburger-inner:after, .hamburger-inner:before {\n    content: \"\";\n    display: block;\n}\n\n.hamburger-inner:before {\n    top: -10px;\n}\n\n.hamburger-inner:after {\n    bottom: -10px;\n}\n\n.hamburger:hover .hamburger-inner, .hamburger:hover .hamburger-inner:after, .hamburger:hover .hamburger-inner:before {\n    background-color: #9E9E9E;\n    transform: scale(.7);\n}\n\n.hamburger--arrow:hover .hamburger-inner:before {\n    transform: translate3d(-8px,0,0) rotate(-45deg) scaleX(.7);\n}\n\n.hamburger--arrow:hover .hamburger-inner:after {\n    transform: translate3d(-8px,0,0) rotate(45deg) scaleX(.7);\n}"
  },
  {
    "path": "src/vender/mduikit/mintooltip.css",
    "content": "/**\n * From https://kazzkiq.github.io/balloon.css/\n */\n:root{--balloon-color: rgba(16,16,16,0.95);--balloon-font-size: 12px;--balloon-move: 4px}button[aria-label][data-balloon-pos]{overflow:visible}[aria-label][data-balloon-pos]{position:relative;cursor:pointer}[aria-label][data-balloon-pos]:after{opacity:0;pointer-events:none;transition:all .18s ease-out .18s;text-indent:0;font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;font-weight:normal;font-style:normal;text-shadow:none;font-size:var(--balloon-font-size);background:var(--balloon-color);border-radius:2px;color:#fff;content:attr(aria-label);padding:.5em 1em;position:absolute;white-space:nowrap;z-index:10}[aria-label][data-balloon-pos]:before{width:0;height:0;border:5px solid transparent;border-top-color:var(--balloon-color);opacity:0;pointer-events:none;transition:all .18s ease-out .18s;content:\"\";position:absolute;z-index:10}[aria-label][data-balloon-pos]:hover:before,[aria-label][data-balloon-pos]:hover:after,[aria-label][data-balloon-pos][data-balloon-visible]:before,[aria-label][data-balloon-pos][data-balloon-visible]:after,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:before,[aria-label][data-balloon-pos]:not([data-balloon-nofocus]):focus:after{opacity:1;pointer-events:none}[aria-label][data-balloon-pos].font-awesome:after{font-family:FontAwesome, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif}[aria-label][data-balloon-pos][data-balloon-break]:after{white-space:pre}[aria-label][data-balloon-pos][data-balloon-break][data-balloon-length]:after{white-space:pre-line;word-break:break-word}[aria-label][data-balloon-pos][data-balloon-blunt]:before,[aria-label][data-balloon-pos][data-balloon-blunt]:after{transition:none}[aria-label][data-balloon-pos][data-balloon-pos=\"up\"]:after{bottom:100%;left:50%;margin-bottom:10px;transform:translate(-50%, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up\"]:before{bottom:100%;left:50%;transform:translate(-50%, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"up\"][data-balloon-visible]:after{transform:translate(-50%, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"up\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"up\"][data-balloon-visible]:before{transform:translate(-50%, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"]:after{bottom:100%;left:0;margin-bottom:10px;transform:translate(0, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"]:before{bottom:100%;left:5px;transform:translate(0, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"][data-balloon-visible]:after{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"up-left\"][data-balloon-visible]:before{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"]:after{bottom:100%;right:0;margin-bottom:10px;transform:translate(0, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"]:before{bottom:100%;right:5px;transform:translate(0, var(--balloon-move));transform-origin:top}[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"][data-balloon-visible]:after{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"up-right\"][data-balloon-visible]:before{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down\"]:after{left:50%;margin-top:10px;top:100%;transform:translate(-50%, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down\"]:before{width:0;height:0;border:5px solid transparent;border-bottom-color:var(--balloon-color);left:50%;top:100%;transform:translate(-50%, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"down\"][data-balloon-visible]:after{transform:translate(-50%, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"down\"][data-balloon-visible]:before{transform:translate(-50%, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"]:after{left:0;margin-top:10px;top:100%;transform:translate(0, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"]:before{width:0;height:0;border:5px solid transparent;border-bottom-color:var(--balloon-color);left:5px;top:100%;transform:translate(0, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"][data-balloon-visible]:after{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"down-left\"][data-balloon-visible]:before{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"]:after{right:0;margin-top:10px;top:100%;transform:translate(0, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"]:before{width:0;height:0;border:5px solid transparent;border-bottom-color:var(--balloon-color);right:5px;top:100%;transform:translate(0, calc(var(--balloon-move) * -1))}[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"][data-balloon-visible]:after{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"down-right\"][data-balloon-visible]:before{transform:translate(0, 0)}[aria-label][data-balloon-pos][data-balloon-pos=\"left\"]:after{margin-right:10px;right:100%;top:50%;transform:translate(var(--balloon-move), -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"left\"]:before{width:0;height:0;border:5px solid transparent;border-left-color:var(--balloon-color);right:100%;top:50%;transform:translate(var(--balloon-move), -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"left\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"left\"][data-balloon-visible]:after{transform:translate(0, -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"left\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"left\"][data-balloon-visible]:before{transform:translate(0, -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"right\"]:after{left:100%;margin-left:10px;top:50%;transform:translate(calc(var(--balloon-move) * -1), -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"right\"]:before{width:0;height:0;border:5px solid transparent;border-right-color:var(--balloon-color);left:100%;top:50%;transform:translate(calc(var(--balloon-move) * -1), -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"right\"]:hover:after,[aria-label][data-balloon-pos][data-balloon-pos=\"right\"][data-balloon-visible]:after{transform:translate(0, -50%)}[aria-label][data-balloon-pos][data-balloon-pos=\"right\"]:hover:before,[aria-label][data-balloon-pos][data-balloon-pos=\"right\"][data-balloon-visible]:before{transform:translate(0, -50%)}[aria-label][data-balloon-pos][data-balloon-length=\"small\"]:after{white-space:normal;width:80px}[aria-label][data-balloon-pos][data-balloon-length=\"medium\"]:after{white-space:normal;width:150px}[aria-label][data-balloon-pos][data-balloon-length=\"large\"]:after{white-space:normal;width:260px}[aria-label][data-balloon-pos][data-balloon-length=\"xlarge\"]:after{white-space:normal;width:380px}@media screen and (max-width: 768px){[aria-label][data-balloon-pos][data-balloon-length=\"xlarge\"]:after{white-space:normal;width:90vw}}\n[aria-label][data-balloon-pos]:before {\n    display: none;\n}\n\n[aria-label][data-balloon-pos]:after {\n    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);\n    border-radius: 5px;\n    font-weight: bold;\n    font-size: 10px;\n}"
  },
  {
    "path": "src/vender/mduikit/progress.jsx",
    "content": "/*!\r\n * React Material Design: Progress\r\n * \r\n * @version : 0.0.1\r\n * @update  : 2017/05/10\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n * @modules : https://kimmobrunfeldt.github.io/progressbar.js/\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: Progress ====\" )\r\n\r\nimport ProgressBar from 'progressbar';\r\n\r\nlet Shape, shape;\r\n\r\n/**\r\n * Custom component: Progress\r\n * \r\n * Reference:\r\n * - https://material.io/guidelines/components/progress-activity.html\r\n * \r\n * @class\r\n */\r\nexport default class Progress extends React.Component {\r\n\r\n    static defaultProps = {\r\n        type     : \"line\",\r\n        options  : {},\r\n        progress : 0,\r\n        text     : undefined,\r\n    }\r\n\r\n    static propTypes = {\r\n        type     : React.PropTypes.oneOf([ \"line\", \"circle\", \"semicircle\", \"custom\" ]),\r\n        options  : React.PropTypes.object,\r\n        progress : React.PropTypes.number,\r\n        text     : React.PropTypes.string,\r\n        onChanged: React.PropTypes.func,\r\n    }\r\n\r\n    componentDidMount() {\r\n\r\n        shape = new Shape(\r\n            $( \"progress-gp\" ).children()[0],\r\n            this.props.options,\r\n        );\r\n\r\n        shape.animate( this.props.progress );\r\n\r\n        this.props.text && shape.setText( this.props.text );\r\n    }\r\n\r\n    shouldComponentUpdate() {\r\n        shape.animate( this.props.progress, () => {\r\n            this.props.onChanged && this.props.onChanged( shape.value() );\r\n        });\r\n        return false;\r\n    }\r\n\r\n    componentWillUnmount() {\r\n        shape.destroy();\r\n    }\r\n\r\n    render() {\r\n\r\n        Shape = ( type => {\r\n            switch ( type ) {\r\n                case \"line\":\r\n                    return ProgressBar.Line;\r\n                case \"circle\":\r\n                    return ProgressBar.Circle;\r\n                case \"semicircle\":\r\n                    return ProgressBar.SemiCircle;\r\n                case \"custom\":\r\n                    return ProgressBar.Path;\r\n            }\r\n        })( this.props.type );\r\n\r\n        return (\r\n            <progress-gp>\r\n                { this.props.children ? this.props.children : <progress-bar></progress-bar> }\r\n            </progress-gp>\r\n        )\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/vender/mduikit/selectfield.jsx",
    "content": "/*!\r\n * React Material Design: SelectField\r\n * \r\n * @version : 0.0.3\r\n * @update  : 2018/04/26\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: SelectField ====\" )\r\n\r\nconst color            = 'rgba(51, 51, 51, .87)',\r\n      secondary_color  = 'rgba(204, 204, 204, 1)',\r\n\r\n      focus_color      = 'rgba(0, 137, 123, .8)',\r\n      border_color     = 'rgba(224, 224, 224, 1)',\r\n      error_color      = 'rgba(244, 67, 54, 1)',\r\n\r\n      selected_color   = 'rgba(255, 64, 129, 1)',\r\n      hover_color      = 'rgba(238, 238, 238, 1)',\r\n      background_color = 'rgba(255, 255, 255, 1)';\r\n\r\nconst cssinjs = () => {\r\n\r\n    const display     = 'block',\r\n          width       = '100%',\r\n          margin      = '8px 0 0 0',\r\n          medium      = '14px',\r\n          large       = '16px',\r\n          lineHeight  = 1.5,\r\n          fontWeight  = 'bold',\r\n          styles      = {\r\n            hidden : 'none',\r\n            root: {},\r\n            root_normal: {\r\n                display,\r\n                position: 'relative',\r\n                margin: 0,\r\n                padding: 0,\r\n\r\n                width,\r\n                height: '45px',\r\n                lineHeight: 1,\r\n\r\n                cursor: 'pointer',\r\n                userSelect: 'none',\r\n            },\r\n\r\n            disable: {\r\n                color: secondary_color,\r\n                cursor: 'not-allowed',\r\n            },\r\n\r\n            border: {\r\n                display,\r\n\r\n                width,\r\n                margin,\r\n\r\n                borderTop: `none ${border_color}`,\r\n                borderLeft: `none ${border_color}`,\r\n                borderRight: `none ${border_color}`,\r\n                borderBottom: `1px solid ${border_color}`,\r\n                boxSizing: 'content-box',\r\n            },\r\n\r\n            border_disable: {\r\n                borderBottom: `1px dashed ${border_color}`,\r\n            },\r\n\r\n            float: {},\r\n\r\n            float_normal: {\r\n                display,\r\n                position: 'absolute',\r\n\r\n                margin,\r\n\r\n                fontSize: medium,\r\n                color: secondary_color,\r\n\r\n                userSelect: 'none',\r\n                pointerEvents: 'none',\r\n\r\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\r\n                transform: 'scale(1) translate( 0px, 0px )',\r\n                transformOrigin: 'left top 0px',\r\n            },\r\n\r\n            float_focus: {\r\n                color: focus_color,\r\n\r\n                margin: `-${margin}`,\r\n\r\n                fontSize: medium,\r\n                fontWeight,\r\n\r\n                transform: 'scale(0.75) translate( 0px, -8px )',\r\n            },\r\n\r\n            error: {\r\n                display,\r\n                position: 'absolute',\r\n\r\n                margin,\r\n                width: '110%',\r\n\r\n                fontSize: medium,\r\n                fontWeight,\r\n                lineHeight,\r\n\r\n                userSelect: 'none',\r\n\r\n                color: error_color,\r\n                transform: 'scale(0.75) translate( -80px, 0 )',\r\n            },\r\n\r\n            text: {\r\n                display,\r\n\r\n                /*height: '20px',*/\r\n\r\n                margin: 0,\r\n                padding: 0,\r\n            },\r\n\r\n            name: {},\r\n            name_normal: {\r\n                display,\r\n\r\n                margin,\r\n                padding: '0 20px 0 0',\r\n\r\n                fontFamily: 'sans-serif',\r\n                fontSize: medium,\r\n                textAlign: 'left',\r\n                lineHeight,\r\n            },\r\n\r\n            placeholder: {\r\n                color: secondary_color,\r\n            },\r\n\r\n            icon: {\r\n                display: 'block',\r\n                position: 'absolute',\r\n\r\n                width: '24px',\r\n                height: '24px',\r\n\r\n                top: '6px',\r\n                right: 0,\r\n\r\n                border: 'none',\r\n                backgroundPosition: 'center',\r\n                backgroundRepeat: 'no-repeat',\r\n                backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAABqSURBVEiJ7dQxCsAgDIXhZ8ktgmetVw31GIF06lI0yeIWJyH4f4hgMzOcXNfRegEFFAAAoGA+ROR2A0STmftu7t5ARAYRTS+uqtt4CACAqvYVkomngBWSjQPxG/yR59tnz7X6rgso4DzwAnJQKlbAmFdgAAAAAElFTkSuQmCC)',\r\n            },\r\n\r\n            bg: {\r\n                display: 'none',\r\n                position: 'fixed',\r\n\r\n                top: 0,\r\n                left: 0,\r\n\r\n                width: '100%',\r\n                height: '100%',\r\n            },\r\n\r\n        };\r\n\r\n    return styles;\r\n}\r\n\r\nconst cssinjs_list = () => {\r\n\r\n    const styles = {\r\n        hidden : 'none',\r\n        root : {},\r\n        root_normal : {\r\n            display: 'block',\r\n            position: 'absolute',\r\n\r\n            top: 0,\r\n            left: 0,\r\n\r\n            margin: 0,\r\n            padding: 0,\r\n\r\n            width: '100%',\r\n            minHeight: '100px',\r\n            maxHeight: '718px',\r\n\r\n            color,\r\n            backgroundColor: background_color,\r\n\r\n            boxSizing: 'border-box',\r\n            boxShadow: '0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2)',\r\n            borderRadius: '2px',\r\n\r\n            zIndex: 2100,\r\n\r\n            overflowY: 'auto',\r\n\r\n            opacity: 0,\r\n            transform: 'scaleY(0)',\r\n            transformOrigin: 'left top 0px',\r\n            transition : 'transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\r\n        },\r\n\r\n        open: {\r\n            opacity: 1,\r\n            transform: 'scaleY(1)',\r\n        },\r\n\r\n        list_filed: {\r\n            display: 'flex',\r\n            alignItems: 'center',\r\n\r\n            padding: '0 16px',\r\n\r\n            height: '56px',\r\n            width: '100%',\r\n\r\n            textAlign: 'left',\r\n\r\n            boxSizing: 'border-box',\r\n            transition: 'all 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms',\r\n        },\r\n\r\n        list_filed_icon: {\r\n            display: 'block',\r\n\r\n            width: '24px',\r\n            height: '24px',\r\n\r\n            margin: '0 16px 0 0',\r\n            padding: '16px',\r\n\r\n            border: 'none',\r\n            backgroundPosition: 'center',\r\n            backgroundRepeat: 'no-repeat',\r\n        },\r\n\r\n        list_filed_value: {\r\n            display: 'inline',\r\n            width: '100%',\r\n            fontSize: 'inherit',\r\n        },\r\n\r\n        list_filed_info: {\r\n            display: 'inline',\r\n            padding: '0 0 0 16px',\r\n\r\n            fontSize: '13px',\r\n            textAlign: 'right',\r\n\r\n            minWidth: '100px',\r\n        },\r\n\r\n    }\r\n\r\n    return styles;\r\n}\r\n\r\nclass ListView extends React.Component {\r\n\r\n    static defaultProps = {\r\n        waves           : \"\",\r\n        items           : [],\r\n        active          : \"\",\r\n    };\r\n\r\n    static propTypes = {\r\n        waves           : React.PropTypes.string,\r\n        items           : React.PropTypes.array,\r\n        active          : React.PropTypes.string,\r\n        onChange        : React.PropTypes.func,\r\n    };\r\n\r\n    style = cssinjs_list()\r\n\r\n    onMouseOver( event ) {\r\n        const $target = $( event.target );\r\n        if ( $target.is( \"list-field\" ) ) {\r\n            $( \"list-field[active=true]\" ).css( \"background-color\", \"transparent\" ).attr( \"active\", false );\r\n            $target.attr( \"active\", true ).css( \"background-color\", hover_color );\r\n        }\r\n    }\r\n\r\n    onClick( event ) {\r\n        let $target = $( event.target );\r\n        while ( !$target.is( \"list-field\" )) { $target = $target.parent(); }\r\n        setTimeout( ()=>this.props.onChange && this.props.onChange( $target.find( \"list-field-name\" ).attr( \"value\" ), $target.find( \"list-field-name\" ).text() ), 130 );\r\n    }\r\n\r\n    render() {\r\n        const style = { ...this.style };\r\n        style.root  = this.props.items.length > 1 ? { ...style.root_normal, ...style.open } : { ...style.root_normal };\r\n\r\n        const list = this.props.items.map( ( item, idx ) => {\r\n            let [ name_style, icon_style, info_style ] =[ { ...style.list_filed_value }, { ...style.list_filed_icon }, { ...style.list_filed_info } ];\r\n            ( !item.info || item.info == \"\" ) && ( info_style.display = style.hidden );\r\n            if ( item.icon && item.icon != \"\" ) {\r\n                icon_style.backgroundImage = `url(${ item.icon })`;\r\n            } else {\r\n                icon_style.display = style.hidden;\r\n            }\r\n            item.name == this.props.active && ( name_style.color = selected_color );\r\n            item.style && item.style.root  && ( style.list_filed = { ...style.list_filed, ...item.style.root });\r\n            item.style && item.style.icon  && ( icon_style       = { ...icon_style, ...item.style.icon });\r\n            item.style && item.style.text  && ( name_style       = { ...name_style, ...item.style.text });\r\n            item.style && item.style.state && ( info_style       = { ...info_style, ...item.style.state });\r\n            return (\r\n                <list-field class={ this.props.waves } style={ style.list_filed } onMouseOver={ (e)=>this.onMouseOver(e) } onClick={ (e)=>this.onClick(e) }>\r\n                    <i style={ icon_style }></i>\r\n                    <list-field-name style={ name_style } value={ item.value }>{ item.name }</list-field-name>\r\n                    <list-field-info style={ info_style }>{ item.info }</list-field-info>\r\n                </list-field>\r\n            )\r\n        });\r\n\r\n        return (\r\n            <list-view style={ style.root }>\r\n                { list }\r\n            </list-view> \r\n        )\r\n    }\r\n}\r\n\r\n/**\r\n * Custom component: SelectField\r\n * \r\n * Reference: \r\n * - https://material.io/guidelines/components/lists-controls.html\r\n * - http://www.material-ui.com/#/components/select-field\r\n * \r\n * @class\r\n */\r\nexport default class SelectField extends React.Component {\r\n\r\n    static defaultProps = {\r\n        name        : \"\",\r\n        disable      : false,\r\n        width        : undefined,\r\n        placeholder  : \"\",\r\n        floatingtext : \"\",\r\n        errortext    : \"\",\r\n        items        : [],\r\n        waves        : \"\",\r\n        tooltip      : {},\r\n    };\r\n\r\n    static propTypes = {\r\n        name         : React.PropTypes.string,\r\n        disable      : React.PropTypes.bool,\r\n        width        : React.PropTypes.string,\r\n        placeholder  : React.PropTypes.string,\r\n        floatingtext : React.PropTypes.string,\r\n        errortext    : React.PropTypes.string,\r\n        items        : React.PropTypes.array,\r\n        waves        : React.PropTypes.string,\r\n        tooltip      : React.PropTypes.object,\r\n        onChange     : React.PropTypes.func,\r\n    };\r\n\r\n    state = {\r\n        name : this.props.name,\r\n    }\r\n\r\n    style = cssinjs()\r\n\r\n    onClick() {\r\n        !this.props.disable && this.props.items.length > 0 && this.setState({ items: this.props.items });\r\n        !this.props.disable && this.props.items.length > 0 && $( this.refs.bg ).css( \"display\", \"block\" );\r\n    }\r\n\r\n    bgOnClick() {\r\n        $( this.refs.bg ).css( \"display\", \"none\" );\r\n        this.setState({ items : [] });\r\n    }\r\n\r\n    onChange( value, name ) {\r\n        this.props.onChange && this.props.onChange( value, name );\r\n        this.setState({\r\n            items : [],\r\n            name,\r\n        });\r\n        $( this.refs.bg ).css( \"display\", \"none\" );\r\n    }\r\n\r\n    componentDidMount() {\r\n        const style  = { ...this.style },\r\n              $error = $( this.refs.error );\r\n        this.props.errortext != \"\" &&\r\n            $error.parent().height( Number.parseInt(style.root.height) + $error.height() );\r\n    }\r\n\r\n    render() {\r\n        const style = { ...this.style };\r\n\r\n        this.props.width              && ( style.root.width = this.props.width );\r\n        this.props.disable            && ( style.border = { ...style.border, ...style.border_disable });\r\n        this.props.floatingtext == \"\" && ( style.float.display = style.hidden  );\r\n\r\n        style.root  = this.props.disable ? { ...style.root_normal, ...style.disable } : { ...style.root_normal };\r\n        style.name  = this.state.name == \"\" ? { ...style.name_normal, ...style.placeholder } : { ...style.name_normal };\r\n        style.float = this.props.placeholder == \"\" && this.state.name == \"\" ? style.float_normal : { ...style.float_normal, ...style.float_focus }\r\n\r\n        const tooltip = this.props.tooltip;\r\n        return (\r\n            <select-field style={ style.root }\r\n                data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }>\r\n                <select-float style={ style.float }>{ this.props.floatingtext }</select-float>\r\n                <div style={ style.text } onClick={ ()=> this.onClick() }>\r\n                    <span style={ style.name } className={ this.props.waves }>\r\n                        { this.state.name == \"\" ? this.props.placeholder : this.state.name \r\n                    }</span>\r\n                    <icon style={ style.icon }></icon>\r\n                </div>\r\n                <select-border style={ style.border }></select-border>\r\n                <select-field-error ref=\"error\" style={ style.error }>{ this.props.errortext }</select-field-error>\r\n                <ListView waves={ this.props.waves } active={ this.state.name } items={ this.state.items } onChange={ (v,n)=>this.onChange(v,n) } />\r\n                <list-bg ref=\"bg\" style={ style.bg } onClick={ ()=>this.bgOnClick() }></list-bg>\r\n            </select-field>\r\n        )\r\n\r\n    }\r\n}"
  },
  {
    "path": "src/vender/mduikit/sidebar.jsx",
    "content": "/*!\n * React Material Design: Sidebar\n * \n * @version : 0.0.4.0104\n * @update  : 2019/12/30\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Sidebar ====\" )\n\nconst color           = \"rgb(90, 90, 90)\",\n      secondary_color = \"rgba(204, 204, 204, 1)\",\n      background_color= \"#fff\";\n\nconst cssinjs = () => {\n    const paddingLeft = '24px',\n          height      = '65px',\n          width       = 256,\n          itemHeight  = '40px',\n          borderStyle = '1px solid rgba(0, 0, 0, 0.06)',\n          styles      = {\n\n        root: {\n            display: 'flex',\n            flexDirection: 'column',\n\n            position: 'fixed',\n\n            top: 0,\n            left: 0 - width,\n\n            width: `${width}px`,\n            height: '100%',\n\n            fontSize: '1.4rem',\n            fontWeight: 500,\n\n            color,\n            backgroundColor: background_color,\n\n            opacity: 0,\n\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n\n            zIndex: 2001,\n        },\n\n        header: {\n            display: 'flex',\n            alignItems: 'center',\n\n            paddingLeft,\n            paddingRight: paddingLeft,\n\n            width: '100%',\n            height,\n\n            fontSize: '1.6rem',\n            textTransform: 'uppercase',\n\n            borderBottom: borderStyle,\n        },\n\n        content: {\n            display: 'block',\n            position: 'relative',\n\n            width: '100%',\n            height: '100%',\n\n            fontSize: '1.3rem',\n            overflowY: 'auto',\n        },\n\n        footer: {\n            display: 'flex',\n            alignItems: 'center',\n\n            marginTop: '8px',\n\n            width: '100%',\n            height,\n\n            borderTop: borderStyle,\n        },\n\n        ul: {\n            margin: 0,\n            padding: 0,\n\n            width: '100%',\n\n            listStyleType: 'none',\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        ul_sub: {\n            marginTop: '-200px',\n        },\n\n        li: {\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'flex-start',\n            justifyContent: 'center',\n\n            position: 'relative',\n\n            minHeight: itemHeight,\n        },\n\n        sub_menu: {\n            width: '100%',\n            overflow: 'hidden',\n        },\n\n        dropdown: {\n            display: 'block',\n            position: 'absolute',\n\n            width: '20px',\n            height: '20px',\n\n            top: '10px',\n            right: 0,\n\n            border: 'none',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n            backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAeklEQVQ4T2NkoAAwUqCXYThojoyMDGBkZNTHFQ7///+/uHz58g0weRQ/R0ZGTmBkZMzHE4iNy5Yta8CqGSQYGRm5gJGRMR7dgP///y9cvnx5ArI41tBGNwCbRpAhOKMKZgAujXg1gyQjIiIcVqxYcQBXGAyHREJqJgEA2HcpEMZShNsAAAAASUVORK5CYII=)',\n        },\n\n        dropup: {\n            backgroundImage: 'url( data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAYAAAA71pVKAAAAwklEQVQ4T82SPQrCQBCF523ASjxB+nTpFSwFwXqLZQ+hR9gj6CGWLbYWBEsh9unS5wRiJWRHLALmjxRpMu28782bYUATChNYmikspVx6799Dqw3GVkqlAK7MfHDO5X0GvbDWOmbmJxHFRFQCWFtry7ZBB9Zar0IIDwBpLWbmXAixtda+/g0asDFGFEVxA7BrT2Hme5Ike2NMqHsNWCl1BnAcOhAzX5xzpw4spVxEUbQZe5qqqjLv/eenm+mTjK0wKfYXTFs9EN0Ulr4AAAAASUVORK5CYII=)'\n        },\n\n        link: {\n            display: 'flex',\n            alignItems: 'center',\n\n            margin: 0,\n            padding: '12px 48px 12px 24px',\n\n            width: '100%',\n            minHeight: itemHeight,\n\n            color,\n            fontSize: '1.4rem',\n        },\n\n        large_link: {\n            paddingLeft: 0,\n            fontSize: '2.2rem',\n        },\n\n        icon: {\n            display: 'block',\n\n            marginLeft: '2px',\n            marginRight: paddingLeft,\n\n            width: '21px',\n            height: '21px',\n\n            border: 'none',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        text: {\n            display: 'block',\n        },\n\n        mask: {\n            display: 'none',\n            position: 'fixed',\n\n            top: 0,\n            left: 0,\n\n            width: '100%',\n            height: '100%',\n\n            opacity: 0,\n            filter: 'alpha(Opacity=50)',\n            backgroundColor: 'rgba(0, 0, 0, .5)',\n\n            zIndex: 2000,\n        },\n\n        font_icon: {\n            display: 'flex',\n            justifyContent: 'center',\n            alignItems: 'center',\n\n            marginRight: '12px',\n\n            fontSize: '18px',\n            color: '#9E9E9E',\n\n            order: -1,\n            display: 'block',\n\n            width: '24px',\n            height: '24px',\n\n            fontSize: '14px',\n            border: 'none',\n        },\n\n        close_icon: {\n            position: 'absolute',\n            top: '0',\n            left: '-256px',\n\n            display: 'flex',\n            justifyContent: 'center',\n            alignItems: 'center',\n\n            width: '48px',\n            height: '48px',\n\n            color: '#9E9E9E',\n            backgroundColor: '#fff',\n\n            fontSize: '2rem',\n            border: 'none',\n            cursor: 'pointer',\n        },\n\n    };\n\n    return styles;\n}\n\n/**\n * React stateless component\n * \n * @param {object} react props, include:\n *   - large        : [PropTypes.boolean]\n *   - name         : [PropTypes.string] <a> text\n *   - value        : [PropTypes.string] <a> value\n *   - route        : [PropTypes.string] <a> href\n *   - icon         : [PropTypes.string] icon\n *   - fontIcon     : [PropTypes.string] icon\n *   - style        : [PropTypes.object] include: icon link text\n *   - tooltip      : [PropTypes.string] tooltip\n *   - waves        : [PropTypes.string] waves\n *   - onClick      : [PropTypes.func]   event handler\n */\nconst Item = ( props ) => {\n    let icon_style = {}, link_style = { ...props.style.link };\n    if ( props.fontIcon ) {\n        icon_style         = { ...props.style.font_icon };\n        icon_style.display = \"flex\";\n    } else if ( props.icon ) {\n        icon_style         = { ...props.style.icon };\n        icon_style.display = \"block\";\n        icon_style.backgroundImage = `url(${props.icon })`;\n    } else {\n        icon_style.display = \"none\";\n    }\n    if ( props.large ) {\n        link_style = { ...props.style.link, ...props.style.large_link };\n    }\n    const tooltip = props.tooltip;\n    return (\n        <a style={ link_style } className={  props.route && props.waves } target={ props.route && props.route.startsWith( \"#\" ) ? \"_self\" : \"_blank\" }\n           href={ props.route } value={ props.value }\n           data-tooltip={ tooltip.text ? tooltip.text : props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }\n           onClick={ props.route && props.onClick && ( evt=>props.onClick(evt)) } >\n            <icon style={ icon_style } dangerouslySetInnerHTML={{__html: props.fontIcon || \"\" }} ></icon>\n            <text style={ props.style.text }>{ props.name }</text>\n        </a>\n    );\n};\n\n/**\n * Custom component: Sidebar\n * \n * Reference:\n * - chrome://md-settings/\n * - https://material.io/guidelines/\n * \n * @class\n */\nclass Sidebar extends React.Component {\n\n    static defaultProps = {\n        icon       : undefined,\n        header     : undefined,\n        footer     : undefined,\n        width      : \"\",\n        items      : [],\n        position   : \"left\",\n        color      : undefined,\n        bgColor    : undefined,\n        headerStyle: undefined,\n        contentStyle: undefined,\n        footerStyle: undefined,\n        maskStyle  : undefined,\n        waves      : \"\",\n        autoClose  : true,\n        showClose  : false,\n        tooltip    : {},\n    };\n\n    static propTypes = {\n        icon       : React.PropTypes.string,\n        header     : React.PropTypes.string,\n        footer     : React.PropTypes.string,\n        width      : React.PropTypes.string,\n        items      : React.PropTypes.array,\n        position   : React.PropTypes.oneOf([ \"left\", \"right\" ]),\n        color      : React.PropTypes.string,\n        bgColor    : React.PropTypes.string,\n        headerStyle: React.PropTypes.object,\n        contentStyle: React.PropTypes.object,\n        footerStyle: React.PropTypes.object,\n        maskStyle  : React.PropTypes.object,\n        autoClose  : React.PropTypes.bool,\n        showClose  : React.PropTypes.bool,\n        waves      : React.PropTypes.string,\n        tooltip    : React.PropTypes.object,\n        onClick    : React.PropTypes.func,\n        onExit     : React.PropTypes.func,\n    };\n\n    style = cssinjs();\n\n    onClick( event ) {\n        let $target = $( event.target );\n        while ( !$target.is( \"a\" ) ) { $target = $target.parent(); }\n        const [ name, value, href ] = [ $target.text(), $target.attr( \"value\" ), $target.attr( \"href\" ) ];\n        $target.parent().parent().find( \"a\" ).removeClass( \"active\" );\n        $target.addClass( \"active\" );\n        this.props.onClick && this.props.onClick( $target, { name, value, href } );\n        this.props.autoClose && this.maskOnClick();\n    }\n\n    liOnClick( event ) {\n        let $target = $( event.target ), i = 0;\n        if ( $target.is( \"dropdown\" ) ) {\n            $target = $target.parent().parent();\n        } else if ( $target.is( \"sub-menu\" ) ) {\n            $target = $target.parent();\n        } else {\n            while ( !$target.is( \"a\" ) ) {\n                if (  i > 10 ) return;\n                $target = $target.parent();\n                i++;\n            }\n        }\n\n        $target     = $target.parent();\n        const style = { ...this.style },\n              $ul   = $target.find( \"ul\" ),\n              state = $target.find( \"dropdown\" ).attr( \"data-state\" );\n\n        if ( state == \"down\" ) {\n            $target.find( \"dropdown\" )\n                .attr( \"data-state\", \"up\" )\n                .css({ ...style.dropdown, ...style.dropup });\n            $ul.css({ ...style.ul });\n        } else {\n            $target.find( \"dropdown\" )\n                .attr( \"data-state\", \"down\" )\n                .css({ ...style.dropdown });\n            style.ul_sub.marginTop = $ul.attr( \"data-margin-top\" );\n            $ul.css({ ...style.ul, ...style.ul_sub });\n        }\n    }\n\n    maskOnClick( event ) {\n        $( \"side\" ).velocity( { left: 0 - Number.parseInt( $( \"side\" ).width() ) }, {\n            progress: ( elements, complete ) => {\n                $( \"side\" ).css( \"opacity\", 1 - complete );\n                $( \"mask\" ).css( \"opacity\", 1 - complete );\n            },\n            complete: () => {\n                $( \"sidebar\" ).css( \"left\", 0 - Number.parseInt( $( \"side\" ).width() ));\n                $( \"mask\" ).css( \"display\", \"none\" );\n                $( \"side close\" ).velocity({ left: 0 - Number.parseInt( $( \"side\" ).width() ) });\n                this.props.onExit && this.props.onExit();\n            }\n        });\n    }\n\n    render() {\n        let menu    = [];\n        const style = { ...this.style },\n              { items, width,\n                header, icon, footer, showClose,\n                color, bgColor,\n                headerStyle, contentStyle, footerStyle, maskStyle } = this.props;\n\n        width   && ( style.root.width = width );\n        width   && ( style.root.left  = 0 - Number.parseInt( width ) );\n        color   && ( style.text.color = color );\n        bgColor && ( style.root.backgroundColor = bgColor );\n\n        headerStyle  && ( style.header  = { ...style.header , ...headerStyle  });\n        contentStyle && ( style.content = { ...style.content, ...contentStyle });\n        footerStyle  && ( style.footer  = { ...style.footer , ...footerStyle  });\n        maskStyle    && ( style.mask    = { ...style.mask,     ...maskStyle   });\n\n        const subMenu = items => items.map( item => list( item ) ),\n              list    = item  => {\n                item.items && item.items.length > 0 &&\n                    ( style.ul_sub.marginTop = 0 - item.items.length * Number.parseInt( style.li.minHeight ));\n                return (\n                    <li style={ style.li } onClick={ item.items && ( evt=>this.liOnClick(evt) ) } >\n                        <Item style={ style }\n                            waves={ this.props.waves } tooltip={ this.props.tooltip }\n                            icon={ item.icon } fontIcon={ item.fontIcon || \"\" }\n                            name={ item.name } value={ item.value } route={ item.route }\n                            onClick={ !item.items && ( evt=>this.onClick(evt) ) } />\n                        { item.items && item.items.length > 0 &&\n                                <sub-menu style={ style.sub_menu }>\n                                    <dropdown style={ style.dropdown } data-state=\"down\"></dropdown>\n                                    <ul data-margin-top={ style.ul_sub.marginTop }\n                                        style={{ ...style.ul, ...style.ul_sub }}>{ subMenu( item.items ) }</ul>\n                                </sub-menu> }\n                    </li>\n                )\n        };\n\n        items && ( menu = items.map( item => list( item ) ) );\n\n        return (\n            <sidebar>\n                <side style={ style.root }>\n                    { header &&\n                    <header style={ style.header }>\n                        <Item style={ style } icon={ icon } name={ header } large={ true }\n                              waves={ this.props.waves } tooltip={ this.props.tooltip }\n                              onClick={ evt=>this.onClick(evt) }/>\n                    </header>\n                    }\n                    <content style={ style.content }>\n                        { menu.length > 0 && <ul style={ style.ul }>{ menu }</ul> }\n                    </content>\n                    { footer &&\n                    <footer style={ style.footer }>\n                        <Item style={ style } name={ footer }\n                              waves={ this.props.waves } tooltip={ this.props.tooltip }\n                              onClick={ evt=>this.onClick(evt) }/>\n                    </footer> }\n                    {\n                        showClose &&\n                        <close className={ this.props.waves } style={ style.close_icon } onClick={ ()=>this.maskOnClick() }>\n                            <div className=\"hamburger hamburger--arrow js-hamburger is-active\">\n                                <div className=\"hamburger-box\">\n                                    <div className=\"hamburger-inner\"></div>\n                                </div>\n                            </div>\n                        </close>\n                    }\n                </side>\n                <mask style={ style.mask } onClick={ evt=>this.maskOnClick(evt) }></mask>\n            </sidebar>\n        )\n    }\n\n}\n\n/**\n * Open sidebar\n */\nfunction Open() {\n    $( \"side\" ).velocity( { left: 0 }, {\n        progress: ( elements, complete ) => {\n            $( \"side\" ).css( \"opacity\", complete );\n            $( \"mask\" ).css( \"opacity\", complete ).css( \"display\", \"block\" );\n        }\n    });\n    $( \"side close\" ).velocity( { left: 256 });\n    tocRender();\n    activeRender();\n}\n\n/**\n * TocRender\n */\nfunction tocRender() {\n    if ( $( \"sidebar content toc\" ).length > 0 ) return;\n    const ids = [], tocs = new Map();\n    $( \"tabs\" ).find( \"tab-label a\" ).map( ( idx, item ) => ids.push( $(item).attr(\"value\") ));\n    ids.forEach( ( id, idx ) => {\n        const levels = [];\n        $($( \"tabs tab-group\" )[idx]).find( \"[data-head-level]\" ).map( ( idx, item ) => {\n            const $item = $( item ),\n                  id    = \"sr-toc-\" + idx,\n                  level = $item.attr( \"data-head-level\" ),\n                  text  = $item.attr( \"data-head-title\" ) || $item.text();\n            levels.push({ id, level, text });\n            $item.attr( \"id\", id );\n        });\n        tocs.set( id, levels );\n    });\n    $( \"sidebar content\" ).find( \"a\" ).map( ( idx, item ) => {\n        let html     = \"\";\n        const $item  = $( item ),\n              id     = $item.attr( \"value\" ),\n              levels = tocs.get( id );\n        ids[idx] && levels.forEach( value => {\n            html += `<outline class=\"md-waves-effect\" data-trigger=\"${ids[idx]}\" data-id=\"${value.id}\" class=\"toc-level-${ value.level }\">${value.text}</outline>`;\n        });\n        html.length > 0 && $item.after( `<toc><i></i>${html}</to>` );\n    });\n    $( \"sidebar content toc outline\" ).on( \"click\", event => {\n        const id      = $( event.currentTarget ).attr( \"data-id\" ),\n              trigger = $( event.currentTarget ).attr( \"data-trigger\" );\n        if ( !location.hash.endsWith( trigger ) ) {\n            $( \"tabs\" ).find( `tab-label a[value=${trigger}]` )[0].click();\n        }\n        // hack code\n        $( \"tabs\" ).find( \"tab-group[active=true]\" ).find( \"#\" + id )[0].scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'start' });\n    });\n}\n\n/**\n * Active Render\n */\nfunction activeRender() {\n    $( \"sidebar content\" ).find( \"a\" ).map( ( idx, item ) => {\n        const $item  = $( item ),\n              id     = $item.attr( \"value\" );\n        if ( location.hash.endsWith( id ) ) {\n            $item.addClass( \"active\" );\n        } else {\n            $item.removeClass( \"active\" );\n        }\n    });\n}\n\nexport {\n    Sidebar,\n    Open\n}"
  },
  {
    "path": "src/vender/mduikit/slider.jsx",
    "content": "/*!\r\n * React Material Design: Slider\r\n * \r\n * @version : 0.0.2\r\n * @update  : 2018/06/05\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: Slider ====\" )\r\n\r\nconst\r\n        thumb_color  = 'rgba(0, 137, 123, 1)',\r\n        color        = 'rgba(51, 51, 51, .87)',\r\n        border_color = 'rgba(224, 224, 224, 1)',\r\n        focus_color  = 'rgba(0, 137, 123, .8)',\r\n        error_color  = 'rgba(244, 67, 54, 1)',\r\n        cssinjs = () => {\r\n\r\n            const\r\n                margin      = '8px 0 0 0',\r\n                display     = 'block',\r\n                medium      = '14px',\r\n                large       = '16px',\r\n                lineHeight  = 1.5,\r\n                fontWeight  = 'bold',\r\n                width       = '100%',\r\n                styles      = {\r\n                root: {\r\n                    position: 'relative',\r\n                    display: 'flex',\r\n                    alignItems: 'center',\r\n                    position: 'relative',\r\n\r\n                    width: '100%',\r\n                    height: '37px',\r\n\r\n                    margin: '8px 0 0 0',\r\n                    padding: 0,\r\n                },\r\n\r\n                group: {\r\n                    position: 'relative',\r\n                    width: '100%',\r\n                },\r\n\r\n                line: {\r\n                    position: 'absolute',\r\n                    width: '20px',\r\n\r\n                    left: 0,\r\n                    bottom: '6px',\r\n\r\n                    width: '20px',\r\n                    height: '3px',\r\n\r\n                    backgroundColor: focus_color,\r\n                    pointerEvents: 'none',\r\n                },\r\n\r\n                text_field: {\r\n                    display,\r\n                    position: 'relative',\r\n                    margin: '0 0 0 30px',\r\n                    padding: 0,\r\n\r\n                    width: '50px',\r\n                    lineHeight: 1,\r\n                },\r\n\r\n                input: {\r\n                    color,\r\n                    backgroundColor: 'transparent',\r\n\r\n                    width,\r\n                    height: '20px',\r\n\r\n                    margin: 0,\r\n                    padding: 0,\r\n\r\n                    fontFamily: 'sans-serif',\r\n                    fontSize: medium,\r\n                    textAlign: 'center',\r\n\r\n                    border: 'none',\r\n                    outline: 'none',\r\n\r\n                    boxShadow: 'none',\r\n                    boxSizing: 'content-box',\r\n                    transition: 'all 0.3s',\r\n                },\r\n\r\n                border : {\r\n                    display,\r\n\r\n                    width,\r\n                    margin,\r\n\r\n                    borderTop: `none ${border_color}`,\r\n                    borderLeft: `none ${border_color}`,\r\n                    borderRight: `none ${border_color}`,\r\n                    borderBottom: `1px solid ${border_color}`,\r\n                    boxSizing: 'content-box',\r\n                },\r\n\r\n                state : {\r\n                    display,\r\n                    position: 'absolute',\r\n\r\n                    width,\r\n                    margin: '-1px 0 0 0',\r\n\r\n                    borderTop: `none ${focus_color}`,\r\n                    borderLeft: `none ${focus_color}`,\r\n                    borderRight: `none ${focus_color}`,\r\n                    borderBottom: `2px solid ${focus_color}`,\r\n                    boxSizing: 'content-box',\r\n\r\n                    transform: 'scaleX(0)',\r\n                    transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\r\n                },\r\n\r\n                state_focus : {\r\n                    transform: 'scaleX(1)',\r\n                },\r\n\r\n                state_error : {\r\n                    transform: 'scaleX(1)',\r\n                    borderTop: `none ${error_color}`,\r\n                    borderLeft: `none ${error_color}`,\r\n                    borderRight: `none ${error_color}`,\r\n                    borderBottom: `2px solid ${error_color}`,\r\n                },\r\n\r\n                error : {\r\n                    display,\r\n                    position: 'relative',\r\n\r\n                    margin,\r\n                    maxWidth: '428px',\r\n\r\n                    fontSize: medium,\r\n                    fontWeight,\r\n                    lineHeight,\r\n                    textAlign: 'initial',\r\n                    wordWrap: 'break-word',\r\n\r\n                    userSelect: 'none',\r\n\r\n                    color: error_color,\r\n                    transform: 'scale(0.75) translate( -73px, 0 )',\r\n                },\r\n\r\n            };\r\n\r\n            return styles;\r\n        },\r\n        range_style = color => {\r\n            return `\r\n                slider input[type=range] {\r\n                    position: absolute;\r\n                    left: 0;\r\n                    bottom: 0;\r\n\r\n                    width: 100%;\r\n                    height: 3px;\r\n\r\n                    margin: 6px 0;\r\n                    padding: 0;\r\n\r\n                    border: none;\r\n                    background-color: transparent;\r\n                    -webkit-appearance: none;\r\n                }\r\n\r\n                slider input[type=range]:focus {\r\n                    outline: none;\r\n                }\r\n\r\n                slider input[type=range]::-webkit-slider-runnable-track {\r\n                    width: 100%;\r\n                    height: 3px;\r\n\r\n                    background-color: #c2c0c2;\r\n\r\n                    box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.5), 0px 0px 0px rgba(13, 13, 13, 0.5);\r\n                    border-radius: 1.3px;\r\n\r\n                    transition: all 0.3s;\r\n                    cursor: pointer;\r\n                }\r\n\r\n                slider input[type=range]::-webkit-slider-thumb {\r\n                    height: 14px;\r\n                    width: 14px;\r\n\r\n                    margin-top: -5px;\r\n\r\n                    background-color: ${color};\r\n\r\n                    border-radius: 50px;\r\n\r\n                    cursor: pointer;\r\n                    -webkit-appearance: none;\r\n                }\r\n\r\n                slider input[type=range]::-moz-range-thumb {\r\n                    height: 14px;\r\n                    width: 14px;\r\n\r\n                    background-color: ${color};\r\n                    border: 0;\r\n                    border-radius: 50px;\r\n\r\n                    cursor: pointer;\r\n                }\r\n            `;\r\n};\r\n\r\n/**\r\n * Custom component: Slider, component e.g.\r\n * \r\n    <slider>\r\n        <group>\r\n            <line></line>\r\n            <input type=\"range\" min=0 max=100 step=1 />\r\n        </group>\r\n        <text-field>\r\n            <input />\r\n            <div>\r\n                <text-field-border/>\r\n                <text-field-state/>\r\n            </div>\r\n            <text-field-error></text-field-error>\r\n        </text-field>\r\n    </slider>\r\n * \r\n * Reference:\r\n * - https://material.io/guidelines/components/sliders.html#sliders-continuous-slider\r\n * - http://materializecss.com/range.html\r\n * \r\n * @class\r\n */\r\nexport default class Slider extends React.Component {\r\n\r\n    static defaultProps = {\r\n        // range\r\n        min         : 0,\r\n        max         : 100,\r\n        step        : 0,\r\n        value       : 0,\r\n        // style\r\n        width       : undefined,\r\n        // slider\r\n        precentColor: undefined,\r\n        thumbColor  : undefined,\r\n        // input\r\n        color       : undefined,\r\n        borderColor : undefined,\r\n        stateColor  : undefined,\r\n        errorColor  : undefined,\r\n\r\n        tooltip     : {},\r\n    };\r\n\r\n    static propTypes = {\r\n        min         : React.PropTypes.number,\r\n        max         : React.PropTypes.number,\r\n        step        : React.PropTypes.number,\r\n        value       : React.PropTypes.number,\r\n\r\n        width       : React.PropTypes.string,\r\n\r\n        precentColor: React.PropTypes.string,\r\n        thumbColor  : React.PropTypes.string,\r\n\r\n        color       : React.PropTypes.string,\r\n        borderColor : React.PropTypes.string,\r\n        stateColor  : React.PropTypes.string,\r\n        errorColor  : React.PropTypes.string,\r\n\r\n        tooltip     : React.PropTypes.object,\r\n        onChange    : React.PropTypes.func,\r\n    }\r\n\r\n    style = cssinjs()\r\n\r\n    lineWidth( value ) {\r\n        const maxWidth = $( this.refs.range ).width(),\r\n              perc     = ( this.props.max - value ) / ( this.props.max - this.props.min );\r\n        $( this.refs.line ).width( maxWidth - ( maxWidth * perc ));\r\n    }\r\n\r\n    onTextChangeFocus( event ) {\r\n        const style   = { ...this.style },\r\n              $target = $( event.target ),\r\n              $state  = $target.next().find( \"text-field-state\" );\r\n        $state.css({ ...style.state, ...style.state_focus });\r\n    }\r\n\r\n    onTextChangeBlur( event ) {\r\n        const style   = { ...this.style },\r\n              $target = $( event.target ),\r\n              $state  = $target.next().find( \"text-field-state\" );\r\n        $state.css({ ...style.state });\r\n    }\r\n\r\n    onTextChange( event ) {\r\n        let   istrue  = true;\r\n        const style   = { ...this.style },\r\n              $target = $( event.target ),\r\n              $state  = $target.next().find( \"text-field-state\" ),\r\n              min     = Number.parseInt( this.props.min ),\r\n              max     = Number.parseInt( this.props.max ),\r\n              value   = Number.parseInt( event.target.value );\r\n         if ( !Number.isInteger( value )) {\r\n            istrue = false;\r\n        } else if ( value < min ) {\r\n            istrue = false;\r\n        } else if ( value > max ) {\r\n            istrue = false;\r\n        }\r\n        if ( !istrue ) {\r\n            $state.css({ ...style.state, ...style.state_error });\r\n        } else {\r\n            $state.css({ ...style.state, ...style.state_focus });\r\n            this.refs.range.value = event.target.value;\r\n            this.refs.input.value = event.target.value;\r\n            this.lineWidth( event.target.value );\r\n            this.props.onChange && this.props.onChange( event.target.value, event );\r\n        }\r\n    }\r\n\r\n    onMouseUp( event ) {\r\n        const style  = { ...this.style },\r\n              $state = $( event.target ).parent().next().find( \"text-field-state\" );\r\n        $state.css({ ...style.state });\r\n    }\r\n\r\n    onChange( event ) {\r\n        const style  = { ...this.style },\r\n              $state = $( event.target ).parent().next().find( \"text-field-state\" );\r\n        $state.css({ ...style.state, ...style.state_focus });\r\n        this.refs.input.value = event.target.value;\r\n        this.lineWidth( event.target.value );\r\n        this.props.onChange && this.props.onChange( event.target.value, event );\r\n    }\r\n\r\n    componentWillMount() {\r\n        $( \"#mduikit-slider\" ).length > 0 && $( \"#mduikit-slider\" ).remove();\r\n        $( \"head\" ).append(`<style type=\"text/css\" id=\"mduikit-slider\">${range_style(this.props.thumbColor ? this.props.thumbColor : thumb_color)}</style>`);\r\n    }\r\n\r\n    componentDidMount() {\r\n        this.refs.range.value = this.props.value;\r\n        this.refs.input.value = this.props.value;\r\n        this.props.width     && $( this.refs.range ).width( this.props.width );\r\n        setTimeout( () => this.lineWidth( this.props.value ), 100 );\r\n    }\r\n\r\n    render() {\r\n        const style = { ...this.style };\r\n        this.props.precentColor && ( style.line.backgroundColor = this.props.precentColor );\r\n        this.props.color && ( style.input.color = this.props.color );\r\n        if ( this.props.borderColor ) {\r\n            style.border.borderTop    = `none ${this.props.borderColor}`;\r\n            style.border.borderLeft   = `none ${this.props.borderColor}`;\r\n            style.border.borderRight  = `none ${this.props.borderColor}`;\r\n            style.border.borderBottom = `1px solid ${this.props.borderColor}`;\r\n        }\r\n        if ( this.props.stateColor ) {\r\n            style.state.borderTop    = `none ${this.props.stateColor}`;\r\n            style.state.borderLeft   = `none ${this.props.stateColor}`;\r\n            style.state.borderRight  = `none ${this.props.stateColor}`;\r\n            style.state.borderBottom = `2px solid ${this.props.stateColor}`;\r\n        }\r\n        if ( this.props.errorColor ) {\r\n            style.error.color              = this.props.errorColor;\r\n            style.state_error.borderTop    = `none ${this.props.errorColor}`;\r\n            style.state_error.borderLeft   = `none ${this.props.errorColor}`;\r\n            style.state_error.borderRight  = `none ${this.props.errorColor}`;\r\n            style.state_error.borderBottom = `2px solid ${this.props.errorColor}`;\r\n        }\r\n\r\n        const events = {\r\n            onFocus  : (e)=>this.onTextChangeFocus(e),\r\n            onBlur   : (e)=>this.onTextChangeBlur(e),\r\n            onChange : (e)=>this.onTextChange(e),\r\n        },\r\n        tooltip = this.props.tooltip;\r\n\r\n        return (\r\n            <slider style={ style.root }\r\n                    data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }>\r\n                <sl-group style={ style.group }>\r\n                    <input ref=\"range\" type=\"range\" min={this.props.min} max={this.props.max} step={this.props.step} onChange={ evt=> this.onChange(evt) } onMouseUp={ evt=>this.onMouseUp(evt)}/>\r\n                    <line ref=\"line\" style={ style.line }></line>\r\n                </sl-group>\r\n                <text-field style={ style.text_field }>\r\n                    <input ref=\"input\" style={ style.input } { ...events } />\r\n                    <div>\r\n                        <text-field-border style={ style.border }/>\r\n                        <text-field-state style={ style.state }/>\r\n                    </div>\r\n                    <text-field-error style={ style.error }></text-field-error>\r\n                </text-field>\r\n            </slider>\r\n        )\r\n    }\r\n\r\n}"
  },
  {
    "path": "src/vender/mduikit/statebutton.jsx",
    "content": "/*!\n * React Material Design: StateButton\n * \n * @version : 0.0.2\n * @update  : 2018/06/27\n * @homepage: https://github.com/kenshin/mduikit-ui\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: StateButton ====\" )\n\nconst raisedstyle = {\n        color           : \"rgba(255, 255, 255, .7)\",\n        backgroundColor : \"rgba(0, 137, 123, 1)\",\n        hoverColor      : \"rgba( 255, 255, 255, .4)\",\n    },\n    flatstyle = {\n        color           : \"rgba(0, 137, 123, .8)\",\n        backgroundColor : \"transparent\",\n        hoverColor      : \"rgba( 153, 153, 153, .4)\"\n    },\n    secondary = {\n        flat: {\n            opacity: 0.6,\n        },\n        raised: {\n            backgroundColor: \"rgba( 153, 153, 153, .2)\",\n        }\n    },\n    disable = {\n        flat: {\n            cursor: \"no-drop\",\n            color: \"rgba(0, 0, 0, 0.298039)\",\n        },\n        raised: {\n            cursor: \"no-drop\",\n            color: \"rgba(0, 0, 0, 0.298039)\",\n            backgroundColor: \"rgb(229, 229, 229)\",\n            boxShadow: \"none\",\n        }\n    },\n    cssinjs = () => {\n        const styles = {\n\n            root: {},\n            normal_root : {\n                display: 'block',\n\n                minWidth: '88px',\n                height: '36px',\n\n                margin: 0,\n                padding: 0,\n\n                fontFamily: 'sans-serif',\n                textDecoration: 'none',\n\n                cursor: 'pointer',\n\n                border: 'none',\n                borderRadius: '2px',\n\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n            },\n\n            mask: {\n                display: '-webkit-flex',\n                justifyContent: 'center',\n                alignItems: 'center',\n\n                width: '100%',\n                height: '100%',\n\n                margin: 0,\n                padding: '0 8px',\n\n                border: 'none',\n                borderRadius: '2px',\n                boxSizing: 'border-box',\n                transition: 'all .5s ease-in-out',\n\n                backgroundColor: 'transparent',\n            },\n\n            raised: {\n                boxShadow: '0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2)',\n            },\n\n            flat: {\n                fontWeight: 400,\n            },\n\n            span : {\n                display: 'flex',\n                alignItems: 'center',\n\n                userSelect: 'none',\n            },\n\n            text: {\n                padding: '0 8px 0',\n\n                textDecoration: 'none',\n                textAlign: 'center',\n                letterSpacing: '.5px',\n\n                fontSize: '15px',\n                lineHeight: '1',\n            },\n\n            icon: {\n                order: -1,\n                display: 'block',\n\n                width: '24px',\n                height: '24px',\n\n                border: 'none',\n                backgroundPosition: 'center',\n                backgroundRepeat: 'no-repeat',\n            },\n\n            font_icon: {\n                display: 'flex',\n                justifyContent: 'center',\n                alignItems: 'center',\n\n                fontSize: '18px',\n                color: '#fff',\n\n                order: -1,\n                display: 'block',\n\n                width: '24px',\n                height: '24px',\n\n                border: 'none',\n            },\n\n        }\n\n        return styles;\n    },\n    statue_button_style = `\n        state-button svg.special {\n            position: fixed;\n            top: -30px;\n            transform: scale(.5);\n        }\n\n        state-button .success path {\n            animation: 500ms draw-success ease-out;\n        }\n\n        @keyframes draw-success {\n            0% {\n                stroke-dasharray: 49,80;\n                stroke-dashoffset: 48;\n                opacity: 0;\n            }\n\n            50% {\n                stroke-dasharray: 49,80;\n                stroke-dashoffset: 48;\n                opacity: 1;\n            }\n\n            100% {\n                stroke-dasharray: 130,80;\n                stroke-dashoffset: 48;\n            }\n        }\n\n        state-button .first-line {\n            animation: 500ms draw-first-line ease-out;\n        }\n        \n        state-button .second-line {\n            animation: 500ms draw-second-line ease-out;\n        }\n        \n        @keyframes draw-first-line {\n            0% {\n                stroke-dasharray: 0,56;\n                stroke-dashoffset: 0;\n            }\n        \n            50% {\n                stroke-dasharray: 0,56;\n                stroke-dashoffset: 0;\n            }\n        \n            100% {\n                stroke-dasharray: 56,330;\n                stroke-dashoffset: 0;\n            }\n        }\n        \n        @keyframes draw-second-line {\n            0% {\n                stroke-dasharray: 0,55;\n                stroke-dashoffset: 1;\n            }\n        \n            50% {\n                stroke-dasharray: 0,55;\n                stroke-dashoffset: 1;\n            }\n        \n            100% {\n                stroke-dasharray: 55,0;\n                stroke-dashoffset: 70;\n            }\n        }\n\n        state-button svg.warning {\n            top: -15px;\n            width: 15px;\n            height: 70px;\n            animation: 0.5s alert-sign-bounce cubic-bezier(0.175, 0.885, 0.32, 1.275);\n        }\n        \n        @keyframes alert-sign-bounce {\n            0% {\n                transform: scale(0);\n                opacity: 0;\n            }\n        \n            50% {\n                transform: scale(0);\n                opacity: 1;\n            }\n        \n            100% {\n                transform: scale(1);\n            }\n        }\n`;\n\nclass SVG extends React.Component {\n    static defaultProps = {\n        state       : undefined,\n        loadingColor: undefined,\n        successColor: undefined,\n        failedColor : undefined,\n        warningColor: undefined,\n    }\n\n    static propTypes = {\n        state        : React.PropTypes.string,\n        loadingColor : React.PropTypes.string,\n        successColor : React.PropTypes.string,\n        failedColor  : React.PropTypes.string,\n        warningColor : React.PropTypes.string,\n    }\n\n    componentWillMount() {\n        $( \"#mduikit-state-button\" ).length > 0 && $( \"#mduikit-state-button\" ).remove();\n        $( \"head\" ).append(`<style type=\"text/css\" id=\"mduikit-state-button\">${statue_button_style}</style>`);\n    }\n\n    render() {\n\n        const loading = `<svg class=\"loading\" width=\"30\" height=\"30\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\" class=\"lds-rolling\">\n                            <circle stroke=\"${this.props.loadingColor}\" stroke-width=\"10\" cx=\"50\" cy=\"50\" fill=\"none\" ng-attr-stroke=\"{{config.color}}\" ng-attr-stroke-width=\"{{config.width}}\" ng-attr-r=\"{{config.radius}}\" ng-attr-stroke-dasharray=\"{{config.dasharray}}\" r=\"30\" stroke-dasharray=\"141.37166941154067 49.12388980384689\" transform=\"rotate(102 50 50)\">\n                                <animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform>\n                            </circle>\n                        </svg>`,\n              success = `<svg class=\"special success\" stroke=\"${this.props.successColor}\" stroke-width=\"10\" stroke-linecap=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\n                            <g transform=\"matrix(0.79961,8.65821e-32,8.39584e-32,0.79961,-489.57,-205.679)\">\n                                <path fill=\"none\" d=\"M616.306,283.025L634.087,300.805L673.361,261.53\"></path>\n                            </g>\n                        </svg>`,\n              failed = `<svg class=\"special failed\" stroke=\"${this.props.failedColor}\" stroke-width=\"10\" stroke-linecap=\"round\">\n                            <g transform=\"matrix(0.79961,8.65821e-32,8.39584e-32,0.79961,-502.652,-204.518)\">\n                                <path class=\"first-line\" d=\"M634.087,300.805L673.361,261.53\" fill=\"none\"/>\n                            </g>\n                            <g transform=\"matrix(-1.28587e-16,-0.79961,0.79961,-1.28587e-16,-204.752,543.031)\">\n                                <path class=\"second-line\" d=\"M634.087,300.805L673.361,261.53\"/>\n                            </g>\n                        </svg>`,\n              warning = `<svg class=\"special warning\" stroke=\"${this.props.warningColor}\" stroke-width=\"10\" stroke-linecap=\"round\" xmlns=\"http://www.w3.org/2000/svg\">\n                            <g transform=\"matrix(1,0,0,1,-615.516,-257.346)\">\n                                <g transform=\"matrix(0.56541,-0.56541,0.56541,0.56541,93.7153,495.69)\">\n                                    <path class=\"line\" d=\"M634.087,300.805L673.361,261.53\" fill=\"none\"/>\n                                </g>\n                                <g transform=\"matrix(2.27612,-2.46519e-32,0,2.27612,-792.339,-404.147)\" stroke=\"none\" fill=\"#fff\">\n                                    <circle class=\"dot\" cx=\"621.52\" cy=\"316.126\" r=\"1.318\" />\n                                </g>\n                            </g>\n                        </svg>`;\n        const states = { loading, success, failed, warning };\n\n        return (\n            this.props.state != \"init\" && <svg-state dangerouslySetInnerHTML={{__html: states[this.props.state] }} ></svg-state>\n        )\n    }\n}\n\n/**\n * Custom <state-button> tag component: StateButton\n * \n * Reference:\n * - https://material.io/guidelines/components/progress-activity.html\n * - https://github.com/Kamonlojn/svg-icons-animate\n * \n * @class\n */\nexport default class StateButton extends React.Component {\n\n    static defaultProps = {\n        text    : \"\",\n        disable : false,\n        icon    : \"\",\n        fontIcon: \"\",\n        order   : \"before\",\n        type    : \"flat\",\n        mode    : \"primary\",\n        style   : undefined,\n        width   : undefined,\n\n        color           : \"\",\n        backgroundColor : \"\",\n        hoverColor      : \"\",\n\n        loadingColor    : \"rgba(255, 255, 255, 1)\",\n        successColor    : \"rgba(255, 255, 255, 1)\",\n        failedColor     : \"rgba(255, 255, 255, 1)\",\n        warningColor    : \"rgba(255, 255, 255, 1)\",\n\n        successBgColor  : \"rgba(139, 195, 74, 1)\",\n        failedBgColor   : \"rgba(244, 67, 54, 1)\",\n        warningBgColor  : \"rgba(255, 193, 7, 1)\",\n\n        tooltip : {},\n        waves   : undefined,\n    }\n\n    static propTypes = {\n        text    : React.PropTypes.string,\n        disable : React.PropTypes.bool,\n        icon    : React.PropTypes.string,\n        fontIcon: React.PropTypes.string,\n        order   : React.PropTypes.oneOf([ \"before\", \"after\" ]),\n        type    : React.PropTypes.oneOf([ \"flat\", \"raised\" ]),\n        mode    : React.PropTypes.oneOf([ \"primary\", \"secondary\" ]),\n        style   : React.PropTypes.object,\n        width   : React.PropTypes.string,\n\n        color           : React.PropTypes.string,\n        hoverColor      : React.PropTypes.string,\n        backgroundColor : React.PropTypes.string,\n\n        loadingColor    : React.PropTypes.string,\n        successColor    : React.PropTypes.string,\n        failedColor     : React.PropTypes.string,\n        warningColor    : React.PropTypes.string,\n\n        successBgColor  : React.PropTypes.string,\n        failedBgColor   : React.PropTypes.string,\n        warningBgColor  : React.PropTypes.string,\n\n        tooltip : React.PropTypes.object,\n        waves   : React.PropTypes.string,\n        onState : React.PropTypes.func,\n    }\n\n    state = {\n        state          : \"init\", // include: init, loading, success, failed, warning\n\n        color          : ((bool)=>bool ? flatstyle.color : raisedstyle.color)( this.props.type != \"raised\" ),\n        backgroundColor: ((bool)=>bool ? flatstyle.backgroundColor : raisedstyle.backgroundColor)( this.props.type != \"raised\" ),\n        hoverColor     : ((bool)=>bool ? flatstyle.hoverColor : raisedstyle.hoverColor)( this.props.type != \"raised\" ),\n    }\n\n    global = {}\n    style  = cssinjs()\n\n    changeStateStyle() {\n        const $root    = $( this.refs.button ),\n              width    = $root.width(),\n              svgWidth = $root.find(\".special\").width() * 0.5,\n              padding  = 16,\n              left     = ( width - svgWidth ) / 2 - padding;\n        $root.find( \".special\" ).css({ left });\n        this.global.state == \"init\" && $root.find( \"button-span\" ).css({ ...this.style.span });\n        $root.css({ \"background-color\": this.global.bgColor[this.global.state] })\n             .find( \"button-span\" ).animate({ opacity: this.global.state == \"init\" ? 1 : 0 });\n    }\n\n    /**\n     * Change state\n     * \n     * @param {string} @see this.state.state\n     */\n    changeState( state ) {\n        this.global.state = state;\n        this.setState({ state });\n        this.changeStateStyle();\n    }\n\n    onMouseOver() {\n        if ( this.global.state == \"loading\" ) return;\n        else if ( [ \"success\", \"failed\", \"warning\" ].includes( this.global.state)) {\n            this.changeState( \"init\" );\n        }\n        const [ style, $mask ] = [ { ...this.style }, $( this.refs.mask ) ];\n        $mask.css( \"background-color\", this.state.hoverColor );\n    }\n\n    onMouseOut() {\n        const [ style, $mask ] = [ { ...this.style }, $( this.refs.mask ) ];\n        $mask.css({ ...style.mask });\n    }\n\n    onClick( event ) {\n        if ( this.global.state == \"loading\" ) return;\n        this.props.onState && this.props.onState( this );\n    }\n\n    componentWillMount() {\n        this.props.color != \"\"           && this.setState({ color: this.props.color });\n        this.props.backgroundColor != \"\" && this.setState({ backgroundColor: this.props.backgroundColor });\n        this.props.hoverColor != \"\"      && this.setState({ hoverColor: this.props.hoverColor });\n    }\n\n    render() {\n        const style   = $.extend( true, {}, this.style ),\n              current = this.props.type == \"raised\" ? { ...style.raised } : { ...style.flat };\n        current.color = this.state.color;\n        current.backgroundColor = this.state.backgroundColor;\n\n        if ( this.props.text == \"\" && this.props.icon != \"\" ) {\n            delete style.normal_root.minWidth;\n            delete style.normal_root.borderRadius;\n            style.normal_root.width = style.normal_root.height;\n        }\n\n        this.props.mode == \"secondary\" &&\n            Object.keys( secondary[ this.props.type ] ).forEach( key => style.mask[ key ] = secondary[ this.props.type ][ key ] );\n\n        this.props.disable &&\n            Object.keys( disable[ this.props.type ] ).forEach( key => current[ key ] = disable[ this.props.type ][ key ] );\n\n        style.root = { ...style.normal_root, ...current };\n\n        this.props.style            && ( style.root = { ...style.root, ...this.props.style } );\n\n        if ( this.props.fontIcon != \"\" ) {\n            style.icon         = { ...style.font_icon };\n            style.icon.display = \"flex\";\n            style.icon.color   = style.color;\n        } else this.props.icon  != \"\" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = \"none\" );\n        //     this.props.icon  != \"\" ? ( style.icon.backgroundImage = `url(${this.props.icon})` ) : ( style.icon.display = \"none\" );\n        this.props.text  == \"\"      && ( style.text.display = \"none\" );\n        this.props.order == \"after\" && ( style.icon.order = 1 );\n        this.props.width            && ( style.root.width = this.props.width );\n        this.state.state != \"init\"  && ( style.span.display = \"none\" );\n\n        const events = this.props.disable ? {} : {\n            onMouseOver : (e)=>this.onMouseOver(e),\n            onMouseOut  : (e)=>this.onMouseOut(e),\n            onClick     : (e)=>this.onClick(e),\n        },\n        tooltip      = this.props.tooltip;\n\n        this.global = {\n            state       : this.state.state,\n            bgColor     : {\n                init    : style.root.backgroundColor,\n                success : this.props.successBgColor,\n                failed  : this.props.failedBgColor,\n                warning : this.props.warningBgColor,\n            }\n        };\n\n        return (\n            <state-button ref=\"button\" style={ style.root } class={ this.props.waves }\n                type={ this.props.type } mode={ this.props.mode } \n                data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }\n                { ...events }>\n                <button-mask ref=\"mask\" style={ style.mask }>\n                    <button-svg>\n                        <SVG state={ this.state.state } { ...this.props }/>\n                    </button-svg>\n                    <button-span style={ style.span }>\n                        <button-icon style={ style.icon }  dangerouslySetInnerHTML={{__html: this.props.fontIcon }} ></button-icon>\n                        <button-text style={ style.text }>{ this.props.text }</button-text>\n                    </button-span>\n                </button-mask>\n            </state-button>\n        )\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/switch.jsx",
    "content": "/*!\n * React Material Design: Switch\n * \n * @version : 0.0.3\n * @update  : 2018/04/26\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Switch ====\" )\n\nconst color           = \"rgba(51, 51, 51, .87)\",\n      secondary_color = \"rgba(204, 204, 204, 1)\",\n\n      thumb_color     = \"rgba(245, 245, 245, 1)\",\n      thumbed_color   = \"rgba(0, 137, 123, 1)\",\n\n      track_color     = \"rgba(189, 189, 189, 1)\",\n      tracked_color   = \"rgba(0, 137, 123, .5)\";\n\nconst cssinjs = () => {\n\n    const styles = {\n        hidden: 'none',\n        root: {\n            display: 'flex',\n            alignItems: 'center',\n            position: 'relative',\n\n            width: '100%',\n            height: '37px',\n\n            margin: '8px 0 0 0',\n            padding: 0,\n\n            overflow: 'visible',\n        },\n\n        large_height : \"46px\",\n\n        enable: {\n            color: color,\n            cursor: 'pointer',\n        },\n\n        disable: {\n            color: secondary_color,\n            cursor: 'not-allowed',\n        },\n\n        label: {\n            display: 'block',\n            width: '100%',\n\n            fontFamily: 'sans-serif',\n            fontSize: '14px',\n            fontWeight: 400,\n\n            userSelect: 'none',\n            pointerEvents: 'none',\n        },\n\n        label_after: {\n            textAlign: 'right',\n            order: 2,\n        },\n\n        label_before: {\n            textAlign: 'left',\n            order: -1,\n        },\n\n        range: {\n            display: 'block',\n            position: 'relative',\n            float: 'left',\n            flexShrink: 0,\n\n            width: '36px',\n\n            margin: '0 0 0 8px',\n            padding: '4px 0px 6px 2px',\n\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        thumb: {},\n\n        thumb_normal: {\n            display: 'block',\n            position: 'absolute',\n            top: '1px',\n            left: '0px',\n\n            width: '20px',\n            height: '20px',\n            color: color,\n            backgroundColor: thumb_color,\n\n            boxShadow: 'rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px',\n            boxSizing: 'border-box',\n            borderRadius: '50%',\n\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        thumbed: {\n            left: '100%',\n            marginLeft: '-20px',\n            backgroundColor: thumbed_color,\n        },\n\n        thumb_disable: {\n            left: 0,\n            marginLeft: 0,\n            backgroundColor: secondary_color,\n        },\n\n        track: {},\n\n        track_normal: {\n            display: 'block',\n            width: '100%',\n            height: '14px',\n\n            borderRadius: '30px',\n            backgroundColor: track_color,\n\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        tracked: {\n            backgroundColor: tracked_color,\n        },\n\n        content: {\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'flex-start',\n            width: '100%',\n        },\n\n        subtitle: {\n            display: '-webkit-box',\n            flexShrink: 2,\n\n            WebkitLineClamp: 1,\n            '-webkit-box-orient': 'vertical',\n\n            overflow: 'hidden',\n            textOverflow: 'ellipsis',\n            textAlign: 'left',\n\n            color: \"rgba( 51, 51, 51, .54 )\",\n        },\n\n    };\n\n    return styles;\n}\n\n/**\n * Custom component: Switich\n * \n * Reference:\n * - https://material.io/guidelines/components/selection-controls.html\n * - http://www.material-ui.com/#/components/toggle\n * \n * @class\n */\nexport default class Switch extends React.Component {\n\n    static defaultProps = {\n        checked      : false,\n        disable      : false,\n        width        : undefined,\n        label        : \"\",\n        order        : \"before\",\n        thumbColor   : undefined,\n        thumbedColor : undefined,\n        trackColor   : undefined,\n        trackedColor : undefined,\n        desc         : \"\",\n        waves        : \"\",\n        tooltip      : \"\",\n    };\n\n    static propTypes = {\n        checked      : React.PropTypes.bool,\n        disable      : React.PropTypes.bool,\n        width        : React.PropTypes.string,\n        label        : React.PropTypes.string,\n        order        : React.PropTypes.string,\n        thumbColor   : React.PropTypes.string,\n        thumbedColor : React.PropTypes.string,\n        trackColor   : React.PropTypes.string,\n        trackedColor : React.PropTypes.string,\n        desc         : React.PropTypes.string,\n        waves        : React.PropTypes.string,\n        tooltip      : React.PropTypes.string,\n        onChange     : React.PropTypes.func,\n    };\n\n    state = {\n        checked : this.props.checked,\n    };\n\n    style = cssinjs();\n\n    onClick() {\n        !this.props.disable && this.setState({\n            checked: !this.state.checked,\n        });\n        !this.props.disable && this.props.onChange && this.props.onChange( !this.state.checked );\n    }\n\n    componentWillReceiveProps( nextProps ) {\n        this.setState({ checked: nextProps.checked });\n    }\n\n    render() {\n        const style = { ...this.style };\n\n        this.props.thumbColor   && ( style.thumb_normal.backgroundColor = this.props.thumbColor );\n        this.props.thumbedColor && ( style.thumbed.backgroundColor      = this.props.thumbedColor );\n        this.props.trackColor   && ( style.track_normal.backgroundColor = this.props.trackColor );\n        this.props.trackedColor && ( style.tracked.backgroundColor      = this.props.trackedColor );\n\n        if ( this.state.checked ) {\n            style.thumb = { ...style.thumb_normal, ...style.thumbed };\n            style.track = { ...style.track_normal, ...style.tracked };\n        } else {\n            style.thumb = { ...style.thumb_normal };\n            style.track = { ...style.track_normal };\n        }\n\n        if ( this.props.disable ) {\n            style.root  = { ...style.root, ...style.disable };\n            style.thumb = { ...style.thumb, ...style.thumb_disable };\n            style.track = { ...style.track_normal };\n        } else {\n            style.root  = { ...style.root, ...style.enable };\n        }\n\n        style.label = this.props.order == \"before\" ? { ...style.label, ...style.label_before } : { ...style.label, ...style.label_after };\n\n        this.props.label == \"\" && ( style.label.display = style.hidden );\n        this.props.width && ( style.root.width  = this.props.width );\n        this.props.desc  && ( style.root.height = style.root.large_height );\n\n        const tooltip = this.props.tooltip;\n\n        return (\n            <switch style={ style.root }\n                    data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }\n                    onClick={ ()=>this.onClick() }>\n                <content style={ style.content }>\n                    <span style={ style.label }>{ this.props.label }</span>\n                    <subtitle style={ style.subtitle }>{ this.props.desc }</subtitle>\n                </content>\n                <switch-rang style={ style.range }>\n                    <thumb style={ style.thumb } className={ this.props.waves }></thumb>\n                    <track style={ style.track }></track>\n                </switch-rang>\n            </switch>\n        )\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/tabs.jsx",
    "content": "/*!\n * React Material Design: Tabs\n * \n * @version : 0.0.3\n * @update  : 2018/04/26\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: Tabs ====\" )\n\nconst color           = 'rgba(255, 255, 255, .7)',\n      secondary_color = \"rgba(204, 204, 204, 1)\",\n      active_color    = 'rgba(255, 255, 255, 1)',\n      header_corlor   = 'transparent';\n\nconst cssinjs = () => {\n    const styles = {\n\n        root: {\n            display: 'block',\n            width: '100%',\n        },\n\n        header: {\n            display: 'flex',\n            justifyContent: 'space-around',\n            alignItems: 'flex-end',\n\n            position: 'relative',\n\n            width: '100%',\n\n            backgroundColor: header_corlor,\n        },\n\n        label: {\n            display: 'flex',\n            alignItems: 'center',\n            justifyContent: 'center',\n\n            position: 'relative',\n\n            padding: '0 24px',\n\n            width: '100%',\n            height: '48px',\n\n            color,\n            backgroundColor: 'transparent',\n\n            fontSize: '1.4rem',\n            textTransform: 'uppercase',\n\n        },\n\n        label_active: {\n            color: active_color,\n            fontWeight: 500,\n        },\n\n        link: {\n            color: 'inherit',\n            backgroundColor: 'transparent',\n\n            overflow: 'hidden',\n            whiteSpace: 'nowrap',\n            textOverflow: 'ellipsis',\n            textDecoration: 'none',\n        },\n\n        link_disable: {\n            color: secondary_color,\n            cursor: 'not-allowed',\n        },\n\n        link_icon: {\n            display: 'flex',\n            flexDirection: 'column',\n            alignItems: 'center',\n        },\n\n        icon: {\n            display: 'block',\n\n            width: '24px',\n            height: '24px',\n\n            border: 'none',\n            backgroundPosition: 'center',\n            backgroundRepeat: 'no-repeat',\n        },\n\n        border: {\n            display: 'block',\n            position: 'absolute',\n\n            left: 0,\n            bottom: 0,\n\n            width: '100%',\n            height: '4px',\n\n            backgroundColor: '#EEFF41',\n\n            transform: 'scaleX(0)',\n            transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n        },\n\n        border_active: {\n            transform: 'scaleX(1)',\n        },\n\n        shadow: {\n            display: 'block',\n            position: 'absolute',\n\n            left: 0,\n            bottom: 0,\n\n            width: '100%',\n            height: '4px',\n\n            boxShadow: '0 1px 2px rgba(0, 0, 0, .12), 0 2px 2px rgba(0, 0, 0, .26)',\n        },\n\n        groups: {\n            display: 'block',\n            width: '100%',\n        },\n\n        group: {\n            display: 'none',\n        },\n\n        group_active: {\n            display: 'block',\n            opacity: 1,\n        }\n\n    };\n    \n    return styles;\n}\n\n/**\n * TabLabel react stateless component\n * \n * @param {object} props, include:\n *   - idx             : [PropTypes.number] index\n *   - name            : [PropTypes.string] name\n *   - value           : [PropTypes.string] value\n *   - icon            : [PropTypes.string] icon path\n *   - active          : [PropTypes.bool]   active\n *   - route           : [PropTypes.string] <a> href value\n *   - disable         : [PropTypes.string] disable\n *   - style           : [PropTypes.object] tab-label style, include: label, border, link, label_active, border_active\n *   - waves           : [PropTypes.string] material waves effect\n *   - tooltip         : [PropTypes.string] tooltip\n *   - onClick         : [PropTypes.func]   onClick event\n */\nconst TabLabel = ( props ) => {\n    const route     = !props.route || props.route == \"\" ? \"#\" : props.route,\n          disable   = props.disable ? true : false,\n          tooltip   = props.tooltip.text ? props.tooltip.text : props[ props.tooltip.target ],\n          style     = props.style,\n          target    = !route.startsWith( \"#\" ) ? \"_blank\" : undefined;\n    props.active  && ( style.label  = { ...style.label, ...style.label_active } );\n    props.active  && ( style.border = { ...style.border, ...style.border_active } );\n    props.disable && ( style.link   = { ...style.link, ...style.link_disable } );\n    if ( props.icon && props.icon != \"\" ) {\n        style.icon.display = \"block\";\n        style.icon.backgroundImage = `url(${props.icon})`;\n        style.link = { ...style.link, ...style.link_icon };\n    } else {\n        style.icon.display = \"none\";\n    }\n    return (\n        <tab-label style={ style.label } class={ props.waves } active={ props.active } onClick={ !disable && ( evt=>props.onClick(evt) )}>\n            <a style={ style.link }\n               id={ props.idx } href={ route } target={ target }\n               data-tooltip={ tooltip } data-tooltip-position={ props.tooltip.position } data-tooltip-delay={ props.tooltip.delay }\n               value={ props.value }\n               disabled={ disable }>\n               <tab-icon style={ style.icon }></tab-icon>\n               { props.name }\n               </a>\n            <tab-border style={ style.border }></tab-border>\n        </tab-label>\n    );\n}\n\n/**\n * Custom <a> tag component: Tabs, component e.g.\n * \n    <tabs>\n        <tab-header>\n            <tab-label class=\"tabactive\">\n                <a href=\"#\">共通</a>\n                <tab-border class=\"borderactive\"></tab-border>\n            </tab-label>\n            <tab-label>\n                <a href=\"#\">聚焦模式</a>\n                <tab-border></tab-border>\n            </tab-label>\n            <tab-shadow></tab-shadow>\n        </tab-header>\n        <tab-groups>\n            <tab-group class=\"groupactive\">\n                aaa\n            </tab-group>\n            <tab-group>\n                bbb\n            </tab-group>\n        </tab-groups>\n    </tabs>\n * \n * Reference:\n * - https://material.io/guidelines/components/tabs.html\n * - http://www.material-ui.com/#/components/tabs\n * \n * @class\n */\nexport default class Tabs extends React.Component {\n\n    static defaultProps = {\n        items    : [],\n        color    : \"\",\n        activeColor: \"\",\n        bgColor    : \"\",\n        headerStyle: undefined,\n        groupsStyle: undefined,\n        borderStyle: undefined,\n        waves    : \"\",\n        tooltip  : \"\",\n    };\n\n    static propTypes = {\n        items    : React.PropTypes.array,\n        color    : React.PropTypes.string,\n        activeColor : React.PropTypes.string,\n        bgColor     : React.PropTypes.string,\n        headerStyle : React.PropTypes.object,\n        groupsStyle : React.PropTypes.object,\n        borderStyle : React.PropTypes.object,\n        waves    : React.PropTypes.string,\n        tooltip  : React.PropTypes.string,\n        onChange : React.PropTypes.func,\n    };\n\n    style = cssinjs();\n\n    componentWillUnmount() {\n        $( \"tabs\" ).remove();\n    }\n\n    tabLabelOnClick( event ) {\n        let $target = $( event.target );\n\n        if($target.is(\"tab-label\")) {\n            const $a = $target.find( \"a\" );\n            $a[0] && $a[0].click();\n            return;\n        }\n\n        if(!$target.is('a')) { $target = $target.parent(); }\n\n        const href = $target.attr('href');\n        if(!href.startsWith( \"#\" )) { return; }\n\n        const style = { ...this.style },\n            idx     = $target.attr( \"id\" ),\n            value   = $target.attr( \"value\" ),\n            name    = $target.text(),\n            $prev   = $( \"tab-label[active=true]\" );\n\n        $( \"tab-label[active=true]\" )\n            .attr( \"active\", false ).css({ ...style.label })\n            .find( \"tab-border\" ).css({ ...style.border });\n\n        $target.parent().attr( \"active\", true )\n            .css({ ...style.label, ...style.label_active })\n            .find( \"tab-border\" ).css({ ...style.border, ...style.border_active });\n\n        $( \"tab-group[active=true]\" )\n            .attr( \"active\", false )\n            .velocity({ opacity: 0 }, { complete: target => {\n                $(target).css({ ...style.group });\n                $($( \"tab-group\" )[idx]).attr( \"active\", true ).css({ ...style.group, ...style.group_active })\n            }});\n\n        this.props.onChange && this.props.onChange( $prev, $target, event );\n    }\n\n    render() {\n        const style = { ...this.style },\n              { items, color, activeColor, bgColor, headerStyle, groupsStyle, borderStyle, children, ...others } = this.props;\n\n        color       && ( style.label.color = color );\n        bgColor     && ( style.header.backgroundColor = bgColor );\n        activeColor && ( style.label_active.color = activeColor );\n\n        headerStyle && ( style.header = { ...style.header, ...headerStyle } );\n        groupsStyle && ( style.groups = { ...style.groups, ...groupsStyle } );\n        borderStyle && ( style.border = { ...style.border, ...borderStyle } );\n\n        const tabLabel  = items && items.map( ( item, idx ) => {\n                  const label_style = {\n                    label        : style.label,\n                    border       : style.border,\n                    link         : style.link,\n                    link_icon    : style.link_icon,\n                    icon         : style.icon,\n                    link_disable : style.link_disable,\n                    label_active : style.label_active,\n                    border_active: style.border_active,\n                  };\n                  return <TabLabel idx={ idx }\n                                   { ...item } { ...others }\n                                   style={ label_style }\n                                   onClick={ evt=> this.tabLabelOnClick(evt) } />;\n              }),\n              tabHeader = tabLabel && <tab-header style={ style.header }>{ tabLabel }<tab-shadow style={ style.shadow }></tab-shadow></tab-header>;\n\n        const activeIdx = items.findIndex( item=>item.active),\n              tabGroup  = children && children.map( ( item, idx ) => {\n                  const group_style = activeIdx == idx ? { ...style.group, ...style.group_active } : { ...style.group };\n                  return <tab-group style={ group_style } active={ activeIdx == idx }>{ item }</tab-group>\n              }),\n              tabGroups = tabGroup && <tab-groups style={ style.groups }>{ tabGroup }</tab-groups>;\n\n        return (\n            <tabs style={ style.root }>\n                { tabHeader }\n                { tabGroups }\n            </tabs>\n        )\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/textfield.jsx",
    "content": "/*!\n * React Material Design: TextField\n * \n * @version : 0.0.3\n * @update  : 2018/06/27\n * @homepage: https://github.com/kenshin/mduikit\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\n * @author  : Kenshin Wang <kenshin@ksria.com>\n * \n * @copyright 2017\n */\n\nconsole.log( \"==== simpread component: TextField ====\" )\n\nlet $target, $float, $state, $border, $error,\n    styles = new Map();\n\nconst [ MIN_ROWS, steps ] = [ 3, 24 ],\n      cssinjs = ()=>{\n\n    const color           = 'rgba(51, 51, 51, .87)',\n          secondary_color = 'rgba(204, 204, 204, 1)',\n\n          focus_color     = 'rgba(0, 137, 123, .8)',\n          border_color    = 'rgba(224, 224, 224, 1)',\n          error_color     = 'rgba(244, 67, 54, 1)',\n\n          margin      = '8px 0 0 0',\n          display     = 'block',\n          medium      = '14px',\n          large       = '16px',\n          lineHeight  = 1.5,\n          fontWeight  = 'bold',\n          width       = '100%',\n          styles      = {\n            hidden : 'none',\n            root: {\n                display,\n                position: 'relative',\n                margin: 0,\n                padding: 0,\n\n                width,\n                lineHeight: 1,\n            },\n\n            input: {\n                color,\n                backgroundColor: 'transparent',\n\n                width,\n                height: '20px',\n\n                margin,\n                padding: 0,\n\n                fontFamily: 'sans-serif',\n                fontSize: medium,\n\n                border: 'none',\n                outline: 'none',\n\n                boxShadow: 'none',\n                boxSizing: 'content-box',\n                transition: 'all 0.3s',\n            },\n\n            textarea : {\n                position: 'relative',\n\n                color,\n                backgroundColor: 'transparent',\n\n                width,\n                height: '60px',\n\n                margin,\n                padding: 0,\n\n                fontFamily: 'sans-serif',\n                fontSize: medium,\n                lineHeight,\n\n                cursor: 'inherit',\n\n                border: 'none',\n                outline: 'none',\n                resize: 'none',\n\n                boxSizing: 'border-box',\n                WebkitTapHighlightColor: 'rgba(0, 0, 0, 0)',\n                WebkitAppearance: 'textfield',\n            },\n\n            disable: {\n                color: secondary_color,\n                cursor: \"no-drop\",\n            },\n\n            disable_border: '1px dotted rgba(224, 224, 224, 1);',\n\n            border : {\n                display,\n\n                width,\n                margin,\n\n                borderTop: `none ${border_color}`,\n                borderLeft: `none ${border_color}`,\n                borderRight: `none ${border_color}`,\n                borderBottom: `1px solid ${border_color}`,\n                boxSizing: 'content-box',\n            },\n\n            float : {},\n\n            float_normal : {\n                display,\n                position: 'absolute',\n\n                margin,\n\n                color: secondary_color,\n\n                fontSize: medium,\n                fontWeight: 'initial',\n\n                userSelect: 'none',\n                pointerEvents: 'none',\n\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n                transform: 'scale(1) translate( 0px, 0px )',\n                transformOrigin: 'left top 0px',\n            },\n\n            float_focus : {\n                color: focus_color,\n\n                margin: `-${margin}`,\n\n                fontSize: medium,\n                fontWeight,\n\n                transform: 'scale(0.75) translate( 0px, -8px )',\n            },\n\n            float_error : {\n                color: error_color,\n            },\n\n            state : {},\n\n            state_normal : {\n                display,\n                position: 'absolute',\n\n                width,\n                margin: '-1px 0 0 0',\n\n                borderTop: `none ${focus_color}`,\n                borderLeft: `none ${focus_color}`,\n                borderRight: `none ${focus_color}`,\n                borderBottom: `2px solid ${focus_color}`,\n                boxSizing: 'content-box',\n\n                transform: 'scaleX(0)',\n                transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',\n            },\n\n            state_focus : {\n                transform: 'scaleX(1)',\n            },\n\n            state_error : {\n                transform: 'scaleX(1)',\n                borderTop: `none ${error_color}`,\n                borderLeft: `none ${error_color}`,\n                borderRight: `none ${error_color}`,\n                borderBottom: `2px solid ${error_color}`,\n            },\n\n            error : {\n                display,\n                position: 'relative',\n\n                margin,\n                maxWidth: '428px',\n\n                fontSize: medium,\n                fontWeight,\n                lineHeight,\n                textAlign: 'initial',\n                wordWrap: 'break-word',\n\n                userSelect: 'none',\n\n                color: error_color,\n                transform: 'scale(0.75) translate( -73px, 0 )',\n            },\n\n        };\n\n    return styles;\n}\n\n/**\n * Custom <input>/<textarea> tag component: TextField\n * \n * Reference:\n * - https://material.io/guidelines/components/text-fields.html\n * - http://www.material-ui.com/#/components/text-field\n * \n * @class\n */\nexport default class TextField extends React.Component {\n\n    static defaultProps = {\n        multi       : false,\n        rows        : MIN_ROWS + 1,\n        password    : false,\n        disable     : false,\n        value       : \"\",\n        override    : false,\n        placeholder : \"\",\n        floatingtext: \"\",\n        errortext   : \"\",\n        tooltip     : {},\n    };\n\n    static propTypes = {\n        multi       : React.PropTypes.bool,\n        rows        : React.PropTypes.number,\n        password    : React.PropTypes.bool,\n        disable     : React.PropTypes.bool,\n        value       : React.PropTypes.string,\n        override    : React.PropTypes.bool,\n        placeholder : React.PropTypes.string,\n        floatingtext: React.PropTypes.string,\n        errortext   : React.PropTypes.string,\n        tooltip     : React.PropTypes.object,\n        onChange    : React.PropTypes.func,\n        onKeyDown   : React.PropTypes.func,\n    }\n\n    state = {\n        id    : Math.round(+new Date()),\n        type  : this.props.password ? \"password\" : \"text\",\n    }\n\n    changeFocus() {\n        setjQueryObj( this.refs );\n        changeState( styles.get(this.state.id), this.props.errortext );\n    }\n\n    changeBlur() {\n        setjQueryObj( this.refs );\n        const style = styles.get(this.state.id);\n        if ( $target.val() == \"\" && $target.attr( \"placeholder\" ) == \"\" ) {\n            $float.css( style.float_normal );\n        } else {\n            $float.css({ ...style.float_normal, ...style.float_focus });\n        }\n        if ( this.props.errortext == \"\" ) $state.css({ ...style.state_normal });\n    }\n\n    change( event ) {\n        if ( this.props.onChange ) this.props.onChange( event );\n    }\n\n    changeKeyDown( event ) {\n        if ( this.props.onKeyDown ) this.props.onKeyDown( event );\n    }\n\n    componentWillUpdate( nextProps ) {\n        for( const key of Object.keys(this.props) ) {\n            if ( this.props[key] != nextProps[key] ) {\n                switch (key) {\n                    case \"errortext\":\n                        setjQueryObj( this.refs );\n                        changeState( styles.get(this.state.id), nextProps.errortext );\n                        break;\n                    case \"value\":\n                        nextProps.override && ( this.refs.target.value = nextProps.value );\n                        break;\n                }\n            }\n        }\n    }\n\n    componentWillMount() {\n        styles.set( this.state.id, cssinjs() );\n        const style = styles.get(this.state.id);\n        if ( this.props.floatingtext == \"\" ) style.float.display = style.hidden;\n        if ( this.props.multi && ( this.props.rows > MIN_ROWS )) {\n            const rows        = this.props.rows - MIN_ROWS,\n                  txheight    = Number.parseInt(style.textarea.height),\n                  inheight    = Number.parseInt(style.input.height);\n             style.textarea.height = `${txheight + rows * steps}px`;\n        }\n        style.float = this.props.placeholder == \"\" && this.props.value == \"\" ? style.float_normal : { ...style.float_normal, ...style.float_focus }\n        style.state = this.props.errortext   == \"\" ? style.state_normal : { ...style.state_normal, ...style.state_error };\n        if ( this.props.disable ) {\n            style.input   = { ...style.input, ...style.disable };\n            style.textarea = { ...style.textarea, ...style.disable };\n            style.border.borderBottom = style.disable_border;\n        }\n    }\n\n    componentDidMount() {\n        this.refs.target.value = this.props.value;\n    }\n\n    render() {\n        const props = {\n            placeholder :this.props.placeholder,\n            onFocus  : ()=>this.changeFocus(),\n            onBlur   : ()=>this.changeBlur(),\n            onChange : (e)=>this.change(e),\n            onKeyDown: (e)=>this.changeKeyDown(e),\n        },\n        style   = styles.get(this.state.id),\n        tooltip = this.props.tooltip,\n        element = this.props.multi ? (\n            <textarea ref=\"target\" disabled={ this.props.disable }\n                       style={ style.textarea }\n                       { ...props }\n            />\n        ) : (\n            <input ref=\"target\" disabled={ this.props.disable }\n                       style={ style.input }\n                       type={ this.state.type } \n                       { ...props }\n             />\n        );\n\n        return (\n            <text-field style={ style.root }\n                data-tooltip={ tooltip.text ? tooltip.text : this.props[ tooltip.target ] } data-tooltip-position={ tooltip.position } data-tooltip-delay={ tooltip.delay }>\n                <text-field-float ref=\"float\" style={ style.float }>{this.props.floatingtext}</text-field-float>\n                { element }\n                <div>\n                    <text-field-border ref=\"border\" style={ style.border }/>\n                    <text-field-state ref=\"state\" style={ style.state }/>\n                </div>\n                <text-field-error ref=\"error\" style={ style.error }>{ this.props.errortext }</text-field-error>\n            </text-field>\n        )\n    }\n\n}\n\n/**\n * Set jQuery object from this.refs\n * \n * @param {object} this.refs\n */\nfunction setjQueryObj( obj ) {\n    $target     = $( obj.target );\n    $float      = $( obj.float );\n    $state      = $( obj.state );\n    $border     = $( obj.border );\n    $error      = $( obj.error );\n}\n\n/**\n * Change state style\n * \n * @param {string} error text\n */\nfunction changeState( style, errortext ) {\n    if ( errortext != \"\" ) {\n        $state.css({ ...style.state_normal, ...style.state_error });\n        $float.css({ ...style.float_normal, ...style.float_focus, ...style.float_error });\n    } else {\n        $state.css({ ...style.state_normal, ...style.state_focus });\n        $float.css({ ...style.float_normal, ...style.float_focus });\n    }\n}"
  },
  {
    "path": "src/vender/mduikit/tooltip.jsx",
    "content": "/*!\r\n * React Material Design: Tooltip\r\n * \r\n * @version : 0.0.3\r\n * @update  : 2019/12/31\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n\r\n * @reference:\r\n * - https://material.io/guidelines/components/tooltips.html\r\n * - http://materializecss.com/dialogs.html\r\n * - https://kazzkiq.github.io/balloon.css/\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: ToolTip ====\" )\r\n\r\nimport 'mintooltip';\r\n\r\nlet started = false, timeout, $target, $back, style, styles = new Map();\r\nconst cssinjs = () => {\r\n    const styles = {\r\n\r\n        root : {\r\n            display: 'flex',\r\n            alignItems: 'center',\r\n            position: 'absolute',\r\n\r\n            top: 0,\r\n            left: 0,\r\n            padding: '5px 16px',\r\n\r\n            minHeight: '32px',\r\n            maxHeight: '150px',\r\n            maxWidth: '400px',\r\n\r\n            fontSize: '14px',\r\n            textAlign: 'center',\r\n\r\n            zIndex: 2000,\r\n\r\n            color: '#fff',\r\n            backgroundColor: 'transparent',\r\n\r\n            borderRadius: '2px',\r\n\r\n            pointerEvents: 'none',\r\n\r\n            opacity: 0,\r\n            overflow: 'hidden',\r\n            visibility: 'hidden',\r\n        },\r\n\r\n        back: {\r\n            position: 'absolute',\r\n\r\n            width: '14px',\r\n            height: '7px',\r\n\r\n            backgroundColor: 'rgba(97, 97, 98, .9)',\r\n\r\n            borderRadius: '0 0 50% 50%',\r\n\r\n            zIndex: -1,\r\n\r\n            transformOrigin: '50% 0%',\r\n\r\n            opacity: 0,\r\n            visibility: 'hidden',\r\n        },\r\n    }\r\n\r\n    return styles;\r\n}\r\n\r\n/**\r\n * Custom component: Tooltip, component e.g. \r\n    <tooltip-gp>\r\n        <tooltip-tips>\r\n            <tooltip-tip id=\"1488523418015\">\r\n                <span>关闭</span>\r\n                <div></div>\r\n            </tooltip-tip>\r\n        </tooltip-tips>\r\n        <tooltip-tips>\r\n            <tooltip-tip id=\"1488523418017\">\r\n                <span>更多</span>\r\n                <div></div>\r\n            </tooltip-tip>\r\n        </tooltip-tips>\r\n    </tooltip-gp>\r\n * \r\n * @class\r\n */\r\nclass ToolTip extends React.Component {\r\n\r\n    static defaultProps = {\r\n        root     : \"\",\r\n        text     : \"\",\r\n        position : \"bottom\",\r\n        delay    : 350,\r\n        $item    : undefined,\r\n    }\r\n\r\n    static propTypes = {\r\n        root     : React.PropTypes.string,\r\n        text     : React.PropTypes.string,\r\n        position : React.PropTypes.oneOf([ \"bottom\", \"top\", \"left\", \"right\" ]),\r\n        delay    : React.PropTypes.number,\r\n        $item    : React.PropTypes.any,\r\n    }\r\n\r\n    state = {\r\n        id : Math.round(+new Date()),\r\n    }\r\n\r\n    onMouseEnter() {\r\n        const showTooltip = ()=> {\r\n            started = true;\r\n            [ $target, $back ] = [ $( this.refs.target ), $( this.refs.back ) ];\r\n\r\n            $target.velocity( \"stop\" );\r\n            $back.velocity( \"stop\" );\r\n            $target.css({ visibility: \"visible\", left: \"0px\", top: \"0px\" });\r\n\r\n            const originWidth   = this.props.$item.outerWidth(),\r\n                  originHeight  = this.props.$item.outerHeight(),\r\n                  tooltipHeight = $target.outerHeight(),\r\n                  tooltipWidth  = $target.outerWidth(),\r\n                  backWidth     = $back[0].offsetWidth,\r\n                  backHeight    = $back[0].offsetHeight;\r\n\r\n            let tooltipVert = \"0px\", tooltipHori = \"0px\",\r\n                scaleXFactor = 8, scaleYFactor = 8, scaleFactor = 0,\r\n                targetTop, targetLeft, top, left;\r\n\r\n            /*if ( this.props.$item.css( \"position\" ) == \"static\" ) {\r\n                top  = this.props.$item.position().top;\r\n                left = this.props.$item.position().left;\r\n            } else {\r\n                top  = this.props.$item.offset().top;\r\n                left = this.props.$item.offset().left;\r\n            }*/\r\n\r\n            top  = this.props.$item[0].getBoundingClientRect().top;\r\n            left = this.props.$item[0].getBoundingClientRect().left;\r\n\r\n            if ( this.props.position == \"bottom\" ) {\r\n                tooltipVert = \"+14px\";\r\n                targetTop   = top  + originHeight;\r\n                targetLeft  = left + ( originWidth - tooltipWidth ) / 2;\r\n            } else if ( this.props.position == \"top\" ) {\r\n                tooltipVert = '-14px';\r\n                targetTop   = top  - tooltipHeight;\r\n                targetLeft  = left + ( originWidth - tooltipWidth ) / 2;\r\n            } else if ( this.props.position == \"left\" ) {\r\n                tooltipHori = '-14px';\r\n                targetTop   = top  + ( originHeight - tooltipHeight ) / 2;\r\n                targetLeft  = left - tooltipWidth;\r\n            } else {\r\n                tooltipHori = '+14px';\r\n                targetTop   = top  + ( originHeight - tooltipHeight ) / 2;\r\n                targetLeft  = left + originWidth + Number.parseInt( tooltipHori ) - Number.parseInt( $target.css( \"padding-left\" ));\r\n            }\r\n\r\n            $back.css({\r\n                top: 0,\r\n                left: 0,\r\n                marginLeft: ( tooltipWidth - backWidth ) / 2\r\n            });\r\n\r\n            const offsetTop  = this.props.root == \"body\" ? $( \"body\" ).scrollTop() : $( \"body\" ).offset().top,\r\n                  offsetLeft = $( \"body\" ).offset().left;\r\n            $target.css({\r\n                top:  targetTop  + ( $( this.props.root ).css( \"position\" ) != \"fixed\" ? offsetTop : 0 ),\r\n                left: targetLeft + ( $( this.props.root ).css( \"position\" ) != \"fixed\" ? offsetLeft: 0 ),\r\n            });\r\n\r\n            scaleXFactor = Math.SQRT2 * tooltipWidth  / parseInt( backWidth  );\r\n            scaleYFactor = Math.SQRT2 * tooltipHeight / parseInt( backHeight );\r\n            scaleFactor  = Math.max( scaleXFactor, scaleYFactor );\r\n\r\n            $target.velocity({ translateY: tooltipVert, translateX: tooltipHori }, { duration: 350, queue: false })\r\n              .velocity({ opacity: 1 }, { duration: 300, delay: 50, queue: false });\r\n            $back.css({ visibility: \"visible\" })\r\n              .velocity({ opacity: 1 }, { duration: 55, delay: 0, queue: false })\r\n              .velocity({ scaleX: scaleFactor, scaleY: scaleFactor }, { duration: 300, delay: 0, queue: false, easing: \"easeInOutQuad\" });\r\n        };\r\n        timeout = setTimeout( showTooltip, this.props.delay );\r\n    }\r\n\r\n    onMouseLeave() {\r\n        started = false;\r\n        const delay = 10;\r\n        clearTimeout( timeout );\r\n        setTimeout( () => {\r\n            [ $target, $back ] = [ $( this.refs.target ), $( this.refs.back ) ];\r\n            $target.velocity({\r\n                opacity: 0, translateY: 0, translateX: 0}, { duration: delay, queue: false });\r\n            $back.velocity({opacity: 0, scaleX: 1, scaleY: 1}, {\r\n                duration: delay,\r\n                queue:    false,\r\n                complete: () => {\r\n                    $back.css({   visibility: \"hidden\" });\r\n                    $target.css({ visibility: \"hidden\" });\r\n                    started = false;\r\n                }\r\n            });\r\n          }, delay );\r\n    }\r\n\r\n    onScroll() {\r\n        $( this.refs.back ).css({   visibility: \"hidden\" });\r\n        $( this.refs.target ).css({ visibility: \"hidden\" });\r\n        started = false;\r\n    }\r\n\r\n    componentDidMount() {\r\n        this.props.$item.on( \"mouseenter\", this.onMouseEnter.bind( this ) );\r\n        this.props.$item.on( \"mouseleave\", this.onMouseLeave.bind( this ) );\r\n        $( document    ).on( \"scroll\",     this.onScroll.bind( this ) );\r\n    }\r\n\r\n    componentWillUnmount() {\r\n        this.props.$item.off( \"mouseenter\", this.onMouseEnter );\r\n        this.props.$item.off( \"mouseleave\", this.onMouseLeave );\r\n        $( document    ).off( \"scroll\",     this.onScroll     );\r\n    }\r\n\r\n    render() {\r\n        styles.set( this.state.id, cssinjs() );\r\n        style = styles.get( this.state.id );\r\n\r\n        return (\r\n            <tooltip-tip ref=\"target\" style={ style.root } id={ this.state.id }>\r\n                <span>{ this.props.text }</span>\r\n                <div ref=\"back\" style={ style.back }></div>\r\n            </tooltip-tip>\r\n        )\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * Render\r\n * \r\n * @param {string} element, e.g. class: .xxx; id: #xxxx; tag: xxx\r\n * @param {string} id\r\n * @param {boolean} usage mintooltip default\r\n */\r\nfunction Render( root, id, is_mini = true ) {\r\n    setTimeout( () => {\r\n        const $root      = !id ? $(root) : $(id);\r\n        $root.find( \"[data-tooltip]\" ).map( ( idx, item ) => {\r\n            const $item  = $(item),\r\n                position = $item.attr( \"data-tooltip-position\" ),\r\n                delay    = $item.attr( \"data-tooltip-delay\" ),\r\n                text     = $item.attr( \"data-tooltip\" );\r\n            if ( is_mini ) {\r\n                $item\r\n                    .removeAttr( \"data-tooltip-position\" )\r\n                    .removeAttr( \"data-tooltip-delay\" )\r\n                    .removeAttr( \"data-tooltip\" )\r\n                    .attr( \"aria-label\", text )\r\n                    .attr( \"data-balloon-pos\", position || \"up\" );\r\n            } else {\r\n                text && text != \"\" && \r\n                ReactDOM.render( <ToolTip root={ root } text={ text } position={ position } delay={ delay } $item={ $item } />, getTooltipRoot( $(root), id ) );\r\n            }\r\n        });\r\n    }, 500 );\r\n}\r\n\r\n/**\r\n * Exit\r\n * \r\n * @param {string} element, e.g. class: .xxx; id: #xxxx; tag: xxx\r\n * @param {string} id\r\n */\r\nfunction Exit( root, id ) {\r\n    const selector = !id ? \"tooltip-gp\" : `tooltip-gp[id=\"${id}\"]`;\r\n    $( selector ).find( \"tooltip-tips\" ).map( ( idx, item )=>{\r\n        ReactDOM.unmountComponentAtNode( $(item)[0] );\r\n    });\r\n    $( selector ).remove();\r\n}\r\n\r\n/**\r\n * Create Tooltip root html\r\n * \r\n * @param  {jquery}  jquery object\r\n * @param  {string}  id\r\n * @return {element} html element\r\n */\r\nfunction getTooltipRoot( $root, id ) {\r\n    const selector = !id ? \"tooltip-gp\" : `tooltip-gp[id=\"${id}\"]`;\r\n    $root.find( selector ).length == 0 && $root.append( !id ? \"<tooltip-gp>\" : `<tooltip-gp id=\"${id}\">` );\r\n    $root.find( selector ).append( \"<tooltip-tips>\" );\r\n    return $root.find( `${selector} tooltip-tips` ).last()[0];\r\n}\r\n\r\nexport { Render, Exit };"
  },
  {
    "path": "src/vender/mduikit/waves.js",
    "content": "/*!\r\n * React Material Design: Waves\r\n * \r\n * @version : 0.0.1\r\n * @update  : 2017/04/19\r\n * @homepage: https://github.com/kenshin/mduikit\r\n * @license : MIT https://github.com/kenshin/mduikit/blob/master/LICENSE\r\n * @author  : Kenshin Wang <kenshin@ksria.com>\r\n * @modules : http://fian.my.id/Waves\r\n * \r\n * @copyright 2017\r\n */\r\n\r\nconsole.log( \"==== simpread component: Waves ====\" )\r\n\r\nimport '../waves/waves.min.css';\r\nimport Waves from '../waves/waves.js';\r\n\r\nconst wavesopts = {\r\n    root     : undefined,\r\n    duration : 500,\r\n    delay    : 200,\r\n};\r\n\r\n/**\r\n * Waves\r\n * \r\n * External library:\r\n * - http://fian.my.id/Waves/\r\n * \r\n * @param {object} option object\r\n */\r\nexport function Render( options ) {\r\n    if ( options && options.root ) {\r\n        const ops = { ...wavesopts, ...options };\r\n        ops.root  = $( ops.root )[0];\r\n        Waves.init( ops );\r\n    } else {\r\n        console.error( \"options param error\" );\r\n    }\r\n}\r\n"
  },
  {
    "path": "src/vender/notify/notify.css",
    "content": "\n/*\n* Notify Group\n*/\nnotify-gp {\n    font: 300 14px -apple-system, PingFang SC, Microsoft Yahei, Lantinghei SC, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif;\n    text-rendering: optimizelegibility;\n    -webkit-text-size-adjust: 100%;\n    -webkit-font-smoothing: antialiased;\n\n    display: -webkit-flex;\n    flex-flow: column nowrap;\n    align-items: flex-end;\n\n    position: fixed;\n\n    top: 0;\n    right: 0;\n\n    margin: 0 15px 0 0;\n    padding: 0;\n\n    text-transform: none;\n\n    pointer-events: none;\n}\n\nnotify-gp notify {\n    display: -webkit-flex;\n    align-items: center;\n\n    margin: 0;\n    margin-top: 15px;\n    padding: 14px 24px;\n\n    min-width: 288px;\n    max-width: 568px;\n\n    /*height: 48px;\n    max-height: 48px;*/\n    min-height: 48px;\n\n    color: rgba(255, 255, 255, 0.9);\n    background-color: #000;\n\n    box-sizing: border-box;\n    border-radius: 4px;\n    pointer-events: auto;\n    user-select: none;\n\n    opacity: 0;\n    transform: scaleY(0);\n    transform-origin: left top 0px;\n    transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 1s cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n\n    box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);\n}\n\nnotify-gp notify-title {\n    font-size: 13px;\n    font-weight: bold;\n}\n\nnotify-gp notify-content {\n    display: block;\n\n    font-size: 14px;\n    font-weight: 400;\n    text-align: left;\n\n    overflow: hidden;\n    /*text-overflow: ellipsis;\n    white-space: nowrap;*/\n}\n\nnotify-gp notify-content a,\nnotify-gp notify-content a:link,\nnotify-gp notify-content a:visited,\nnotify-gp notify-content a:active {\n    margin: inherit;\n    padding-bottom: 5px;\n\n    color: #fff;\n    font-size: inherit;\n\n    text-decoration: none;\n\n    transition: color .5s;\n}\n\nnotify-gp notify-content a:hover {\n    margin: initial;\n    padding: initial;\n\n    color: inherit;\n    font-size: inherit;\n\n    text-decoration: none;\n}\n\nnotify-gp notify-i {\n    display: none;\n    justify-content: center;\n    align-items: center;\n\n    margin: 0 10px 0 0;\n\n    width: 24px;\n    height: 24px;\n\n    background-position: center;\n    background-repeat: no-repeat;\n}\n\nnotify-gp notify-action,\nnotify-gp notify-cancel {\n    display: none;\n\n    margin: 0 8px;\n\n    max-width: 80px;\n    min-width: 56px;\n    height: 36px;\n    line-height: 34px;\n\n    color: #bb86fc;\n\n    font-weight: 500;\n    font-size: inherit;\n    text-transform: uppercase;\n    text-align: center;\n\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n\n    transition: all 500ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    cursor: pointer;\n}\n\nnotify-gp notify-action:active,\nnotify-gp notify-cancel:active {\n    border-radius: 4px;\n    background-color: rgba(98, 0, 238, .3);\n}\n\nnotify-gp notify-cancel {\n    margin: 0;\n}\n\nnotify-gp notify-a {\n    display: block;\n    position: absolute;\n\n    top: 5px;\n    right: 5px;\n\n    cursor: pointer;\n}\n\nnotify-gp notify-exit {\n    display: none;\n    justify-content: center;\n    align-items: center;\n\n    margin-left: 5px;\n\n    width: 36px;\n    height: 36px;\n    min-width: 36px;\n    min-height: 36px;\n\n    background-color: transparent;\n\n    border-radius: 50%;\n    transition: all 500ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;\n    cursor: pointer;\n}\n\nnotify-gp notify-exit:hover {\n    background-color: rgba(255, 255, 255, 0.4);\n}\n\nnotify-gp notify-exit:active {\n    background-color: rgba(255, 255, 255, 0.2);\n}\n\nnotify-gp notify-a notify-span {\n    display: block;\n    width: 16px;\n    height: 16px;\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDklEQVQ4T6VT0VFCQQzcrQA7ECoRK1AqEDugA6ECsQPsADvgVSAlaAlWEGdvkjchczI45Osud9nc7m2IEmY2BfAEYA5A6xsARwAHAB8ktR6DeWNmKwAvXlSxY78i+RabEcDM9gAe/qoq+T3JhXINwDu/Xlgc1zYk13TOn+XZA4C7AvgN4LbkZgJYO+84O5C8N7Odi6n8O8llh+ZGAD3uO5LPDgIvzoDRbBDAV+dputBAXKNecQM5B9CefQlAj0JwVmdLdGSwHI1CFXEgOS8ihia1WRNRdpU9JwlatpWVc0gr3c0xu95IAfdPK2uoHkcrJxANkzTJdPKTf3ROchvJk2n0LxNPfV9vnDVEJ+P8C6jMhLeGEqMKAAAAAElFTkSuQmCC);\n    opacity: .9;\n}\n\nnotify-gp notify-i.holdon {\n    display: block;\n    margin: 0 0 0 24px;\n\n    width: 20px;\n    height: 20px;\n\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAQAAAAngNWGAAAATUlEQVR4AWMYSuB/4P+V/1lRRFiBIoEYCoGC//+vAypFKFsHFFkJV4AsAVGKzsOjFFUZHqUElCGUwpRRrpCw1YQ9Qzh4SA5wwlE4hAAAiFGQefYhNJkAAAAASUVORK5CYII=);\n    cursor: pointer;\n}\n\nnotify-gp .notify-show {\n    opacity: 1;\n    transform: scaleY(1)!important;\n}\n\nnotify-gp .notify-hide {\n    animation-name: fadeOutUp;\n    animation-duration: 450ms;\n    animation-fill-mode: both;\n}\n\nnotify-gp .notify-success {\n    background-color: rgb(76, 175, 80);\n}\n\nnotify-gp .notify-warning {\n    background-color: rgb(255, 160, 0);\n}\n\nnotify-gp .notify-error {\n    background-color: rgba(239, 83, 80, 1);\n}\n\nnotify-gp .notify-info {\n    background-color: rgb(25, 118, 210);\n}\n\nnotify-gp .notify-modal {\n    flex-flow: column nowrap;\n    align-items: flex-start;\n\n    height: auto;\n    max-height: 200px;\n\n    box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2);\n}\n\nnotify-gp .notify-modal .notify-modal-content {\n    margin-top: 5px;\n    font-size: 13px;\n    white-space: normal;\n}\n\nnotify-gp .notify-modal .notify-modal-content a {\n    margin: 0;\n    padding: 0;\n\n    color: inherit;\n\n    font-size: inherit;\n    text-decoration: underline;\n    \n    cursor: pointer;\n}\n\nnotify-gp .notify-modal .notify-modal-content a:hover,\nnotify-gp .notify-modal .notify-modal-content a:active,\nnotify-gp .notify-modal .notify-modal-content a:visited,\nnotify-gp .notify-modal .notify-modal-content a:focus {\n    color: inherit;\n}\n\nnotify-gp .notify-snackbar {\n    position: fixed;\n    bottom: 0;\n    left: 50%;\n    margin-bottom: 5px;\n    transform-origin: left bottom 0px;\n}\n\n.notify-position-lt-corner {\n    align-items: flex-start;\n\n    margin: 0 0 0 15px;\n\n    left: 0;\n    right: initial;\n}\n\n.notify-position-lb-corner {\n    flex-flow: column-reverse wrap-reverse;\n\n    margin: 0 0 15px 15px;\n\n    right: initial;\n    top: initial;\n\n    left: 0;\n    bottom: 0;\n}\n\n.notify-position-rb-corner {\n    flex-flow: column-reverse wrap-reverse;\n    align-items: flex-start;\n\n    margin: 0 15px 15px 0;\n\n    top: initial;\n    left: initial;\n\n    bottom: 0;\n    right: 0;\n}\n\n@keyframes fadeOutUp {\n    from {\n        opacity: 1;\n    }\n\n    to {\n        margin-top: 0;\n        padding: 0;\n        height: 0;\n        min-height: 0;\n        opacity: 0;\n        transform: scaleY(0);\n    }\n}\n\n@media (pointer: coarse) {\n    notify-gp {\n        top: initial;\n        bottom: 0;\n        left: 0;\n\n        margin: 0 10px 10px 10px;\n    }\n\n    notify-gp notify {\n        width: 100%;\n        max-width: 600px;\n    }\n\n    notify-gp .notify-show,\n    notify-gp .notify-hide {\n        transform-origin: bottom!important;\n    }\n\n    notify-gp .notify-snackbar {\n        position: initial;\n    }\n}"
  },
  {
    "path": "src/vender/notify/notify.js",
    "content": "\"use strict\";\r\n\r\n/*\r\n* Options:\r\n* - title   ( string, optional, if value is \"\" not show.)\r\n*\r\n* - content ( string, required)\r\n*\r\n* - type    ( int, NORMAL/SUCCESS/WARING/ERROR/INFO)\r\n*           ( optional, default is NORMAL )\r\n*\r\n* - mode    ( string, toast/modal/snackbar)\r\n*           ( optional, default is toast )\r\n*\r\n* - delay   ( boolean, optional )\r\n*           ( default is 1000 * 5 )\r\n*\r\n* - icon    ( string,  optional )\r\n*\r\n* - action  ( string,  optional )\r\n* - callback( func,    optional )\r\n*           ( when action != \"\" must set callback )\r\n*\r\n* Param:\r\n* - string：\r\n*   - 1：content\r\n*   - 2：type content or title content\r\n*\r\n* - object\r\n*   - { type: xxx, title: xxx, content: xxx, mode: xxx, icon: xxx, delay: 500, action: xxx, callback:()=>{xxxx} }\r\n*\r\n* Example:\r\n* new Notify().Render( \"一个参数的 toast\" );\r\n* new Notify().Render( 0, \"两个参数的 toast\" );\r\n* new Notify().Render( 1, \"两个参数的 toast\" );\r\n* new Notify().Render( 2, \"两个参数的 toast\" );\r\n* new Notify().Render( 3, \"两个参数的 toast\" );\r\n* new Notify().Render( \"snackbar\", \"两个参数的 snackbar\" );\r\n* new Notify().Render( \"三个参数的 callback\", \"undo\", ()=>{console.log(\"bbbbbb\")} );\r\n* new Notify().Render( \"snackbar\", \"四个参数的 snackbar callback\", \"undo\", ()=>{console.log(\"rrrrrr\")} );\r\n* new Notify().Render( \"SimpTab 版本提示\", `已更新到最新版本，详细请看 <a>CHANGELOG</a>` );\r\n* new Notify().Render({ content: \"带 icon 的 toast\", icon: \"<path>/weight_icon.png\" } );\r\n* new Notify().Render({ content: \"带 delay 的 toast\", delay: 10000 } );\r\n* new Notify().Render({ content: \"带 icon 的 snackbar\", icon: \"<path>/fontsize_icon.png\" });\r\n* new Notify().Render({ content: \"带 callback 的 toast\", icon: \"<path>/icon.png\", mode: \"snackbar\", action: \"提交\", callback: ()=>{console.log(\"dddddddd\")}} );\r\n* new Notify().Render( \"错误的 callback\", \"undo\", '()=>{console.log(\"eeeeeeee\")}' );\r\n* new Notify().Render({ content: \"带确认的 toast\", action: \"提交\", cancel: \"取消\", callback: type => {\r\n     console.log( \"current type is\", type )\r\n  }});\r\n  new Notify().Render({ content: \"一直存在带 close 的 toast\", state: \"holdon\" });\r\n*\r\n  const notify = new Notify().Render({ content: \"加载中，请稍等...\", state: \"loading\" });\r\n  setTimeout( ()=>{\r\n    notify.complete();\r\n    new Notify().Render(\"加载完成！\");\r\n  }, 2000);\r\n* Notify.Position = rt( default ) | rb | lt | lb\r\n*\r\n*/\r\nvar Notify = ( function () {\r\n    var VERSION = \"2.0.2.0621\",\r\n        name    = \"notify\",\r\n        root    = \"notify-gp\",\r\n        roottmpl= \"<\" + root + \">\",\r\n        num     = 0,\r\n        NORMAL  = 0,\r\n        SUCCESS = 1,\r\n        WARNING = 2,\r\n        ERROR   = 3,\r\n        INFO    = 4,\r\n        MODE    = {\r\n            toast    : \"toast\",\r\n            modal    : \"modal\",\r\n            snackbar : \"snackbar\",\r\n        },\r\n        STATE   = {\r\n            loading  : \"loading\",\r\n            holdon   : \"holdon\",\r\n        },\r\n        POSITION= {\r\n            lefttop     : \"lt\",\r\n            leftbottom  : \"lb\",\r\n            rightbottom : \"rb\",\r\n        },\r\n        options = {\r\n            version : VERSION,\r\n            title   : \"\",\r\n            content : \"\",\r\n            type    : NORMAL,\r\n            mode    : MODE.toast,\r\n            state   : undefined,\r\n            flat    : false,\r\n            delay   : 1000 * 5,\r\n            icon    : \"\",\r\n            action  : \"\",\r\n            cancel  : \"\",\r\n            exit    : undefined,\r\n            callback: undefined,\r\n            complete: undefined,\r\n        },\r\n        timer      = {},\r\n        $root,\r\n        TMPL       = '\\\r\n        <notify>\\\r\n            <notify-a href=\"javascript:;\"><notify-span></notify-span></notify-a>\\\r\n            <notify-i></notify-i>\\\r\n            <notify-title></notify-title>\\\r\n            <notify-content></notify-content>\\\r\n            <notify-action></notify-action>\\\r\n            <notify-cancel></notify-cancel>\\\r\n            <notify-exit></notify-exit>\\\r\n        </notify>',\r\n        exit       = '<svg t=\"1577940123220\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" p-id=\"1411\" width=\"24\" height=\"24\"><path d=\"M512 421.490332 331.092592 240.582924C306.351217 215.841549 265.464551 215.477441 240.470996 240.470996 215.303191 265.638801 215.527553 306.037221 240.582924 331.092592L421.490332 512 240.582925 692.907407C215.84155 717.648782 215.477441 758.535449 240.470996 783.529004 265.638801 808.696809 306.037222 808.472446 331.092593 783.417075L512 602.509668 692.907407 783.417075C717.648782 808.15845 758.535449 808.522559 783.529004 783.529004 808.696809 758.361199 808.472446 717.962778 783.417075 692.907407L602.509668 512 783.417076 331.092592C808.158451 306.351217 808.522559 265.464551 783.529004 240.470996 758.361199 215.303191 717.962779 215.527553 692.907408 240.582924L512 421.490332Z\" p-id=\"1412\" fill=\"#ffffff\"></path></svg>',\r\n        loading    = '\\\r\n            <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\" preserveAspectRatio=\"xMidYMid\" class=\"lds-rolling\">\\\r\n                <circle stroke=\"#fff\" stroke-width=\"10\" cx=\"50\" cy=\"50\" fill=\"none\" ng-attr-stroke=\"{{config.color}}\" ng-attr-stroke-width=\"{{config.width}}\" ng-attr-r=\"{{config.radius}}\" ng-attr-stroke-dasharray=\"{{config.dasharray}}\" r=\"30\" stroke-dasharray=\"141.37166941154067 49.12388980384689\" transform=\"rotate(102 50 50)\">\\\r\n                    <animateTransform attributeName=\"transform\" type=\"rotate\" calcMode=\"linear\" values=\"0 50 50;360 50 50\" keyTimes=\"0;1\" dur=\"1s\" begin=\"0s\" repeatCount=\"indefinite\"></animateTransform>\\\r\n                </circle>\\\r\n            </svg>',\r\n        prefix     = function( value ) {\r\n            return name + \"-\" + value;\r\n        },\r\n        registyElement = function( name, elements ) {\r\n            elements.forEach( function( item ) {\r\n                document.createElement( prefix( item ));\r\n            });\r\n        },\r\n        closeHandle = function( event ) {\r\n            $root.off( \"click\", \".\" + event.data + \" notify-a\", closeHandle );\r\n            hidden( $(this).parent() );\r\n        },\r\n        delayHandler = function( item ) {\r\n            clearTimeout( timer[item] );\r\n            delete timer[item];\r\n            hidden( this );\r\n        },\r\n        callbackHander = function( event ) {\r\n            event.data[1] && event.data[1]( event.data[2] );\r\n            $root.off( \"click\", \".\" + event.data[0] + \" notify-action\", callbackHander );\r\n            hidden( $(this).parent() );\r\n        },\r\n        completeHandler = function() {\r\n            hidden( this );\r\n        },\r\n        hidden = function( target ) {\r\n            target[0].addEventListener( 'animationend', function(e) {\r\n                target.remove();\r\n                if ($root.children().length === 0 ) $root.css( \"z-index\", 0 );\r\n            }, false );\r\n            target.css({ width: target[0].offsetWidth }).addClass( 'notify-hide' );\r\n        },\r\n        render = function() {\r\n            var $target  = $( TMPL ),\r\n                $title   = $target.find(prefix( \"title\"   )),\r\n                $content = $target.find(prefix( \"content\" )),\r\n                $close   = $target.find(prefix( \"a\"       )),\r\n                $icon    = $target.find(prefix( \"i\"       )),\r\n                $action  = $target.find(prefix( \"action\"  )),\r\n                $cancel  = $target.find(prefix( \"cancel\"  )),\r\n                $exit    = $target.find(prefix( \"exit\"    )),\r\n                item     = \"notify-item-\" + num++,\r\n                position = this.constructor.Position,\r\n                isMobile = {\r\n                    Android: function() {\r\n                        return navigator.userAgent.match(/Android/i);\r\n                    },\r\n                    BlackBerry: function() {\r\n                        return navigator.userAgent.match(/BlackBerry/i);\r\n                    },\r\n                    iOS: function() {\r\n                        return navigator.userAgent.match(/iPhone|iPad|iPod/i);\r\n                    },\r\n                    Opera: function() {\r\n                        return navigator.userAgent.match(/Opera Mini/i);\r\n                    },\r\n                    Windows: function() {\r\n                        return navigator.userAgent.match(/IEMobile/i);\r\n                    },\r\n                    verify: function() {\r\n                        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows()) == null ? false : true;\r\n                    }\r\n                };\r\n\r\n            this.title   ? $title.text( this.title )     : $title.hide();\r\n            this.content ? $content.html( this.content ) : $content.hide();\r\n\r\n            this.update = function( content ) {\r\n                this.content = content;\r\n                this.content ? $content.html( this.content ) : $content.hide();\r\n            }\r\n\r\n            if ( this.mode === MODE.modal ) {\r\n                $target.addClass( \"notify-modal\" );\r\n                $content.addClass( \"notify-modal-content\" );\r\n                $root.on( \"click\", \".\" + item + \" notify-a\", item, closeHandle );\r\n            } else {\r\n                $close.hide();\r\n                this.mode == MODE.snackbar && $target.addClass( \"notify-snackbar\" );\r\n            }\r\n\r\n            if ( this.mode !== MODE.modal && this.icon !== \"\" ) {\r\n                if ( this.icon.indexOf( '<i' ) > -1 ) {\r\n                    $icon.html( this.icon ).css({ display: 'flex' });\r\n                } else $icon.css({ \"background-image\": \"url(\" + this.icon + \")\", \"display\": \"block\" });\r\n            }\r\n\r\n            switch( this.type ) {\r\n                case 1:\r\n                    this.state != STATE.holdon && this.icon == \"\" && $icon.html( '<i class=\"fas fa-check\"></i>' ).css({ display: 'flex' });\r\n                    $target.addClass( \"notify-success\" );\r\n                    break;\r\n                case 2:\r\n                    this.state != STATE.holdon && this.icon == \"\" && $icon.html( '<i class=\"fas fa-exclamation\"></i>' ).css({ display: 'flex' });\r\n                    $target.addClass( \"notify-warning\" );\r\n                    break;\r\n                case 3:\r\n                    this.state != STATE.holdon && this.icon == \"\" && $icon.html( '<i class=\"fas fa-bug\"></i>' ).css({ display: 'flex' });\r\n                    $target.addClass( \"notify-error\" );\r\n                    break;\r\n                case 4:\r\n                    this.state != STATE.holdon && this.icon == \"\" && $icon.html( '<i class=\"fas fa-info\"></i>' ).css({ display: 'flex' });\r\n                    $target.addClass( \"notify-info\" );\r\n                    break;\r\n            }\r\n\r\n            if ( this.action !== \"\" && this.callback && typeof this.callback == \"function\" ) {\r\n                $content.css( \"width\", \"100%\" );\r\n                $action.text( this.action ).css( \"display\", \"block\" );\r\n                $root.on( \"click\", \".\" + item + \" notify-action\", [ item, this.callback, \"action\" ], callbackHander );\r\n            }\r\n\r\n            if ( this.cancel !== \"\" && this.callback && typeof this.callback == \"function\" ) {\r\n                $content.css( \"width\", \"100%\" );\r\n                $cancel.text( this.cancel ).css( \"display\", \"block\" );\r\n                $root.on( \"click\", \".\" + item + \" notify-cancel\", [ item, this.callback, \"cancel\" ], callbackHander );\r\n            }\r\n\r\n            if ( this.type != 0 && this.icon.indexOf( '<i' ) > -1 ) {\r\n                var css = function( element, property ) {\r\n                    return window.getComputedStyle( element, null ).getPropertyValue( property ).toLowerCase().replace( / /g, \"\" );\r\n                }, $span = $( '<span style=\"display:none;\" class=\"verify-fas fas\"></span>' )\r\n                $( 'body' ).append( $span );\r\n                !/fontawesome/.test( css( $span[0], 'font-family' ) ) && $icon.remove();\r\n                $span.remove();\r\n            }\r\n\r\n            this.mode !== MODE.modal && this.state !== STATE.loading && this.state !== STATE.holdon && ( this.action == \"\" || !this.callback || typeof this.callback != \"function\" ) &&\r\n                ( timer[item] = setTimeout( delayHandler.bind( $target, item ), this.delay ) );\r\n\r\n            if ( this.state == STATE.loading ) {\r\n                $icon.html( loading );\r\n                $icon.css({ display: \"block\" });\r\n                this.complete = completeHandler.bind( $target );\r\n            }\r\n\r\n            if ( this.state == STATE.holdon ) {\r\n                $icon.css({ display: \"block\" }).addClass( \"holdon\" );\r\n                $cancel.after( $icon[0].outerHTML );\r\n                $target.find( \"notify-i:first\" ).remove();\r\n                $root.on( \"click\", \".\" + item + \" notify-i\", [ item, this.callback, \"holdon\" ], callbackHander );\r\n                if ( !this.action || !this.cancel ) $content.css({ width: \"100%\" });\r\n            }\r\n\r\n            if ( this.flat ) {\r\n                $target.css({ \"box-shadow\": \"none\", \"border-radius\": \"2px\" });\r\n            }\r\n\r\n            if ( position == POSITION.rightbottom || position == POSITION.leftbottom ) {\r\n                $target.css({ \"transform-origin\": \"left bottom 0px\" });\r\n                $root.addClass( \"notify-position-\" + position + \"-corner\" );\r\n            } else if ( position == POSITION.lefttop ) {\r\n                $root.addClass( \"notify-position-\" + position + \"-corner\" );\r\n            }\r\n\r\n            $target.addClass( item );\r\n            $root.css( \"z-index\", 2147483647 );\r\n            isMobile.verify() ? $root.prepend( $target ) : $root.append( $target );\r\n\r\n            if ( this.mode == MODE.snackbar || this.exit ) {\r\n                $target.css( \"margin-left\", \"-\" + $target.width()/2 + \"px\" );\r\n                if ( this.cancel == \"\" ) {\r\n                    $exit.html( exit ).css( \"display\", \"flex\" );\r\n                    $root.on( \"click\", \".\" + item + \" notify-exit\", closeHandle );\r\n                }\r\n            }\r\n            setTimeout( function() { $target.addClass( \"notify-show\" ); }, 200 );\r\n        };\r\n\r\n    function Notify() {\r\n        registyElement( name, [ \"gp\", \"div\", \"a\", \"span\", \"title\", \"content\", \"i\" ] );\r\n        if ( $( \"html\" ).find ( root ).length == 0 ) {\r\n            $( \"html\" ).append( roottmpl );\r\n            $root = $( root );\r\n        }\r\n    }\r\n\r\n    Notify.prototype.title   = options.title;\r\n    Notify.prototype.content = options.content;\r\n    Notify.prototype.type    = options.type;\r\n    Notify.prototype.mode    = options.mode;\r\n    Notify.prototype.state   = options.state;\r\n    Notify.prototype.delay   = options.delay;\r\n    Notify.prototype.icon    = options.icon;\r\n    Notify.prototype.flat    = options.flat;\r\n    Notify.prototype.action  = options.action;\r\n    Notify.prototype.cancel  = options.cancel;\r\n    Notify.prototype.callback= options.callback;\r\n    Notify.prototype.complete= options.complete;\r\n    Notify.Position          = undefined;\r\n\r\n    Notify.prototype.Render  = function () {\r\n\r\n        var self = this;\r\n\r\n        if ( arguments.length === 1 && typeof arguments[0] === \"object\" ) {\r\n            options = arguments[0];\r\n\r\n            Object.keys( options ).forEach( function( item ) {\r\n                self[item] = options[item];\r\n            });\r\n\r\n            render.bind( self )();\r\n        }\r\n        else if ( typeof arguments[0] !== \"object\" && arguments.length > 0 && arguments.length < 5 ) {\r\n            switch ( arguments.length ) {\r\n                case 1:\r\n                    this.content = arguments[0];\r\n                    break;\r\n                case 2:\r\n                    if ( arguments[0] == MODE.snackbar ) {\r\n                        this.mode = arguments[0];\r\n                    }\r\n                    else if ( typeof arguments[0] == \"number\" ) {\r\n                        this.type  = arguments[0];\r\n                    } else {\r\n                        this.mode  = MODE.modal,\r\n                        this.title = arguments[0];\r\n                    }\r\n                    this.content   = arguments[1];\r\n                    break;\r\n                case 3:\r\n                    this.content   = arguments[0];\r\n                    this.action    = arguments[1];\r\n                    this.callback  = arguments[2];\r\n                    this.exit      = true;\r\n                    break;\r\n                case 4:\r\n                    if ( arguments[0] == MODE.snackbar ) {\r\n                        this.mode      = arguments[0];\r\n                        this.content   = arguments[1];\r\n                        this.action    = arguments[2];\r\n                        this.callback  = arguments[3];\r\n                    }\r\n                    break;\r\n            }\r\n            render.bind( self )();\r\n        }\r\n        else {\r\n            console.error( \"Arguments error\", arguments );\r\n        }\r\n        return self;\r\n    };\r\n\r\n    Notify.prototype.Clone  = function () {\r\n        return new Notify();\r\n    };\r\n\r\n    return Notify;\r\n\r\n})();\r\n\r\nmodule.exports = Notify;"
  },
  {
    "path": "src/vender/sha1.js",
    "content": "/*\n A JavaScript implementation of the SHA family of hashes, as\n defined in FIPS PUB 180-2 as well as the corresponding HMAC implementation\n as defined in FIPS PUB 198a\n\n Copyright Brian Turek 2008-2016\n Distributed under the BSD License\n See http://caligatio.github.com/jsSHA/ for more information\n\n Several functions taken from Paul Johnston\n*/\n'use strict';(function(E){function n(c,b,f){var a=0,d=[0],e=\"\",g=null,e=f||\"UTF8\";if(\"UTF8\"!==e&&\"UTF16BE\"!==e&&\"UTF16LE\"!==e)throw\"encoding must be UTF8, UTF16BE, or UTF16LE\";if(\"HEX\"===b){if(0!==c.length%2)throw\"srcString of HEX type must be in byte increments\";g=w(c);a=g.binLen;d=g.value}else if(\"TEXT\"===b||\"ASCII\"===b)g=x(c,e),a=g.binLen,d=g.value;else if(\"B64\"===b)g=y(c),a=g.binLen,d=g.value;else if(\"BYTES\"===b)g=z(c),a=g.binLen,d=g.value;else throw\"inputFormat must be HEX, TEXT, ASCII, B64, or BYTES\";\nthis.getHash=function(c,b,e,f){var g=null,h=d.slice(),m=a,p;3===arguments.length?\"number\"!==typeof e&&(f=e,e=1):2===arguments.length&&(e=1);if(e!==parseInt(e,10)||1>e)throw\"numRounds must a integer >= 1\";switch(b){case \"HEX\":g=A;break;case \"B64\":g=B;break;case \"BYTES\":g=C;break;default:throw\"format must be HEX, B64, or BYTES\";}if(\"SHA-1\"===c)for(p=0;p<e;p+=1)h=u(h,m),m=160;else throw\"Chosen SHA variant is not supported\";return g(h,D(f))};this.getHMAC=function(c,b,f,g,r){var h,m,p,t,n=[],v=[];h=null;\nswitch(g){case \"HEX\":g=A;break;case \"B64\":g=B;break;case \"BYTES\":g=C;break;default:throw\"outputFormat must be HEX, B64, or BYTES\";}if(\"SHA-1\"===f)m=64,t=160;else throw\"Chosen SHA variant is not supported\";if(\"HEX\"===b)h=w(c),p=h.binLen,h=h.value;else if(\"TEXT\"===b||\"ASCII\"===b)h=x(c,e),p=h.binLen,h=h.value;else if(\"B64\"===b)h=y(c),p=h.binLen,h=h.value;else if(\"BYTES\"===b)h=z(c),p=h.binLen,h=h.value;else throw\"inputFormat must be HEX, TEXT, ASCII, B64, or BYTES\";c=8*m;b=m/4-1;if(m<p/8){if(\"SHA-1\"===\nf)h=u(h,p);else throw\"Unexpected error in HMAC implementation\";for(;h.length<=b;)h.push(0);h[b]&=4294967040}else if(m>p/8){for(;h.length<=b;)h.push(0);h[b]&=4294967040}for(m=0;m<=b;m+=1)n[m]=h[m]^909522486,v[m]=h[m]^1549556828;if(\"SHA-1\"===f)f=u(v.concat(u(n.concat(d),c+a)),c+t);else throw\"Unexpected error in HMAC implementation\";return g(f,D(r))}}function x(c,b){var f=[],a,d=[],e=0,g,k,q;if(\"UTF8\"===b)for(g=0;g<c.length;g+=1)for(a=c.charCodeAt(g),d=[],128>a?d.push(a):2048>a?(d.push(192|a>>>6),d.push(128|\na&63)):55296>a||57344<=a?d.push(224|a>>>12,128|a>>>6&63,128|a&63):(g+=1,a=65536+((a&1023)<<10|c.charCodeAt(g)&1023),d.push(240|a>>>18,128|a>>>12&63,128|a>>>6&63,128|a&63)),k=0;k<d.length;k+=1){for(q=e>>>2;f.length<=q;)f.push(0);f[q]|=d[k]<<24-e%4*8;e+=1}else if(\"UTF16BE\"===b||\"UTF16LE\"===b)for(g=0;g<c.length;g+=1){a=c.charCodeAt(g);\"UTF16LE\"===b&&(k=a&255,a=k<<8|a>>8);for(q=e>>>2;f.length<=q;)f.push(0);f[q]|=a<<16-e%4*8;e+=2}return{value:f,binLen:8*e}}function w(c){var b=[],f=c.length,a,d,e;if(0!==\nf%2)throw\"String of HEX type must be in byte increments\";for(a=0;a<f;a+=2){d=parseInt(c.substr(a,2),16);if(isNaN(d))throw\"String of HEX type contains invalid characters\";for(e=a>>>3;b.length<=e;)b.push(0);b[a>>>3]|=d<<24-a%8*4}return{value:b,binLen:4*f}}function z(c){var b=[],f,a,d;for(a=0;a<c.length;a+=1)f=c.charCodeAt(a),d=a>>>2,b.length<=d&&b.push(0),b[d]|=f<<24-a%4*8;return{value:b,binLen:8*c.length}}function y(c){var b=[],f=0,a,d,e,g,k;if(-1===c.search(/^[a-zA-Z0-9=+\\/]+$/))throw\"Invalid character in base-64 string\";\nd=c.indexOf(\"=\");c=c.replace(/\\=/g,\"\");if(-1!==d&&d<c.length)throw\"Invalid '=' found in base-64 string\";for(d=0;d<c.length;d+=4){k=c.substr(d,4);for(e=g=0;e<k.length;e+=1)a=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".indexOf(k[e]),g|=a<<18-6*e;for(e=0;e<k.length-1;e+=1){for(a=f>>>2;b.length<=a;)b.push(0);b[a]|=(g>>>16-8*e&255)<<24-f%4*8;f+=1}}return{value:b,binLen:8*f}}function A(c,b){var f=\"\",a=4*c.length,d,e;for(d=0;d<a;d+=1)e=c[d>>>2]>>>8*(3-d%4),f+=\"0123456789abcdef\".charAt(e>>>\n4&15)+\"0123456789abcdef\".charAt(e&15);return b.outputUpper?f.toUpperCase():f}function B(c,b){var f=\"\",a=4*c.length,d,e,g;for(d=0;d<a;d+=3)for(g=d+1>>>2,e=c.length<=g?0:c[g],g=d+2>>>2,g=c.length<=g?0:c[g],g=(c[d>>>2]>>>8*(3-d%4)&255)<<16|(e>>>8*(3-(d+1)%4)&255)<<8|g>>>8*(3-(d+2)%4)&255,e=0;4>e;e+=1)8*d+6*e<=32*c.length?f+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt(g>>>6*(3-e)&63):f+=b.b64Pad;return f}function C(c){var b=\"\",f=4*c.length,a,d;for(a=0;a<f;a+=1)d=c[a>>>2]>>>\n8*(3-a%4)&255,b+=String.fromCharCode(d);return b}function D(c){var b={outputUpper:!1,b64Pad:\"=\"};try{c.hasOwnProperty(\"outputUpper\")&&(b.outputUpper=c.outputUpper),c.hasOwnProperty(\"b64Pad\")&&(b.b64Pad=c.b64Pad)}catch(f){}if(\"boolean\"!==typeof b.outputUpper)throw\"Invalid outputUpper formatting option\";if(\"string\"!==typeof b.b64Pad)throw\"Invalid b64Pad formatting option\";return b}function r(c,b){return c<<b|c>>>32-b}function t(c,b){var f=(c&65535)+(b&65535);return((c>>>16)+(b>>>16)+(f>>>16)&65535)<<\n16|f&65535}function v(c,b,f,a,d){var e=(c&65535)+(b&65535)+(f&65535)+(a&65535)+(d&65535);return((c>>>16)+(b>>>16)+(f>>>16)+(a>>>16)+(d>>>16)+(e>>>16)&65535)<<16|e&65535}function u(c,b){var f=[],a,d,e,g,k,q,n,l,u,h=[1732584193,4023233417,2562383102,271733878,3285377520];for(a=(b+65>>>9<<4)+15;c.length<=a;)c.push(0);c[b>>>5]|=128<<24-b%32;c[a]=b&4294967295;c[a-1]=b/4294967296|0;u=c.length;for(n=0;n<u;n+=16){a=h[0];d=h[1];e=h[2];g=h[3];k=h[4];for(l=0;80>l;l+=1)f[l]=16>l?c[l+n]:r(f[l-3]^f[l-8]^f[l-14]^\nf[l-16],1),q=20>l?v(r(a,5),d&e^~d&g,k,1518500249,f[l]):40>l?v(r(a,5),d^e^g,k,1859775393,f[l]):60>l?v(r(a,5),d&e^d&g^e&g,k,2400959708,f[l]):v(r(a,5),d^e^g,k,3395469782,f[l]),k=g,g=e,e=r(d,30),d=a,a=q;h[0]=t(a,h[0]);h[1]=t(d,h[1]);h[2]=t(e,h[2]);h[3]=t(g,h[3]);h[4]=t(k,h[4])}return h}\"function\"===typeof define&&define.amd?define(function(){return n}):\"undefined\"!==typeof exports?\"undefined\"!==typeof module&&module.exports?module.exports=exports=n:exports=n:E.jsSHA=n})(this);\n"
  },
  {
    "path": "src/vender/turndown/turndown-plugin-gfm.js",
    "content": "var turndownPluginGfm = (function (exports) {\n'use strict';\n\nvar highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;\n\nfunction highlightedCodeBlock (turndownService) {\n  turndownService.addRule('highlightedCodeBlock', {\n    filter: function (node) {\n      var firstChild = node.firstChild;\n      return (\n        node.nodeName === 'DIV' &&\n        highlightRegExp.test(node.className) &&\n        firstChild &&\n        firstChild.nodeName === 'PRE'\n      )\n    },\n    replacement: function (content, node, options) {\n      var className = node.className || '';\n      var language = (className.match(highlightRegExp) || [null, ''])[1];\n\n      return (\n        '\\n\\n' + options.fence + language + '\\n' +\n        node.firstChild.textContent +\n        '\\n' + options.fence + '\\n\\n'\n      )\n    }\n  });\n}\n\nfunction strikethrough (turndownService) {\n  turndownService.addRule('strikethrough', {\n    filter: ['del', 's', 'strike'],\n    replacement: function (content) {\n      return '~' + content + '~'\n    }\n  });\n}\n\nvar indexOf = Array.prototype.indexOf;\nvar every = Array.prototype.every;\nvar rules = {};\n\nrules.tableCell = {\n  filter: ['th', 'td'],\n  replacement: function (content, node) {\n    return cell(content, node)\n  }\n};\n\nrules.tableRow = {\n  filter: 'tr',\n  replacement: function (content, node) {\n    var borderCells = '';\n    var alignMap = { left: ':--', right: '--:', center: ':-:' };\n\n    if (isHeadingRow(node)) {\n      for (var i = 0; i < node.childNodes.length; i++) {\n        var border = '---';\n        var align = (\n          node.childNodes[i].getAttribute('align') || ''\n        ).toLowerCase();\n\n        if (align) border = alignMap[align] || border;\n\n        borderCells += cell(border, node.childNodes[i]);\n      }\n    }\n    return '\\n' + content + (borderCells ? '\\n' + borderCells : '')\n  }\n};\n\nrules.table = {\n  // Only convert tables with a heading row.\n  // Tables with no heading row are kept using `keep` (see below).\n  filter: function (node) {\n    return node.nodeName === 'TABLE' && isHeadingRow(node.rows[0])\n  },\n\n  replacement: function (content) {\n    // Ensure there are no blank lines\n    content = content.replace('\\n\\n', '\\n');\n    return '\\n\\n' + content + '\\n\\n'\n  }\n};\n\nrules.tableSection = {\n  filter: ['thead', 'tbody', 'tfoot'],\n  replacement: function (content) {\n    return content\n  }\n};\n\n// A tr is a heading row if:\n// - the parent is a THEAD\n// - or if its the first child of the TABLE or the first TBODY (possibly\n//   following a blank THEAD)\n// - and every cell is a TH\nfunction isHeadingRow (tr) {\n  var parentNode = tr.parentNode;\n  return (\n    parentNode.nodeName === 'THEAD' ||\n    (\n      parentNode.firstChild === tr &&\n      (parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&\n      every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })\n    )\n  )\n}\n\nfunction isFirstTbody (element) {\n  var previousSibling = element.previousSibling;\n  return (\n    element.nodeName === 'TBODY' && (\n      !previousSibling ||\n      (\n        previousSibling.nodeName === 'THEAD' &&\n        /^\\s*$/i.test(previousSibling.textContent)\n      )\n    )\n  )\n}\n\nfunction cell (content, node) {\n  var index = indexOf.call(node.parentNode.childNodes, node);\n  var prefix = ' ';\n  if (index === 0) prefix = '| ';\n  return prefix + content + ' |'\n}\n\nfunction tables (turndownService) {\n  turndownService.keep(function (node) {\n    return node.nodeName === 'TABLE' && !isHeadingRow(node.rows[0])\n  });\n  for (var key in rules) turndownService.addRule(key, rules[key]);\n}\n\nfunction taskListItems (turndownService) {\n  turndownService.addRule('taskListItems', {\n    filter: function (node) {\n      return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'\n    },\n    replacement: function (content, node) {\n      return (node.checked ? '[x]' : '[ ]') + ' '\n    }\n  });\n}\n\nfunction gfm (turndownService) {\n  turndownService.use([\n    highlightedCodeBlock,\n    strikethrough,\n    tables,\n    taskListItems\n  ]);\n}\n\nexports.gfm = gfm;\nexports.highlightedCodeBlock = highlightedCodeBlock;\nexports.strikethrough = strikethrough;\nexports.tables = tables;\nexports.taskListItems = taskListItems;\n\nreturn exports;\n\n}({}));\n\nmodule.exports = turndownPluginGfm;"
  },
  {
    "path": "src/vender/turndown/turndown.js",
    "content": "var TurndownService = (function () {\n'use strict';\n\nfunction extend (destination) {\n  for (var i = 1; i < arguments.length; i++) {\n    var source = arguments[i];\n    for (var key in source) {\n      if (source.hasOwnProperty(key)) destination[key] = source[key];\n    }\n  }\n  return destination\n}\n\nfunction repeat (character, count) {\n  return Array(count + 1).join(character)\n}\n\nvar blockElements = [\n  'address', 'article', 'aside', 'audio', 'blockquote', 'body', 'canvas',\n  'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption',\n  'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',\n  'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav',\n  'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table',\n  'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'\n];\n\nfunction isBlock (node) {\n  return blockElements.indexOf(node.nodeName.toLowerCase()) !== -1\n}\n\nvar voidElements = [\n  'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',\n  'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'\n];\n\nfunction isVoid (node) {\n  return voidElements.indexOf(node.nodeName.toLowerCase()) !== -1\n}\n\nvar voidSelector = voidElements.join();\nfunction hasVoid (node) {\n  return node.querySelector && node.querySelector(voidSelector)\n}\n\nvar rules = {};\n\nrules.paragraph = {\n  filter: 'p',\n\n  replacement: function (content) {\n    return '\\n\\n' + content + '\\n\\n'\n  }\n};\n\nrules.lineBreak = {\n  filter: 'br',\n\n  replacement: function (content, node, options) {\n    return options.br + '\\n'\n  }\n};\n\nrules.heading = {\n  filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],\n\n  replacement: function (content, node, options) {\n    var hLevel = Number(node.nodeName.charAt(1));\n\n    if (options.headingStyle === 'setext' && hLevel < 3) {\n      var underline = repeat((hLevel === 1 ? '=' : '-'), content.length);\n      return (\n        '\\n\\n' + content + '\\n' + underline + '\\n\\n'\n      )\n    } else {\n      return '\\n\\n' + repeat('#', hLevel) + ' ' + content + '\\n\\n'\n    }\n  }\n};\n\nrules.blockquote = {\n  filter: 'blockquote',\n\n  replacement: function (content) {\n    content = content.replace(/^\\n+|\\n+$/g, '');\n    content = content.replace(/^/gm, '> ');\n    return '\\n\\n' + content + '\\n\\n'\n  }\n};\n\nrules.list = {\n  filter: ['ul', 'ol'],\n\n  replacement: function (content, node) {\n    var parent = node.parentNode;\n    if (parent.nodeName === 'LI' && parent.lastElementChild === node) {\n      return '\\n' + content\n    } else {\n      return '\\n\\n' + content + '\\n\\n'\n    }\n  }\n};\n\nrules.listItem = {\n  filter: 'li',\n\n  replacement: function (content, node, options) {\n    content = content\n      .replace(/^\\n+/, '') // remove leading newlines\n      .replace(/\\n+$/, '\\n') // replace trailing newlines with just a single one\n      .replace(/\\n/gm, '\\n    '); // indent\n    var prefix = options.bulletListMarker + '   ';\n    var parent = node.parentNode;\n    if (parent.nodeName === 'OL') {\n      var start = parent.getAttribute('start');\n      var index = Array.prototype.indexOf.call(parent.children, node);\n      prefix = (start ? Number(start) + index : index + 1) + '.  ';\n    }\n    return (\n      prefix + content + (node.nextSibling && !/\\n$/.test(content) ? '\\n' : '')\n    )\n  }\n};\n\nrules.indentedCodeBlock = {\n  filter: function (node, options) {\n    return (\n      options.codeBlockStyle === 'indented' &&\n      node.nodeName === 'PRE' &&\n      node.firstChild &&\n      node.firstChild.nodeName === 'CODE'\n    )\n  },\n\n  replacement: function (content, node, options) {\n    return (\n      '\\n\\n    ' +\n      node.firstChild.textContent.replace(/\\n/g, '\\n    ') +\n      '\\n\\n'\n    )\n  }\n};\n\nrules.fencedCodeBlock = {\n  filter: function (node, options) {\n    return (\n      options.codeBlockStyle === 'fenced' &&\n      node.nodeName === 'PRE' &&\n      node.firstChild &&\n      node.firstChild.nodeName === 'CODE'\n    )\n  },\n\n  replacement: function (content, node, options) {\n    var className = node.firstChild.className || '';\n    var language = (className.match(/language-(\\S+)/) || [null, ''])[1];\n\n    return (\n      '\\n\\n' + options.fence + language + '\\n' +\n      node.firstChild.textContent +\n      '\\n' + options.fence + '\\n\\n'\n    )\n  }\n};\n\nrules.horizontalRule = {\n  filter: 'hr',\n\n  replacement: function (content, node, options) {\n    return '\\n\\n' + options.hr + '\\n\\n'\n  }\n};\n\nrules.inlineLink = {\n  filter: function (node, options) {\n    return (\n      options.linkStyle === 'inlined' &&\n      node.nodeName === 'A' &&\n      node.getAttribute('href')\n    )\n  },\n\n  replacement: function (content, node) {\n    var href = node.getAttribute('href');\n    var title = node.title ? ' \"' + node.title + '\"' : '';\n    return '[' + content + '](' + href + title + ')'\n  }\n};\n\nrules.referenceLink = {\n  filter: function (node, options) {\n    return (\n      options.linkStyle === 'referenced' &&\n      node.nodeName === 'A' &&\n      node.getAttribute('href')\n    )\n  },\n\n  replacement: function (content, node, options) {\n    var href = node.getAttribute('href');\n    var title = node.title ? ' \"' + node.title + '\"' : '';\n    var replacement;\n    var reference;\n\n    switch (options.linkReferenceStyle) {\n      case 'collapsed':\n        replacement = '[' + content + '][]';\n        reference = '[' + content + ']: ' + href + title;\n        break\n      case 'shortcut':\n        replacement = '[' + content + ']';\n        reference = '[' + content + ']: ' + href + title;\n        break\n      default:\n        var id = this.references.length + 1;\n        replacement = '[' + content + '][' + id + ']';\n        reference = '[' + id + ']: ' + href + title;\n    }\n\n    this.references.push(reference);\n    return replacement\n  },\n\n  references: [],\n\n  append: function (options) {\n    var references = '';\n    if (this.references.length) {\n      references = '\\n\\n' + this.references.join('\\n') + '\\n\\n';\n      this.references = []; // Reset references\n    }\n    return references\n  }\n};\n\nrules.emphasis = {\n  filter: ['em', 'i'],\n\n  replacement: function (content, node, options) {\n    if (!content.trim()) return ''\n    return options.emDelimiter + content + options.emDelimiter\n  }\n};\n\nrules.strong = {\n  filter: ['strong', 'b'],\n\n  replacement: function (content, node, options) {\n    if (!content.trim()) return ''\n    return options.strongDelimiter + content + options.strongDelimiter\n  }\n};\n\nrules.code = {\n  filter: function (node) {\n    var hasSiblings = node.previousSibling || node.nextSibling;\n    var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;\n\n    return node.nodeName === 'CODE' && !isCodeBlock\n  },\n\n  replacement: function (content) {\n    if (!content.trim()) return ''\n\n    var delimiter = '`';\n    var leadingSpace = '';\n    var trailingSpace = '';\n    var matches = content.match(/`+/gm);\n    if (matches) {\n      if (/^`/.test(content)) leadingSpace = ' ';\n      if (/`$/.test(content)) trailingSpace = ' ';\n      while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`';\n    }\n\n    return delimiter + leadingSpace + content + trailingSpace + delimiter\n  }\n};\n\nrules.image = {\n  filter: 'img',\n\n  replacement: function (content, node) {\n    var alt = node.alt || '';\n    var src = node.getAttribute('src') || '';\n    var title = node.title || '';\n    var titlePart = title ? ' \"' + title + '\"' : '';\n    return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''\n  }\n};\n\n/**\n * Manages a collection of rules used to convert HTML to Markdown\n */\n\nfunction Rules (options) {\n  this.options = options;\n  this._keep = [];\n  this._remove = [];\n\n  this.blankRule = {\n    replacement: options.blankReplacement\n  };\n\n  this.keepReplacement = options.keepReplacement;\n\n  this.defaultRule = {\n    replacement: options.defaultReplacement\n  };\n\n  this.array = [];\n  for (var key in options.rules) this.array.push(options.rules[key]);\n}\n\nRules.prototype = {\n  add: function (key, rule) {\n    this.array.unshift(rule);\n  },\n\n  keep: function (filter) {\n    this._keep.unshift({\n      filter: filter,\n      replacement: this.keepReplacement\n    });\n  },\n\n  remove: function (filter) {\n    this._remove.unshift({\n      filter: filter,\n      replacement: function () {\n        return ''\n      }\n    });\n  },\n\n  forNode: function (node) {\n    if (node.isBlank) return this.blankRule\n    var rule;\n\n    if ((rule = findRule(this.array, node, this.options))) return rule\n    if ((rule = findRule(this._keep, node, this.options))) return rule\n    if ((rule = findRule(this._remove, node, this.options))) return rule\n\n    return this.defaultRule\n  },\n\n  forEach: function (fn) {\n    for (var i = 0; i < this.array.length; i++) fn(this.array[i], i);\n  }\n};\n\nfunction findRule (rules, node, options) {\n  for (var i = 0; i < rules.length; i++) {\n    var rule = rules[i];\n    if (filterValue(rule, node, options)) return rule\n  }\n  return void 0\n}\n\nfunction filterValue (rule, node, options) {\n  var filter = rule.filter;\n  if (typeof filter === 'string') {\n    if (filter === node.nodeName.toLowerCase()) return true\n  } else if (Array.isArray(filter)) {\n    if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true\n  } else if (typeof filter === 'function') {\n    if (filter.call(rule, node, options)) return true\n  } else {\n    throw new TypeError('`filter` needs to be a string, array, or function')\n  }\n}\n\n/**\n * The collapseWhitespace function is adapted from collapse-whitespace\n * by Luc Thevenard.\n *\n * The MIT License (MIT)\n *\n * Copyright (c) 2014 Luc Thevenard <lucthevenard@gmail.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n/**\n * collapseWhitespace(options) removes extraneous whitespace from an the given element.\n *\n * @param {Object} options\n */\nfunction collapseWhitespace (options) {\n  var element = options.element;\n  var isBlock = options.isBlock;\n  var isVoid = options.isVoid;\n  var isPre = options.isPre || function (node) {\n    return node.nodeName === 'PRE'\n  };\n\n  if (!element.firstChild || isPre(element)) return\n\n  var prevText = null;\n  var prevVoid = false;\n\n  var prev = null;\n  var node = next(prev, element, isPre);\n\n  while (node !== element) {\n    if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE\n      var text = node.data.replace(/[ \\r\\n\\t]+/g, ' ');\n\n      if ((!prevText || / $/.test(prevText.data)) &&\n          !prevVoid && text[0] === ' ') {\n        text = text.substr(1);\n      }\n\n      // `text` might be empty at this point.\n      if (!text) {\n        node = remove(node);\n        continue\n      }\n\n      node.data = text;\n\n      prevText = node;\n    } else if (node.nodeType === 1) { // Node.ELEMENT_NODE\n      if (isBlock(node) || node.nodeName === 'BR') {\n        if (prevText) {\n          prevText.data = prevText.data.replace(/ $/, '');\n        }\n\n        prevText = null;\n        prevVoid = false;\n      } else if (isVoid(node)) {\n        // Avoid trimming space around non-block, non-BR void elements.\n        prevText = null;\n        prevVoid = true;\n      }\n    } else {\n      node = remove(node);\n      continue\n    }\n\n    var nextNode = next(prev, node, isPre);\n    prev = node;\n    node = nextNode;\n  }\n\n  if (prevText) {\n    prevText.data = prevText.data.replace(/ $/, '');\n    if (!prevText.data) {\n      remove(prevText);\n    }\n  }\n}\n\n/**\n * remove(node) removes the given node from the DOM and returns the\n * next node in the sequence.\n *\n * @param {Node} node\n * @return {Node} node\n */\nfunction remove (node) {\n  var next = node.nextSibling || node.parentNode;\n\n  node.parentNode.removeChild(node);\n\n  return next\n}\n\n/**\n * next(prev, current, isPre) returns the next node in the sequence, given the\n * current and previous nodes.\n *\n * @param {Node} prev\n * @param {Node} current\n * @param {Function} isPre\n * @return {Node}\n */\nfunction next (prev, current, isPre) {\n  if ((prev && prev.parentNode === current) || isPre(current)) {\n    return current.nextSibling || current.parentNode\n  }\n\n  return current.firstChild || current.nextSibling || current.parentNode\n}\n\n/*\n * Set up window for Node.js\n */\n\nvar root = (typeof window !== 'undefined' ? window : {});\n\n/*\n * Parsing HTML strings\n */\n\nfunction canParseHTMLNatively () {\n  var Parser = root.DOMParser;\n  var canParse = false;\n\n  // Adapted from https://gist.github.com/1129031\n  // Firefox/Opera/IE throw errors on unsupported types\n  try {\n    // WebKit returns null on unsupported types\n    if (new Parser().parseFromString('', 'text/html')) {\n      canParse = true;\n    }\n  } catch (e) {}\n\n  return canParse\n}\n\nfunction createHTMLParser () {\n  var Parser = function () {};\n\n  {\n    if (shouldUseActiveX()) {\n      Parser.prototype.parseFromString = function (string) {\n        var doc = new window.ActiveXObject('htmlfile');\n        doc.designMode = 'on'; // disable on-page scripts\n        doc.open();\n        doc.write(string);\n        doc.close();\n        return doc\n      };\n    } else {\n      Parser.prototype.parseFromString = function (string) {\n        var doc = document.implementation.createHTMLDocument('');\n        doc.open();\n        doc.write(string);\n        doc.close();\n        return doc\n      };\n    }\n  }\n  return Parser\n}\n\nfunction shouldUseActiveX () {\n  var useActiveX = false;\n  try {\n    document.implementation.createHTMLDocument('').open();\n  } catch (e) {\n    if (window.ActiveXObject) useActiveX = true;\n  }\n  return useActiveX\n}\n\nvar HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser();\n\nfunction RootNode (input) {\n  var root;\n  if (typeof input === 'string') {\n    var doc = htmlParser().parseFromString(\n      // DOM parsers arrange elements in the <head> and <body>.\n      // Wrapping in a custom element ensures elements are reliably arranged in\n      // a single element.\n      '<x-turndown id=\"turndown-root\">' + input + '</x-turndown>',\n      'text/html'\n    );\n    root = doc.getElementById('turndown-root');\n  } else {\n    root = input.cloneNode(true);\n  }\n  collapseWhitespace({\n    element: root,\n    isBlock: isBlock,\n    isVoid: isVoid\n  });\n\n  return root\n}\n\nvar _htmlParser;\nfunction htmlParser () {\n  _htmlParser = _htmlParser || new HTMLParser();\n  return _htmlParser\n}\n\nfunction Node (node) {\n  node.isBlock = isBlock(node);\n  node.isCode = node.nodeName.toLowerCase() === 'code' || node.parentNode.isCode;\n  node.isBlank = isBlank(node);\n  node.flankingWhitespace = flankingWhitespace(node);\n  return node\n}\n\nfunction isBlank (node) {\n  return (\n    ['A', 'TH', 'TD', 'IFRAME', 'SCRIPT', 'AUDIO', 'VIDEO'].indexOf(node.nodeName) === -1 &&\n    /^\\s*$/i.test(node.textContent) &&\n    !isVoid(node) &&\n    !hasVoid(node)\n  )\n}\n\nfunction flankingWhitespace (node) {\n  var leading = '';\n  var trailing = '';\n\n  if (!node.isBlock) {\n    var hasLeading = /^[ \\r\\n\\t]/.test(node.textContent);\n    var hasTrailing = /[ \\r\\n\\t]$/.test(node.textContent);\n\n    if (hasLeading && !isFlankedByWhitespace('left', node)) {\n      leading = ' ';\n    }\n    if (hasTrailing && !isFlankedByWhitespace('right', node)) {\n      trailing = ' ';\n    }\n  }\n\n  return { leading: leading, trailing: trailing }\n}\n\nfunction isFlankedByWhitespace (side, node) {\n  var sibling;\n  var regExp;\n  var isFlanked;\n\n  if (side === 'left') {\n    sibling = node.previousSibling;\n    regExp = / $/;\n  } else {\n    sibling = node.nextSibling;\n    regExp = /^ /;\n  }\n\n  if (sibling) {\n    if (sibling.nodeType === 3) {\n      isFlanked = regExp.test(sibling.nodeValue);\n    } else if (sibling.nodeType === 1 && !isBlock(sibling)) {\n      isFlanked = regExp.test(sibling.textContent);\n    }\n  }\n  return isFlanked\n}\n\nvar reduce = Array.prototype.reduce;\nvar leadingNewLinesRegExp = /^\\n*/;\nvar trailingNewLinesRegExp = /\\n*$/;\nvar escapes = [\n  [/\\\\/g, '\\\\\\\\'],\n  [/\\*/g, '\\\\*'],\n  [/^-/g, '\\\\-'],\n  [/^\\+ /g, '\\\\+ '],\n  [/^(=+)/g, '\\\\$1'],\n  [/^(#{1,6}) /g, '\\\\$1 '],\n  [/`/g, '\\\\`'],\n  [/^~~~/g, '\\\\~~~'],\n  [/\\[/g, '\\\\['],\n  [/\\]/g, '\\\\]'],\n  [/^>/g, '\\\\>'],\n  [/_/g, '\\\\_'],\n  [/^(\\d+)\\. /g, '$1\\\\. ']\n];\nescapes = [];\n\nfunction TurndownService (options) {\n  if (!(this instanceof TurndownService)) return new TurndownService(options)\n\n  var defaults = {\n    rules: rules,\n    headingStyle: 'setext',\n    hr: '* * *',\n    bulletListMarker: '*',\n    codeBlockStyle: 'indented',\n    fence: '```',\n    emDelimiter: '_',\n    strongDelimiter: '**',\n    linkStyle: 'inlined',\n    linkReferenceStyle: 'full',\n    br: '  ',\n    blankReplacement: function (content, node) {\n      return node.isBlock ? '\\n\\n' : ''\n    },\n    keepReplacement: function (content, node) {\n      return node.isBlock ? '\\n\\n' + node.outerHTML + '\\n\\n' : node.outerHTML\n    },\n    defaultReplacement: function (content, node) {\n      return node.isBlock ? '\\n\\n' + content + '\\n\\n' : content\n    }\n  };\n  this.options = extend({}, defaults, options);\n  this.rules = new Rules(this.options);\n}\n\nTurndownService.prototype = {\n  /**\n   * The entry point for converting a string or DOM node to Markdown\n   * @public\n   * @param {String|HTMLElement} input The string or DOM node to convert\n   * @returns A Markdown representation of the input\n   * @type String\n   */\n\n  turndown: function (input) {\n    if (!canConvert(input)) {\n      throw new TypeError(\n        input + ' is not a string, or an element/document/fragment node.'\n      )\n    }\n\n    if (input === '') return ''\n\n    var output = process.call(this, new RootNode(input));\n    return postProcess.call(this, output)\n  },\n\n  /**\n   * Add one or more plugins\n   * @public\n   * @param {Function|Array} plugin The plugin or array of plugins to add\n   * @returns The Turndown instance for chaining\n   * @type Object\n   */\n\n  use: function (plugin) {\n    if (Array.isArray(plugin)) {\n      for (var i = 0; i < plugin.length; i++) this.use(plugin[i]);\n    } else if (typeof plugin === 'function') {\n      plugin(this);\n    } else {\n      throw new TypeError('plugin must be a Function or an Array of Functions')\n    }\n    return this\n  },\n\n  /**\n   * Adds a rule\n   * @public\n   * @param {String} key The unique key of the rule\n   * @param {Object} rule The rule\n   * @returns The Turndown instance for chaining\n   * @type Object\n   */\n\n  addRule: function (key, rule) {\n    this.rules.add(key, rule);\n    return this\n  },\n\n  /**\n   * Keep a node (as HTML) that matches the filter\n   * @public\n   * @param {String|Array|Function} filter The unique key of the rule\n   * @returns The Turndown instance for chaining\n   * @type Object\n   */\n\n  keep: function (filter) {\n    this.rules.keep(filter);\n    return this\n  },\n\n  /**\n   * Remove a node that matches the filter\n   * @public\n   * @param {String|Array|Function} filter The unique key of the rule\n   * @returns The Turndown instance for chaining\n   * @type Object\n   */\n\n  remove: function (filter) {\n    this.rules.remove(filter);\n    return this\n  },\n\n  /**\n   * Escapes Markdown syntax\n   * @public\n   * @param {String} string The string to escape\n   * @returns A string with Markdown syntax escaped\n   * @type String\n   */\n\n  escape: function (string) {\n    return escapes.reduce(function (accumulator, escape) {\n      return accumulator.replace(escape[0], escape[1])\n    }, string)\n  }\n};\n\n/**\n * Reduces a DOM node down to its Markdown string equivalent\n * @private\n * @param {HTMLElement} parentNode The node to convert\n * @returns A Markdown representation of the node\n * @type String\n */\n\nfunction process (parentNode) {\n  var self = this;\n  return reduce.call(parentNode.childNodes, function (output, node) {\n    node = new Node(node);\n\n    var replacement = '';\n    if (node.nodeType === 3) {\n      replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue);\n    } else if (node.nodeType === 1) {\n      replacement = replacementForNode.call(self, node);\n    }\n\n    return join(output, replacement)\n  }, '')\n}\n\n/**\n * Appends strings as each rule requires and trims the output\n * @private\n * @param {String} output The conversion output\n * @returns A trimmed version of the ouput\n * @type String\n */\n\nfunction postProcess (output) {\n  var self = this;\n  this.rules.forEach(function (rule) {\n    if (typeof rule.append === 'function') {\n      output = join(output, rule.append(self.options));\n    }\n  });\n\n  return output.replace(/^[\\t\\r\\n]+/, '').replace(/[\\t\\r\\n\\s]+$/, '')\n}\n\n/**\n * Converts an element node to its Markdown equivalent\n * @private\n * @param {HTMLElement} node The node to convert\n * @returns A Markdown representation of the node\n * @type String\n */\n\nfunction replacementForNode (node) {\n  var rule = this.rules.forNode(node);\n  var content = process.call(this, node);\n  var whitespace = node.flankingWhitespace;\n  if (whitespace.leading || whitespace.trailing) content = content.trim();\n  return (\n    whitespace.leading +\n    rule.replacement(content, node, this.options) +\n    whitespace.trailing\n  )\n}\n\n/**\n * Determines the new lines between the current output and the replacement\n * @private\n * @param {String} output The current conversion output\n * @param {String} replacement The string to append to the output\n * @returns The whitespace to separate the current output and the replacement\n * @type String\n */\n\nfunction separatingNewlines (output, replacement) {\n  var newlines = [\n    output.match(trailingNewLinesRegExp)[0],\n    replacement.match(leadingNewLinesRegExp)[0]\n  ].sort();\n  var maxNewlines = newlines[newlines.length - 1];\n  return maxNewlines.length < 2 ? maxNewlines : '\\n\\n'\n}\n\nfunction join (string1, string2) {\n  var separator = separatingNewlines(string1, string2);\n\n  // Remove trailing/leading newlines and replace with separator\n  string1 = string1.replace(trailingNewLinesRegExp, '');\n  string2 = string2.replace(leadingNewLinesRegExp, '');\n\n  return string1 + separator + string2\n}\n\n/**\n * Determines whether an input can be converted\n * @private\n * @param {String|HTMLElement} input Describe this parameter\n * @returns Describe what it returns\n * @type String|Object|Array|Boolean|Number\n */\n\nfunction canConvert (input) {\n  return (\n    input != null && (\n      typeof input === 'string' ||\n      (input.nodeType && (\n        input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11\n      ))\n    )\n  )\n}\n\nreturn TurndownService;\n\n}());\n\n\nmodule.exports = TurndownService;"
  },
  {
    "path": "src/vender/waves/waves.js",
    "content": "/*!\n * Waves v0.7.5\n * http://fian.my.id/Waves\n *\n * Copyright 2014-2016 Alfiana E. Sibuea and other contributors\n * Released under the MIT license\n * https://github.com/fians/Waves/blob/master/LICENSE\n */\n\n;(function(window, factory) {\n    'use strict';\n\n    // AMD. Register as an anonymous module.  Wrap in function so we have access\n    // to root via `this`.\n    if (typeof define === 'function' && define.amd) {\n        define([], function() {\n            return factory.apply(window);\n        });\n    }\n\n    // Node. Does not work with strict CommonJS, but only CommonJS-like\n    // environments that support module.exports, like Node.\n    else if (typeof exports === 'object') {\n        module.exports = factory.call(window);\n    }\n\n    // Browser globals.\n    else {\n        window.Waves = factory.call(window);\n    }\n})(typeof global === 'object' ? global : this, function() {\n    'use strict';\n\n    var Waves            = Waves || {};\n    var $$               = document.querySelectorAll.bind(document);\n    var toString         = Object.prototype.toString;\n    var isTouchAvailable = 'ontouchstart' in window;\n\n\n    // Find exact position of element\n    function isWindow(obj) {\n        return obj !== null && obj === obj.window;\n    }\n\n    function getWindow(elem) {\n        return isWindow(elem) ? elem : elem.nodeType === 9 && elem.defaultView;\n    }\n\n    function isObject(value) {\n        var type = typeof value;\n        return type === 'function' || type === 'object' && !!value;\n    }\n\n    function isDOMNode(obj) {\n        return isObject(obj) && obj.nodeType > 0;\n    }\n\n    function getWavesElements(nodes) {\n        var stringRepr = toString.call(nodes);\n\n        if (stringRepr === '[object String]') {\n            return $$(nodes);\n        } else if (isObject(nodes) && /^\\[object (Array|HTMLCollection|NodeList|Object)\\]$/.test(stringRepr) && nodes.hasOwnProperty('length')) {\n            return nodes;\n        } else if (isDOMNode(nodes)) {\n            return [nodes];\n        }\n\n        return [];\n    }\n\n    function offset(elem) {\n        var docElem, win,\n            box = { top: 0, left: 0 },\n            doc = elem && elem.ownerDocument;\n\n        docElem = doc.documentElement;\n\n        if (typeof elem.getBoundingClientRect !== typeof undefined) {\n            box = elem.getBoundingClientRect();\n        }\n        win = getWindow(doc);\n        return {\n            top: box.top + win.pageYOffset - docElem.clientTop,\n            left: box.left + win.pageXOffset - docElem.clientLeft\n        };\n    }\n\n    function convertStyle(styleObj) {\n        var style = '';\n\n        for (var prop in styleObj) {\n            if (styleObj.hasOwnProperty(prop)) {\n                style += (prop + ':' + styleObj[prop] + ';');\n            }\n        }\n\n        return style;\n    }\n\n    var Effect = {\n\n        // Effect duration\n        duration: 750,\n\n        // Effect delay (check for scroll before showing effect)\n        delay: 200,\n\n        show: function(e, element, velocity) {\n\n            // Disable right click\n            if (e.button === 2) {\n                return false;\n            }\n\n            element = element || this;\n\n            // Create ripple\n            var ripple = document.createElement('div');\n            ripple.className = 'md-waves-ripple md-waves-rippling';\n            element.appendChild(ripple);\n\n            // Get click coordinate and element width\n            var pos       = offset(element);\n            var relativeY = 0;\n            var relativeX = 0;\n            // Support for touch devices\n            if('touches' in e && e.touches.length) {\n                relativeY   = (e.touches[0].pageY - pos.top);\n                relativeX   = (e.touches[0].pageX - pos.left);\n            }\n            //Normal case\n            else {\n                relativeY   = (e.pageY - pos.top);\n                relativeX   = (e.pageX - pos.left);\n            }\n            // Support for synthetic events\n            relativeX = relativeX >= 0 ? relativeX : 0;\n            relativeY = relativeY >= 0 ? relativeY : 0;\n\n            var scale     = 'scale(' + ((element.clientWidth / 100) * 3) + ')';\n            var translate = 'translate(0,0)';\n\n            if (velocity) {\n                translate = 'translate(' + (velocity.x) + 'px, ' + (velocity.y) + 'px)';\n            }\n\n            // Attach data to element\n            ripple.setAttribute('data-hold', Date.now());\n            ripple.setAttribute('data-x', relativeX);\n            ripple.setAttribute('data-y', relativeY);\n            ripple.setAttribute('data-scale', scale);\n            ripple.setAttribute('data-translate', translate);\n\n            // Set ripple position\n            var rippleStyle = {\n                top: relativeY + 'px',\n                left: relativeX + 'px'\n            };\n\n            ripple.classList.add('md-waves-notransition');\n            ripple.setAttribute('style', convertStyle(rippleStyle));\n            ripple.classList.remove('md-waves-notransition');\n\n            // Scale the ripple\n            rippleStyle['-webkit-transform'] = scale + ' ' + translate;\n            rippleStyle['-moz-transform'] = scale + ' ' + translate;\n            rippleStyle['-ms-transform'] = scale + ' ' + translate;\n            rippleStyle['-o-transform'] = scale + ' ' + translate;\n            rippleStyle.transform = scale + ' ' + translate;\n            rippleStyle.opacity = '1';\n\n            var duration = e.type === 'mousemove' ? 2500 : Effect.duration;\n            rippleStyle['-webkit-transition-duration'] = duration + 'ms';\n            rippleStyle['-moz-transition-duration']    = duration + 'ms';\n            rippleStyle['-o-transition-duration']      = duration + 'ms';\n            rippleStyle['transition-duration']         = duration + 'ms';\n\n            ripple.setAttribute('style', convertStyle(rippleStyle));\n        },\n\n        hide: function(e, element) {\n            element = element || this;\n\n            var ripples = element.getElementsByClassName('md-waves-rippling');\n\n            for (var i = 0, len = ripples.length; i < len; i++) {\n                removeRipple(e, element, ripples[i]);\n            }\n        }\n    };\n\n    /**\n     * Collection of wrapper for HTML element that only have single tag\n     * like <input> and <img>\n     */\n    var TagWrapper = {\n\n        // Wrap <input> tag so it can perform the effect\n        input: function(element) {\n\n            var parent = element.parentNode;\n\n            // If input already have parent just pass through\n            if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('md-waves-effect')) {\n                return;\n            }\n\n            // Put element class and style to the specified parent\n            var wrapper       = document.createElement('i');\n            wrapper.className = element.className + ' md-waves-input-wrapper';\n            element.className = 'md-waves-button-input';\n\n            // Put element as child\n            parent.replaceChild(wrapper, element);\n            wrapper.appendChild(element);\n\n            // Apply element color and background color to wrapper\n            var elementStyle    = window.getComputedStyle(element, null);\n            var color           = elementStyle.color;\n            var backgroundColor = elementStyle.backgroundColor;\n\n            wrapper.setAttribute('style', 'color:' + color + ';background:' + backgroundColor);\n            element.setAttribute('style', 'background-color:rgba(0,0,0,0);');\n\n        },\n\n        // Wrap <img> tag so it can perform the effect\n        img: function(element) {\n\n            var parent = element.parentNode;\n\n            // If input already have parent just pass through\n            if (parent.tagName.toLowerCase() === 'i' && parent.classList.contains('md-waves-effect')) {\n                return;\n            }\n\n            // Put element as child\n            var wrapper  = document.createElement('i');\n            parent.replaceChild(wrapper, element);\n            wrapper.appendChild(element);\n\n        }\n    };\n\n    /**\n     * Hide the effect and remove the ripple. Must be\n     * a separate function to pass the JSLint...\n     */\n    function removeRipple(e, el, ripple) {\n\n        // Check if the ripple still exist\n        if (!ripple) {\n            return;\n        }\n\n        ripple.classList.remove('md-waves-rippling');\n\n        var relativeX = ripple.getAttribute('data-x');\n        var relativeY = ripple.getAttribute('data-y');\n        var scale     = ripple.getAttribute('data-scale');\n        var translate = ripple.getAttribute('data-translate');\n\n        // Get delay beetween mousedown and mouse leave\n        var diff = Date.now() - Number(ripple.getAttribute('data-hold'));\n        var delay = 350 - diff;\n\n        if (delay < 0) {\n            delay = 0;\n        }\n\n        if (e.type === 'mousemove') {\n            delay = 150;\n        }\n\n        // Fade out ripple after delay\n        var duration = e.type === 'mousemove' ? 2500 : Effect.duration;\n\n        setTimeout(function() {\n\n            var style = {\n                top: relativeY + 'px',\n                left: relativeX + 'px',\n                opacity: '0',\n\n                // Duration\n                '-webkit-transition-duration': duration + 'ms',\n                '-moz-transition-duration': duration + 'ms',\n                '-o-transition-duration': duration + 'ms',\n                'transition-duration': duration + 'ms',\n                '-webkit-transform': scale + ' ' + translate,\n                '-moz-transform': scale + ' ' + translate,\n                '-ms-transform': scale + ' ' + translate,\n                '-o-transform': scale + ' ' + translate,\n                'transform': scale + ' ' + translate\n            };\n\n            ripple.setAttribute('style', convertStyle(style));\n\n            setTimeout(function() {\n                try {\n                    el.removeChild(ripple);\n                } catch (e) {\n                    return false;\n                }\n            }, duration);\n\n        }, delay);\n    }\n\n\n    /**\n     * Disable mousedown event for 500ms during and after touch\n     */\n    var TouchHandler = {\n\n        /* uses an integer rather than bool so there's no issues with\n         * needing to clear timeouts if another touch event occurred\n         * within the 500ms. Cannot mouseup between touchstart and\n         * touchend, nor in the 500ms after touchend. */\n        touches: 0,\n\n        allowEvent: function(e) {\n\n            var allow = true;\n\n            if (/^(mousedown|mousemove)$/.test(e.type) && TouchHandler.touches) {\n                allow = false;\n            }\n\n            return allow;\n        },\n        registerEvent: function(e) {\n            var eType = e.type;\n\n            if (eType === 'touchstart') {\n\n                TouchHandler.touches += 1; // push\n\n            } else if (/^(touchend|touchcancel)$/.test(eType)) {\n\n                setTimeout(function() {\n                    if (TouchHandler.touches) {\n                        TouchHandler.touches -= 1; // pop after 500ms\n                    }\n                }, 500);\n\n            }\n        }\n    };\n\n\n    /**\n     * Delegated click handler for .md-waves-effect element.\n     * returns null when .md-waves-effect element not in \"click tree\"\n     */\n    function getWavesEffectElement(e) {\n\n        if (TouchHandler.allowEvent(e) === false) {\n            return null;\n        }\n\n        var element = null;\n        var target = e.target || e.srcElement;\n\n        while (target.parentElement !== null) {\n            if (target.classList.contains('md-waves-effect') && (!(target instanceof SVGElement))) {\n                element = target;\n                break;\n            }\n            target = target.parentElement;\n        }\n\n        return element;\n    }\n\n    /**\n     * Bubble the click and show effect if .md-waves-effect elem was found\n     */\n    function showEffect(e) {\n\n        // Disable effect if element has \"disabled\" property on it\n        // In some cases, the event is not triggered by the current element\n        // if (e.target.getAttribute('disabled') !== null) {\n        //     return;\n        // }\n\n        var element = getWavesEffectElement(e);\n\n        if (element !== null) {\n\n            // Make it sure the element has either disabled property, disabled attribute or 'disabled' class\n            if (element.disabled || element.getAttribute('disabled') || element.classList.contains('disabled')) {\n                return;\n            }\n\n            TouchHandler.registerEvent(e);\n\n            if (e.type === 'touchstart' && Effect.delay) {\n\n                var hidden = false;\n\n                var timer = setTimeout(function () {\n                    timer = null;\n                    Effect.show(e, element);\n                }, Effect.delay);\n\n                var hideEffect = function(hideEvent) {\n\n                    // if touch hasn't moved, and effect not yet started: start effect now\n                    if (timer) {\n                        clearTimeout(timer);\n                        timer = null;\n                        Effect.show(e, element);\n                    }\n                    if (!hidden) {\n                        hidden = true;\n                        Effect.hide(hideEvent, element);\n                    }\n                };\n\n                var touchMove = function(moveEvent) {\n                    if (timer) {\n                        clearTimeout(timer);\n                        timer = null;\n                    }\n                    hideEffect(moveEvent);\n                };\n\n                element.addEventListener('touchmove', touchMove, false);\n                element.addEventListener('touchend', hideEffect, false);\n                element.addEventListener('touchcancel', hideEffect, false);\n\n            } else {\n\n                Effect.show(e, element);\n\n                if (isTouchAvailable) {\n                    element.addEventListener('touchend', Effect.hide, false);\n                    element.addEventListener('touchcancel', Effect.hide, false);\n                }\n\n                element.addEventListener('mouseup', Effect.hide, false);\n                element.addEventListener('mouseleave', Effect.hide, false);\n            }\n        }\n    }\n\n    Waves.init = function(options) {\n        var body = document.body;\n\n        options = options || {};\n\n        if ('duration' in options) {\n            Effect.duration = options.duration;\n        }\n\n        if ('delay' in options) {\n            Effect.delay = options.delay;\n        }\n\n        if ( 'root' in options ) {\n            body = options.root;\n        }\n\n        if (isTouchAvailable) {\n            body.addEventListener('touchstart', showEffect, false);\n            body.addEventListener('touchcancel', TouchHandler.registerEvent, false);\n            body.addEventListener('touchend', TouchHandler.registerEvent, false);\n        }\n\n        body.addEventListener('mousedown', showEffect, false);\n    };\n\n\n    /**\n     * Attach Waves to dynamically loaded inputs, or add .md-waves-effect and other\n     * waves classes to a set of elements. Set drag to true if the ripple mouseover\n     * or skimming effect should be applied to the elements.\n     */\n    Waves.attach = function(elements, classes) {\n\n        elements = getWavesElements(elements);\n\n        if (toString.call(classes) === '[object Array]') {\n            classes = classes.join(' ');\n        }\n\n        classes = classes ? ' ' + classes : '';\n\n        var element, tagName;\n\n        for (var i = 0, len = elements.length; i < len; i++) {\n\n            element = elements[i];\n            tagName = element.tagName.toLowerCase();\n\n            if (['input', 'img'].indexOf(tagName) !== -1) {\n                TagWrapper[tagName](element);\n                element = element.parentElement;\n            }\n\n            if (element.className.indexOf('md-waves-effect') === -1) {\n                element.className += ' md-waves-effect' + classes;\n            }\n        }\n    };\n\n\n    /**\n     * Cause a ripple to appear in an element via code.\n     */\n    Waves.ripple = function(elements, options) {\n        elements = getWavesElements(elements);\n        var elementsLen = elements.length;\n\n        options          = options || {};\n        options.wait     = options.wait || 0;\n        options.position = options.position || null; // default = centre of element\n\n\n        if (elementsLen) {\n            var element, pos, off, centre = {}, i = 0;\n            var mousedown = {\n                type: 'mousedown',\n                button: 1\n            };\n            var hideRipple = function(mouseup, element) {\n                return function() {\n                    Effect.hide(mouseup, element);\n                };\n            };\n\n            for (; i < elementsLen; i++) {\n                element = elements[i];\n                pos = options.position || {\n                    x: element.clientWidth / 2,\n                    y: element.clientHeight / 2\n                };\n\n                off      = offset(element);\n                centre.x = off.left + pos.x;\n                centre.y = off.top + pos.y;\n\n                mousedown.pageX = centre.x;\n                mousedown.pageY = centre.y;\n\n                Effect.show(mousedown, element);\n\n                if (options.wait >= 0 && options.wait !== null) {\n                    var mouseup = {\n                        type: 'mouseup',\n                        button: 1\n                    };\n\n                    setTimeout(hideRipple(mouseup, element), options.wait);\n                }\n            }\n        }\n    };\n\n    /**\n     * Remove all ripples from an element.\n     */\n    Waves.calm = function(elements) {\n        elements = getWavesElements(elements);\n        var mouseup = {\n            type: 'mouseup',\n            button: 1\n        };\n\n        for (var i = 0, len = elements.length; i < len; i++) {\n            Effect.hide(mouseup, elements[i]);\n        }\n    };\n\n    /**\n     * Deprecated API fallback\n     */\n    Waves.displayEffect = function(options) {\n        console.error('Waves.displayEffect() has been deprecated and will be removed in future version. Please use Waves.init() to initialize Waves effect');\n        Waves.init(options);\n    };\n\n    return Waves;\n});\n"
  },
  {
    "path": "src/vender/webdav.js",
    "content": "/* A simple WebDav implementation in JavaScript\n   https://github.com/aslakhellesoy/webdavjs @license MIT\n*/\nvar WebDAV = {\n    GET: function(url, callback) {\n        return this.request('GET', url, {}, null, 'text', callback);\n    },\n\n    PROPFIND: function(url, callback) {\n        return this.request('PROPFIND', url, {\n            Depth: \"1\"\n        }, null, 'xml', callback);\n    },\n\n    MKCOL: function(url, callback) {\n        return this.request('MKCOL', url, {}, null, 'text', callback);\n    },\n\n    DELETE: function(url, callback) {\n        return this.request('DELETE', url, {}, null, 'text', callback);\n    },\n\n    PUT: function(url, data, callback) {\n        return this.request('PUT', url, {}, data, 'text', callback);\n    },\n\n    Author: function(user, password) {\n        this.user = user;\n        this.password = password;\n    },\n\n    request: function(verb, url, headers, data, type, callback) {\n        var xhr = new XMLHttpRequest();\n        var body = function() {\n                var b = xhr.responseText;\n                if (type == 'xml') {\n                    var xml = xhr.responseXML;\n                    if (xml) {\n                        b = xml.firstChild.nextSibling ? xml.firstChild.nextSibling : xml.firstChild;\n                    }\n                }\n                return b;\n            };\n\n        if (callback) {\n            xhr.onreadystatechange = function() {\n                if (xhr.readyState === XMLHttpRequest.DONE && verb == \"PROPFIND\") {\n                    var b = body();\n                    if (b) {\n                        callback(b);\n                    }\n                } else if (xhr.readyState === XMLHttpRequest.DONE) {\n                    callback(xhr);\n                }\n            };\n        }\n        xhr.open(verb, url, !! callback);\n        xhr.setRequestHeader(\"Content-Type\", \"text/xml; charset=UTF-8\");\n        this.user && this.password &&\n            xhr.setRequestHeader(\"Authorization\", \"Basic \" + btoa(this.user + \":\" + this.password))\n        for (var header in headers) {\n            xhr.setRequestHeader(header, headers[header]);\n        }\n        xhr.send(data);\n\n        if (!callback) {\n            return body();\n        }\n    }\n};\n\n// An Object-oriented API around WebDAV.\nWebDAV.Fs = function(rootUrl, user, password) {\n    WebDAV.Author(user, password);\n    this.rootUrl = rootUrl;\n    var fs = this;\n\n    this.file = function(href) {\n        this.type = 'file';\n\n        this.url = fs.urlFor(href);\n\n        this.name = fs.nameFor(this.url);\n\n        this.read = function(callback) {\n            return WebDAV.GET(this.url, callback);\n        };\n\n        this.write = function(data, callback) {\n            return WebDAV.PUT(this.url, data, callback);\n        };\n\n        this.rm = function(callback) {\n            return WebDAV.DELETE(this.url, callback);\n        };\n\n        return this;\n    };\n\n    this.dir = function(href) {\n        this.type = 'dir';\n\n        this.url = fs.urlFor(href);\n\n        this.name = fs.nameFor(this.url);\n\n        this.children = function(callback) {\n            var childrenFunc = function(doc) {\n                    if (doc.childNodes == null) {\n                        throw ('No such directory: ' + url);\n                    }\n                    var result = [];\n                    // Start at 1, because the 0th is the same as self.\n                    for (var i = 1; i < doc.childNodes.length; i++) {\n                        var response = doc.childNodes[i];\n                        var href = decodeURI( response.getElementsByTagName('d:href')[0].firstChild.nodeValue.replace(/\\/$/, ''));\n                        var propstat = response.getElementsByTagName('d:propstat')[0];\n                        var prop = propstat.getElementsByTagName('d:prop')[0];\n                        var resourcetype = prop.getElementsByTagName('d:resourcetype')[0];\n                        var collection = resourcetype.getElementsByTagName('d:collection')[0];\n\n                        if (collection) {\n                            result[i - 1] = new fs.dir(href);\n                        } else {\n                            result[i - 1] = new fs.file(href);\n                        }\n                    }\n                    return result;\n                };\n\n            if (callback) {\n                WebDAV.PROPFIND(this.url, function(doc) {\n                    callback(childrenFunc(doc));\n                });\n            } else {\n                return childrenFunc(WebDAV.PROPFIND(this.url));\n            }\n        };\n\n        this.rm = function(callback) {\n            return WebDAV.DELETE(this.url, callback);\n        };\n\n        this.mkdir = function(callback) {\n            return WebDAV.MKCOL(this.url, callback);\n        };\n\n        return this;\n    };\n\n    this.urlFor = function(href) {\n        return (/^http/.test(href) ? href : this.rootUrl + href);\n    };\n\n    this.nameFor = function(url) {\n        return url.replace(/.*\\/(.*)/, '$1');\n    };\n\n    return this;\n};\n\nif (typeof module !== 'undefined') {\n    module.exports = WebDAV;\n}"
  },
  {
    "path": "src/vender/wiz.js",
    "content": "/**\n * Soure from https://github.com/xcffl/WizWebClipperWE\n */\n\nvar Base64 = {\n    // private property\n    _keyStr : \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",\n    // public method for encoding\n    encode : function(input) {\n        var output = \"\";\n        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;\n        var i = 0;\n\n        input = Base64._utf8_encode(input);\n\n        while (i < input.length) {\n\n            chr1 = input.charCodeAt(i++);\n            chr2 = input.charCodeAt(i++);\n            chr3 = input.charCodeAt(i++);\n\n            enc1 = chr1 >> 2;\n            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);\n            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);\n            enc4 = chr3 & 63;\n\n            if (isNaN(chr2)) {\n                enc3 = enc4 = 64;\n            } else if (isNaN(chr3)) {\n                enc4 = 64;\n            }\n            output = output + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);\n        }\n\n        return output;\n    },\n    // private method for UTF-8 encoding\n    _utf8_encode : function(string) {\n        string = string.replace(/\\r\\n/g, \"\\n\");\n        var utftext = \"\";\n        for (var n = 0; n < string.length; n++) {\n            var c = string.charCodeAt(n);\n\n            if (c < 128) {\n                utftext += String.fromCharCode(c);\n            } else if ((c > 127) && (c < 2048)) {\n                utftext += String.fromCharCode((c >> 6) | 192);\n                utftext += String.fromCharCode((c & 63) | 128);\n            } else {\n                utftext += String.fromCharCode((c >> 12) | 224);\n                utftext += String.fromCharCode(((c >> 6) & 63) | 128);\n                utftext += String.fromCharCode((c & 63) | 128);\n            }\n\n        }\n\n        return utftext;\n    }\n};\n\nvar wiz_base64Encode = function( str ) {\n    var scriptFilter = function (html) {\n        return html.replace(/<script[^<>]*\\/>/ig, \"\").replace(/<script[^<>]*>(((?!<\\/script>).)|(\\r?\\n))*<\\/script>/ig, \"\");\n    };\n    if (!str || str.length < 1) {\n        return \"\";\n    }\n    var base64str = Base64.encode(scriptFilter(str));\n    return base64str;\n}\n\nvar genGuid = function() {\n    return (genGuidItem() + genGuidItem() + \"-\" + genGuidItem() + \"-\" + genGuidItem() + \"-\" + genGuidItem() + \"-\" + genGuidItem() + genGuidItem() + genGuidItem());\n}\n\nvar genGuidItem = function() {\n    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);\n}\n\nfunction getParams( url, title, htmlStr ) {\n    var params       = \"\";\n    var docType      = document.doctype;\n    var source_url   = wiz_base64Encode( url );\n    var source_html  = \"\";\n    var source_title = wiz_base64Encode( title );\n\n    if (!!docType && !!docType.systemId && !!docType.publicId) {\n        docType = '<!DOCTYPE HTML PUBLIC \"' + docType.publicId + '\" \"' + docType.systemId + '\" >';\n    } else {\n        docType = '<!DOCTYPE HTML>';\n    }\n    source_html = wiz_base64Encode( docType + htmlStr );\n\n    params  = \"param-location='\" + source_url   + \"' \";\n    params += \"param-title='\"    + source_title + \"' \";\n\n    params += \"0_FrameURL='\"     + source_url   + \"' \";\n    params += \"0_FrameHtml='\"    + source_html  + \"' \";\n\n    params = \"param-fcount='1' \" + params;\n\n    return params;\n}\n\nfunction getInfos( info, access_token ) {\n    var docGuid = genGuid();\n\n    info.params = info.params + ' save-command=\"' + info.cmd + '\" userid=\"' + info.userid +\n                  '\" location=\"' + wiz_base64Encode(info.category) + '\" comment=\"' + wiz_base64Encode('')  +\n                  '\" tag=\"' + wiz_base64Encode(info.tag) + '\"';\n\n    info.params = \"myWiz='\" + access_token + \"@userguid' SaveResources='true' document_guid='\" + docGuid + \"' \" + info.params;\n    var params = {\n        type    : 'clipper',\n        data    : info.params,\n        customId: docGuid\n    };\n    return params;\n}\n\nif ( typeof module !== 'undefined' ) {\n    module.exports = {\n        getParams: getParams,\n        getInfos : getInfos,\n    };\n}"
  },
  {
    "path": "src/website_list.json",
    "content": "{\r\n    \"sites\"  : [{\r\n        \"name\"    : \"cnbeta.com\",\r\n        \"url\"     : \"http*://*.cnbeta.com/articles/*/*\",\r\n        \"title\"   : \"[[{$('header.title h1').text()}]]\",\r\n        \"desc\"    : \"[[{$('.article-summary').text()}]]\",\r\n        \"include\" : \"<div class='cnbeta-article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='clearfix'>\",\r\n            \"<div class='rating_box'>\",\r\n            \"[['<strong>[广告]活动入口:</strong>']]\",\r\n            \"<div class='article-relation'>\",\r\n            \"<div class='topic'>\",\r\n            \"<div class='tac'>\",\r\n            \"<div class='article-share-code'>\",\r\n            \"<div class='article-global'>\",\r\n            \"<div class='article-topic'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.qdaily.com\",\r\n        \"url\"     : \"http*://m.qdaily.com/mobile/articles/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"<div class='excerpt'>\",\r\n        \"include\" : \"<div class='detail'>\",\r\n        \"exclude\" : [\r\n            \"<div class='author-share'>\",\r\n            \"<div class='icon-pics'>\",\r\n            \"<div class='embed-control'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qdaily.com\",\r\n        \"url\"     : \"http://www.qdaily.com/articles/\",\r\n        \"title\"   : \"<h2 class='title'>\",\r\n        \"desc\"    : \"<p class='excerpt'>\",\r\n        \"include\" : \"<div class='article-detail-bd'>\",\r\n        \"exclude\" : [\r\n            \"<div class='author-share'>\",\r\n            \"<div class='embed-control'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"topic.readhub.cn\",\r\n        \"url\"     : \"https://readhub.cn/topic/*\",\r\n        \"title\"   : \"<h2 class='topicTitle___3DA7c'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.content___2CL42').find('.summary___3oqrM').parent().html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"newsflashes.36kr.com\",\r\n        \"url\"     : \"https://36kr.com/newsflashes/*\",\r\n        \"title\"   : \"<a class='item-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<pre class='pre-item-des'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"36kr.com\",\r\n        \"url\"     : \"http://36kr.com/p/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"<div class='summary'>\",\r\n        \"include\" : \"<div class='articleDetailContent'>\",\r\n        \"exclude\" : [\r\n            \"<div class='author-panel'>\",\r\n            \"<section class='article-footer-label'>\",\r\n            \"<section class='report-code-section'>\",\r\n            \"<section class='single-post-tags'>\",\r\n            \"<div class='share-nav'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"s.pingwest.com\",\r\n        \"url\"     : \"http*://www.pingwest.com/s/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('section.content')[1]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wire.pingwest.com\",\r\n        \"url\"     : \"http*://www.pingwest.com/wire/\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='sc-container'>\",\r\n        \"exclude\" : [\r\n            \"<p class='post-footer-wx'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"pingwest.com\",\r\n        \"url\"     : \"http*://www.pingwest.com/a/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class=\\\"article-style\\\">\",\r\n        \"exclude\" : [\r\n            \"<p class='post-footer-wx'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"jiemian.com\",\r\n        \"url\"     : \"http://www.jiemian.com/article/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-main'>\",\r\n        \"exclude\" : [\r\n            \"<div class='share-view'>\",\r\n            \"[[[$('.article-btn')]]]\",\r\n            \"<div class='article-footer'>\",\r\n            \"<p class='report-view'>\",\r\n            \"[[/src=\\\\S+(146241438767289600_a580xH)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/news/\",\r\n        \"title\"   : \"<h1 class='c-single-normal__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<div class='entry-header'>\",\r\n            \"<div class='entry-meta'>\",\r\n            \"<a class='entry-comment-number'>\",\r\n            \"<div class='article-header'>\",\r\n            \"<div class='article-jiong-banner'>\",\r\n            \"<div class='popup'>\",\r\n            \"<div class='article-sns-tool'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"minapp.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/minapp/\",\r\n        \"title\"   : \"<h1 class='c-single-normal__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"[['<span>本文由知晓程序授权转载</span>']]\",\r\n            \"[[/src=\\\\S+(new_zx-1)\\\\S+'/]]\",\r\n            \"<div class='entry-header'>\",\r\n            \"<div class='entry-meta'>\",\r\n            \"<a class='entry-comment-number'>\",\r\n            \"<div class='article-header'>\",\r\n            \"<div class='article-jiong-banner'>\",\r\n            \"<div class='popup'>\",\r\n            \"<div class='article-sns-tool'>\",\r\n            \"[[/src=\\\\S+(zxcx-qun-1)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"app.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/app/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<div class='c-appso-download'>\",\r\n            \"[['<strong>本文由让手机更好用</strong>']]\",\r\n            \"[[/src=\\\\S+(AppSo-qrcode-signature)\\\\S+'/]]\",\r\n            \"[['<strong>▽</strong>']]\",\r\n            \"<div class='entry-header'>\",\r\n            \"<div class='entry-meta'>\",\r\n            \"<a class='entry-comment-number'>\",\r\n            \"<div class='article-header'>\",\r\n            \"<div class='article-jiong-banner'>\",\r\n            \"<div class='popup'>\",\r\n            \"<div class='article-sns-tool'>\",\r\n            \"[[/src=\\\\S+(banner2)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dasheng.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/dasheng/\",\r\n        \"title\"   : \"<p class='c-single-dasheng__content'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"data.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/data/\",\r\n        \"title\"   : \"<p class='c-single-data__desc'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"weizhizao.ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/weizhizao/\",\r\n        \"title\"   : \"<h1 class='c-single-normal__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ifanr.com\",\r\n        \"url\"     : \"http://www.ifanr.com/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"[['<span>快速关注知晓程序</span>']]\",\r\n            \"[['<strong>本文由知晓程序原创出品</strong>']]\",\r\n            \"[['<strong>爱范儿旗下专注于小程序生态的公众号</strong>']]\",\r\n            \"<div class='entry-header'>\",\r\n            \"<div class='entry-meta'>\",\r\n            \"<a class='entry-comment-number'>\",\r\n            \"<div class='article-header'>\",\r\n            \"<div class='entry-content__tags'>\",\r\n            \"<div class='article-info'>\",\r\n            \"<div class='article-jiong-banner'>\",\r\n            \"<div class='popup'>\",\r\n            \"<div class='article-sns-tool'>\",\r\n            \"[[/src=\\\\S+(zxcx-1)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"minapp.com\",\r\n        \"url\"     : \"https://minapp.com/article/\",\r\n        \"title\"   : \"<h1 class='article-header-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='origin-content'>\",\r\n        \"exclude\" : [\r\n            \"[['<strong>本文由知晓程序</strong>']]\",\r\n            \"[[/src=\\\\S+(zxcx_0208)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"io.sspai.com\",\r\n        \"url\"     : \"https://ios.sspai.com/**/*/*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<span class='ss-app-card'>\",\r\n            \"<div class='ss-left'>\",\r\n            \"<div class='ss-right'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"sspai.com\",\r\n        \"url\"     : \"https://sspai.com/post/\",\r\n        \"title\"   : \"[[{$('title').text().replace( ' - 少数派', '' )}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$('.articleWidth-content').find('.content')]]]\",\r\n        \"exclude\" : [\r\n            \"<img id='s1' >\",\r\n            \"<hr>\",\r\n            \"[['<span>我们主张分享真实的产品体验</span>']]\",\r\n            \"[['<span>本文内容仅代表作者本人观点</span>']]\",\r\n            \"[[/src=\\\\S+(342459.png)\\\\S+'/]]\",\r\n            \"<sup>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.dgtle.com\",\r\n        \"url\"     : \"http*://www.dgtle.com/portal.php*\",\r\n        \"title\"   : \"<h1 class='cr_h1title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='view_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dgtle.com\",\r\n        \"url\"     : \"http*://www.dgtle.com/article-*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[{$('.view_content').html()||$('#view_content').html()||$('#articleContent').html()||$('.forum-viewthread-article-box').html()}]]\",\r\n        \"exclude\" : [\r\n            \"<div id='comments_top'>\",\r\n            \"[[/src=\\\\S+(xxxxxbbs)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bbs.dgtle.com\",\r\n        \"url\"     : \"http*://*.dgtle.com/thread-*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='forum-viewthread-article-box'>\",\r\n        \"exclude\" : [\r\n            \"<div id='comments_top'>\",\r\n            \"[[/src=\\\\S+(xxxxxbbs)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zhuanlan.zhihu.com\",\r\n        \"url\"     : \"https://zhuanlan.zhihu.com/p/\",\r\n        \"title\"   : \"<h1 class='Post-Title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[[$('.Post-RichText')]]]\",\r\n        \"exclude\" : [\r\n            \"[[{(function(){$('sr-rd-content figure noscript').each(function(){var noscript = $(this).text();if(noscript.match(/<img/)) {var img = $(this).parent().empty().append(noscript).find('img');var src = img.attr('src') || '';if(src.match(/.gif/)) img.attr('real_src',src);}});}())}]]\"\r\n        ],\r\n        \"css\"     : \".simpread-font {overflow: initial!important;}\"\r\n    },{\r\n        \"name\"    : \"dudu.zhihu.com\",\r\n        \"url\"     : \"http://dudu.zhihu.com/story/\",\r\n        \"title\"   : \"<h1 class='headline-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"daily.zhihu.com\",\r\n        \"url\"     : \"https://daily.zhihu.com/story/\",\r\n        \"title\"   : \"<h1 class='headline-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[[$('.content')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"question.zhihu.com\",\r\n        \"url\"     : \"https://www.zhihu.com/question/**\",\r\n        \"type\"    : \"multi\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.AuthorInfo-name')}]]\"},\r\n            {\"url\"  : \"[[{$('.AuthorInfo-avatar')}]]\"}\r\n        ],\r\n        \"title\"   : \"[[{$('main .QuestionHeader-main').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.RichContent-inner')}]]\",\r\n        \"exclude\" : [\r\n            \"[[{(function(){$('sr-rd-content figure noscript').each(function(){var noscript = $(this).text();if(noscript.match(/<img/)) {var img = $(this).parent().empty().append(noscript).find('img');var src = img.attr('src') || '';if(src.match(/.gif/)) img.attr('real_src',src);}});}())}]]\",\r\n            \"<i class='icon-external'>\",\r\n            \"<div class='RichText-MCNLinkCardContainer'>\"\r\n        ],\r\n        \"css\"     : \".simpread-font {overflow: initial!important;}\\n.MCNLinkCard-info { margin-left: 12px;}\"\r\n    },{\r\n        \"name\"    : \"geekpark.net\",\r\n        \"url\"     : \"http://www.geekpark.net/topics/\",\r\n        \"title\"   : \"<h1 class='topic-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<article class='article-body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.geekpark.net\",\r\n        \"url\"     : \"https://www.geekpark.net/news/*\",\r\n        \"title\"   : \"<h1 class='topic-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"jianshu.com\",\r\n        \"url\"     : \"http://www.jianshu.com/p/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"[[[$('sr-rd-content button[aria-label=\\\"复制代码\\\"]')]]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"waerfa.com\",\r\n        \"url\"     : \"http://www.waerfa.com/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='s-single-article'>\",\r\n        \"exclude\" : [\r\n            \"<div class='u-post-share-wrap'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"mp.weixin.qq.com\",\r\n        \"url\"     : \"http://mp.weixin.qq.com/*\",\r\n        \"title\"   : \"[[{$('.rich_media_title_ios').text()||document.getElementById('activity-name').lastChild.data}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='js_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.guokr.com\",\r\n        \"url\"     : \"http://www.guokr.com/article/\",\r\n        \"title\"   : \"<h1 id='articleTitle'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')}]]\",\r\n        \"include\" : \"<div id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article-extend'>\",\r\n            \"<p class='copyright'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"post.guokr.com\",\r\n        \"url\"     : \"http://www.guokr.com/post/\",\r\n        \"title\"   : \"<h1 id='articleTitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"question.guokr.com\",\r\n        \"url\"     : \"http://www.guokr.com/question/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.p_author_name')}]]\"},\r\n            {\"url\"  : \"[[{$('.p_author_face').find('img')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1 id='articleTitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.gbbcode-content')}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zuimeia.com\",\r\n        \"url\"     : \"http://zuimeia.com/app/\",\r\n        \"title\"   : \"<span class='sub-title'>\",\r\n        \"desc\"    : \"<div class='short-des'>\",\r\n        \"include\" : \"<div id='article_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"creatorsdaily.com\",\r\n        \"url\"     : \"[[/https://creatorsdaily.com/posts/\\\\w+/]]\",\r\n        \"title\"   : \"<span class='sub-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[`//*[@id='__next']/section[1]/main[1]/div[1]/div[1]/div[1]/div[1]/div[1]/div[1]`]]\",\r\n        \"exclude\" : [\r\n            \"[[`/html[1]/div[1]/sr-read[1]/sr-rd-content[1]/div[1]`]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"spaces.ac.cn\",\r\n        \"url\"     : \"https://spaces.ac.cn/archives/*\",\r\n        \"title\"   : \"<span class='sub-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='PostContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"huxiu.com\",\r\n        \"url\"     : \"https://www.huxiu.com/article/\",\r\n        \"title\"   : \"<h1 class='t-h1'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content-wrap'>\",\r\n        \"exclude\" : [\r\n            \"<div class='neirong-shouquan'>\",\r\n            \"<div class='neirong-shouquan-public'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"moment.douban.com\",\r\n        \"url\"     : \"https://moment.douban.com/post/*/\",\r\n        \"title\"   : \"<h1 id='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"movie.douban.com\",\r\n        \"url\"     : \"[[/https:\\\\/\\\\/movie.douban.com\\\\/subject\\\\/\\\\d+\\\\/?/]]\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='link-report'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"note.douban.com\",\r\n        \"url\"     : \"[[/https?:\\\\/\\\\/www.douban.com\\\\/note\\\\/\\\\d+\\\\/?/]]\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='link-report'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.douban.com\",\r\n        \"url\"     : \"https://m.douban.com/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"reading.book.douban.com\",\r\n        \"url\"     : \"https://book.douban.com/reading/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class=\\\"book-content\\\">\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"annotation.book.douban.com\",\r\n        \"url\"     : \"https://book.douban.com/annotation/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[`//*[@id='link-report']`]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content p {white-space: pre-wrap;}\"\r\n    },{\r\n        \"name\"    : \"review.book.douban.com\",\r\n        \"url\"     : \"https://book.douban.com/review/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='link-report'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"people.douban.com\",\r\n        \"url\"     : \"https://www.douban.com/people/*/status/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class=\\\"status-saying\\\">\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"group.douban.com\",\r\n        \"url\"     : \"http*://www.douban.com/group/topic/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.topic-doc h3 a,h4 a')}]]\"},\r\n            {\"url\"  : \"[[{$('.user-face img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.paginator .prev a').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.paginator .next a').attr('href')}]]\"}\r\n        ],\r\n        \"include\" : \"[[{$('.article #link-report,.reply-content')}]]\",\r\n        \"exclude\" : [\r\n            \"\"            \r\n        ]\r\n    },{\r\n        \"name\"    : \"douban.com\",\r\n        \"url\"     : \"https://www.douban.com/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='link-report'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qingmang.me\",\r\n        \"url\"     : \"http://qingmang.me/magazines/*/**/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<header>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"leiphone.com\",\r\n        \"url\"     : \"https://www.leiphone.com/news/*/*.html\",\r\n        \"title\"   : \"<h1 class='headTit'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='lph-article-comView'>\",\r\n        \"exclude\" : [\r\n            \"[['<span>公众号：雷锋网</span>']]\",\r\n            \"[['<p>未经授权禁止转载</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.tmtpost.com\",\r\n        \"url\"     : \"http://m.tmtpost.com/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"<p class='post-abstract'>\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<h1>\",\r\n            \"<div class='post-info'>\",\r\n            \"<p class='post-abstract'>\",\r\n            \"<div class='pro_icon'>\",\r\n            \"<div class='pro_intro tc'>\",\r\n            \"[['<strong>更多精彩内容</strong>']]\",\r\n            \"[['<strong>关注钛媒体微信号</strong>']]\",\r\n            \"[[/src=\\\\S+(wzny_ewm)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tmtpost.com\",\r\n        \"url\"     : \"http://www.tmtpost.com/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"<p class='post-abstract'>\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<h1>\",\r\n            \"<div class='post-info'>\",\r\n            \"<p class='post-abstract'>\",\r\n            \"<div class='pro_icon'>\",\r\n            \"<div class='pro_intro tc'>\",\r\n            \"[['<strong>更多精彩内容</strong>']]\",\r\n            \"[['<strong>关注钛媒体微信号</strong>']]\",\r\n            \"[[/src=\\\\S+(wzny_ewm)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"songshuhui.net\",\r\n        \"url\"     : \"http://songshuhui.net/archives/\",\r\n        \"title\"   : \"<span class='contenttitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='bshare-custom'>\",\r\n            \"<div class='my-related-posts-box'>\",\r\n            \"[['<strong>相关文章</strong>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"infzm.com\",\r\n        \"url\"     : \"http://www.infzm.com/content/\",\r\n        \"title\"   : \"<h1 class='articleHeadline'>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<section id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"nationalgeographic.com.cn\",\r\n        \"url\"     : \"http://www.nationalgeographic.com.cn/**/*/*.html\",\r\n        \"title\"   : \"<div class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='detailMain_box_img'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"appinn.com\",\r\n        \"url\"     : \"http*://www.appinn.com/*/\",\r\n        \"title\"   : \"[[{$('.entry-title').text()||$('.post-title').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.entry-content').html()||$('#content-area').html()}]]\",\r\n        \"exclude\" : [\r\n            \"<div class='open_social_box'>\",\r\n            \"<div class='os-share-box'>\",\r\n            \"<div class='sharing-options'>\",\r\n            \"<div class='wptouch-custom-showcase'>\",\r\n            \"[[/src=\\\\S+(ds-logo-1-2-64)\\\\S+'/]]\",\r\n            \"[[/src=\\\\S+(down)\\\\S+'/]]\",\r\n            \"<button class='simplefavorite-button'>\",\r\n            \"<noscript>\",\r\n            \"<button>\",\r\n            \"<div class='wpulike'>\",\r\n            \"<div class='bottomad'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"free.apprcn.com\",\r\n        \"url\"     : \"http*://free.apprcn.com/*\",\r\n        \"title\"   : \"<h2 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='wumii-hook'>\",\r\n            \"<div id='wumiiDisplayDiv'>\",\r\n            \"<div id='wp-share-list-container'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"apprcn.com\",\r\n        \"url\"     : \"http://www.apprcn.com/*.html\",\r\n        \"title\"   : \"<h2 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='wumii-hook'>\",\r\n            \"<div id='wumiiDisplayDiv'>\",\r\n            \"<div id='wp-share-list-container'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"playpcesor.com\",\r\n        \"url\"     : \"http*://www.playpcesor.com/**/*.html\",\r\n        \"title\"   : \"<h3 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"steachs.com\",\r\n        \"url\"     : \"https://steachs.com/archives/\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-contents'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"azofreeware.com\",\r\n        \"url\"     : \"http*://www.azofreeware.com/**/*.html\",\r\n        \"title\"   : \"<h2 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.weibo.cn\",\r\n        \"url\"     : \"http*://m.weibo.cn/**/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='weibo-text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"weibo.com\",\r\n        \"url\"     : \"http*://weibo.com/ttarticle/p/*\",\r\n        \"title\"   : \"[[{$('.main_editor ').find('.title').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='WB_editor_iframe'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"leikeji.com\",\r\n        \"url\"     : \"http://www.leikeji.com/article/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='signature-qr'>\",\r\n            \"<ul class='tag-list'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"xueqiu.com\",\r\n        \"url\"     : \"http*://xueqiu.com/*/*\",\r\n        \"title\"   : \"<h1 class='article__bd__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article__bd__detail'>\",\r\n        \"exclude\" : [\r\n            \"<script class='single-description'>\",\r\n            \"<a class='ke_img_link'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chuansong.me\",\r\n        \"url\"     : \"http://chuansong.me/n/\",\r\n        \"title\"   : \"<h2 id='activity-name'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='js_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"stream.107cine.cm\",\r\n        \"url\"     : \"http://107cine.com/stream/*\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='flow_content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='postfix'>\",\r\n            \"[['<p>影视工业网不会对原创文章作任何编辑</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zhuanti.107cine.cm\",\r\n        \"url\"     : \"http://107cine.com/zhuanti/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='zhuanti'>\",\r\n        \"exclude\" : [\r\n            \"<div class='z-top'>\",\r\n            \"<div class='datu'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"jiemi.baike.com\",\r\n        \"url\"     : \"http://jiemi.baike.com/pa/detail?id=*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='jiemi-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wikipedia.org\",\r\n        \"url\"     : \"https://*.wikipedia.org/wiki/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='mw-parser-output'>\",\r\n        \"exclude\" : [\r\n            \"<div id='toc'>\",\r\n            \"<table class='infobox'>\",\r\n            \"<span class='mw-editsection'>\",\r\n            \"<table class='navbox'>\",\r\n            \"<div class='thumb'>\",\r\n            \"<div class='dablink'>\",\r\n            \"<table class='metadata'>\",\r\n            \"<math>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.pmcaff.com\",\r\n        \"url\"     : \"http*://www.pmcaff.com/article/index/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='editorContent'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(FlHy2hPIJYsjjeQRRKHhk0yfoLv)\\\\S+'/]]\",\r\n            \"[['<b>本文由PMCAFF专栏作者</b>']]\",\r\n            \"[['<strong>本文由</strong>']]\",\r\n            \"[['<strong>版权归原作者所有</strong>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"discuss.pmcaff.com\",\r\n        \"url\"     : \"http*://www.pmcaff.com/discuss/answer/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"<div class='discussion-content'>\",\r\n        \"include\" : \"[[{$('.panel-body').find('.content').html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"answer.pmcaff.com\",\r\n        \"url\"     : \"http*://www.pmcaff.com/discuss/index/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='editorContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dxy.cn\",\r\n        \"url\"     : \"http://*.dxy.cn/article/*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='x_detail_btm'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"faq.dxy.com\",\r\n        \"url\"     : \"https://dxy.com/faq/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='editor-style'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"column.dxy.com\",\r\n        \"url\"     : \"https://dxy.com/column/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='editor-body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"creative.adquan.com\",\r\n        \"url\"     : \"http://creative.adquan.com/show/*\",\r\n        \"title\"   : \"<h2 class='text_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='con_Text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"post.adquan.com\",\r\n        \"url\"     : \"http://www.adquan.com/post-*.html\",\r\n        \"title\"   : \"<h2 class='text_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='con_Text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"gonglve.mafengwo.cn\",\r\n        \"url\"     : \"http*://www.mafengwo.cn/gonglve/ziyouxing/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='_j_content'>\",\r\n        \"exclude\" : [\r\n            \"<section class='travelnote'>\",\r\n            \"<div class='product-box'>\",\r\n            \"<div class='pro_list'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"i.mafengwo.cn\",\r\n        \"url\"     : \"http*://www.mafengwo.cn/i/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='vc_article'>\",\r\n        \"exclude\" : [\r\n            \"<a class='_j_anchor'>\",\r\n            \"<div id='_j_paragraph_1'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bohaishibei.com\",\r\n        \"url\"     : \"https://bohaishibei.com/post/*/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"read.bilibili.com\",\r\n        \"url\"     : \"https://www.bilibili.com/read/cv*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-holder'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"note.space.bilibili.com\",\r\n        \"url\"     : \"http*://space.bilibili.com/v/note-list\",\r\n        \"title\"   : \"[[{$('.note-single-item--selected .note-item__detail--title').text()||$('title').text()}]]\",\r\n        \"desc\"    : \"[[{$('.note-single-item--selected .note-item__detail--summary').text()}]]\",\r\n        \"include\" : \"[[[(function(){var note=$('.ql-editor');if(note.length){if(note.text().length<100)note.append('<p>笔记内容过短，未避免阅读模式失败，自动填充内容。</p><br><p>'+''.padEnd(80,'#')+'</p>');}else{note=$('<p>未能获取到笔记内容，请确认是否存在笔记内容。</p><br><p>'+''.padEnd(80,'#')+'</p>');$('head').append(note);}return note;}())]]]\",\r\n        \"exclude\" : [\r\n            \"[[{(function(){var video_url=$('.note-single-item--selected .note-item__detail--info a').attr('href');var title=$('sr-rd-title').text().trim();var div=$(`<div><p>视频：<a href='${video_url}'>${title}</a></p></div>`);$('sr-rd-content').prepend(div);$('sr-rd-content div[data-seconds]').each(function(){var div=$(this);var time=div.find('div span').text();var p=div.attr('data-index');var t=div.attr('data-seconds');var url=video_url+`&p=${p}&t=${t}`;div.html(`<p><a href='${url}'>视频时间点：${time}</a></p>`);});$('sr-rd-content').children('div').contents().each(function(){if(this.nodeType===3)this.nodeValue=''});}())}]]\",\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"yixieshi.com\",\r\n        \"url\"     : \"http*://www.yixieshi.com/*.html\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"iplaysoft.com\",\r\n        \"url\"     : \"https://www.iplaysoft.com/*.html\",\r\n        \"title\"   : \"<h1 id='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.jobbole.com\",\r\n        \"url\"     : \"http://blog.jobbole.com/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='copyright-area'>\",\r\n            \"<blockquote class='rewards'>\",\r\n            \"<div class='post-adds'>\",\r\n            \"<div id='author-bio'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"android.jobbole.com\",\r\n        \"url\"     : \"http://android.jobbole.com/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='copyright-area'>\",\r\n            \"<blockquote class='rewards'>\",\r\n            \"<div class='post-adds'>\",\r\n            \"<div id='author-bio'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ios.jobbole.com\",\r\n        \"url\"     : \"http://ios.jobbole.com/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='copyright-area'>\",\r\n            \"<blockquote class='rewards'>\",\r\n            \"<div class='post-adds'>\",\r\n            \"<div id='author-bio'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"python.jobbole.com\",\r\n        \"url\"     : \"http://python.jobbole.com/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='copyright-area'>\",\r\n            \"<blockquote class='rewards'>\",\r\n            \"<div class='post-adds'>\",\r\n            \"<div id='author-bio'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"top.jobbole.com\",\r\n        \"url\"     : \"http://top.jobbole.com/*/\",\r\n        \"title\"   : \"<h1 class='p-tit-single'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='p-entry'>\",\r\n        \"exclude\" : [\r\n            \"<div class='textwidget'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cnodejs.org\",\r\n        \"url\"     : \"https://cnodejs.org/topic/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='topic_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"w3cplus.com\",\r\n        \"url\"     : \"https://www.w3cplus.com/**/*.html\",\r\n        \"title\"   : \"<h1 id='page-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='body-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"w3cways.com\",\r\n        \"url\"     : \"https://www.w3cways.com/*.html\",\r\n        \"title\"   : \"[[{$('.container').find('h2 span').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<p class='post-copyright'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"div.io\",\r\n        \"url\"     : \"http://div.io/topic/\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='topic-firstfloor-detail'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.aotu.io\",\r\n        \"url\"     : \"https://news.aotu.io/a/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news-body'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>未经许可，禁止转载。</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"aotu.io\",\r\n        \"url\"     : \"https://aotu.io/notes/**/*/*/\",\r\n        \"title\"   : \"<h1 class='post-tit'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='post-revision'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ruby-china.org\",\r\n        \"url\"     : \"https://ruby-china.org/topics/\",\r\n        \"title\"   : \"<h1 class='media-heading'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.panel-body')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"infoq.cn\",\r\n        \"url\"     : \"http*://*.infoq.cn/**/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[(function(){var wrap = $('.article-preview,.article-typo');if(!wrap.length) {wrap = $('<p>异步加载型页面，本次未能获取到内容元素。<br>请将 infoq.cn 加入排除列表后，刷新页面并手动进入阅读模式尝试</p><br><p>'+''.padEnd(100,'#')+'</p>');$('head').append(wrap);}return wrap;}())]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"segmentfault.com\",\r\n        \"url\"     : \"https://segmentfault.com/a/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article__content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"post.juejin.im\",\r\n        \"url\"     : \"http*://juejin.im/post/*\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"entry.juejin.im\",\r\n        \"url\"     : \"http*://juejin.im/entry/*\",\r\n        \"title\"   : \"[[{$('.entry-public-view h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qianduan.net\",\r\n        \"url\"     : \"https://www.qianduan.net/*/\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"importnew.com\",\r\n        \"url\"     : \"http://www.importnew.com/*.html\",\r\n        \"title\"   : \"<div class='entry-header'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"solidot.org\",\r\n        \"url\"     : \"https://www.solidot.org/story?sid=*\",\r\n        \"title\"   : \"<div class='bg_htit'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='p_mainnew'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ifeve.com\",\r\n        \"url\"     : \"http://ifeve.com/*/\",\r\n        \"title\"   : \"<h3 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post_content'>\",\r\n        \"exclude\" : [\r\n            \"<div id='jiathis_style_32x32'>\",\r\n            \"<div class='abh_box'>\",\r\n            \"<span class='wpfp-span'>\",\r\n            \"<div class='yarpp-related'>\",\r\n            \"[['<p>转载请注明</p>']]\",\r\n            \"[[/src=\\\\S+(ifeve500X150)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"code.oschina.net\",\r\n        \"url\"     : \"https://www.oschina.net/code/snippet_*_*\",\r\n        \"title\"   : \"[[{$('.QTitle').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='Body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='Title'>\",\r\n            \"<div id='toolbar_wrapper'>\",\r\n            \"[[[$('.code_pieces').find('h2')]]]\",\r\n            \"<div class='toolbar'>\",\r\n            \"<div class='QuestionRelations'>\",\r\n            \"<div id='related_codes'>\",\r\n            \"<div class='code_comments'>\",\r\n            \"<div class='CommentForm'>\",\r\n            \"[['<p>开源中国-程序员在线工具</p>']]\",\r\n            \"<i class='jump_to_code'>\",\r\n            \"<div class='code_report'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"translate.oschina.net\",\r\n        \"url\"     : \"https://www.oschina.net/translate/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"project.oschina.net\",\r\n        \"url\"     : \"https://www.oschina.net/p/\",\r\n        \"title\"   : \"[[{$('header.box').find('a').attr('title')}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='editor-viewer'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"my.oschina.net\",\r\n        \"url\"     : \"https://my.oschina.net/**/*/blog/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(hot3)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"oschina.net\",\r\n        \"url\"     : \"https://www.oschina.net/news/**/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(hot3)\\\\S+'/]]\",\r\n            \"<div class='thumb'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"question.oschina.net\",\r\n        \"url\"     : \"https://www.oschina.net/question/*_*\",\r\n        \"title\"   : \"<span class='question-title-link'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<header class='detail'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.iteye.com\",\r\n        \"url\"     : \"http*://www.iteye.com/news/*\",\r\n        \"title\"   : \"<h3 id='h3-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='news_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.iteye.com\",\r\n        \"url\"     : \"http*://*.iteye.com/blog/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='iteye-blog-content-contain'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"geek.csdn.net\",\r\n        \"url\"     : \"http*://geek.csdn.net/news/detail/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_description'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>欢迎关注CSDN大数据公众号</p>']]\",\r\n            \"[['<strong>关注以下公众号</strong>']]\",\r\n            \"[[/src=\\\\S+(20170119091646909)\\\\S+'/]]\",\r\n            \"[[/src=\\\\S+(20170119152206448)\\\\S+'/]]\",\r\n            \"[[/src=\\\\S+(20170120110155787)\\\\S+'/]]\",\r\n            \"[[/src=\\\\S+(8tPUexU)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.csdn.net\",\r\n        \"url\"     : \"http*://blog.csdn.net/*/article/details/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[[(function(){var e=$('#content_views');if(!e.length)e=$('#article_content');return e;}())]]]\",\r\n        \"exclude\" : [\r\n            \"<div class='meau-gotop-box'>\",\r\n            \"<div class='more-toolbox'>\",\r\n            \"<div class='person-messagebox'>\"\r\n        ],\r\n        \"css\"     : \".markdown_views pre code{background-color:transparent!important;}\"\r\n    },{\r\n        \"name\"    : \"lib.csdn.net\",\r\n        \"url\"     : \"http*://lib.csdn.net/article/*/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='divtexts'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"hacpai.com\",\r\n        \"url\"     : \"https://hacpai.com/article/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"freebuf.com\",\r\n        \"url\"     : \"http*://www.freebuf.com/**/*/*.html\",\r\n        \"title\"   : \"[[{$('.title h2').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='contenttxt'>\",\r\n        \"exclude\" : [\r\n            \"[['<b>注明来自</b>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ruanyifeng.com\",\r\n        \"url\"     : \"http://www.ruanyifeng.com/blog/**/*/*.html\",\r\n        \"title\"   : \"<h1 id='page-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='main-content'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>（完）</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ruanyf.yuque.com\",\r\n        \"url\"     : \"[[/https://www.yuque.com/ruanyf/weekly/issue-\\\\d+/]]\",\r\n        \"title\"   : \"<h1 id='page-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[`//*[@id='content']/div[2]/div[1]/div[1]`]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\": \"span svg { display: none; }\"\r\n    },{\r\n        \"name\"    : \"v2ex.com\",\r\n        \"url\"     : \"https://www.v2ex.com/t/\",\r\n        \"title\"   : \"[[{$('.header h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='topic_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.cnblogs.com\",\r\n        \"url\"     : \"https://news.cnblogs.com/n/\",\r\n        \"title\"   : \"<div id='news_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='news_body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cnblogs.com\",\r\n        \"url\"     : \"https://www.cnblogs.com/**/*/*.html\",\r\n        \"title\"   : \"<a id='cb_post_title_url'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='cnblogs_post_body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tuicool.com\",\r\n        \"url\"     : \"http://www.tuicool.com/articles/\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article_body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"w3ctech.com\",\r\n        \"url\"     : \"https://www.w3ctech.com/topic/\",\r\n        \"title\"   : \"[[{$('.topic_info ').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='callout'>\",\r\n        \"exclude\" : [\r\n            \"[['<h2>关注我们</h2>']]\",\r\n            \"[['<p>本文原文</p>']]\",\r\n            \"[['<p>我们专注于H5技术生态的改善</p>']]\",\r\n            \"[['<p>欢迎有兴趣的同行一起来玩儿</p>']]\",\r\n            \"[[/src=\\\\S+(yufe2015)\\\\S+'/]]\",\r\n            \"<div id='wx-qrcode'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"html-js.com\",\r\n        \"url\"     : \"http://www.html-js.com/article/\",\r\n        \"title\"   : \"[[{$('.wrapper ').find('div[rel=bookmark]').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='entry-meta'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"open-open.com\",\r\n        \"url\"     : \"http://www.open-open.com/lib/*/*.html\",\r\n        \"title\"   : \"<h1 id='articleTitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"colobu.com\",\r\n        \"url\"     : \"http://colobu.com/**/*/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-entry'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"liqi.io\",\r\n        \"url\"     : \"http://liqi.io/*/\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"[['<span>你也可以分享自己的利器</span>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"daqianduan.com\",\r\n        \"url\"     : \"http://www.daqianduan.com/*.html\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"caibaojian.com\",\r\n        \"url\"     : \"http://caibaojian.com/*.html\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article_index'>\",\r\n            \"[['<span>原创翻译，未经许可，禁止转载</span>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"hao.caibaojian.com\",\r\n        \"url\"     : \"http://hao.caibaojian.com/*.html\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article_index'>\",\r\n            \"[['<span>原创翻译，未经许可，禁止转载</span>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"xfenglin.com\",\r\n        \"url\"     : \"http://xfenglin.com/a/\",\r\n        \"title\"   : \"[[{$('.t-head').find('h2').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article__content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ibm.com\",\r\n        \"url\"     : \"https://www.ibm.com/developerworks/cn/**/*/*.html\",\r\n        \"title\"   : \"<h1 id='ibm-pagetitle-h1'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='ibm-col-6-4'>\",\r\n        \"exclude\" : [\r\n            \"<h1 id='ibm-pagetitle-h1'>\",\r\n            \"<div class='dw-article-topbar'>\",\r\n            \"<div class='ibm-alternate-rule'>\",\r\n            \"<h4 id='artrelatedtopics'>\",\r\n            \"<ul class='ibm-plain-list'>\",\r\n            \"<div id='dw-article-cmts-top'>\",\r\n            \"<div class='dw-article-cmts-container'>\",\r\n            \"<p class='ibm-ind-link'>\",\r\n            \"<div class='dw-article-sidebar'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"guanggoo.com\",\r\n        \"url\"     : \"http://www.guanggoo.com/t/\",\r\n        \"title\"   : \"<h3 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.ui-content')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ctolib.com\",\r\n        \"url\"     : \"http://www.ctolib.com/*.html\",\r\n        \"title\"   : \"<h3 class='m-t-xs'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"<a class='anchor'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zuojj.com\",\r\n        \"url\"     : \"http://www.zuojj.com/archives/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='copyright_con'>\",\r\n            \"<div class='article-social'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wkee.net\",\r\n        \"url\"     : \"http://wkee.net/post/\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content0'>\",\r\n        \"exclude\" : [\r\n            \"<p class='author'>\",\r\n            \"<div class='sharebar'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"xituqu.com\",\r\n        \"url\"     : \"https://xituqu.com/*.html\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"[['<h2>关注稀土区微信公众号，获取一手资源</h2>']]\",\r\n            \"[[/src=\\\\S+(006tNc79gw1f6gzqizciyj3076076aai)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"linuxsight.com\",\r\n        \"url\"     : \"http://www.linuxsight.com/blog/\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='single-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='ad-site'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"coyee.com\",\r\n        \"url\"     : \"https://coyee.com/article/\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='sections'>\",\r\n        \"exclude\" : [\r\n            \"<div class='section_side_bar'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"36dsj.com\",\r\n        \"url\"     : \"http://www.36dsj.com/archives/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>转载请注明来自</p>']]\",\r\n            \"[['<p>End.</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cocoachina.com\",\r\n        \"url\"     : \"http://www.cocoachina.com/*/*/*.html\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='detailbody'>\",\r\n        \"exclude\" : [\r\n            \"<td class='gutter'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dataunion.org\",\r\n        \"url\"     : \"http://dataunion.org/*.html\",\r\n        \"title\"   : \"<h1 class='mscctitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content-text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"linux.cn\",\r\n        \"url\"     : \"https://linux.cn/article-*.html\",\r\n        \"title\"   : \"<h1 id='article_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article_content'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>本文由</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"techug.com\",\r\n        \"url\"     : \"http://www.techug.com/post/\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content0'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.ituring.com.cn\",\r\n        \"url\"     : \"http://www.ituring.com.cn/article/\",\r\n        \"title\"   : \"[[{$('.article-title').find('h2').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article'>\",\r\n        \"exclude\" : [\r\n            \"[['<h2>加入图灵访谈微信</h2>']]\",\r\n            \"[[/src=\\\\S+(01YrJhnWRMld)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"book.ituring.com.cn\",\r\n        \"url\"     : \"http://www.ituring.com.cn/book/tupubarticle/\",\r\n        \"title\"   : \"[[{$('.online-book-title').find('h3').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-detail'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"issue.github.com\",\r\n        \"url\"     : \"https://github.com/*/*/issues/*\",\r\n        \"title\"   : \"[[{$('h1.gh-header-title .js-issue-title').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('#discussion_bucket table td.js-comment-body').html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"markdown.github.com\",\r\n        \"url\"     : \"https://github.com/**/*/*.md\",\r\n        \"title\"   : \"[[{$('title').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='markdown-body'>\",\r\n        \"exclude\" : [\r\n            \"<a class='anchor'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"msdn.microsoft.com\",\r\n        \"url\"     : \"https://msdn.microsoft.com/**/*/*.aspx\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"<div id='navigationButtons'>\"\r\n        ]\r\n    },{\r\n      \"name\"    : \"docs.microsoft.com\",\r\n      \"url\"     : \"https://docs.microsoft.com/*/*/*\",\r\n      \"title\"   : \"<h1>\",\r\n      \"desc\"    : \"\",\r\n      \"include\" : \"<div id='main'>\",\r\n      \"exclude\" : [\r\n          \"\"\r\n      ]\r\n    },{\r\n        \"name\"    : \"developer.mozilla.org\",\r\n        \"url\"     : \"https://developer.mozilla.org/*/docs/*/**/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article id='wikiArticle'>\",\r\n        \"exclude\" : [\r\n            \"<aside id='helpful-survey'>\",\r\n            \"<a class='section-edit'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"apps.mozilla.org\",\r\n        \"url\"     : \"https://developer.mozilla.org/*/Apps/*/**/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article id='wikiArticle'>\",\r\n        \"exclude\" : [\r\n            \"<aside id='helpful-survey'>\",\r\n            \"<a class='section-edit'>\"\r\n        ]\r\n    },{\r\n      \"name\"    : \"article.52im.net\",\r\n      \"url\"     : \"http://www.52im.net/article-*.html\",\r\n      \"title\"   : \"<h1 class='ph'>\",\r\n      \"desc\"    : \"<div class='js-ptl-atcl-summery'>\",\r\n      \"include\" : \"<td id='article_content'>\",\r\n      \"exclude\" : [\r\n          \"\"\r\n      ]\r\n    },{\r\n      \"name\"    : \"thread.52im.net\",\r\n      \"url\"     : \"http://www.52im.net/thread-*.html\",\r\n      \"title\"   : \"<span id='thread_subject'>\",\r\n      \"desc\"    : \"\",\r\n      \"include\" : \"[[{$('td.t_f:first').html()}]]\",\r\n      \"exclude\" : [\r\n          \"\"\r\n      ]\r\n    },{\r\n        \"name\"    : \"edaoe.com\",\r\n        \"url\"     : \"http*://www.edaoe.com/*.html\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article-social'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"gallery.edaoe.com\",\r\n        \"url\"     : \"http://www.edaoe.com/gallery/*.html\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article-social'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chinabyte.cm\",\r\n        \"url\"     : \"http://*.chinabyte.com/**/*/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='art_txt'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wiki.jikexueyuan.com\",\r\n        \"url\"     : \"http://wiki.jikexueyuan.com/project/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('#previous_link').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('#next_link').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='markdown-body'>\",\r\n        \"exclude\" : [\r\n            \"<h1>\",\r\n            \"<a rel='nofollow'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ourcoders.com\",\r\n        \"url\"     : \"http://ourcoders.com/thread/show/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.threadblock')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"uisdc.com\",\r\n        \"url\"     : \"http://www.uisdc.com/*\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='similarity'>\",\r\n            \"<div class='post-copyright'>\",\r\n            \"[[/src=\\\\S+(youshege725)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"kancloud.cn\",\r\n        \"url\"     : \"http*://www.kancloud.cn/*/*/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.article-navigation').find('.prev a').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.article-navigation').find('.next a').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"[[{$('.article-head').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.article-body')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(5a5e9e309af5e)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"index.gitbook.com\",\r\n        \"url\"     : \"http*://*.gitbooks.io/*/content/\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.navigation-prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.navigation-next').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='page-inner'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"gitbook.com\",\r\n        \"url\"     : \"http*://*.gitbooks.io/*/content/**/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.navigation-prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.navigation-next').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='page-inner'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"index.developers.weixin.qq.com\",\r\n        \"url\"     : \"http*://developers.weixin.qq.com/miniprogram/dev/\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.navigation-prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.navigation-next').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='markdown-section'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"developers.weixin.qq.com\",\r\n        \"url\"     : \"http*://developers.weixin.qq.com/miniprogram/dev/**/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.navigation-prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.navigation-next').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='markdown-section'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"williamlong.info\",\r\n        \"url\"     : \"http*://www.williamlong.info/archives/*.html\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artibody'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.wanqu.co\",\r\n        \"url\"     : \"https://wanqu.co/b/*/*.html*\",\r\n        \"title\"   : \"[[{$('.blog-post-body h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='blog-post-body'>\",\r\n        \"exclude\" : [\r\n            \"<h1>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"hacknews.codedata.cn\",\r\n        \"url\"     : \"http://www.codedata.cn/hacknews/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"topics.codedata.cn\",\r\n        \"url\"     : \"http://www.codedata.cn/topics/*\",\r\n        \"title\"   : \"<h1 class='media-heading'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"teakki.com\",\r\n        \"url\"     : \"https://teakki.com/p/*\",\r\n        \"title\"   : \"<h1 class='content-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cppdaxue.com\",\r\n        \"url\"     : \"http://cppdaxue.com/*/*.html\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article-content>\",\r\n        \"exclude\" : [\r\n            \"<div class='article-social'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bobao.360.cn\",\r\n        \"url\"     : \"http://bobao.360.cn/*/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article_box'>\",\r\n        \"exclude\" : [\r\n            \"<h2>\",\r\n            \"<div class='article-msg'>\",\r\n            \"<hr>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"thebigdata.cn\",\r\n        \"url\"     : \"http://www.thebigdata.cn/*/*.html\",\r\n        \"title\"   : \"<h1 class='aTitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.codingnow.com\",\r\n        \"url\"     : \"https://blog.codingnow.com/*/*/*.html\",\r\n        \"title\"   : \"<h3 class='entry-header'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"coolshell.cn\",\r\n        \"url\"     : \"https://coolshell.cn/articles/*.html\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='jiathis_style'>\",\r\n            \"<div id='wp_rp_first'>\",\r\n            \"<div class='post-ratings'>\",\r\n            \"<footer class='entry-footer'>\",\r\n            \"[[/src=\\\\S+(qrcode_for_gh_dd9d8c843f20_860)\\\\S+'/]]\",\r\n            \"[[/src=\\\\S+(100offer_600_200)\\\\S+'/]]\",\r\n            \"[['<p>微信公众账号可以在手机端搜索文章</p>']]\",\r\n            \"[['<p>全文完</p>']]\",\r\n            \"[['<strong>转载本站文章请注明作者</strong>']]\",\r\n            \"[['<div>——===</div>']]\",\r\n            \"[['<b>酷壳404页面</b>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"kotlinguides.github.io\",\r\n        \"url\"     : \"https://*.github.io/kotlin-guides/*.html\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"readhub.me\",\r\n        \"url\"     : \"http*://readhub.me/topic/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='summary___1i4y3'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"questions.stackoverflow.com\",\r\n        \"url\"     : \"https://stackoverflow.com/questions/**/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.user-details').find('a')}]]\"},\r\n            {\"url\"  : \"[[{$('.gravatar-wrapper-32').find('img')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.js-post-body')}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\": \"code { background-color: transparent; }\"\r\n    },{\r\n        \"name\"    : \"threadreaderapp.com\",\r\n        \"url\"     : \"https://threadreaderapp.com/thread/*.html\",\r\n        \"title\"   : \"[[{(function(){return $($('.content-tweet')[0]).text().replace('\\\\n','')}())}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{(function(){let html='';$('.container.narrow').find('.content-tweet').map((idx,item)=>{if(idx>0){html+=$(item).html()}});return html})()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\": \"\"\r\n    },{\r\n        \"name\"    : \"voidcn.com\",\r\n        \"url\"     : \"http://www.voidcn.com/blog/**/*/*.html\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='wumii-hook'>\",\r\n            \"[[/src=\\\\S+(pixel)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.thepaper.cn\",\r\n        \"url\"     : \"http://m.thepaper.cn/newsDetail_*\",\r\n        \"title\"   : \"<h1 class='t_newsinfo'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_part_limit'>\",\r\n        \"exclude\" : [\r\n            \"<div class='news_bl'>\",\r\n            \"<div id='clickForMore'>\",\r\n            \"<div class='relations_div'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"thepaper.cn\",\r\n        \"url\"     : \"http://www.thepaper.cn/newsDetail_*\",\r\n        \"title\"   : \"<h1 class='news_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_txt'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"vice.cn\",\r\n        \"url\"     : \"http://www.vice.cn/read/\",\r\n        \"title\"   : \"<h3 class='article-title'>\",\r\n        \"desc\"    : \"<div class='article-summary'>\",\r\n        \"include\" : \"<div class='article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='contributor-list'>\",\r\n            \"<span class='site-signature-icon'></span>\",\r\n            \"<div class='social-share social-share-btm'>\",\r\n            \"<div class='article-copyright'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"jandan.net\",\r\n        \"url\"     : \"http://jandan.net/**/*/*.html\",\r\n        \"title\"   : \"[[{$('.post h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.post')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"<script>\",\r\n            \"<h3>\",\r\n            \"<div class='time_s'>\",\r\n            \"<h1>\",\r\n            \"<div class='shang'>\",\r\n            \"<div class='star-rating>\",\r\n            \"<a class='jandan-zan'>\",\r\n            \"<span class='comment-big'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.smzdm.com\",\r\n        \"url\"     : \"http*://news.smzdm.com/p/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_content'>\",\r\n        \"exclude\" : [\r\n            \"<h1 class='item-name'>\",\r\n            \"<span class='embed-card'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"post.smzdm.com\",\r\n        \"url\"     : \"http*://post.smzdm.com/p/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[`//*[@id='articleId']`]]\",\r\n        \"exclude\" : [\r\n            \"<h1 class='item-name'>\",\r\n            \"<div class='coupon-card-wraper'>\",\r\n            \"<div class='brand-card-box'>\"\r\n        ],\r\n        \"css\"     : \".embed-card-logo { margin-right: 14px }\"\r\n    },{\r\n        \"name\"    : \"news.mydrivers.com\",\r\n        \"url\"     : \"http://news.mydrivers.com/*/*/*.htm\",\r\n        \"title\"   : \"<div id='thread_subject'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_info'>\",\r\n        \"exclude\" : [\r\n            \"<div class='btnPrev'>\",\r\n            \"<div class='btnNext'>\",\r\n            \"<div class='postpage'>\",\r\n            \"<div class='adggg1'>\",\r\n            \"<p class='news_bq'>\",\r\n            \"<p class='jcuo1'>\",\r\n            \"<div id='weixin'>\",\r\n            \"<table>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ftchinese.com\",\r\n        \"url\"     : \"http://www.ftchinese.com/story/\",\r\n        \"title\"   : \"<h1 class='story-headline'>\",\r\n        \"desc\"    : \"<div class='story-lead'>\",\r\n        \"include\" : \"<div class='story-body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"young.ifeng.com\",\r\n        \"url\"     : \"http://young.ifeng.com/a/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='ya_main_con'>\",\r\n        \"exclude\" : [\r\n            \"<p class='yamc_zrbj'>\",\r\n            \"<p class='yamc_sming'>\",\r\n            \"<span class='ifengLogo'>\",\r\n            \"[['<p>原标题</p>']]\",\r\n            \"[['<strong>获取更多有趣又有料的内容</strong>']]\",\r\n            \"[[/src=\\\\S+(a76d59f9f93e84b_size52_w700_h236)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.ifeng.com\",\r\n        \"url\"     : \"http://*.ifeng.com/a/*/*.shtml\",\r\n        \"title\"   : \"<h1 id='artical_topic'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artical_real'>\",\r\n        \"exclude\" : [\r\n            \"<span class='ifengLogo'>\",\r\n            \"[['<p>原标题</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.mtime.com\",\r\n        \"url\"     : \"http://news.mtime.com/**/*/*.html\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"<div class='newsnote'>\",\r\n        \"include\" : \"<div id='newsContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"feng.com\",\r\n        \"url\"     : \"http://*.feng.com/**/*/*.shtml\",\r\n        \"title\"   : \"[[{$('.h2').text()==''?$('h1').text():$('.h2').text()}]]\",\r\n        \"desc\"    : \"<p class='content_title'>\",\r\n        \"include\" : \"<div id='article_content'>\",\r\n        \"exclude\" : [\r\n            \"<p class='content_title'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"caixin.com\",\r\n        \"url\"     : \"http://*.caixin.com/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"<div id='subhead'>\",\r\n        \"include\" : \"<div id='Main_Content_Val'>\",\r\n        \"exclude\" : [\r\n             \"[[/src=\\\\S+(favicon)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.mittrchina.com\",\r\n        \"url\"     : \"http*://www.mittrchina.com/news/*\",\r\n        \"title\"   : \"<div class='title'>\",\r\n        \"desc\"    : \"<div class='description'>\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.ithome.com\",\r\n        \"url\"     : \"http*://wap.ithome.com/html/*.htm\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='paragraph'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"livesino.net\",\r\n        \"url\"     : \"https://livesino.net/archives/*.live\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ithome.com\",\r\n        \"url\"     : \"https://www.ithome.com/html/**/*/*.htm\",\r\n        \"title\"   : \"[[{$('.post_title').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='paragraph'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.sohu.com\",\r\n        \"url\"     : \"http*://news.sohu.com/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='contentText'>\",\r\n        \"exclude\" : [\r\n            \"<div class='conserve-photo'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"sohu.com\",\r\n        \"url\"     : \"http*://www.sohu.com/a/*_*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article class='article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.sina.com.cn\",\r\n        \"url\"     : \"http*://news.sina.com.cn/**/*/*.shtml\",\r\n        \"title\"   : \"<h1 class='main-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tech.sina.com.cn\",\r\n        \"url\"     : \"http*://tech.sina.com.cn/**/*/*.shtml\",\r\n        \"title\"   : \"<h1 class='main-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artibody'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"finance.sina.com.cn\",\r\n        \"url\"     : \"http*://finance.sina.com.cn/**/*/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artibody'>\",\r\n        \"exclude\" : [\r\n            \"[['<p>【新浪财经股吧】</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"sports.sina.com.cn\",\r\n        \"url\"     : \"http*://sports.sina.com.cn/**/*.shtml\",\r\n        \"title\"   : \"<h1 class='main-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artibody'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"collection.sina.com.cn\",\r\n        \"url\"     : \"http*://collection.sina.com.cn/**/*.shtml\",\r\n        \"title\"   : \"<h1 id='main_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='artibody'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.sina.com.cn\",\r\n        \"url\"     : \"http*://blog.sina.com.cn/s/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='articalContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.workercn.cn\",\r\n        \"url\"     : \"http://news.workercn.cn/*/*/**/*.shtml\",\r\n        \"title\"   : \"<div class='ad_tl_main'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='ad_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.xinhuanet.com\",\r\n        \"url\"     : \"http://news.xinhuanet.com/**/*/*.htm\",\r\n        \"title\"   : \"<div class='h-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='p-detail'>\",\r\n        \"exclude\" : [\r\n            \"<div class='zan-wap'>\",\r\n            \"<div class='p-tags'>\",\r\n            \"[[/src=\\\\S+(129713364_15065013734641n)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.gold678.com\",\r\n        \"url\"     : \"http*://www.gold678.com/C/**/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='news_inter_area'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"stockstar.com\",\r\n        \"url\"     : \"http://*.stockstar.com/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='container-article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"takungpao.com\",\r\n        \"url\"     : \"http://*.takungpao.com/**/*/*.html\",\r\n        \"title\"   : \"<h1 class='tpk_con_tle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='tpk_text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"money.jrj.com.cn\",\r\n        \"url\"     : \"http://money.jrj.com.cn/**/*/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='titimg'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.ynet.com\",\r\n        \"url\"     : \"http://news.ynet.com/**/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleAll'>\",\r\n        \"exclude\" : [\r\n            \"<ul class='pageBox'>\",\r\n            \"<span class='authors'>\",\r\n            \"<a class='scrollLeft'>\",\r\n            \"<a class='scrollRight'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qianlong.com\",\r\n        \"url\"     : \"http://*.qianlong.com/*/*/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"duozhi.com\",\r\n        \"url\"     : \"http*://www.duozhi.com/**/*.shtml\",\r\n        \"title\"   : \"<h1 class='subject-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='subject-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"app.myzaker.com\",\r\n        \"url\"     : \"http*://app.myzaker.com/news/article.php*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"<p id='ID_disclaimer'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.kankanews.com\",\r\n        \"url\"     : \"http*://m.kankanews.com/n/**/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"<p class='download'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"kankanews.com\",\r\n        \"url\"     : \"http*://www.kankanews.com/a/**/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='textBody'>\",\r\n        \"exclude\" : [\r\n            \"<p id='kankan_bq'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cbnweek.com\",\r\n        \"url\"     : \"https://www.cbnweek.com/articles/normal/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \".image-placeholder{background:transparent;padding-bottom:0!important}\"\r\n    },{\r\n        \"name\"    : \"user.guancha.cn\",\r\n        \"url\"     : \"http://user.guancha.cn/main/content?id=*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-txt'>\",\r\n        \"exclude\" : [\r\n            \"<ul class='pagination'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"guancha.cn\",\r\n        \"url\"     : \"https://www.guancha.cn/**/*html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('a.prev').last().attr('onclick')}]]\"},\r\n            {\"next\" : \"[[{$('a.next').attr('onclick')}]]\"}\r\n        ],\r\n        \"include\" : \"[[[$('.content-main .content')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.myzaker.com\",\r\n        \"url\"     : \"http*://www.myzaker.com/article/*/\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article_content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"people.com.cn\",\r\n        \"url\"     : \"http://*.people.com.cn/**/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='box_con'>\",\r\n        \"exclude\" : [\r\n            \"<div class='edit'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chinanews.com\",\r\n        \"url\"     : \"http://www.chinanews.com/*/**/*/*.shtml\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='left_zw'>\",\r\n        \"exclude\" : [\r\n            \"<table>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.toutiaocdn.com\",\r\n        \"url\"     : \"http*://m.toutiaocdn.com/*\",\r\n        \"title\"   : \"<h1 class='article__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article__content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"toutiao.com\",\r\n        \"url\"     : \"http*://www.toutiao.com/a*/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"toutiao.com\",\r\n        \"url\"     : \"http*://www.toutiao.com/i*/\",\r\n        \"title\"   : \"<h1 class='article-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cwzg.cm\",\r\n        \"url\"     : \"http://www.cwzg.cn/*/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"<div class='article-brief'>\",\r\n        \"include\" : \"<div id='article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"idianzixun.com\",\r\n        \"url\"     : \"http://www.yidianzixun.com/article/*\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content-bd'>\",\r\n        \"exclude\" : [\r\n            \"<div class='postfix'>\",\r\n            \"<p class='yidian-wm-copyright-bottom'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"reader.s-reader.com\",\r\n        \"url\"     : \"http://reader.s-reader.com/article/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"<h1>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.163.com\",\r\n        \"url\"     : \"http*://news.163.com/**/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='endText'>\",\r\n        \"exclude\" : [\r\n            \"<div class='ep-source'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"renjian.163.com\",\r\n        \"url\"     : \"http*://renjian.163.com/**/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='endText'>\",\r\n        \"exclude\" : [\r\n            \"<div class='ep-source'>\",\r\n            \"<div class='ep-pages'>\",\r\n            \"<p class='end'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dy.163.com\",\r\n        \"url\"     : \"http*://dy.163.com/v2/article/detail/*.html\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tech.163.com\",\r\n        \"url\"     : \"http*://*.163.com/**/*/*.html\",\r\n        \"title\"   : \"[[{$('.post_content_main').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post_text'>\",\r\n        \"exclude\" : [\r\n            \"<div class='ep-source cDGray'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"mi.techweb.com.cn\",\r\n        \"url\"     : \"http*://mi.techweb.com.cn/tmt/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"techweb.com.cn\",\r\n        \"url\"     : \"http*://www.techweb.com.cn/*/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"donews.com\",\r\n        \"url\"     : \"http://www.donews.com/**/*/*.html\",\r\n        \"title\"   : \"[[{$($('.contentbox').find('h2')[0]).text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.article-con')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"flashnews.donews.com\",\r\n        \"url\"     : \"http://www.donews.com/flashnews/detail/\",\r\n        \"title\"   : \"[[{$($('.contentbox').find('h2')[0]).text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.article-con')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"newseed.pedaily.cn\",\r\n        \"url\"     : \"http://newseed.pedaily.cn/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"<div class='subject'>\",\r\n        \"include\" : \"<div id='news-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.newseed.cn\",\r\n        \"url\"     : \"http*://news.newseed.cn/p/*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='news-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"digi.tech.qq.com\",\r\n        \"url\"     : \"http://digi.tech.qq.com/a/*/*.htm\",\r\n        \"title\"   : \"[[{$('.hd').find('h1').text()}]]\",\r\n        \"desc\"    : \"<p class='titdd-Article'>\",\r\n        \"include\" : \"<div class='Cnt-Main-Article-QQ'>\",\r\n        \"exclude\" : [\r\n            \"<p class='titdd-Article'>\",\r\n            \"<div class='wxdigi'>\",\r\n            \"<a id='backqqcom'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"m.qq.com\",\r\n        \"url\"     : \"http*://xw.qq.com/*/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article_body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qq.com\",\r\n        \"url\"     : \"http://*.qq.com/a/*/*.htm\",\r\n        \"title\"   : \"[[{$('.hd h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='Cnt-Main-Article-QQ'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"view.inews.qq.com\",\r\n        \"url\"     : \"http://view.inews.qq.com/a/\",\r\n        \"title\"   : \"<p class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"<p class='title'>\",\r\n            \"<div class='src'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"view2.inews.qq.com\",\r\n        \"url\"     : \"http://view.news.qq.com/original/intouchtoday/*.html\",\r\n        \"title\"   : \"<h2 id='sharetitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleContent'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"new.qq.com\",\r\n        \"url\"     : \"http*://new.qq.com/**/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content-article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.china.com.cn\",\r\n        \"url\"     : \"http://news.china.com.cn/**/*/*.htm\",\r\n        \"title\"   : \"<h1 class='articleTitle'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='articleBody'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.huanqiu.com/\",\r\n        \"url\"     : \"http*://*.huanqiu.com/*/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='la_con'>\",\r\n        \"exclude\" : [\r\n            \"<div class='editorSign'>\",\r\n            \"<div class='reTopics'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.mindynode.com\",\r\n        \"url\"     : \"https://news.mindynode.com/zh/links/*\",\r\n        \"title\"   : \"<div id='parsed_title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='parsed_content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='reTopics'>\",\r\n            \"<p class='read-art-extra-bonus'>\",\r\n            \"<div class='editorSign'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"8btc.com\",\r\n        \"url\"     : \"http://www.8btc.com/*-*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.article-content')[0]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"<div class='content-source-info'>\",\r\n            \"<div class='content-bottom'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wiki.8btc.com\",\r\n        \"url\"     : \"http://www.8btc.com/wiki/*\",\r\n        \"title\"   : \"<div class='page-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='col-xs-12'>\",\r\n        \"exclude\" : [\r\n            \"<div class='page-title'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"vip.qikan.com\",\r\n        \"url\"     : \"http://nlc.vip.qikan.com/text/Article.aspx*\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='newartbox'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zhidx.com\",\r\n        \"url\"     : \"http://zhidx.com/p/\",\r\n        \"title\"   : \"[[{$('.finTit').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='finCnt'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(ZDX-Card-for-PC-e1466578693228)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"iheima.com\",\r\n        \"url\"     : \"http://www.iheima.com/zixun/*/*/*.shtml\",\r\n        \"title\"   : \"<div class='title'>\",\r\n        \"desc\"    : \"<div class='outline'>\",\r\n        \"include\" : \"<div class='main-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='title'>\",\r\n            \"<div class='outline'>\",\r\n            \"<div class='author'>\",\r\n            \"<div class='copyright'>\",\r\n            \"<div class='mesinfo'>\",\r\n            \"<div class='article-list'>\",\r\n            \"<div class='mobile-common'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"pintu360.com\",\r\n        \"url\"     : \"http*://www.pintu360.com/article/\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"<div class='article-note'>\",\r\n        \"include\" : \"[[[$('.article-content').find('.text')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tech2ipo.com\",\r\n        \"url\"     : \"http://tech2ipo.com/*\",\r\n        \"title\"   : \"[[{$('.title').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"column.iresearch.cn\",\r\n        \"url\"     : \"http://column.iresearch.cn/b/*/*.shtml\",\r\n        \"title\"   : \"[[{$('.title').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='m-article'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.iresearch.cn\",\r\n        \"url\"     : \"http://news.iresearch.cn/content/**/*/*.shtml\",\r\n        \"title\"   : \"[[{$('.title').find('h1').text()}]]\",\r\n        \"desc\"    : \"<div class='review'>\",\r\n        \"include\" : \"<div class='m-article'>\",\r\n        \"exclude\" : [\r\n            \"<div class='review'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"lanjingtmt.com\",\r\n        \"url\"     : \"http://www.lanjingtmt.com/news/detail/\",\r\n        \"title\"   : \"[[{$('.newsTitle').find('h1').text()}]]\",\r\n        \"desc\"    : \"<div class='dm_zy'>\",\r\n        \"include\" : \"<div id='pageTxt'>\",\r\n        \"exclude\" : [\r\n            \"<div class='dm_zy'>\",\r\n            \"[[/src=\\\\S+(1438828721808210)\\\\S+'/]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"timetimetime.net\",\r\n        \"url\"     : \"http://www.timetimetime.net/*/*.html\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[[$('.neir')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"mifengtd.cn\",\r\n        \"url\"     : \"http://www.mifengtd.cn/articles/\",\r\n        \"title\"   : \"<a class='p-name'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='e-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"vgtime.com\",\r\n        \"url\"     : \"http://www.vgtime.com/article/\",\r\n        \"title\"   : \"<h1 class='art_max_width'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='paragraph'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"keke289.com\",\r\n        \"url\"     : \"http://www.keke289.com/news/\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='article-bd'>\",\r\n        \"exclude\" : [\r\n            \"<div class='article-annotation'>\",\r\n            \"[['<strong>关注科客网官方微信kekebat</strong>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"ebrun.com\",\r\n        \"url\"     : \"http://www.ebrun.com/*/*.shtml\",\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='cmsDiv'>\",\r\n        \"exclude\" : [\r\n            \"<p class='ybfirst_go'>\",\r\n            \"<div id='corp'>\",\r\n            \"[[/src=\\\\S+(2017013185214858280529103)\\\\S+'/]]\",\r\n            \"[['<span>百人讲新年纳新 588份精华课件免费赠</span>']]\",\r\n            \"[['<p>【版权提示】亿邦动力网倡导尊重与保护知识产权。</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cokeji.com\",\r\n        \"url\"     : \"http://www.cokeji.com/*.html\",\r\n        \"title\"   : \"[[{$('.single-header').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='single-main'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"199it.com\",\r\n        \"url\"     : \"http://www.199it.com/archives/\",\r\n        \"title\"   : \"<h1 class='entry-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"aliresearch.com\",\r\n        \"url\"     : \"http://www.aliresearch.com/blog/article/detail/id/\",\r\n        \"title\"   : \"<h2>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section id='contents'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cn.engadget.com\",\r\n        \"url\"     : \"http*://cn.engadget.com/*/*/*/*/\",\r\n        \"title\"   : \"[[{$('head').find('title').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chinaventure.com.cn\",\r\n        \"url\"     : \"https://www.chinaventure.com.cn/cmsmodel/news/detail/\",\r\n        \"title\"   : \"<h1 class='h1_01'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content_01'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"baijia.baidu.com\",\r\n        \"url\"     : \"https://baijia.baidu.com/s?id=*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='news-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"jingyan.baidu.com\",\r\n        \"url\"     : \"http*://jingyan.baidu.com/article/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='format-exp'>\",\r\n        \"exclude\" : [\r\n            \"<span class='exp-album-enter-mask'>\",\r\n            \"<span class='enter-step-btn'>\",\r\n            \"<span class='last-item-end'>\",\r\n            \"<div class='quote-item'>\",\r\n            \"<div class='list-icon'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"g-cores.com\",\r\n        \"url\"     : \"http://www.g-cores.com/articles/\",\r\n        \"title\"   : \"<h1 class='story_title'>\",\r\n        \"desc\"    : \"<p class='story_desc'>\",\r\n        \"include\" : \"<div class='story'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"tieba.baidu.com\",\r\n        \"url\"     : \"http*://tieba.baidu.com/p/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.p_author_name')}]]\"},\r\n            {\"url\"  : \"[[{$('.p_author_face').find('img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.pb_list_pager').find('.tP').prev().attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.pb_list_pager').find('.tP').next().attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"[[{$('.core_title_txt').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.p_content')}]]\",\r\n        \"exclude\" : [\r\n            \"<span class='apc_src_wrapper'>\",\r\n            \"<div class='save_face_bg_2'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qgc.qq.com\",\r\n        \"url\"     : \"http*://qgc.qq.com/*/t/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.gn')}]]\"},\r\n            {\"url\"  : \"[[{$('.avatar').find('img').filter(':even')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.pctmessage')}]]\",\r\n        \"exclude\" : [\r\n            \"<span class='apc_src_wrapper'>\",\r\n            \"<div class='save_face_bg_2'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.chiphell.com\",\r\n        \"url\"     : \"https://www.chiphell.com/article-*.html\",\r\n        \"title\"   : \"<h1 class='ph'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<table class='vwtb'>\",\r\n        \"exclude\" : [\r\n            \"<i class='pstatus'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chiphell.com\",\r\n        \"url\"     : \"https://www.chiphell.com/thread-*.html\",\r\n        \"title\"   : \"<span id='thread_subject'>\",\r\n        \"desc\"    : \"\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.favatar').find('.authi')}]]\"},\r\n            {\"url\"  : \"[[{$('.avatar').find('img')}]]\"}\r\n        ],\r\n        \"include\" : \"[[{$('.t_f')}]]\",\r\n        \"exclude\" : [\r\n            \"<i class='pstatus'>\",\r\n            \"[[/src=\\\\S+(none.gif)\\\\S+'/]]\",\r\n            \"<div class='aimg_tip'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"worldcup.fifa.com\",\r\n        \"url\"     : \"http*://www.fifa.com/worldcup/news/*\",\r\n        \"title\"   : \"<h1 class='d3-o-article__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='d3-o-article__body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"voice.hupu.com\",\r\n        \"url\"     : \"http*://voice.hupu.com/*/*.html\",\r\n        \"title\"   : \"<h1 class='headline'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='artical-content-read'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bbs.hupu.com\",\r\n        \"url\"     : \"https://bbs.hupu.com/*.html\",\r\n        \"title\"   : \"<h1 id='j_data'>\",\r\n        \"desc\"    : \"\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.author').find('.left a')}]]\"},\r\n            {\"url\"  : \"[[{$('.user').find('img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('a.prevPage').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('a.nextPage').attr('href')}]]\"}\r\n        ],\r\n        \"include\" : \"[[{$('table.case')}]]\",\r\n        \"exclude\" : [\r\n            \"<div class='video-play-1'>\",\r\n            \"<div class='vote_box'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"qu.la\",\r\n        \"url\"     : \"http://www.qu.la/book/*/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('#A1').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('#A3').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"[['<a>章节错误,点此举报</a>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"biquge.com.tw\",\r\n        \"url\"     : \"http://www.biquge.com.tw/**/*.html\",\r\n        \"title\"   : \"[[{$('.bookname').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"xxbiquge.com\",\r\n        \"url\"     : \"http://www.xxbiquge.com/**/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$($('.bottem2').find('a')[0]).attr('href')}]]\"},\r\n            {\"next\" : \"[[{$($('.bottem2').find('a')[2]).attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"[['<a>章节错误,点此举报</a>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"biquge5200.com\",\r\n        \"url\"     : \"http://www.biquge5200.com/**/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$($('.bottem2').find('a')[1]).attr('href')}]]\"},\r\n            {\"next\" : \"[[{$($('.bottem2').find('a')[3]).attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"piaotian.com\",\r\n        \"url\"     : \"http://www.piaotian.com/html/**/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$($('.bottomlink').find('a')[0]).attr('href')}]]\"},\r\n            {\"next\" : \"[[{$($('.bottomlink').find('a')[5]).attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"<h1>\",\r\n            \"<div class='toplink'>\",\r\n            \"<table>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zanghaihua.org\",\r\n        \"url\"     : \"http://www.zanghaihua.org/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$($('.linkbtn').find('a')[0]).attr('href')}]]\"},\r\n            {\"next\" : \"[[{$($('.linkbtn').find('a')[2]).attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"[[{$('.chaptertitle').find('h1').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='bookcontent'>\",\r\n        \"exclude\" : [\r\n            \"<script>\",\r\n            \"<a>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"read.qidian.com\",\r\n        \"url\"     : \"https://read.qidian.com/chapter/**/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('#j_chapterPrev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('#j_chapterNext').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h3 class='j_chapterName'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='read-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"vipreader.qidian.com\",\r\n        \"url\"     : \"https://vipreader.qidian.com/chapter/**/*\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('#j_chapterPrev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('#j_chapterNext').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h3 class='j_chapterName'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='read-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"37zw.net\",\r\n        \"url\"     : \"http*://www.37zw.net/**/*.html\",\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$($('.bottem2').find('a')[1]).attr('href')}]]\"},\r\n            {\"next\" : \"[[{$($('.bottem2').find('a')[3]).attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<h1>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"lofter.com\",\r\n        \"url\"     : \"http://*.lofter.com/post/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.content').html()||$('.text').html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"pin-cong.com\",\r\n        \"url\"     : \"https://www.pin-cong.com/p/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.user-details').find('a')}]]\"},\r\n            {\"url\"  : \"[[{$('.gravatar-wrapper-32').find('img')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.post-text')}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bbs.tianya.cn\",\r\n        \"url\"     : \"http://bbs.tianya.cn/*.shtml\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.user-details').find('a')}]]\"},\r\n            {\"url\"  : \"[[{$('.gravatar-wrapper-32').find('img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('.js-keyboard-prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('.js-keyboard-next').attr('href')}]]\"}\r\n        ],\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.bbs-content')}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"applysquare.com\",\r\n        \"url\"     : \"https://www.applysquare.com/topic-cn/*\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('.user-details').find('a')}]]\"},\r\n            {\"url\"  : \"[[{$('.gravatar-wrapper-32').find('img')}]]\"}\r\n        ],\r\n        \"title\"   : \"<div class='thread-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.qa-html-content')}]]\",\r\n        \"exclude\" : [\r\n            \"<span class='show-content'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"aktuell.faz.net\",\r\n        \"url\"     : \"http*://www.faz.net/aktuell/**/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='atc-Text'>\",\r\n        \"exclude\" : [\r\n            \"<aside class='atc-ContainerMore'>\",\r\n            \"<div class='o-Ratio_Content'>\",\r\n            \"<div class='ctn-PlaceholderContent'>\",\r\n            \"<div class='atc-ContainerInfo'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cn.nytstyle.com\",\r\n        \"url\"     : \"https://cn.nytstyle.com/*/*/*/\",\r\n        \"title\"   : \"<h3 class='articleHeadline'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='authorIdentification'>\",\r\n            \"<div class='articleCR'>\",\r\n            \"<div class='articleTool'>\",\r\n            \"<div id='disqus_thread'>\",\r\n            \"<div id='subscribe_cont'>\",\r\n            \"<div class='articleByside'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"sueddeutsche.de\",\r\n        \"url\"     : \"http://www.sueddeutsche.de/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section id='article-body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"nytimes.com\",\r\n        \"url\"     : \"https://www.nytimes.com/*/*/*/**/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='meteredContent'>\",\r\n        \"exclude\" : [\r\n            \"<button class='css-1vkv6l7'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cn.nytimes.com\",\r\n        \"url\"     : \"https://cn.nytimes.com/*/*/*/\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<section class='article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='authorIdentification'>\",\r\n            \"<div class='articleCR'>\",\r\n            \"<div class='articleTool'>\",\r\n            \"<div id='disqus_thread'>\",\r\n            \"<div id='subscribe_cont'>\",\r\n            \"<div class='articleByside'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"theverge.com\",\r\n        \"url\"     : \"https://www.theverge.com/**/*\",\r\n        \"title\"   : \"<h1 class='c-page-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='c-entry-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"medium.com\",\r\n        \"url\"     : \"https://*.medium.com/**\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$($('article .n.p')[0])]]]\",\r\n        \"exclude\" : [\r\n            \"<div class=\\\"sr-rd-content-exclude\\\">\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"2.medium.com\",\r\n        \"url\"     : \"https://medium.com/@*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$($('article .n.p')[0])]]]\",\r\n        \"exclude\" : [\r\n            \"<div class=\\\"sr-rd-content-exclude\\\">\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"3.medium.com\",\r\n        \"url\"     : \"https://medium.com/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$($('article .n.p')[0])]]]\",\r\n        \"exclude\" : [\r\n            \"<div class=\\\"sr-rd-content-exclude\\\">\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"4.medium.com\",\r\n        \"url\"     : \"https://medium.*.com/**\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$($('article .n.p')[1])]]]\",\r\n        \"exclude\" : [\r\n            \"<div class=\\\"sr-rd-content-exclude\\\">\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.bbc.com\",\r\n        \"url\"     : \"http*://www.bbc.com/news/**/*\",\r\n        \"title\"   : \"<h1 class='story-body__h1'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='story-body__inner'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"simp.bbc.com\",\r\n        \"url\"     : \"http*://www.bbc.com/zhongwen/**/*\",\r\n        \"title\"   : \"<h1 class='story-body__h1'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='story-body__inner'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"wsj.com\",\r\n        \"url\"     : \"https://www.wsj.com/articles/**/*\",\r\n        \"title\"   : \"<h1 class='wsj-article-headline'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='wsj-snippet-body'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"economist.com\",\r\n        \"url\"     : \"https://www.economist.com/**/*\",\r\n        \"title\"   : \"[[{$('.flytitle-and-title__title:first').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='blog-post__text'>\",\r\n        \"exclude\" : [\r\n            \"<div class='newsletter-form--inline'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.zerohedge.com\",\r\n        \"url\"     : \"https://www.zerohedge.com/news/**/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class=\\\"node__content\\\">\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"theinitium.com\",\r\n        \"url\"     : \"https://theinitium.com/article/*\",\r\n        \"title\"   : \"<h1 class=\\\"p-article__title\\\">\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$($('.p-article__content')[1]).html()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.cnbc.com\",\r\n        \"url\"     : \"https://www.cnbc.com/*/*/*/*.html\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article_body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='inline-player'>\",\r\n            \"<twitter-widget>\",\r\n            \"<iframe>\",\r\n            \"<div class='embed-container'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.vox.com\",\r\n        \"url\"     : \"https://www.vox.com/*/*/*/**/*\",\r\n        \"title\"   : \"<h1 class='c-page-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='c-entry-content '>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.theatlantic.com\",\r\n        \"url\"     : \"https://www.theatlantic.com/*/archive/**/*\",\r\n        \"title\"   : \"<h1 class='c-article-header__hed'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='l-article__container'>\",\r\n        \"exclude\" : [\r\n            \"<address>\",\r\n            \"<figcaption class='c-lead-media__credit'>\",\r\n            \"<span class='c-menu__section__icon'>\",\r\n            \"<svg>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"time.com\",\r\n        \"url\"     : \"http*://time.com/*/*/\",\r\n        \"title\"   : \"<h1 class='heading-content'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div class='newsletter-inline'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zh.asia.mercurymarine.com\",\r\n        \"url\"     : \"https://www.mercurymarine.com/zh/asia/news/*\",\r\n        \"title\"   : \"[[{$('.formatted-copy h2').text()}]]\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='formatted-copy'>\",\r\n        \"exclude\" : [\r\n            \"<h2>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.scientificamerican.com\",\r\n        \"url\"     : \"https://www.scientificamerican.com/article/*\",\r\n        \"title\"   : \"<h1 class='article-header__title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-block'>\",\r\n        \"exclude\" : [\r\n            \"<figure class='newsletter-promo'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"latimes.com\",\r\n        \"url\"     : \"http*://www.latimes.com/**/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='collection-cards'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.abcnews.com\",\r\n        \"url\"     : \"https://abcnews.go.com/*/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-copy'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"thehill.com\",\r\n        \"url\"     : \"https://thehill.com/*/**/*\",\r\n        \"title\"   : \"<h1 class='title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"[[{$('.field-item.even').html()}]]\",\r\n        \"exclude\" : [\r\n            \"<iframe>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.nieman.harvard.edu\",\r\n        \"url\"     : \"https://nieman.harvard.edu/news/*/*/*\",\r\n        \"title\"   : \"<h1 class='blog-h1'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='ui-block-inner'>\",\r\n        \"exclude\" : [\r\n            \"<div class='tags-top'>\",\r\n            \"<div class='social-tools-bottom'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"cjr.org\",\r\n        \"url\"     : \"https://www.cjr.org/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div id='article-content'>\",\r\n        \"exclude\" : [\r\n            \"<div id='mc_embed_signup'>\",\r\n            \"<div id='post-survey-box'>\",\r\n            \"<small>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"9to5mac.com\",\r\n        \"url\"     : \"https://9to5mac.com/*/*/*/*\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-body'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(volta-2.0-cable)\\\\S+'/]]\",\r\n            \"<div class='the9ca7210'>\",\r\n            \"[['<p>Check out 9to5Mac on YouTube</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"9to5google.com\",\r\n        \"url\"     : \"https://9to5google.com/*/*/*/*\",\r\n        \"title\"   : \"<h1 class='post-title'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-body'>\",\r\n        \"exclude\" : [\r\n            \"[[/src=\\\\S+(volta-2.0-cable)\\\\S+'/]]\",\r\n            \"<div class='the9ca7210'>\",\r\n            \"[['<p>Check out 9to5Mac on YouTube</p>']]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"news.cnet.com\",\r\n        \"url\"     : \"http*://www.cnet.com/news/*\",\r\n        \"title\"   : \"<h1 class='speakableText'>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<article id='article-body'>\",\r\n        \"exclude\" : [\r\n            \"<svg class='svg-symbol'>\",\r\n            \"<div class='shortcode'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"engadget.com\",\r\n        \"url\"     : \"https://www.engadget.com/*/*/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-text'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"gizmodo.com\",\r\n        \"url\"     : \"https://gizmodo.com/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='post-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='meta--pe'>\",\r\n            \"<div class='instream-native-video'>\",\r\n            \"<svg>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.macworld.com\",\r\n        \"url\"     : \"https://www.macworld.com/article/*/*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('h3[itemprop=\\\"description\\\"]').text()}]]\",\r\n        \"include\" : \"<div id='drr-container'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.newscientist.com\",\r\n        \"url\"     : \"https://www.newscientist.com/article/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='article-content'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \".simpread-hidden{display:none!important;}\"\r\n    },{\r\n        \"name\"    : \"readwrite.com\",\r\n        \"url\"     : \"https://readwrite.com/*/*/*/*/\",\r\n        \"title\"   : \"<h1 class=\\\"entry-title\\\">\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='entry-content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='clearfix'>\",\r\n            \"<div class='menu-social-media-container'>\",\r\n            \"<div class='popular-tags'>\",\r\n            \"<div class='related_posts'>\",\r\n            \"<div class='about-author'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.techrepublic.com\",\r\n        \"url\"     : \"https://www.techrepublic.com/article/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('p[itemprop=\\\"description\\\"]').text()}]]\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='newsletter-shortcode'>\",\r\n            \"<div class='shortcode'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.techrepublic.com\",\r\n        \"url\"     : \"https://www.techrepublic.com/blog/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('p[itemprop=\\\"description\\\"]').text()}]]\",\r\n        \"include\" : \"<div class='content'>\",\r\n        \"exclude\" : [\r\n            \"<div class='newsletter-shortcode'>\",\r\n            \"<div class='shortcode'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"dailydot.com\",\r\n        \"url\"     : \"https://www.dailydot.com/*/*\",\r\n        \"title\"   : \"<h1 class='dd-article-headline'>\",\r\n        \"desc\"    : \"[[{$('p[itemprop=\\\"description\\\"]').text()}]]\",\r\n        \"include\" : \"<div class='dd-content-body-inner'>\",\r\n        \"exclude\" : [\r\n            \"<div class='moviereview'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"technologyreview.com\",\r\n        \"url\"     : \"https://www.technologyreview.com/s/*/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"\",\r\n        \"include\" : \"<div class='storyContent'>\",\r\n        \"exclude\" : [\r\n            \"<aside>\",\r\n            \"<svg>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"52pojie.cn\",\r\n        \"url\"     : \"http*://www.52pojie.cn/thread-*\",\r\n        \"title\"   : \"[[{$('meta[name=keywords]').attr('content')||$('title').text()}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('*.pls *.avatar img').parent().parent().parent().parent().find('div.pi a')}]]\"},\r\n            {\"url\"  : \"[[{$('*.pls *.avatar img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('div.pg a.prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('div.pg a.nxt').attr('href')}]]\"}\r\n        ],\r\n        \"include\" : \"[[{(function(){$('img[file]').each(function(){ var img=$(this); img.attr('real_src',img.attr('file'));});$('.t_fsz .pattl').each(function(){$(this).parent().find('.t_f').append(this);});return $('.t_f');}())}]]\",\r\n        \"exclude\" : [\r\n            \"[[[$('sr-read .tip')]]]\",\r\n            \"[[[$('sr-read .pstatus')]]]\"\r\n        ],\r\n        \"css\"     : \"sr-rd-mult sr-rd-mult-content{width:100%;overflow:hidden}sr-rd-content table td,sr-rd-content table th{border:1px solid transparent}sr-rd-content pre{white-space:pre-wrap}\"\r\n    },{\r\n        \"name\"    : \"bbs.pcbeta.com\",\r\n        \"url\"     : \"http*://bbs.pcbeta.com/viewthread-*\",\r\n        \"title\"   : \"[[{$('meta[name=keywords]').attr('content')||$('title').text()}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"avatar\"  : [\r\n            {\"name\" : \"[[{$('*.pls *.avatar img').parent().parent().parent().parent().find('div.pi a')}]]\"},\r\n            {\"url\"  : \"[[{$('*.pls *.avatar img')}]]\"}\r\n        ],\r\n        \"paging\"  : [\r\n            {\"prev\" : \"[[{$('div.pg a.prev').attr('href')}]]\"},\r\n            {\"next\" : \"[[{$('div.pg a.nxt').attr('href')}]]\"}\r\n        ],\r\n        \"include\" : \"[[{(function(){$('img[file]').each(function(){ var img=$(this); img.attr('real_src',img.attr('file'));});$('.t_fsz .pattl').each(function(){$(this).parent().find('.t_f').append(this);});return $('.t_f');}())}]]\",\r\n        \"exclude\" : [\r\n            \"[[[$('sr-read .tip')]]]\",\r\n            \"[[[$('sr-read .pstatus')]]]\"\r\n        ],\r\n        \"css\"     : \"sr-rd-mult sr-rd-mult-content{width:100%;overflow:hidden}sr-rd-content table td,sr-rd-content table th{border:1px solid transparent}sr-rd-content pre{white-space:pre-wrap}\"\r\n    },{\r\n        \"name\"    : \"dedao.cn\",\r\n        \"url\"     : \"http*://www.dedao.cn/article/*\",\r\n        \"title\"   : \"[[{$('.article-title').text()||$('title').text()}]]\",\r\n        \"desc\"    : \"[[{$('div.course-title').text()||$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[(function () { var body = $('.article-body'); if (!body.length) { body = $('<p>异步加载型页面，本次未能获取到内容元素，请重新进入阅读模式尝试</p><br><p>'+''.padEnd(100,'#')+'</p>'); $('head').append(body); } return body; }())]]]\",\r\n        \"exclude\" : [\r\n            \"<div class='em-menu'>\",\r\n            \"[[{$('.elite-add-to-note')}]]\",\r\n            \"[[{$('.letter')}]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"chongdiantou.com\",\r\n        \"url\"     : \"http*://www.chongdiantou.com/wp/archives/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[`//*[contains(@class,'post-content')]`]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"blog.sciencenet.cn\",\r\n        \"url\"     : \"http*://blog.sciencenet.cn/blog-*.html\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$('#blog_article')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"article.imooc.com\",\r\n        \"url\"     : \"http*://www.imooc.com/article/*\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$('.detail-content')]]]\",\r\n        \"exclude\" : [\r\n            \"<div class='showMore'>\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"zqb.cyol.com\",\r\n        \"url\"     : \"http://zqb.cyol.com/html/**/nw.*.htm\",\r\n        \"title\"   : \"[[{$('meta[name=description]').attr('content')}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$('.c_c')]]]\",\r\n        \"exclude\" : [\r\n            \"[[{(function(){var e=$('sr-rd-content'); var content=e.html();content=content.replaceAll(/<!--[^<]*-->/g,'');e.html(content);}())}]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"so.gushiwen.org\",\r\n        \"url\"     : \"http*://so.gushiwen.org/**/*.aspx\",\r\n        \"title\"   : \"<title>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[(function(){left=$('.main3 .left');['译文','赏析','注释'].forEach(text=>{left.find(`img[alt='${text}']`).click();});left.find(\\\"div a:contains('展开阅读全文')\\\").each((i,e)=>{e.click()});return $('.main3 .left').children().eq(0).nextUntil('.title');}())]]]\",\r\n        \"exclude\" : [\r\n            \"[[[$('.yizhu')]]]\",\r\n            \"[[[$('.tool')]]]\",\r\n            \"[[[$('a[href$=\\\")\\\"]')]]]\",\r\n            \"[[[$('div textarea')]]]\"\r\n        ]\r\n    },{\r\n        \"name\"    : \"bbs.pediy.com\",\r\n        \"url\"     : \"http*://bbs.pediy.com/thread-*\",\r\n        \"title\"   : \"[[{$('meta[name=keywords]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[[$('div.message:first')]]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \".simpread-read-root {align-items: baseline;}\"\r\n    },{\r\n        \"name\"    : \"sciencedirect.com\",\r\n        \"url\"     : \"https://www.sciencedirect.com/science/article/pii/*\",\r\n        \"title\"   : \"<span class='title-text'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[{(()=>{let html=\\\"\\\",abstract=\\\"\\\",graphical=\\\"\\\",keywords=\\\"\\\",body=\\\"\\\",references=\\\"\\\";abstract=$(\\\"body #abstracts\\\").html();$(\\\"body .Keywords.u-font-serif .keywords-section\\\").map((idx,item)=>{keywords+=\\\"<h2>\\\"+$(item).find(\\\"h2\\\").text()+\\\"</h2>\\\";let keyword=\\\"\\\";$(item).find(\\\".keyword\\\").map((idx,item)=>{keyword+=`<span>${item.innerText}&nbsp;&nbsp;</span>`});keywords+=`<span>${keyword}</span>`});body=$(\\\"body #body\\\").html();references=$(\\\"body dl.references\\\").html();html=abstract+keywords+body+references;$(\\\"html\\\").append(`<div class=\\\"simpread-clone-root\\\"style=\\\"display:none;\\\">${html}</div>`);$(\\\".simpread-clone-root\\\").find(\\\"h2\\\").removeAttr(\\\"class\\\").removeAttr(\\\"id\\\");html=$(\\\".simpread-clone-root\\\").html();$(\\\".simpread-clone-root\\\").remove();return html})()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"\"\r\n    },{\r\n        \"name\"    : \"ncbi.nlm.nih.gov\",\r\n        \"url\"     : \"https://www.ncbi.nlm.nih.gov/pmc/articles/*/\",\r\n        \"title\"   : \"<h1 class='content-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='tsec sec'>\",\r\n        \"exclude\" : [\r\n            \"<div class='jig-ncbiinpagenav-goto-container'>\",\r\n            \"<div class='ts_bar small'>\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content .figure {\\n    all: unset;\\n}\\nsr-rd-content .head, sr-rd-content .sub-head {\\n    border-bottom: none;\\n}\"\r\n    },{\r\n        \"name\"    : \"journals.biologists.com\",\r\n        \"url\"     : \"https://journals.biologists.com/bio/article/*/*/*/*/*\",\r\n        \"title\"   : \"<h1 class='wi-article-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[{(()=>{let html=\\\"\\\";html=$(\\\"body .widget-items[data-widgetname=ArticleFulltext]\\\").html();$(\\\"html\\\").append(`<div class=\\\"simpread-clone-root\\\"style=\\\"display:none;\\\">${html}</div>`);$(\\\".simpread-clone-root\\\").find(\\\"h2,h3\\\").removeAttr(\\\"class\\\").removeAttr(\\\"id\\\");html=$(\\\".simpread-clone-root\\\").html();$(\\\".simpread-clone-root\\\").remove();return html})()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content section.abstract, sr-rd-content section.abstract * {\\n    all: unset!important;\\n}\\nsr-rd-content .citation div {\\n    all: unset;\\n}\\n\\nsr-rd-content .citation.mixed-citation {\\n    all: unset;\\n}\"\r\n    },{\r\n        \"name\"    : \"cell.com\",\r\n        \"url\"     : \"https://www.cell.com/*/fulltext/*\",\r\n        \"title\"   : \"<h1 class='article-header__title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='article__sections'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content .dropBlock__body {\\n    display: none;\\n}\\nsr-rd-content .refs ol:not([class^=rlist]):not([class~=rlist])>li.ref:before {\\n    display: none;\\n}\"\r\n    },{\r\n        \"name\"    : \"elifesciences.org\",\r\n        \"url\"     : \"https://elifesciences.org/articles/*\",\r\n        \"title\"   : \"<h1 class='content-header__title--x-long'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[{(()=>{let html=\\\"\\\";html=$($(\\\"body .content-container\\\")[0]).html();return html})()}]]\",\r\n        \"exclude\" : [\r\n            \"<div class='speech-bubble--container'>\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content .article-section__toggle:after {\\n    display: none!important;\\n}\\nsr-rd-content a.article-section__toggle {\\n    text-decoration: none!important;\\n}\\nsr-rd-content .article-section--js {\\n    border: none!important;\\n}\"\r\n    },{\r\n        \"name\"    : \"pnas.org\",\r\n        \"url\"     : \"https://www.pnas.org/content/*/*/*\",\r\n        \"title\"   : \"<h1 class='highwire-cite-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='fulltext-view'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"\"\r\n    },{\r\n        \"name\"    : \"science.sciencemag.org\",\r\n        \"url\"     : \"https://science.sciencemag.org/content/*/*/*\",\r\n        \"title\"   : \"<div class='highwire-cite-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='fulltext-view'>\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"\"\r\n    },{\r\n        \"name\"    : \"nature.com\",\r\n        \"url\"     : \"https://www.nature.com/articles/*\",\r\n        \"title\"   : \"<h1 class='c-article-title'>\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"<div class='c-article-body'>\",\r\n        \"exclude\" : [\r\n            \"<div id='article-info-section'>\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content .c-article-box__container {\\n    height: initial!important;\\n}\\nsr-rd-content .c-article-box__controls {\\n    display: none;\\n}\"\r\n    },{\r\n        \"name\"    : \"frontiersin.org\",\r\n        \"url\"     : \"https://www.frontiersin.org/articles/*/*/full\",\r\n        \"title\"   : \"[[{$('meta[property=\\\"og:title\\\"').attr('content')}]]\",\r\n        \"desc\"    : \"[[{$('meta[name=Description]').attr('content')||$('meta[name=description]').attr('content')}]]\",\r\n        \"include\" : \"[[{(()=>{let html=\\\"\\\";if($($(\\\"body .mb0\\\")[0]).length>0){html=`<div>${$($(\\\"body .mb0\\\")[0]).html()}</div>`}html+=$(\\\"body .JournalFullText\\\").html();return html})()}]]\",\r\n        \"exclude\" : [\r\n            \"\"\r\n        ],\r\n        \"css\"     : \"sr-rd-content .FigureDesc {\\n    background: #eee;\\n    padding: 20px;\\n    height: 100%;\\n}\\n\\nsr-rd-content .FigureDesc .sr-rd-content-center-small {\\n    all: unset!important;\\n}\\n\\nsr-rd-content .FigureDesc img {\\n    float: left;\\n    margin-top: 40px;\\n    margin-right: 30px;\\n}\\n\\nsr-rd-content .FigureDesc p {\\n    color: #3e3d40;\\n    font-family: Georgia,\\\"Times New Roman\\\",Times,serif;\\n    font-size: 18px;\\n    line-height: 28px;\\n    font-weight: 300;\\n    margin: 0 0 10px;\\n}\"\r\n    }]\r\n}"
  },
  {
    "path": "webpack.config.ext.js",
    "content": "\nconst webpack = require( 'webpack' ),\n    plugins = [\n\n      // public reqire( xxx )\n      new webpack.ProvidePlugin({\n        React    : 'react',\n        ReactDOM : 'react-dom',\n        Notify   : 'notify',\n      }),\n\n      // chunk files\n      new webpack.optimize.CommonsChunkPlugin({\n        names     : [ 'vendors', 'common' ],\n        minChunks : Infinity\n      }),\n\n      // defined environment variable\n      new webpack.DefinePlugin({\n        'process.env.NODE_ENV': JSON.stringify( 'production' ) // or development\n      }),\n\n    ],\n\n    // conditions environment\n    isProduction = function () {\n      return process.env.NODE_ENV === 'production';\n    },\n\n    // only when environment variable is 'production' call\n    deploy = ( function () {\n      var CopyWebpackPlugin  = require( 'copy-webpack-plugin'  ),\n          CleanWebpackPlugin = require( 'clean-webpack-plugin' );\n\n      // development verify\n      if ( !isProduction() ) {\n        // copy files\n        plugins.push(\n          new CopyWebpackPlugin([\n            { from   : 'src/options/options.html',        to : '../options/' },\n            { from   : 'src/options/custom.html',         to : '../options/' },\n            { from   : 'src/options/sitemgr.html',        to : '../options/' },\n            //{ from   : \"src/website_list.json\" ,          to : '../' },\n            //{ context: 'src/assets/css/', from : \"*\" ,    to : '../assets/css' },\n            //{ context: 'src/assets/images/', from : \"*\" , to : '../assets/images' },\n            //{ context: 'src/_locales/',    from : \"*/*\" , to : '../_locales/' },\n          ])\n        )\n      }\n\n      // environment verify\n      if ( isProduction() ) {\n\n        // delete publish folder\n        plugins.push(\n          new CleanWebpackPlugin([ 'publish' ], {\n            verbose: true,\n            dry    : false,\n          })\n        );\n\n        // copy files\n        plugins.push(\n          new CopyWebpackPlugin([\n            { from   : \"ext/manifest.json\" ,              to : '../' },\n            { from   : \"src/website_list.json\" ,          to : '../' },\n            { from   : 'src/options/options.html',        to : '../options/' },\n            { from   : 'src/options/custom.html',         to : '../options/' },\n            { from   : 'src/options/sitemgr.html',        to : '../options/' },\n            { context: 'src/assets/images/', from : \"*\" , to : '../assets/images' },\n            { context: 'src/_locales/',    from : \"*/*\" , to : '../_locales/' },\n          ])\n        );\n\n        // call uglifyjs plugin\n        plugins.push(\n          new webpack.optimize.UglifyJsPlugin({\n            compress: {\n              sequences: true,\n              dead_code: true,\n              conditionals: true,\n              booleans: true,\n              unused: true,\n              if_return: true,\n              join_vars: true,\n              drop_console: true\n            },\n            mangle: {\n              except: [ '$', 'exports', 'require' ]\n            },\n            output: {\n              comments: false\n            }\n          })\n        );\n\n      }\n    })(),\n\n    // webpack config\n    config = {\n      entry: {\n\n        common : [\n          'babel-polyfill',\n\n          'jquery',\n\n          'storage',\n          'message',\n          'browser',\n          'version',\n          'menu',\n          'watch',\n          'util',\n        ],\n\n        vendors : [\n\n          // react\n          './node_modules/react/dist/react.min.js',\n          './node_modules/react-dom/dist/react-dom.min.js',\n\n          // vendors\n          //'minimatch',\n          //'to-markdown',\n          'velocity',\n          'filesaver',\n          'dom2image',\n          'notify',\n\n          // only read\n          //'pangu',\n          //'mousetrap',\n          //'progressbar',\n          //'util',\n          //'highlight',\n          //'output',\n\n          // service\n          'export',\n          'theme',\n          'stylesheet',\n          'config',\n\n          // module\n          'focusopt',\n          'readopt',\n          'themesel',\n          'shortcuts',\n          'include',\n          'exclude',\n          'name',\n          'url',\n          'modals',\n\n          // olny options\n          //'welcome',\n          //'commonopt',\n          //'authorize',\n          //'labsopt',\n          //'unrdist',\n          //'about',\n          //'timeago',\n\n          // mduikit\n          'tooltip',\n          'waves',\n          'textfield',\n          'button',\n          'selectfield',\n\n          // only read\n          //'fab',\n          //'progress',\n          //'dialog',\n\n          // option custom\n          //'switch',\n          //'tabs',\n          //'sidebar',\n          //'list',\n        ],\n\n        contentscripts : './ext/contentscripts.js',\n        background     : './ext/background.js',\n        options        : './src/options/options.js',\n        custom         : './src/options/custom.js',\n        sitemgr        : './src/options/sitemgr.js',\n      },\n\n      output: {\n        path     :  isProduction() ? './publish/bundle' : './ext/bundle',\n        filename : '[name].js'\n      },\n\n      devServer: {\n        inline: true,\n        port  : 7777\n      },\n\n      plugins: plugins,\n\n      module: {\n        loaders: [{\n            test: /\\.js[x]?$/,\n            exclude: /node_modules/,\n            loader: 'babel',\n            query: {\n              presets: [ 'es2015', 'stage-0', 'react' ]\n            }\n        },\n        { test: /\\.css$/,           loader: 'style!css!postcss' },\n        { test: /\\.(png|jpg|gif)$/, loader: 'url?limit=12288'   },\n        {\n          test  : require.resolve( './src/vender/jquery-2.1.1.min.js' ),\n          loader: 'expose?jQuery!expose?$'\n        },\n        {\n          test  : require.resolve( './src/vender/mousetrap.min.js' ),\n          loader: 'expose?Mousetrap'\n        }\n        ]\n      },\n\n      postcss: function () {\n        return [\n          require( 'postcss-cssnext' )()\n        ]\n      },\n\n      node: {\n        fs: 'empty'\n      },\n\n      resolve: {\n        alias : {\n          notify_css : __dirname + '/src/vender/notify/notify.css',\n          carous_css : __dirname + '/src/vender/carousel/carousel.css',\n\n          markdown   : __dirname + '/node_modules/to-markdown/dist/to-markdown.js',\n          epubpress  : __dirname + '/node_modules/epub-press-js/build/index.js',\n          nanoid     : __dirname + '/node_modules/nanoid/generate.js',\n\n          jquery     : __dirname + '/src/vender/jquery-2.1.1.min.js',\n          mousetrap  : __dirname + '/src/vender/mousetrap.min.js',\n          velocity   : __dirname + '/src/vender/velocity.min.js',\n          timeago    : __dirname + '/src/vender/timeago.min.js',\n          carousel   : __dirname + '/src/vender/carousel/carousel.js',\n          dom2image  : __dirname + '/src/vender/dom2image.min.js',\n          filesaver  : __dirname + '/src/vender/filesaver.min.js',\n          instapaper : __dirname + '/src/vender/instapaper.js',\n\n          util       : __dirname + '/src/service/util.js',\n          local      : __dirname + '/src/service/local.js',\n          storage    : __dirname + '/src/service/storage.js',\n          message    : __dirname + '/src/service/message.js',\n          browser    : __dirname + '/src/service/browser.js',\n          theme      : __dirname + '/src/service/theme.js',\n          stylesheet : __dirname + '/src/service/stylesheet.js',\n          config     : __dirname + '/src/service/config.js',\n          version    : __dirname + '/src/service/version.js',\n          menu       : __dirname + '/src/service/menu.js',\n          watch      : __dirname + '/src/service/watch.js',\n          export     : __dirname + '/src/service/export.js',\n          highlight  : __dirname + '/src/service/highlight.js',\n          output     : __dirname + '/src/service/output.js',\n          runtime    : __dirname + '/src/service/runtime.js',\n\n          focus      : __dirname + '/src/focus/focus.js',\n          controlbar : __dirname + '/src/focus/controlbar.jsx',\n\n          read       : __dirname + '/src/read/read.jsx',\n          toc        : __dirname + '/src/read/toc.jsx',\n          special    : __dirname + '/src/read/special.jsx',\n          readctlbar : __dirname + '/src/read/controlbar.jsx',\n          schedule   : __dirname + '/src/read/progressbar.jsx',\n\n          keyboard   : __dirname + '/src/module/keyboard.js',\n          modals     : __dirname + '/src/module/modals.jsx',\n          focusopt   : __dirname + '/src/module/focus.jsx',\n          readopt    : __dirname + '/src/module/read.jsx',\n          commonopt  : __dirname + '/src/module/common.jsx',\n          pluginsopt : __dirname + '/src/module/plugins.jsx',\n          sitesopt   : __dirname + '/src/module/sites.jsx',\n          labsopt    : __dirname + '/src/module/labs.jsx',\n          accountopt : __dirname + '/src/module/account.jsx',\n          about      : __dirname + '/src/module/about.jsx',\n          unrdist    : __dirname + '/src/module/unrdist.jsx',\n          welcome    : __dirname + '/src/module/welcome.jsx',\n          authorize  : __dirname + '/src/module/authorize.jsx',\n          siteeditor : __dirname + '/src/module/siteeditor.jsx',\n          actionbar  : __dirname + '/src/module/actionbar.jsx',\n          pluginbar  : __dirname + '/src/module/pluginbar.jsx',\n          sitebar    : __dirname + '/src/module/sitebar.jsx',\n          enhancesite: __dirname + '/src/module/enhancesite.jsx',\n          editor     : __dirname + '/src/module/common/editor.jsx',\n          themesel   : __dirname + '/src/module/common/theme.jsx',\n          shortcuts  : __dirname + '/src/module/common/shortcuts.jsx',\n          include    : __dirname + '/src/module/common/include.jsx',\n          exclude    : __dirname + '/src/module/common/exclude.jsx',\n          name       : __dirname + '/src/module/common/name.jsx',\n          url        : __dirname + '/src/module/common/url.jsx',\n\n          wavess     : __dirname + '/src/vender/waves/waves.js',\n          notify     : __dirname + '/src/vender/notify/notify.js',\n\n          textfield  : __dirname + '/src/vender/mduikit/textfield.jsx',\n          fab        : __dirname + '/src/vender/mduikit/fab.jsx',\n          fap        : __dirname + '/src/vender/mduikit/fap.jsx',\n          button     : __dirname + '/src/vender/mduikit/button.jsx',\n          statebutton: __dirname + '/src/vender/mduikit/statebutton.jsx',\n          selectfield: __dirname + '/src/vender/mduikit/selectfield.jsx',\n          ac         : __dirname + '/src/vender/mduikit/autocomplete.jsx',\n          dropdown   : __dirname + '/src/vender/mduikit/dropdown.jsx',\n          switch     : __dirname + '/src/vender/mduikit/switch.jsx',\n          tabs       : __dirname + '/src/vender/mduikit/tabs.jsx',\n          progress   : __dirname + '/src/vender/mduikit/progress.jsx',\n          sidebar    : __dirname + '/src/vender/mduikit/sidebar.jsx',\n          list       : __dirname + '/src/vender/mduikit/list.jsx',\n          dialog     : __dirname + '/src/vender/mduikit/dialog.jsx',\n          slider     : __dirname + '/src/vender/mduikit/slider.jsx',\n          tooltip    : __dirname + '/src/vender/mduikit/tooltip.jsx',\n          waves      : __dirname + '/src/vender/mduikit/waves.js',\n\n          puread     : __dirname + '/src/vender/puread/puread.js',\n          puplugin   : __dirname + '/src/vender/puread/plugin.js',\n\n        }\n      }\n\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "webpack.config.js",
    "content": "\nconst webpack = require( 'webpack' ),\n    plugins = [\n\n      // public reqire( xxx )\n      new webpack.ProvidePlugin({\n        React    : 'react',\n        ReactDOM : 'react-dom',\n        Notify   : 'notify',\n      }),\n\n      // chunk files\n      new webpack.optimize.CommonsChunkPlugin({\n        names     : [ 'vendors', 'common' ],\n        minChunks : Infinity\n      }),\n\n      // defined environment variable\n      new webpack.DefinePlugin({\n        'process.env.NODE_ENV': JSON.stringify( 'production' ) // or development\n      }),\n\n    ],\n\n    // conditions environment\n    isProduction = function () {\n      return process.env.NODE_ENV === 'production';\n    },\n\n    // only when environment variable is 'production' call\n    deploy = ( function () {\n      var CopyWebpackPlugin  = require( 'copy-webpack-plugin'  ),\n          CleanWebpackPlugin = require( 'clean-webpack-plugin' );\n\n      // environment verify\n      if ( isProduction() ) {\n\n        // delete publish folder\n        plugins.push(\n          new CleanWebpackPlugin([ 'publish' ], {\n            verbose: true,\n            dry    : false,\n          })\n        );\n\n        // copy files\n        plugins.push(\n          new CopyWebpackPlugin([\n            { from   : \"src/manifest.json\" ,              to : '../' },\n            { from   : \"src//help_tips.json\" ,            to : '../' },\n            { from   : \"src/website_list.json\" ,          to : '../' },\n            { from   : \"src/ga.js\" ,                      to : '../' },\n            { from   : 'src/options/options.html',        to : '../options/' },\n            { from   : 'src/options/custom.html',         to : '../options/' },\n            { from   : 'src/options/sitemgr.html',        to : '../options/' },\n            { from   : 'src/options/notice.html',         to : '../options/' },\n            { from   : 'src/options/corb.html',           to : '../options/' },\n            { context: 'src/assets/images/', from : \"*\" , to : '../assets/images' },\n            { context: 'src/_locales/',    from : \"*/*\" , to : '../_locales/' },\n          ])\n        );\n\n        // call uglifyjs plugin\n        plugins.push(\n          new webpack.optimize.UglifyJsPlugin({\n            compress: {\n              sequences: true,\n              dead_code: true,\n              conditionals: true,\n              booleans: true,\n              unused: true,\n              if_return: true,\n              join_vars: true,\n              drop_console: true\n            },\n            mangle: {\n              except: [ '$', 'exports', 'require' ]\n            },\n            output: {\n              comments: false\n            }\n          })\n        );\n\n      }\n    })(),\n\n    // webpack config\n    config = {\n      entry: {\n\n        common : [\n          'babel-polyfill',\n\n          'jquery',\n\n          'storage',\n          'message',\n          'browser',\n          'version',\n          'menu',\n          'watch',\n          'util',\n        ],\n\n        vendors : [\n\n          // react\n          './node_modules/react/dist/react.min.js',\n          './node_modules/react-dom/dist/react-dom.min.js',\n\n          // vendors\n          //'minimatch',\n          //'to-markdown',\n          'velocity',\n          'filesaver',\n          'dom2image',\n          'notify',\n\n          // only read\n          //'pangu',\n          //'mousetrap',\n          //'progressbar',\n          //'util',\n          //'highlight',\n          //'output',\n\n          // service\n          'export',\n          'theme',\n          'stylesheet',\n          'config',\n\n          // module\n          'focusopt',\n          'readopt',\n          'themesel',\n          'shortcuts',\n          'include',\n          'exclude',\n          'name',\n          'url',\n          'setting',\n\n          // olny options\n          //'welcome',\n          //'commonopt',\n          //'authorize',\n          //'labsopt',\n          //'unrdist',\n          //'about',\n          //'timeago',\n\n          // mduikit\n          'tooltip',\n          'waves',\n          'textfield',\n          'button',\n          'selectfield',\n\n          // only read\n          //'fab',\n          //'progress',\n          //'dialog',\n\n          // option custom\n          //'switch',\n          //'tabs',\n          //'sidebar',\n          //'list',\n        ],\n\n        contentscripts : './src/contentscripts.js',\n        background     : './src/background.js',\n        options        : './src/options/options.js',\n        custom         : './src/options/custom.js',\n        sitemgr        : './src/options/sitemgr.js',\n        notice         : './src/options/notice.js',\n        corb           : './src/options/corb.js',\n      },\n\n      output: {\n        path     :  isProduction() ? './publish/bundle' : './src/bundle',\n        filename : '[name].js'\n      },\n\n      devServer: {\n        inline: true,\n        port  : 7777\n      },\n\n      plugins: plugins,\n\n      module: {\n        loaders: [{\n            test: /\\.js[x]?$/,\n            exclude: /node_modules/,\n            loader: 'babel',\n            query: {\n              presets: [ 'es2015', 'stage-0', 'react' ]\n            }\n        },\n        { test: /\\.css$/,           loader: 'style!css!postcss' },\n        { test: /\\.(png|jpg|gif)$/, loader: 'url?limit=12288'   },\n        {\n          test  : require.resolve( './src/vender/jquery-2.1.1.min.js' ),\n          loader: 'expose?jQuery!expose?$'\n        },\n        {\n          test  : require.resolve( './src/vender/mousetrap.min.js' ),\n          loader: 'expose?Mousetrap'\n        }\n        ]\n      },\n\n      postcss: function () {\n        return [\n          require( 'postcss-cssnext' )()\n        ]\n      },\n\n      node: {\n        fs: 'empty'\n      },\n\n      resolve: {\n        alias : {\n          notify_css : __dirname + '/src/vender/notify/notify.css',\n          carous_css : __dirname + '/src/vender/carousel/carousel.css',\n\n          epubpress  : __dirname + '/node_modules/epub-press-js/build/index.js',\n          nanoid     : __dirname + '/node_modules/nanoid/generate.js',\n\n          jquery     : __dirname + '/src/vender/jquery-2.1.1.min.js',\n          axios      : __dirname + '/src/vender/axios.min.js',\n          mousetrap  : __dirname + '/src/vender/mousetrap.min.js',\n          velocity   : __dirname + '/src/vender/velocity.min.js',\n          timeago    : __dirname + '/src/vender/timeago.min.js',\n          carousel   : __dirname + '/src/vender/carousel/carousel.js',\n          dom2image  : __dirname + '/src/vender/dom2image.min.js',\n          filesaver  : __dirname + '/src/vender/filesaver.min.js',\n          instapaper : __dirname + '/src/vender/instapaper.js',\n          webdav     : __dirname + '/src/vender/webdav.js',\n          wiz        : __dirname + '/src/vender/wiz.js',\n          markdown   : __dirname + '/src/vender/turndown/turndown.js',\n          mdgfm      : __dirname + '/src/vender/turndown/turndown-plugin-gfm.js',\n          intro      : __dirname + '/src/vender/intro/intro.min.js',\n          intro_css  : __dirname + '/src/vender/intro/intro.min.css',\n\n          util       : __dirname + '/src/service/util.js',\n          local      : __dirname + '/src/service/local.js',\n          storage    : __dirname + '/src/service/storage.js',\n          message    : __dirname + '/src/service/message.js',\n          browser    : __dirname + '/src/service/browser.js',\n          theme      : __dirname + '/src/service/theme.js',\n          stylesheet : __dirname + '/src/service/stylesheet.js',\n          config     : __dirname + '/src/service/config.js',\n          version    : __dirname + '/src/service/version.js',\n          menu       : __dirname + '/src/service/menu.js',\n          watch      : __dirname + '/src/service/watch.js',\n          export     : __dirname + '/src/service/export.js',\n          highlight  : __dirname + '/src/service/highlight.js',\n          output     : __dirname + '/src/service/output.js',\n          runtime    : __dirname + '/src/service/runtime.js',\n          permission : __dirname + '/src/service/permission.js',\n          offline    : __dirname + '/src/service/offline.js',\n          snapshot   : __dirname + '/src/service/snapshot.js',\n          tips       : __dirname + '/src/service/tips.js',\n\n          focus      : __dirname + '/src/focus/focus.js',\n          controlbar : __dirname + '/src/focus/controlbar.jsx',\n\n          read       : __dirname + '/src/read/read.jsx',\n          toc        : __dirname + '/src/read/toc.jsx',\n          special    : __dirname + '/src/read/special.jsx',\n          readctlbar : __dirname + '/src/read/controlbar.jsx',\n          schedule   : __dirname + '/src/read/progressbar.jsx',\n\n          keyboard   : __dirname + '/src/module/keyboard.js',\n          setting    : __dirname + '/src/module/setting.jsx',\n          focusopt   : __dirname + '/src/module/focus.jsx',\n          readopt    : __dirname + '/src/module/read.jsx',\n          commonopt  : __dirname + '/src/module/common.jsx',\n          pluginsopt : __dirname + '/src/module/plugins.jsx',\n          sitesopt   : __dirname + '/src/module/sites.jsx',\n          labsopt    : __dirname + '/src/module/labs.jsx',\n          accountopt : __dirname + '/src/module/account.jsx',\n          about      : __dirname + '/src/module/about.jsx',\n          unrdist    : __dirname + '/src/module/unrdist.jsx',\n          welcome    : __dirname + '/src/module/welcome.jsx',\n          authorize  : __dirname + '/src/module/authorize.jsx',\n          siteeditor : __dirname + '/src/module/siteeditor.jsx',\n          actionbar  : __dirname + '/src/module/actionbar.jsx',\n          pluginbar  : __dirname + '/src/module/pluginbar.jsx',\n          sitebar    : __dirname + '/src/module/sitebar.jsx',\n          enhancesite: __dirname + '/src/module/enhancesite.jsx',\n          sharecard  : __dirname + '/src/module/sharecard.jsx',\n          notice     : __dirname + '/src/module/notice.jsx',\n          guide      : __dirname + '/src/module/guide.jsx',\n          urlscheme  : __dirname + '/src/module/urlscheme.jsx',\n          feedback   : __dirname + '/src/module/feedback.jsx',\n          editor     : __dirname + '/src/module/common/editor.jsx',\n          themesel   : __dirname + '/src/module/common/theme.jsx',\n          shortcuts  : __dirname + '/src/module/common/shortcuts.jsx',\n          include    : __dirname + '/src/module/common/include.jsx',\n          exclude    : __dirname + '/src/module/common/exclude.jsx',\n          name       : __dirname + '/src/module/common/name.jsx',\n          url        : __dirname + '/src/module/common/url.jsx',\n\n          wavess     : __dirname + '/src/vender/waves/waves.js',\n          notify     : __dirname + '/src/vender/notify/notify.js',\n\n          mduikit_css: __dirname + '/src/vender/mduikit/mduikit.css',\n          textfield  : __dirname + '/src/vender/mduikit/textfield.jsx',\n          fab        : __dirname + '/src/vender/mduikit/fab.jsx',\n          fap        : __dirname + '/src/vender/mduikit/fap.jsx',\n          button     : __dirname + '/src/vender/mduikit/button.jsx',\n          statebutton: __dirname + '/src/vender/mduikit/statebutton.jsx',\n          selectfield: __dirname + '/src/vender/mduikit/selectfield.jsx',\n          ac         : __dirname + '/src/vender/mduikit/autocomplete.jsx',\n          dropdown   : __dirname + '/src/vender/mduikit/dropdown.jsx',\n          switch     : __dirname + '/src/vender/mduikit/switch.jsx',\n          tabs       : __dirname + '/src/vender/mduikit/tabs.jsx',\n          progress   : __dirname + '/src/vender/mduikit/progress.jsx',\n          sidebar    : __dirname + '/src/vender/mduikit/sidebar.jsx',\n          list       : __dirname + '/src/vender/mduikit/list.jsx',\n          dialog     : __dirname + '/src/vender/mduikit/dialog.jsx',\n          slider     : __dirname + '/src/vender/mduikit/slider.jsx',\n          tooltip    : __dirname + '/src/vender/mduikit/tooltip.jsx',\n          mintooltip : __dirname + '/src/vender/mduikit/mintooltip.css',\n          waves      : __dirname + '/src/vender/mduikit/waves.js',\n\n          puread     : __dirname + '/src/vender/puread/puread.min.js',\n          puplugin   : __dirname + '/src/vender/puread/puplugin.min.js',\n\n        }\n      }\n\n};\n\nmodule.exports = config;\n"
  }
]