From f794515cc7cf374a3da7416786e3f5ee2b4dc006 Mon Sep 17 00:00:00 2001 From: debuggerx Date: Wed, 15 Dec 2021 18:52:31 +0800 Subject: [PATCH] wip: add md editor for scheme description. --- .../cherry_toast/lib/resources/constants.dart | 1 - app/3rd_party/markdown_editor_ot/.gitignore | 15 + app/3rd_party/markdown_editor_ot/CHANGELOG.md | 5 + app/3rd_party/markdown_editor_ot/LICENSE | 339 ++++++++++ app/3rd_party/markdown_editor_ot/README.md | 23 + app/3rd_party/markdown_editor_ot/icons/demo.css | 539 +++++++++++++++ .../markdown_editor_ot/icons/demo_index.html | 720 +++++++++++++++++++++ .../markdown_editor_ot/icons/iconfont.css | 113 ++++ .../markdown_editor_ot/icons/iconfont.eot | Bin 0 -> 4920 bytes app/3rd_party/markdown_editor_ot/icons/iconfont.js | 1 + .../markdown_editor_ot/icons/iconfont.svg | 98 +++ .../markdown_editor_ot/icons/iconfont.ttf | Bin 0 -> 4752 bytes .../markdown_editor_ot/icons/iconfont.woff | Bin 0 -> 2804 bytes .../markdown_editor_ot/icons/iconfont.woff2 | Bin 0 -> 2296 bytes .../markdown_editor_ot/lib/customize_physics.dart | 29 + .../markdown_editor_ot/lib/fonts/iconfont.ttf | Bin 0 -> 4752 bytes .../markdown_editor_ot/lib/markdown_editor.dart | 4 + .../markdown_editor_ot/lib/src/action.dart | 288 +++++++++ .../markdown_editor_ot/lib/src/edit_perform.dart | 82 +++ .../markdown_editor_ot/lib/src/editor.dart | 352 ++++++++++ .../markdown_editor_ot/lib/src/preview.dart | 60 ++ app/3rd_party/markdown_editor_ot/pubspec.yaml | 61 ++ app/lib/main.dart | 5 +- app/lib/pages/gesture_editor.dart | 42 +- app/lib/widgets/dde_markdown_field.dart | 112 ++++ app/lib/widgets/dde_text_field.dart | 10 +- app/linux/flutter/generated_plugin_registrant.cc | 4 + app/linux/flutter/generated_plugins.cmake | 1 + app/pubspec.yaml | 4 + app/resources/langs/en.json | 4 + app/resources/langs/zh-CN.json | 4 + 31 files changed, 2907 insertions(+), 9 deletions(-) create mode 100644 app/3rd_party/markdown_editor_ot/.gitignore create mode 100644 app/3rd_party/markdown_editor_ot/CHANGELOG.md create mode 100644 app/3rd_party/markdown_editor_ot/LICENSE create mode 100644 app/3rd_party/markdown_editor_ot/README.md create mode 100644 app/3rd_party/markdown_editor_ot/icons/demo.css create mode 100644 app/3rd_party/markdown_editor_ot/icons/demo_index.html create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.css create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.eot create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.js create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.svg create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.ttf create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.woff create mode 100644 app/3rd_party/markdown_editor_ot/icons/iconfont.woff2 create mode 100644 app/3rd_party/markdown_editor_ot/lib/customize_physics.dart create mode 100644 app/3rd_party/markdown_editor_ot/lib/fonts/iconfont.ttf create mode 100644 app/3rd_party/markdown_editor_ot/lib/markdown_editor.dart create mode 100644 app/3rd_party/markdown_editor_ot/lib/src/action.dart create mode 100644 app/3rd_party/markdown_editor_ot/lib/src/edit_perform.dart create mode 100644 app/3rd_party/markdown_editor_ot/lib/src/editor.dart create mode 100644 app/3rd_party/markdown_editor_ot/lib/src/preview.dart create mode 100644 app/3rd_party/markdown_editor_ot/pubspec.yaml create mode 100644 app/lib/widgets/dde_markdown_field.dart diff --git a/app/3rd_party/cherry_toast/lib/resources/constants.dart b/app/3rd_party/cherry_toast/lib/resources/constants.dart index de2c168..8bc76ca 100755 --- a/app/3rd_party/cherry_toast/lib/resources/constants.dart +++ b/app/3rd_party/cherry_toast/lib/resources/constants.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; const String PACKAGE_NAME = "cherry_toast"; diff --git a/app/3rd_party/markdown_editor_ot/.gitignore b/app/3rd_party/markdown_editor_ot/.gitignore new file mode 100644 index 0000000..4272058 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/.gitignore @@ -0,0 +1,15 @@ +.DS_Store +.dart_tool/ + +.packages +.flutter-plugins +.pub/ + +.idea/ +*.iml +*.lock + + +build/ +ios/ +android/ diff --git a/app/3rd_party/markdown_editor_ot/CHANGELOG.md b/app/3rd_party/markdown_editor_ot/CHANGELOG.md new file mode 100644 index 0000000..9a5c387 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/CHANGELOG.md @@ -0,0 +1,5 @@ +## 1.0.2 +* Fix icon not display. +## 1.0.1 +* Change directory structure. +## 1.0.0 diff --git a/app/3rd_party/markdown_editor_ot/LICENSE b/app/3rd_party/markdown_editor_ot/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/app/3rd_party/markdown_editor_ot/README.md b/app/3rd_party/markdown_editor_ot/README.md new file mode 100644 index 0000000..53551d9 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/README.md @@ -0,0 +1,23 @@ +# markdown_editor + +Simple and easy to implement your markdown editor, it uses its own parser. + +![show](https://xia-weiyang.github.io/gif/markdown_editor.gif) + +If you only need to render, you can refer to [https://github.com/xia-weiyang/markdown_core](https://github.com/xia-weiyang/markdown_core) + +``` dart +MarkdownEditor( + initText: 'initText', + initTitle: 'initText', + onTapLink: (link){ + print('点击了链接 $link'); + }, + imageWidget: (imageUrl) { + return // Your image widget ; + }, + imageSelect: (){ // Click image select btn + return // selected image link; + }, +) +``` diff --git a/app/3rd_party/markdown_editor_ot/icons/demo.css b/app/3rd_party/markdown_editor_ot/icons/demo.css new file mode 100644 index 0000000..a67054a --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/icons/demo.css @@ -0,0 +1,539 @@ +/* Logo 字体 */ +@font-face { + font-family: "iconfont logo"; + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); + src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), + url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); +} + +.logo { + font-family: "iconfont logo"; + font-size: 160px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* tabs */ +.nav-tabs { + position: relative; +} + +.nav-tabs .nav-more { + position: absolute; + right: 0; + bottom: 0; + height: 42px; + line-height: 42px; + color: #666; +} + +#tabs { + border-bottom: 1px solid #eee; +} + +#tabs li { + cursor: pointer; + width: 100px; + height: 40px; + line-height: 40px; + text-align: center; + font-size: 16px; + border-bottom: 2px solid transparent; + position: relative; + z-index: 1; + margin-bottom: -1px; + color: #666; +} + + +#tabs .active { + border-bottom-color: #f00; + color: #222; +} + +.tab-container .content { + display: none; +} + +/* 页面布局 */ +.main { + padding: 30px 100px; + width: 960px; + margin: 0 auto; +} + +.main .logo { + color: #333; + text-align: left; + margin-bottom: 30px; + line-height: 1; + height: 110px; + margin-top: -50px; + overflow: hidden; + *zoom: 1; +} + +.main .logo a { + font-size: 160px; + color: #333; +} + +.helps { + margin-top: 40px; +} + +.helps pre { + padding: 20px; + margin: 10px 0; + border: solid 1px #e7e1cd; + background-color: #fffdef; + overflow: auto; +} + +.icon_lists { + width: 100% !important; + overflow: hidden; + *zoom: 1; +} + +.icon_lists li { + width: 100px; + margin-bottom: 10px; + margin-right: 20px; + text-align: center; + list-style: none !important; + cursor: default; +} + +.icon_lists li .code-name { + line-height: 1.2; +} + +.icon_lists .icon { + display: block; + height: 100px; + line-height: 100px; + font-size: 42px; + margin: 10px auto; + color: #333; + -webkit-transition: font-size 0.25s linear, width 0.25s linear; + -moz-transition: font-size 0.25s linear, width 0.25s linear; + transition: font-size 0.25s linear, width 0.25s linear; +} + +.icon_lists .icon:hover { + font-size: 100px; +} + +.icon_lists .svg-icon { + /* 通过设置 font-size 来改变图标大小 */ + width: 1em; + /* 图标和文字相邻时,垂直对齐 */ + vertical-align: -0.15em; + /* 通过设置 color 来改变 SVG 的颜色/fill */ + fill: currentColor; + /* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 + normalize.css 中也包含这行 */ + overflow: hidden; +} + +.icon_lists li .name, +.icon_lists li .code-name { + color: #666; +} + +/* markdown 样式 */ +.markdown { + color: #666; + font-size: 14px; + line-height: 1.8; +} + +.highlight { + line-height: 1.5; +} + +.markdown img { + vertical-align: middle; + max-width: 100%; +} + +.markdown h1 { + color: #404040; + font-weight: 500; + line-height: 40px; + margin-bottom: 24px; +} + +.markdown h2, +.markdown h3, +.markdown h4, +.markdown h5, +.markdown h6 { + color: #404040; + margin: 1.6em 0 0.6em 0; + font-weight: 500; + clear: both; +} + +.markdown h1 { + font-size: 28px; +} + +.markdown h2 { + font-size: 22px; +} + +.markdown h3 { + font-size: 16px; +} + +.markdown h4 { + font-size: 14px; +} + +.markdown h5 { + font-size: 12px; +} + +.markdown h6 { + font-size: 12px; +} + +.markdown hr { + height: 1px; + border: 0; + background: #e9e9e9; + margin: 16px 0; + clear: both; +} + +.markdown p { + margin: 1em 0; +} + +.markdown>p, +.markdown>blockquote, +.markdown>.highlight, +.markdown>ol, +.markdown>ul { + width: 80%; +} + +.markdown ul>li { + list-style: circle; +} + +.markdown>ul li, +.markdown blockquote ul>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown>ul li p, +.markdown>ol li p { + margin: 0.6em 0; +} + +.markdown ol>li { + list-style: decimal; +} + +.markdown>ol li, +.markdown blockquote ol>li { + margin-left: 20px; + padding-left: 4px; +} + +.markdown code { + margin: 0 3px; + padding: 0 5px; + background: #eee; + border-radius: 3px; +} + +.markdown strong, +.markdown b { + font-weight: 600; +} + +.markdown>table { + border-collapse: collapse; + border-spacing: 0px; + empty-cells: show; + border: 1px solid #e9e9e9; + width: 95%; + margin-bottom: 24px; +} + +.markdown>table th { + white-space: nowrap; + color: #333; + font-weight: 600; +} + +.markdown>table th, +.markdown>table td { + border: 1px solid #e9e9e9; + padding: 8px 16px; + text-align: left; +} + +.markdown>table th { + background: #F7F7F7; +} + +.markdown blockquote { + font-size: 90%; + color: #999; + border-left: 4px solid #e9e9e9; + padding-left: 0.8em; + margin: 1em 0; +} + +.markdown blockquote p { + margin: 0; +} + +.markdown .anchor { + opacity: 0; + transition: opacity 0.3s ease; + margin-left: 8px; +} + +.markdown .waiting { + color: #ccc; +} + +.markdown h1:hover .anchor, +.markdown h2:hover .anchor, +.markdown h3:hover .anchor, +.markdown h4:hover .anchor, +.markdown h5:hover .anchor, +.markdown h6:hover .anchor { + opacity: 1; + display: inline-block; +} + +.markdown>br, +.markdown>p>br { + clear: both; +} + + +.hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; +} + +.hljs-comment, +.hljs-meta { + color: #969896; +} + +.hljs-string, +.hljs-variable, +.hljs-template-variable, +.hljs-strong, +.hljs-emphasis, +.hljs-quote { + color: #df5000; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-type { + color: #a71d5d; +} + +.hljs-literal, +.hljs-symbol, +.hljs-bullet, +.hljs-attribute { + color: #0086b3; +} + +.hljs-section, +.hljs-name { + color: #63a35c; +} + +.hljs-tag { + color: #333333; +} + +.hljs-title, +.hljs-attr, +.hljs-selector-id, +.hljs-selector-class, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #795da3; +} + +.hljs-addition { + color: #55a532; + background-color: #eaffea; +} + +.hljs-deletion { + color: #bd2c00; + background-color: #ffecec; +} + +.hljs-link { + text-decoration: underline; +} + +/* 代码高亮 */ +/* PrismJS 1.15.0 +https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ +/** + * prism.js default theme for JavaScript, CSS and HTML + * Based on dabblet (http://dabblet.com) + * @author Lea Verou + */ +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + text-shadow: 0 1px white; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +pre[class*="language-"]::-moz-selection, +pre[class*="language-"] ::-moz-selection, +code[class*="language-"]::-moz-selection, +code[class*="language-"] ::-moz-selection { + text-shadow: none; + background: #b3d4fc; +} + +pre[class*="language-"]::selection, +pre[class*="language-"] ::selection, +code[class*="language-"]::selection, +code[class*="language-"] ::selection { + text-shadow: none; + background: #b3d4fc; +} + +@media print { + + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; +} + +:not(pre)>code[class*="language-"], +pre[class*="language-"] { + background: #f5f2f0; +} + +/* Inline code */ +:not(pre)>code[class*="language-"] { + padding: .1em; + border-radius: .3em; + white-space: normal; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: slategray; +} + +.token.punctuation { + color: #999; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol, +.token.deleted { + color: #905; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: #690; +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string { + color: #9a6e3a; + background: hsla(0, 0%, 100%, .5); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: #07a; +} + +.token.function, +.token.class-name { + color: #DD4A68; +} + +.token.regex, +.token.important, +.token.variable { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} + +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} diff --git a/app/3rd_party/markdown_editor_ot/icons/demo_index.html b/app/3rd_party/markdown_editor_ot/icons/demo_index.html new file mode 100644 index 0000000..efc05fc --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/icons/demo_index.html @@ -0,0 +1,720 @@ + + + + + IconFont Demo + + + + + + + + + + + +
+

+ +
+
+
    + +
  • + +
    format-bold
    +
    
    +
  • + +
  • + +
    format-color-text
    +
    
    +
  • + +
  • + +
    format-header-1
    +
    
    +
  • + +
  • + +
    format-header-2
    +
    
    +
  • + +
  • + +
    format-header-3
    +
    
    +
  • + +
  • + +
    format-header-4
    +
    
    +
  • + +
  • + +
    format-header-5
    +
    
    +
  • + +
  • + +
    format-header-6
    +
    
    +
  • + +
  • + +
    format-italic
    +
    
    +
  • + +
  • + +
    format-list-bulleted
    +
    
    +
  • + +
  • + +
    format-list-numbers
    +
    
    +
  • + +
  • + +
    format-quote
    +
    
    +
  • + +
  • + +
    format-size
    +
    
    +
  • + +
  • + +
    format-strikethrough
    +
    
    +
  • + +
  • + +
    format-title
    +
    
    +
  • + +
  • + +
    format-underline
    +
    
    +
  • + +
  • + +
    image
    +
    
    +
  • + +
  • + +
    link-variant
    +
    
    +
  • + +
  • + +
    redo
    +
    
    +
  • + +
  • + +
    redo-variant
    +
    
    +
  • + +
  • + +
    timer
    +
    
    +
  • + +
  • + +
    undo-variant
    +
    
    +
  • + +
  • + +
    undo
    +
    
    +
  • + +
  • + +
    weather-cloudy
    +
    
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 兼容性最好,支持 IE6+,及所有现代浏览器。
  • +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。
  • +
+
+

注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'iconfont';
+  src: url('iconfont.eot');
+  src: url('iconfont.eot?#iefix') format('embedded-opentype'),
+      url('iconfont.woff2') format('woff2'),
+      url('iconfont.woff') format('woff'),
+      url('iconfont.ttf') format('truetype'),
+      url('iconfont.svg#iconfont') format('svg');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="iconfont">&#x33;</span>
+
+
+

"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + format-bold +
    +
    .icon-format-bold +
    +
  • + +
  • + +
    + format-color-text +
    +
    .icon-format-color-text +
    +
  • + +
  • + +
    + format-header-1 +
    +
    .icon-format-header- +
    +
  • + +
  • + +
    + format-header-2 +
    +
    .icon-format-header-1 +
    +
  • + +
  • + +
    + format-header-3 +
    +
    .icon-format-header-2 +
    +
  • + +
  • + +
    + format-header-4 +
    +
    .icon-format-header-3 +
    +
  • + +
  • + +
    + format-header-5 +
    +
    .icon-format-header-4 +
    +
  • + +
  • + +
    + format-header-6 +
    +
    .icon-format-header-5 +
    +
  • + +
  • + +
    + format-italic +
    +
    .icon-format-italic +
    +
  • + +
  • + +
    + format-list-bulleted +
    +
    .icon-format-list-bulleted +
    +
  • + +
  • + +
    + format-list-numbers +
    +
    .icon-format-list-numbers +
    +
  • + +
  • + +
    + format-quote +
    +
    .icon-format-quote +
    +
  • + +
  • + +
    + format-size +
    +
    .icon-format-size +
    +
  • + +
  • + +
    + format-strikethrough +
    +
    .icon-format-strikethrough +
    +
  • + +
  • + +
    + format-title +
    +
    .icon-format-title +
    +
  • + +
  • + +
    + format-underline +
    +
    .icon-format-underline +
    +
  • + +
  • + +
    + image +
    +
    .icon-image +
    +
  • + +
  • + +
    + link-variant +
    +
    .icon-link-variant +
    +
  • + +
  • + +
    + redo +
    +
    .icon-redo +
    +
  • + +
  • + +
    + redo-variant +
    +
    .icon-redo-variant +
    +
  • + +
  • + +
    + timer +
    +
    .icon-timer +
    +
  • + +
  • + +
    + undo-variant +
    +
    .icon-undo-variant +
    +
  • + +
  • + +
    + undo +
    +
    .icon-undo +
    +
  • + +
  • + +
    + weather-cloudy +
    +
    .icon-weather-cloudy +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 兼容性良好,支持 IE8+,及所有现代浏览器。
  • +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
  • 不过因为本质上还是使用的字体,所以多色图标还是不支持的。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="iconfont icon-xxx"></span>
+
+
+

" + iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    format-bold
    +
    #icon-format-bold
    +
  • + +
  • + +
    format-color-text
    +
    #icon-format-color-text
    +
  • + +
  • + +
    format-header-1
    +
    #icon-format-header-
    +
  • + +
  • + +
    format-header-2
    +
    #icon-format-header-1
    +
  • + +
  • + +
    format-header-3
    +
    #icon-format-header-2
    +
  • + +
  • + +
    format-header-4
    +
    #icon-format-header-3
    +
  • + +
  • + +
    format-header-5
    +
    #icon-format-header-4
    +
  • + +
  • + +
    format-header-6
    +
    #icon-format-header-5
    +
  • + +
  • + +
    format-italic
    +
    #icon-format-italic
    +
  • + +
  • + +
    format-list-bulleted
    +
    #icon-format-list-bulleted
    +
  • + +
  • + +
    format-list-numbers
    +
    #icon-format-list-numbers
    +
  • + +
  • + +
    format-quote
    +
    #icon-format-quote
    +
  • + +
  • + +
    format-size
    +
    #icon-format-size
    +
  • + +
  • + +
    format-strikethrough
    +
    #icon-format-strikethrough
    +
  • + +
  • + +
    format-title
    +
    #icon-format-title
    +
  • + +
  • + +
    format-underline
    +
    #icon-format-underline
    +
  • + +
  • + +
    image
    +
    #icon-image
    +
  • + +
  • + +
    link-variant
    +
    #icon-link-variant
    +
  • + +
  • + +
    redo
    +
    #icon-redo
    +
  • + +
  • + +
    redo-variant
    +
    #icon-redo-variant
    +
  • + +
  • + +
    timer
    +
    #icon-timer
    +
  • + +
  • + +
    undo-variant
    +
    #icon-undo-variant
    +
  • + +
  • + +
    undo
    +
    #icon-undo
    +
  • + +
  • + +
    weather-cloudy
    +
    #icon-weather-cloudy
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/app/3rd_party/markdown_editor_ot/icons/iconfont.css b/app/3rd_party/markdown_editor_ot/icons/iconfont.css new file mode 100644 index 0000000..a87d3b4 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/icons/iconfont.css @@ -0,0 +1,113 @@ +@font-face {font-family: "iconfont"; + src: url('iconfont.eot?t=1556453824121'); /* IE9 */ + src: url('iconfont.eot?t=1556453824121#iefix') format('embedded-opentype'), /* IE6-IE8 */ + url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'), + url('iconfont.woff?t=1556453824121') format('woff'), + url('iconfont.ttf?t=1556453824121') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ + url('iconfont.svg?t=1556453824121#iconfont') format('svg'); /* iOS 4.1- */ +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-format-bold:before { + content: "\e757"; +} + +.icon-format-color-text:before { + content: "\e758"; +} + +.icon-format-header-:before { + content: "\e75b"; +} + +.icon-format-header-1:before { + content: "\e75c"; +} + +.icon-format-header-2:before { + content: "\e75d"; +} + +.icon-format-header-3:before { + content: "\e75e"; +} + +.icon-format-header-4:before { + content: "\e75f"; +} + +.icon-format-header-5:before { + content: "\e760"; +} + +.icon-format-italic:before { + content: "\e762"; +} + +.icon-format-list-bulleted:before { + content: "\e764"; +} + +.icon-format-list-numbers:before { + content: "\e765"; +} + +.icon-format-quote:before { + content: "\e768"; +} + +.icon-format-size:before { + content: "\e769"; +} + +.icon-format-strikethrough:before { + content: "\e76a"; +} + +.icon-format-title:before { + content: "\e76f"; +} + +.icon-format-underline:before { + content: "\e770"; +} + +.icon-image:before { + content: "\e7ac"; +} + +.icon-link-variant:before { + content: "\e7d8"; +} + +.icon-redo:before { + content: "\e873"; +} + +.icon-redo-variant:before { + content: "\e874"; +} + +.icon-timer:before { + content: "\e8eb"; +} + +.icon-undo-variant:before { + content: "\e907"; +} + +.icon-undo:before { + content: "\e908"; +} + +.icon-weather-cloudy:before { + content: "\e92d"; +} + diff --git a/app/3rd_party/markdown_editor_ot/icons/iconfont.eot b/app/3rd_party/markdown_editor_ot/icons/iconfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..954e8600eb413da8c6a9f7c48ccabfcb16b30385 GIT binary patch literal 4920 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 literal 0 HcmV?d00001 diff --git a/app/3rd_party/markdown_editor_ot/icons/iconfont.js b/app/3rd_party/markdown_editor_ot/icons/iconfont.js new file mode 100644 index 0000000..d7b3f07 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/icons/iconfont.js @@ -0,0 +1 @@ +!function(v){var h,t='',o=(h=document.getElementsByTagName("script"))[h.length-1].getAttribute("data-injectcss");if(o&&!v.__iconfont__svg__cssinject__){v.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(h){console&&console.log(h)}}!function(h){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(h,0);else{var o=function(){document.removeEventListener("DOMContentLoaded",o,!1),h()};document.addEventListener("DOMContentLoaded",o,!1)}else document.attachEvent&&(t=h,l=v.document,i=!1,a=function(){i||(i=!0,t())},(e=function(){try{l.documentElement.doScroll("left")}catch(h){return void setTimeout(e,50)}a()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,a())});var t,l,i,a,e}(function(){var h,o;(h=document.createElement("div")).innerHTML=t,t=null,(o=h.getElementsByTagName("svg")[0])&&(o.setAttribute("aria-hidden","true"),o.style.position="absolute",o.style.width=0,o.style.height=0,o.style.overflow="hidden",function(h,o){o.firstChild?function(h,o){o.parentNode.insertBefore(h,o)}(h,o.firstChild):o.appendChild(h)}(o,document.body))})}(window); \ No newline at end of file diff --git a/app/3rd_party/markdown_editor_ot/icons/iconfont.svg b/app/3rd_party/markdown_editor_ot/icons/iconfont.svg new file mode 100644 index 0000000..d299081 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/icons/iconfont.svg @@ -0,0 +1,98 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/3rd_party/markdown_editor_ot/icons/iconfont.ttf b/app/3rd_party/markdown_editor_ot/icons/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fb6f07442d2aaf77f14a677bd07849bfa7599c7f GIT binary patch literal 4752 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 S15(KsX%xD literal 0 HcmV?d00001 diff --git a/app/3rd_party/markdown_editor_ot/lib/customize_physics.dart b/app/3rd_party/markdown_editor_ot/lib/customize_physics.dart new file mode 100644 index 0000000..48f9398 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/lib/customize_physics.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class CustomizePhysics extends ScrollPhysics { + const CustomizePhysics({ + ScrollPhysics? parent, + }) : super(parent: parent); + + @override + CustomizePhysics applyTo(ScrollPhysics? ancestor) { + return CustomizePhysics(parent: buildParent(ancestor)); + } + + @override + bool shouldAcceptUserOffset(ScrollMetrics position) => false; + + @override + bool get allowImplicitScrolling => false; + + @override + double applyBoundaryConditions(ScrollMetrics position, double value) { + return 0; + } + + @override + Simulation? createBallisticSimulation( + ScrollMetrics position, double velocity) { + return null; + } +} diff --git a/app/3rd_party/markdown_editor_ot/lib/fonts/iconfont.ttf b/app/3rd_party/markdown_editor_ot/lib/fonts/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..fb6f07442d2aaf77f14a677bd07849bfa7599c7f GIT binary patch literal 4752 zcmd^DYiu0V6+Y+A+8g}LdS7uu%&vF625gq%<%~ngGe#9@`u5dd+$b z32MtgsY+Xk2BZWUs;W{@MZ6%8`l}TZ`O%-HRV8eNN+2X?$BB!OsG#Z(ulwDZS#S*P zk4pX38J|1no_p_k-*fMpn1~ulCxr$M+^6n+qx$QQiFg;Nd%t;Ke{AUOZ`_9dchJv_ z6|%G6{>xuKArcr@8Yc7SCjS1G_k8}FS1|VjKxzsE=ljYf;BNxL zQ-$)`aUkIL4w~_NaV)#lbL+EMKZ1UukUcxgchI*mz6JfjOtzp6-(USMQ48dY!fbJ_ z95~_m6Vc|sVa{cO?ux#q(}NC7CqgT&S^u<~6i`1obN9Yet%ILYqc!*1%5SgAiPm>* z(LqWE-AyDU6_4c9PW!0d>`7_^UZOR#Z;dxl2kjsp*?;(cth5+gBb?7oWuTM#(!#kd4*z(wSwvtTiS-ty%YP!X^-*1^p=maSLST7@7r$85vU+AbMov zEI=H|IAZ}KN=DHF#FmU%3lL>8UbO)6CgYj~h&(ONS%4g%<+26H23lUR0C_>zp{oRB z2wjJ+5|Ar&z1ISnHRh9`d+AYngO<6CkMXnoE50Fa7b9X+=;C?truaxXtjs9CR6cU} z9J?G(NuObde=e5bjaJkV>Ix;}9qg%B%r?sYVNUmPchD6Cl8A^s7cOiIhu_fAY|}rv zEyCi|J=M>7$G+nnThTe34zt;&eS3oUoZ{7rIJs}<5g8|JQy;_$b2o0|MvkY0{qN!&`#;!nAQlT9%rd5ghqk;|)8CTWeX~rC$9!Iw zr#=eVLmcXSRqLZ#f$_WP^s?MmNVAl`hPXlPs&zOj zRO}XYHHxj34u@~GK0p7GA}X0R%4B$&!>KU-*6eTfVkT3gNmteMSTgl!ME%%C$UP1w zv5PHG;!c@wY5d8Qiz{XvWh|4SFBFl=RF;k$(GMTiqtT4@CymWi7c#s6WlltTPDC;b zJyr7c_<9x~+uE;Dk%1Pdi(T=wE9i>XSs$V5Sfp1~UB#!eAUEiO+1l$Wb{(>_HW}>l znj1c;c}Xs==qoa^wgB5Eh*4ynje~m^N(5?wL{V#Dk1w6F==Q97zZ29B5lii6xqq>gf7iS<)I2==!rq0w z!^3;H&Es~vyZRD+?u%_6&ySn`{a9Nwk2Htco3QYO;bFA9lQEAQE4#qqVq1T_|8moR z7;I|})#6e?9^vF<96%^A@+gY={LY|DaSdKjU;MCdyzBKxhKs$IaBRF8zl)VC&vZ_I zPei+3VuaStsf;4);C687Y!NW=E>4EP54_?|f4bZ0cO&ch>MreQPtQ?p*M$qm@nrrOhoE%e7R8)1U>PJ!|}=!X8YRE_P}85@sZ2#Ivnp_e)*bXNA<}}rXtU= zsDe3H6y*wL3jbhm+t3|59M@j9W{y0*%)~wFS8R=S2qb5elW_=zLLtAiJMDMMeJ=L= zm$q7aoGbXRdq^ zlO<6>Rl?n+OKu-%h(F22^qJFz6R=N6pG9QYLBwWAMBs`eLjiI2WblW_XD>DStYbD4 z7&;O?HrL|ak~+kEC?#+V#?;{8(ddbD;_yR4IbW2=4h|ozmkfCPh?B*^RQjMiV`K~o zx3KrEN&p&BxB`0E#vQ=%+e^lzM%?mB zs3YvaEt7ia2R6>g^Ve-$;1>CojVqwPZQ~B$@7Z`AZbKj2cs*@q#l{;b!)_aIqzLC4 z?k;KBvNj$VJr~H06=x=jGv$UFa!i|?&u2@uZta`VN^`m5Okii(Z1z(lE721GE!A#0I{y>HucVm@_x( zGl$FbB!0A&@lIg<1h^Hc0Bpalp$1LzzymaEj-3IYG0g6xDRb2<-AVm;YZle!_nDR! zWS*Gk3BLGk@wHDo3s%^{bzIL*-o&?X18&Pr+>FZI%A47RyS}4POc?-AmR%)Cm zmI~Q&?`SbU?y~W*V!l}FEo*1XEjC5|YeXycw%**`xuGB1(2sBECpPqx%{ISWIh)Uo zdF)<3HwS&@^LY*F=Uz{onJ ActionImageState(); +} + +class ActionImageState extends State { + IconData? _getImageIconCode() { + return _defaultImageAttributes + .firstWhere((img) => img.type == widget.type) + .iconData; + } + + void _disposeAction() { + var firstWhere = + _defaultImageAttributes.firstWhere((img) => img.type == widget.type); + if (firstWhere.type == ActionType.image && widget.getCursorPosition != null) { + var cursorPosition = widget.getCursorPosition!(); + if (widget.imageSelect != null) { + widget.imageSelect!().then( + (str) { + debugPrint('Image select $str'); + if (str.isNotEmpty) { + // 延迟执行它,等待TextFiled获取焦点 + // 否则将无法成功插入文本 + Timer(const Duration(milliseconds: 200), () { + widget.tap(widget.type, '![]($str)', 0, cursorPosition); + }); + } + }, + onError: print, + ); + return; + } + } + widget.tap(widget.type, firstWhere.text ?? '', firstWhere.positionReverse ?? 0); + } + + @override + Widget build(BuildContext context) { + return Tooltip( + preferBelow: false, + message: _defaultImageAttributes + .firstWhere((img) => img.type == widget.type) + .tip, + child: IconButton( + icon: Icon( + _getImageIconCode(), + color: widget.color, + ), + onPressed: _disposeAction, + ), + ); + } +} + +const _fontPackage = 'markdown_editor_ot'; + +const _defaultImageAttributes = [ + ImageAttributes( + type: ActionType.done, + tip: '完成', + iconData: Icons.done, + ), + ImageAttributes( + type: ActionType.undo, + tip: '撤销', + iconData: const IconData( + 0xe907, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.redo, + tip: '恢复', + iconData: const IconData( + 0xe874, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.image, + text: '![]()', + tip: '图片', + positionReverse: 3, + iconData: const IconData( + 0xe7ac, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.link, + text: '[]()', + tip: '链接', + positionReverse: 3, + iconData: const IconData( + 0xe7d8, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.fontBold, + text: '****', + tip: '加粗', + positionReverse: 2, + iconData: const IconData( + 0xe757, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.fontItalic, + text: '**', + tip: '斜体', + positionReverse: 1, + iconData: const IconData( + 0xe762, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.fontStrikethrough, + text: '~~~~', + tip: '删除线', + positionReverse: 2, + iconData: const IconData( + 0xe76a, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.textQuote, + text: '\n>', + tip: '文字引用', + positionReverse: 0, + iconData: const IconData( + 0xe768, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.list, + text: '\n- ', + tip: '无序列表', + positionReverse: 0, + iconData: const IconData( + 0xe764, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.h4, + text: '\n#### ', + tip: '四级标题', + positionReverse: 0, + iconData: const IconData( + 0xe75e, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.h5, + text: '\n##### ', + tip: '五级标题', + positionReverse: 0, + iconData: const IconData( + 0xe75f, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.h1, + text: '\n# ', + tip: '一级标题', + positionReverse: 0, + iconData: const IconData( + 0xe75b, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.h2, + text: '\n## ', + tip: '二级标题', + positionReverse: 0, + iconData: const IconData( + 0xe75c, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), + ImageAttributes( + type: ActionType.h3, + text: '\n### ', + tip: '三级标题', + positionReverse: 0, + iconData: const IconData( + 0xe75d, + fontFamily: 'MyIconFont', + fontPackage: _fontPackage, + ), + ), +]; + +enum ActionType { + done, + undo, + redo, + image, + link, + fontBold, + fontItalic, + fontStrikethrough, + fontDeleteLine, + textQuote, + list, + h1, + h2, + h3, + h4, + h5, +} + +class ImageAttributes { + const ImageAttributes({ + this.tip = '', + this.text, + this.positionReverse, + required this.type, + required this.iconData, + }); + + final ActionType type; + final IconData iconData; + final String tip; + final String? text; + final int? positionReverse; +} + +/// Call this method after clicking the [ActionImage] and completing a series of actions. +/// [text] Adding text. +/// [position] Cursor position that reverse order. +/// [cursorPosition] Will start insert text at this position. +typedef void TapFinishCallback( + ActionType type, + String text, + int positionReverse, [ + int? cursorPosition, +]); + +/// Call this method after clicking the ImageAction. +/// return your select image path. +typedef Future ImageSelectCallback(); + +/// Get the current cursor position. +typedef int GetCursorPosition(); diff --git a/app/3rd_party/markdown_editor_ot/lib/src/edit_perform.dart b/app/3rd_party/markdown_editor_ot/lib/src/edit_perform.dart new file mode 100644 index 0000000..2ab3320 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/lib/src/edit_perform.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; + +/// 撤销与前进 +class EditPerform { + EditPerform( + this._textEditingController, { + this.initText = '', + }); + + /// 最大的存储长度 + final _maxLength = 50; + + /// 初始文本 + final String initText; + + var _undoList = <_EditData>[]; + var _redoList = <_EditData>[]; + + final TextEditingController _textEditingController; + + void change(text) { + if (_textEditingController.text != '') { + if (_undoList.isNotEmpty) { + if (_textEditingController.text == _undoList.last.text) return; + } + if (_undoList.length >= _maxLength) _undoList.removeAt(0); + _undoList.add(_EditData(_textEditingController.text, + _textEditingController.selection.baseOffset)); + _redoList.clear(); + } + } + + /// 撤销 + void undo() { +// print(_undoList); + if (_undoList.isNotEmpty) { + _redoList.add(_undoList.last); + _undoList.removeLast(); + if (_undoList.isNotEmpty) { + _textEditingController.value = TextEditingValue( + text: _undoList.last.text, + selection: TextSelection( + extentOffset: _undoList.last.position, + baseOffset: _undoList.last.position), + ); + } else { + _textEditingController.value = TextEditingValue( + text: initText, + selection: TextSelection( + extentOffset: initText.length, baseOffset: initText.length), + ); + } + } + } + + /// 恢复 + void redo() { +// print(_redoList); + if (_redoList.isNotEmpty) { + _textEditingController.value = TextEditingValue( + text: _redoList.last.text, + selection: TextSelection( + extentOffset: _redoList.last.position, + baseOffset: _redoList.last.position), + ); + _undoList.add(_redoList.last); + _redoList.removeLast(); + } + } +} + +class _EditData { + final String text; + final int position; + + _EditData(this.text, this.position); + + @override + String toString() { + return 'text:$text position:$position'; + } +} diff --git a/app/3rd_party/markdown_editor_ot/lib/src/editor.dart b/app/3rd_party/markdown_editor_ot/lib/src/editor.dart new file mode 100644 index 0000000..e2a5df5 --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/lib/src/editor.dart @@ -0,0 +1,352 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:markdown_editor_ot/src/action.dart'; +import 'package:markdown_editor_ot/customize_physics.dart'; +import 'package:markdown_editor_ot/src/edit_perform.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +typedef void OnComplete(String content); + +class MdEditor extends StatefulWidget { + MdEditor({ + Key? key, + this.textStyle, + this.padding = const EdgeInsets.all(0.0), + this.initText, + this.hintText, + this.hintTextStyle, + this.imageSelect, + this.textChange, + this.actionIconColor, + this.cursorColor, + this.appendBottomWidget, + this.splitWidget, + this.textFocusNode, + required this.onComplete, + }) : super(key: key); + + final TextStyle? textStyle; + final TextStyle? hintTextStyle; + final EdgeInsetsGeometry padding; + final String? initText; + final String? hintText; + + /// see [ImageSelectCallback] + final ImageSelectCallback? imageSelect; + + final VoidCallback? textChange; + + /// Change icon color, eg: color of font_bold icon. + final Color? actionIconColor; + + final Color? cursorColor; + + final Widget? appendBottomWidget; + + final Widget? splitWidget; + + final FocusNode? textFocusNode; + + final OnComplete onComplete; + + @override + State createState() => MdEditorState(); +} + +class MdEditorState extends State with AutomaticKeepAliveClientMixin { + final _textEditingController = TextEditingController(text: ''); + var _editPerform; + SharedPreferences? _pres; + + String getText() { + return _textEditingController.value.text; + } + + // 将文本框光标移动至末尾 + void moveTextCursorToEnd() { + final str = _textEditingController.text; + _textEditingController.value = TextEditingValue(text: str, selection: TextSelection.collapsed(offset: str.length)); + } + + @override + void initState() { + super.initState(); + _textEditingController.text = widget.initText ?? ''; + + _editPerform = EditPerform( + _textEditingController, + initText: _textEditingController.text, + ); + } + + void _disposeText( + ActionType type, + String text, + int index, [ + int? cursorPosition, + ]) { + final _tempKey = 'markdown_editor_${type.toString()}'; + _pres?.setInt(_tempKey, (_pres?.getInt(_tempKey) ?? 0) + 1); + debugPrint('$_tempKey ${_pres?.getInt(_tempKey)}'); + + var position = cursorPosition ?? _textEditingController.selection.base.offset; + + if (position < 0) { + print('WARN: The insert position value is $position'); + return; + } + + var startText = _textEditingController.text.substring(0, position); + var endText = _textEditingController.text.substring(position); + + var str = startText + text + endText; + _textEditingController.value = + TextEditingValue(text: str, selection: TextSelection.collapsed(offset: startText.length + text.length - index)); + + if (widget.textChange != null) widget.textChange!(); + + _editPerform.change(_textEditingController.text); + } + + /// 获取光标位置 + int _getCursorPosition() { + if (_textEditingController.text.isEmpty) return 0; + if (_textEditingController.selection.base.offset < 0) return _textEditingController.text.length; + return _textEditingController.selection.base.offset; + } + + Future _initSharedPreferences() async { + _pres = await SharedPreferences.getInstance(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return Stack( + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 40.0), + child: SingleChildScrollView( + child: Padding( + padding: widget.padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextField( + maxLines: null, + minLines: 7, + textAlignVertical: TextAlignVertical.top, + cursorColor: widget.cursorColor, + cursorWidth: 1.5, + controller: _textEditingController, + focusNode: widget.textFocusNode, + autofocus: false, + scrollPhysics: const CustomizePhysics(), + style: widget.textStyle ?? + TextStyle( + fontSize: 17, + height: kIsWeb ? null : 1.3, + ), + onChanged: (text) { + _editPerform.change(text); + if (widget.textChange != null) widget.textChange!(); + }, + decoration: InputDecoration( + hintText: widget.hintText ?? '请输入内容', + border: InputBorder.none, + hintStyle: widget.hintTextStyle, + ), + ), + widget.appendBottomWidget ?? const SizedBox(), + ], + ), + ), + ), + ), + Align( + alignment: Alignment.bottomLeft, + child: Container( + height: 40.0, + width: MediaQuery.of(context).size.width, + child: Ink( + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark ? Colors.black87 : const Color(0xFFF0F0F0), + boxShadow: [ + BoxShadow( + color: + Theme.of(context).brightness == Brightness.dark ? Colors.black87 : const Color(0xAAF0F0F0)), + ], + ), + child: FutureBuilder( + future: _pres == null ? _initSharedPreferences() : null, + builder: (con, snap) { + final widgets = []; + + widgets.add(ActionImage( + type: ActionType.done, + color: widget.actionIconColor, + tap: (t, s, i, [p]) { + widget.onComplete(getText()); + }, + )); + + widgets.add(ActionImage( + type: ActionType.undo, + color: widget.actionIconColor, + tap: (t, s, i, [p]) { + _editPerform.undo(); + }, + )); + widgets.add(ActionImage( + type: ActionType.redo, + color: widget.actionIconColor, + tap: (t, s, i, [p]) { + _editPerform.redo(); + }, + )); + + // sort + if (snap.connectionState == ConnectionState.done || snap.connectionState == ConnectionState.none) + widgets.addAll(_getSortActionWidgets().map((sort) => sort.widget)); + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: widgets, + ), + ); + }, + ), + ), + ), + ), + ], + ); + } + + /// Sort action buttons by used count. + List<_SortActionWidget> _getSortActionWidgets() { + final sortWidget = <_SortActionWidget>[]; + final key = 'markdown_editor'; + final getSortValue = (ActionType type) { + return int.parse((_pres?.get('${key}_${type.toString()}') ?? '0').toString()); + }; + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.image), + widget: ActionImage( + type: ActionType.image, + color: widget.actionIconColor, + tap: _disposeText, + imageSelect: widget.imageSelect, + getCursorPosition: _getCursorPosition, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.link), + widget: ActionImage( + type: ActionType.link, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.fontBold), + widget: ActionImage( + type: ActionType.fontBold, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.fontItalic), + widget: ActionImage( + type: ActionType.fontItalic, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.fontStrikethrough), + widget: ActionImage( + type: ActionType.fontStrikethrough, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.textQuote), + widget: ActionImage( + type: ActionType.textQuote, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.list), + widget: ActionImage( + type: ActionType.list, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.h4), + widget: ActionImage( + type: ActionType.h4, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.h5), + widget: ActionImage( + type: ActionType.h5, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.h1), + widget: ActionImage( + type: ActionType.h1, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.h2), + widget: ActionImage( + type: ActionType.h2, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + sortWidget.add(_SortActionWidget( + sortValue: getSortValue(ActionType.h3), + widget: ActionImage( + type: ActionType.h3, + color: widget.actionIconColor, + tap: _disposeText, + ), + )); + + sortWidget.sort((a, b) => (b.sortValue).compareTo(a.sortValue)); + + return sortWidget; + } + + @override + bool get wantKeepAlive => true; +} + +class _SortActionWidget { + final ActionImage widget; + final int sortValue; + + _SortActionWidget({ + required this.widget, + required this.sortValue, + }); +} diff --git a/app/3rd_party/markdown_editor_ot/lib/src/preview.dart b/app/3rd_party/markdown_editor_ot/lib/src/preview.dart new file mode 100644 index 0000000..b14babf --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/lib/src/preview.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:markdown_core/builder.dart'; +import 'package:markdown_core/markdown.dart'; + +class MdPreview extends StatefulWidget { + MdPreview({ + Key? key, + required this.text, + this.padding = const EdgeInsets.all(0.0), + this.onTapLink, + required this.widgetImage, + this.textStyle, + }) : super(key: key); + + final String text; + final EdgeInsetsGeometry padding; + final WidgetImage widgetImage; + final TextStyle? textStyle; + + /// Call this method when it tap link of markdown. + /// If [onTapLink] is null,it will open the link with your default browser. + final TapLinkCallback? onTapLink; + + @override + State createState() => MdPreviewState(); +} + +class MdPreviewState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return SingleChildScrollView( + child: Padding( + padding: widget.padding, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Markdown( + data: widget.text, + maxWidth: constraints.maxWidth, + linkTap: (link) { + debugPrint(link); + if (widget.onTapLink != null) { + widget.onTapLink!(link); + } + }, + image: widget.widgetImage, + textStyle: widget.textStyle, + ); + }, + ), + ), + ); + } + + @override + bool get wantKeepAlive => true; +} + +typedef void TapLinkCallback(String link); diff --git a/app/3rd_party/markdown_editor_ot/pubspec.yaml b/app/3rd_party/markdown_editor_ot/pubspec.yaml new file mode 100644 index 0000000..00f847b --- /dev/null +++ b/app/3rd_party/markdown_editor_ot/pubspec.yaml @@ -0,0 +1,61 @@ +name: markdown_editor_ot +description: Simple and easy to implement your markdown editor, it uses its own parser. +version: 1.0.2 +homepage: https://github.com/xia-weiyang/markdown_editor_ot + +environment: + sdk: '>=2.12.0 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + shared_preferences: ^2.0.4 + markdown_core: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.io/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + + fonts: + - family: MyIconFont + fonts: + - asset: packages/markdown_editor_ot/fonts/iconfont.ttf + + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.io/custom-fonts/#from-packages \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index eeca30d..74cf9c3 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -39,6 +39,7 @@ class MyApp extends StatelessWidget { builder: (context, child) { var isDarkMode = context.watch().isDarkMode; var brightnessMode = context.watch().brightnessMode; + var activeColor = context.watch().currentActiveColor; H().sp.updateInt(SPKeys.brightnessMode, brightnessMode?.index ?? 0); late bool showDarkMode; if (brightnessMode == BrightnessMode.system) { @@ -48,7 +49,9 @@ class MyApp extends StatelessWidget { } return MaterialApp( title: CodegenLoader.mapLocales[context.locale.toString()]?[LocaleKeys.app_name], - theme: showDarkMode ? darkTheme : lightTheme, + theme: (showDarkMode ? darkTheme : lightTheme).copyWith( + highlightColor: activeColor, + ), localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, diff --git a/app/lib/pages/gesture_editor.dart b/app/lib/pages/gesture_editor.dart index deff43b..b29d6e7 100644 --- a/app/lib/pages/gesture_editor.dart +++ b/app/lib/pages/gesture_editor.dart @@ -12,6 +12,7 @@ import 'package:dde_gesture_manager/utils/keyboard_mapper.dart'; import 'package:dde_gesture_manager/utils/notificator.dart'; import 'package:dde_gesture_manager/widgets/dde_button.dart'; import 'package:dde_gesture_manager/widgets/dde_data_table.dart'; +import 'package:dde_gesture_manager/widgets/dde_markdown_field.dart'; import 'package:dde_gesture_manager/widgets/dde_text_field.dart'; import 'package:dde_gesture_manager/widgets/table_cell_shortcut_listener.dart'; import 'package:dde_gesture_manager/widgets/table_cell_text_field.dart'; @@ -263,7 +264,7 @@ class GestureEditor extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.all(8.0), - child: Text('name:'), + child: Text(LocaleKeys.gesture_editor_info_name).tr(), ), Expanded( child: DTextField( @@ -273,24 +274,57 @@ class GestureEditor extends StatelessWidget { val = val.trim(); schemeProvider.setProps(name: val); var localSchemesProvider = context.read(); - if (!localSchemesProvider.schemes!.every((element) => element.scheme.name != val)) { + if (!localSchemesProvider.schemes! + .where((element) => element.scheme.id != schemeProvider.id) + .every((element) => element.scheme.name != val)) { Notificator.error( context, title: LocaleKeys.info_scheme_name_conflict_title.tr(), description: LocaleKeys.info_scheme_name_conflict_description.tr(), ); - return; + return false; } - ; var localSchemeEntry = localSchemesProvider.schemes! .firstWhere((ele) => ele.scheme.id == schemeProvider.id); localSchemeEntry.scheme.name = val; localSchemeEntry.save(localSchemesProvider); + return true; }, ), ), ], ), + Divider(), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Padding( + padding: const EdgeInsets.only(top: 5), + child: Text(LocaleKeys.gesture_editor_info_description).tr(), + ), + ), + Expanded( + child: ConstrainedBox( + constraints: BoxConstraints(minHeight: kMinInteractiveDimension, maxHeight: 300), + child: DMarkdownField( + initText: schemeProvider.description, + readOnly: schemeProvider.readOnly, + onComplete: (content) { + content = content.trim(); + schemeProvider.setProps(description: content); + var localSchemesProvider = context.read(); + var localSchemeEntry = localSchemesProvider.schemes! + .firstWhere((ele) => ele.scheme.id == schemeProvider.id); + localSchemeEntry.scheme.description = content; + localSchemeEntry.save(localSchemesProvider); + }, + ), + ), + ), + ], + ), ], ), ), diff --git a/app/lib/widgets/dde_markdown_field.dart b/app/lib/widgets/dde_markdown_field.dart new file mode 100644 index 0000000..9e07d53 --- /dev/null +++ b/app/lib/widgets/dde_markdown_field.dart @@ -0,0 +1,112 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:dde_gesture_manager/constants/constants.dart'; +import 'package:dde_gesture_manager/extensions.dart'; +import 'package:dde_gesture_manager/models/settings.provider.dart'; +import 'package:flutter/material.dart'; +import 'package:markdown_editor_ot/markdown_editor.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class DMarkdownField extends StatefulWidget { + const DMarkdownField({ + Key? key, + required this.initText, + required this.onComplete, + required this.readOnly, + }) : super(key: key); + + final bool readOnly; + final String? initText; + final OnComplete onComplete; + + @override + _DMarkdownFieldState createState() => _DMarkdownFieldState(); +} + +class _DMarkdownFieldState extends State { + String? _previewText; + + bool get isPreview => _previewText != null || widget.readOnly; + + final FocusNode _focusNode = FocusNode(); + + @override + void initState() { + _previewText = widget.initText; + super.initState(); + } + + _launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + throw 'Could not launch $url'; + } + } + + @override + void didUpdateWidget(covariant DMarkdownField oldWidget) { + if (oldWidget.initText != widget.initText) { + setState(() { + _previewText = widget.initText; + }); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + return Focus( + child: Builder(builder: (context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(defaultBorderRadius), + color: Colors.grey.withOpacity(.3), + border: Border.all( + width: 2, + color: Focus.of(context).hasFocus && !widget.readOnly + ? context.watch().activeColor ?? Color(0xff565656) + : Color(0xff565656)), + ), + child: isPreview + ? GestureDetector( + onTap: widget.readOnly + ? null + : () { + setState(() { + _previewText = null; + }); + }, + child: MouseRegion( + cursor: widget.readOnly ? SystemMouseCursors.basic : SystemMouseCursors.click, + child: MdPreview( + text: _previewText ?? '', + padding: EdgeInsets.only(left: 15), + onTapLink: _launchURL, + widgetImage: (imageUrl) => CachedNetworkImage( + imageUrl: imageUrl, + placeholder: (context, url) => const SizedBox( + width: double.infinity, + height: 300, + child: Center(child: CircularProgressIndicator()), + ), + errorWidget: (context, url, error) => const Icon(Icons.error), + ), + ), + ), + ) + : MdEditor( + initText: widget.initText, + textFocusNode: _focusNode, + padding: EdgeInsets.symmetric(horizontal: 5), + onComplete: (content) { + setState(() { + _previewText = content; + }); + widget.onComplete(content); + }, + ), + ); + }), + ); + } +} diff --git a/app/lib/widgets/dde_text_field.dart b/app/lib/widgets/dde_text_field.dart index 17fd26d..aec1aa3 100644 --- a/app/lib/widgets/dde_text_field.dart +++ b/app/lib/widgets/dde_text_field.dart @@ -8,7 +8,7 @@ class DTextField extends StatefulWidget { final String? initText; final String? hint; final bool readOnly; - final Function(String value) onComplete; + final bool Function(String value) onComplete; const DTextField({ Key? key, @@ -40,8 +40,11 @@ class _DTextFieldState extends State { } _handleFocusChange() { - if (!_focusNode.hasFocus) { - widget.onComplete(_controller.text); + if (!_focusNode.hasFocus && !widget.readOnly) { + var ok = widget.onComplete(_controller.text); + if (!ok) { + _focusNode.requestFocus(); + } } } @@ -74,6 +77,7 @@ class _DTextFieldState extends State { padding: const EdgeInsets.only(left: 15), child: TextField( readOnly: widget.readOnly, + style: widget.readOnly ? TextStyle(color: Colors.grey) : null, focusNode: _focusNode, cursorColor: context.watch().activeColor, decoration: InputDecoration.collapsed(hintText: widget.hint), diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index da1524f..0d852ba 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -7,12 +7,16 @@ #include "generated_plugin_registrant.h" #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_platform_alert_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterPlatformAlertPlugin"); flutter_platform_alert_plugin_register_with_registrar(flutter_platform_alert_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); g_autoptr(FlPluginRegistrar) window_manager_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); window_manager_plugin_register_with_registrar(window_manager_registrar); diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index 5a9d113..9945fd3 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_platform_alert + url_launcher_linux window_manager ) diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 42a6021..979df7b 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -38,10 +38,14 @@ dependencies: uuid: ^3.0.5 adaptive_scrollbar: ^2.1.0 flutter_platform_alert: ^0.2.1 + markdown_editor_ot: + path: 3rd_party/markdown_editor_ot cherry_toast: path: 3rd_party/cherry_toast xdg_directories_web: path: 3rd_party/xdg_directories_web + cached_network_image: ^3.2.0 + url_launcher: ^6.0.17 dev_dependencies: flutter_test: diff --git a/app/resources/langs/en.json b/app/resources/langs/en.json index f668426..276b910 100644 --- a/app/resources/langs/en.json +++ b/app/resources/langs/en.json @@ -55,6 +55,10 @@ "built_in": "built-in", "commandline": "commandline", "shortcut": "shortcut" + }, + "info": { + "name": "Scheme Name", + "description": "Description" } }, "operation": { diff --git a/app/resources/langs/zh-CN.json b/app/resources/langs/zh-CN.json index c623dcf..53cdbdb 100644 --- a/app/resources/langs/zh-CN.json +++ b/app/resources/langs/zh-CN.json @@ -55,6 +55,10 @@ "built_in": "内置操作", "commandline": "命令行", "shortcut": "快捷键" + }, + "info": { + "name": "方案名称", + "description": "方案描述" } }, "operation": {