├── Dockerfile ├── LICENSE ├── README.md ├── chromeDriver ├── chromedriver_linux64 │ ├── LICENSE.chromedriver │ └── chromedriver ├── chromedriver_mac64 │ ├── LICENSE.chromedriver │ └── chromedriver ├── chromedriver_mac_arm64 │ ├── LICENSE.chromedriver │ └── chromedriver └── chromedriver_win32 │ ├── LICENSE.chromedriver │ └── chromedriver.exe ├── docker-compose.yml ├── pom.xml ├── readme_images ├── Snipaste_2020-10-19_15-16-27.png ├── Snipaste_2020-10-19_15-16-40.png ├── Snipaste_2020-10-19_15-16-52.png ├── favicon.ico ├── img.png ├── img_1.png ├── img_2.png ├── img_3.png ├── img_4.png ├── img_5.png ├── my.png └── start.md ├── src ├── main │ ├── java │ │ └── com │ │ │ ├── liangtengyu │ │ │ └── markdown │ │ │ │ ├── MarkDownApplication.java │ │ │ │ ├── config │ │ │ │ ├── AppBean.java │ │ │ │ ├── ApplicationConfig.java │ │ │ │ ├── StartupConfig.java │ │ │ │ └── ThreadPoolConfig.java │ │ │ │ ├── controller │ │ │ │ ├── PageController.java │ │ │ │ ├── RequestController.java │ │ │ │ └── SettingController.java │ │ │ │ ├── dao │ │ │ │ ├── MDDao.java │ │ │ │ ├── PICDao.java │ │ │ │ ├── SETTINGDao.java │ │ │ │ └── UserTemplateDao.java │ │ │ │ ├── entity │ │ │ │ ├── MD.java │ │ │ │ ├── MarkDown.java │ │ │ │ ├── PIC.java │ │ │ │ ├── SETTING.java │ │ │ │ └── UserTemplate.java │ │ │ │ ├── service │ │ │ │ ├── FilelistService.java │ │ │ │ ├── HandleService.java │ │ │ │ ├── Impl │ │ │ │ │ ├── CSDNHandleService.java │ │ │ │ │ ├── CsdnBlogHandleService.java │ │ │ │ │ ├── FilelistServiceImpl.java │ │ │ │ │ ├── JianshuHandleService.java │ │ │ │ │ ├── JuejinHandleService.java │ │ │ │ │ ├── MarkDownService.java │ │ │ │ │ ├── SaveFileServiceImpl.java │ │ │ │ │ ├── SegmentFaultHandleService.java │ │ │ │ │ ├── SettingServiceImpl.java │ │ │ │ │ ├── V2exHandleService.java │ │ │ │ │ ├── WeiXinHandleService.java │ │ │ │ │ ├── YuqueHandleService.java │ │ │ │ │ └── ZhihuHandleService.java │ │ │ │ ├── ResolveService.java │ │ │ │ ├── SaveFileService.java │ │ │ │ └── SettingService.java │ │ │ │ └── utils │ │ │ │ ├── ImageUtil.java │ │ │ │ └── MarkDownUtil.java │ │ │ └── overzealous │ │ │ └── remark │ │ │ ├── Remark.java │ │ │ └── convert │ │ │ ├── CmLine.java │ │ │ ├── Codeblock.java │ │ │ ├── Header.java │ │ │ ├── Image.java │ │ │ └── InlineStyle.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── data.sql │ │ ├── schema.sql │ │ ├── static │ │ ├── css │ │ │ ├── about.ae075234.css │ │ │ ├── app.400b5e86.css │ │ │ └── chunk-vendors.07e3bc5b.css │ │ ├── favicon.ico │ │ ├── fonts │ │ │ ├── fontello.068ca2b3.ttf │ │ │ ├── fontello.8d4a4e6f.woff2 │ │ │ ├── fontello.a782baa8.woff │ │ │ └── fontello.e73a0647.eot │ │ ├── img │ │ │ ├── 1614755729311.82e3994d.jpg │ │ │ └── fontello.9354499c.svg │ │ ├── index.html │ │ └── js │ │ │ ├── about.37e01380.js │ │ │ ├── app.c2a083e4.js │ │ │ └── chunk-vendors.531895a6.js │ │ └── templates │ │ └── index.html └── test │ └── java │ └── com │ └── liangtengyu │ └── markdown │ └── MarkDownApplicationTests.java ├── vue_project ├── .gitignore ├── README.md ├── babel.config.js ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ ├── 1614755729311.jpg │ │ └── logo.png │ ├── components │ │ └── WebInfo.vue │ ├── main.js │ ├── router │ │ └── index.js │ └── views │ │ ├── About.vue │ │ ├── Filelist.vue │ │ ├── Home.vue │ │ ├── config.vue │ │ ├── manage.vue │ │ └── upload.vue ├── vue.config.js └── yarn.lock └── windows └── tomarkdown.rar /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8 2 | 3 | COPY /chromeDriver /chromeDriver 4 | EXPOSE 9999 5 | COPY ./target/markdown_resolve.jar /markdown_resolve.jar 6 | ENTRYPOINT ["java", "-jar", "/markdown_resolve.jar"] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |

ToMarkdown

7 | 8 | 9 | 10 | >⚡️功能描述: 将HTTP页面 解析为Markdown文件格式 保存以及管理 11 | > 12 | >目前支持: 微信公众号,知乎,知乎专栏,简书,知否(SegmentFault),掘金,CSDN,V2EX,博客园,语雀 13 | 14 | 15 | 16 | [>>> 快速开始](./readme_images/start.md) 17 | 18 | [>>> 在线体验](http://124.70.33.149:9999) 19 | 20 | 21 | --- 22 | 23 | 24 |
简介
25 | 26 | >看到喜欢的文章想通过Markdown的格式保存.可以通过本项目一键解析保存图片和文档 27 | 28 | 29 | 30 | --- 31 | 32 |
开始
33 | 34 | [开始使用](./readme_images/start.md) 35 | 36 | --- 37 | 后端技术栈: 38 | 1. springboot 39 | 2. Jsoup 40 | 3. Remark 41 | 42 | 43 | --- 44 | 前端: 45 | 项目目录:vue_project 46 | 47 | 1. axios 请求组件 48 | 2. mavoneditor markdown显示编辑组件 49 | 3. ant-design-vue 50 | 51 | 52 | --- 53 | 54 | 55 | 56 | 57 | 界面截图 58 | 59 | ![pic](./readme_images/Snipaste_2020-10-19_15-16-27.png) 60 | ![pic](./readme_images/Snipaste_2020-10-19_15-16-40.png) 61 | 62 | --- 63 | 64 | 保存文件以及映射 配置 65 | 66 | ![pic](./readme_images/img_1.png) 67 | 68 | --- 69 | 在线数据库管理 70 | 71 | ![pic](./readme_images/img_2.png) 72 | ![pic](./readme_images/img_3.png) 73 | 74 | --- 75 | 文章列表管理 76 | ![pic](./readme_images/img_4.png) 77 | 78 | 79 | --- 80 | 81 | 82 | > [公众号](/vue_project/src/assets/1614755729311.jpg) 83 | 84 | 85 |
86 | 87 |
88 | 89 | 90 | --- 91 | 92 | 2023/11/10 93 | 94 | - 新增加语雀解析 95 | - docker镜像更新 96 | 97 | --- 98 | 2023/06/30 99 | 100 | - 更新在线服务器地址 101 | - 切换至新服务器 102 | 103 | --- 104 | 2022/04/06 105 | - 修复知乎专栏 106 | - 修复读取不全 107 | 108 | --- 109 | 2021/05/27 新增特性: 110 | - 文章管理列表 分页 111 | - 删除 / 更新 本地文件/本地图片 112 | - 在线编辑文档保存功能 113 | - 回显解析的博客URL 114 | - 修复一些小bug 115 | 116 | --- 117 | 118 | 2021/04/07 新增特性: 119 | - 修复 CSDN图片下载返回code:302 120 | - 修复 知否图片下载返回code:302 121 | - 启用线程池,多线程协同下载图片,速度更快 122 | - 修复一些小bug 123 | 124 | 125 | --- 126 | 127 | 2021/03/05 新增特性: 128 | - h2database管理配置 129 | - 页面更新配置表单 130 | - 图片代理地址动态更新 131 | - md文件保存到数据库中 132 | 133 | PS:更改配置后需要重启服务器加载配置 134 | 135 | --- 136 | 137 | 138 | 2020/12/28 新增特性: 139 | - 配置通过application.yml保存文件到目录👍 140 | - 图片本地保存🐶 141 | - 图片本地代理服务🐼 142 | 143 | --- -------------------------------------------------------------------------------- /chromeDriver/chromedriver_linux64/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/chromeDriver/chromedriver_linux64/chromedriver -------------------------------------------------------------------------------- /chromeDriver/chromedriver_mac64/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/chromeDriver/chromedriver_mac64/chromedriver -------------------------------------------------------------------------------- /chromeDriver/chromedriver_mac_arm64/chromedriver: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/chromeDriver/chromedriver_mac_arm64/chromedriver -------------------------------------------------------------------------------- /chromeDriver/chromedriver_win32/chromedriver.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/chromeDriver/chromedriver_win32/chromedriver.exe -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | container_name: markdown_resolve 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | image: markdown_resolve 9 | environment: 10 | - TZ=Asia/Shanghai 11 | ports: 12 | - "9999:9999" -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.1.4.RELEASE 10 | 11 | 12 | 13 | 14 | 1.8 15 | markdown_resolve 16 | 17 | 18 | 19 | 20 | com.liangtengyu.markdown 21 | markdown 22 | 0.0.1-SNAPSHOT 23 | markdown 24 | 25 | 26 | 27 | 28 | 29 | com.h2database 30 | h2 31 | runtime 32 | 33 | 34 | 35 | org.seleniumhq.selenium 36 | selenium-java 37 | 3.141.59 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-data-jpa 43 | 44 | 45 | org.projectlombok 46 | lombok 47 | 1.18.12 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-starter-test 57 | test 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-web 63 | 64 | 65 | 66 | org.springframework.boot 67 | spring-boot-starter-thymeleaf 68 | 69 | 70 | 71 | org.jsoup 72 | jsoup 73 | 1.11.3 74 | 75 | 76 | 77 | com.kotcrab.remark 78 | remark 79 | 1.0.0 80 | 81 | 82 | 83 | org.springframework.boot 84 | spring-boot-devtools 85 | true 86 | 87 | 88 | com.alibaba 89 | fastjson 90 | 1.2.75 91 | 92 | 93 | 94 | cn.hutool 95 | hutool-all 96 | 5.6.2 97 | 98 | 99 | 100 | 101 | 102 | markdown_resolve 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-maven-plugin 107 | 108 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /readme_images/Snipaste_2020-10-19_15-16-27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/Snipaste_2020-10-19_15-16-27.png -------------------------------------------------------------------------------- /readme_images/Snipaste_2020-10-19_15-16-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/Snipaste_2020-10-19_15-16-40.png -------------------------------------------------------------------------------- /readme_images/Snipaste_2020-10-19_15-16-52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/Snipaste_2020-10-19_15-16-52.png -------------------------------------------------------------------------------- /readme_images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/favicon.ico -------------------------------------------------------------------------------- /readme_images/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img.png -------------------------------------------------------------------------------- /readme_images/img_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img_1.png -------------------------------------------------------------------------------- /readme_images/img_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img_2.png -------------------------------------------------------------------------------- /readme_images/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img_3.png -------------------------------------------------------------------------------- /readme_images/img_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img_4.png -------------------------------------------------------------------------------- /readme_images/img_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/img_5.png -------------------------------------------------------------------------------- /readme_images/my.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/readme_images/my.png -------------------------------------------------------------------------------- /readme_images/start.md: -------------------------------------------------------------------------------- 1 | **方式1** 🦁 2 | 3 | 4 | 线上使用: 5 | 6 | [>>>访问线上服务器<<<](http://markdown.liangtengyu.com:9999) 7 | 8 | --- 9 | 10 | **方式2** 🐵 11 | 12 | 1. Git clone 13 | 2. idea 或 eclipse 打开项目 14 | 3. 访问 http://127.0.0.1:9999 15 | 16 | --- 17 | 18 | **方式3** 🦄 19 | 20 | docker方式 21 | ```shell 22 | docker pull 843328437/markdown_resolve:latest 23 | ``` 24 | [运行前将 [/Users/tengyu/Desktop/docker/] 替换为你保存图片的路径] 25 | ```shell 26 | docker run -p 9999:9999 -v /Users/tengyu/Desktop/docker/mds:/ROOT/mds -v /Users/tengyu/Desktop/docker/pics:/ROOT/pics --name markdown 843328437/markdown_resolve:latest 27 | ``` 28 | 29 | 访问 http://127.0.0.1:9999 30 | 31 | --- 32 | 33 | **方式4** 🦄(初始版本,后续未更新) 34 | 35 | 1. 下载exe文件 36 | 2. 运行(需要jre环境) 37 | 3. 访问 http://127.0.0.1:9999 38 | 39 | 40 | [Download](../windows/tomarkdown.rar) 41 | 42 | --- -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/MarkDownApplication.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MarkDownApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MarkDownApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/config/AppBean.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.config; 2 | 3 | import org.springframework.beans.BeansException; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class AppBean implements ApplicationContextAware { 10 | 11 | private static ApplicationContext applicationContext; 12 | 13 | /** 14 | * 服务器启动,Spring容器初始化时,当加载了当前类为bean组件后, 15 | * 将会调用下面方法注入ApplicationContext实例 16 | */ 17 | @Override 18 | public void setApplicationContext(ApplicationContext arg0) throws BeansException { 19 | AppBean.applicationContext = arg0; 20 | } 21 | 22 | public static ApplicationContext getApplicationContext(){ 23 | return applicationContext; 24 | } 25 | 26 | /** 27 | * 用bean组件的name来获取bean 28 | * @param beanName 29 | * @return 30 | */ 31 | @SuppressWarnings("unchecked") 32 | public static T getBean(String beanName){ 33 | return (T) applicationContext.getBean(beanName); 34 | } 35 | 36 | /** 37 | * 用类来获取bean 38 | * @param c 39 | * @return 40 | */ 41 | public static T getBean(Class c){ 42 | return (T) applicationContext.getBean(c); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/config/ApplicationConfig.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.config; 2 | 3 | import com.liangtengyu.markdown.dao.SETTINGDao; 4 | import com.liangtengyu.markdown.entity.SETTING; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.InitializingBean; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 10 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 11 | 12 | 13 | @Slf4j 14 | @Configuration 15 | public class ApplicationConfig implements WebMvcConfigurer, InitializingBean { 16 | @Autowired 17 | SETTINGDao settingDao; 18 | 19 | public String dynamicAddress; 20 | 21 | @Override 22 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 23 | registry.addResourceHandler("/images/**").addResourceLocations("file:"+dynamicAddress); 24 | log.info("图片服务器映射成功"); 25 | 26 | } 27 | 28 | 29 | @Override 30 | public void afterPropertiesSet() throws Exception { 31 | SETTING set = settingDao.findbyname("Image_Save_Path"); 32 | String configValue = set.getConfigValue(); 33 | //少/的加/ 34 | if (!configValue.endsWith("/")) { 35 | configValue += "/"; 36 | } 37 | dynamicAddress= configValue; 38 | log.info("加载用户图片保存路径:"+dynamicAddress); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/config/StartupConfig.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.CommandLineRunner; 5 | import org.springframework.stereotype.Component; 6 | 7 | /** 8 | * @Author: lty 9 | * @Date: 2021/1/25 20:41 10 | */ 11 | @Slf4j 12 | @Component 13 | public class StartupConfig implements CommandLineRunner { 14 | 15 | @Override 16 | public void run(String... args) throws Exception { 17 | log.info("\n" + 18 | " _____ __ __ _ _ \n" + 19 | " |_ _| ___ | \\/ | __ _ _ _ | |__ __| | ___ __ __ __ _ _ \n" + 20 | " | | / _ \\ | |\\/| | / _` | | '_| | / / / _` | / _ \\ \\ V V /| ' \\ \n" + 21 | " _|_|_ \\___/ |_|__|_| \\__,_| _|_|_ |_\\_\\ \\__,_| \\___/ \\_/\\_/ |_||_| \n" + 22 | "_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"|_|\"\"\"\"\"| \n" + 23 | "\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-'\"`-0-0-' " 24 | +"\n" ); 25 | log.info("服务启动完成! 请访问 ->>>> http://127.0.0.1:9999"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/config/ThreadPoolConfig.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.config; 2 | 3 | import cn.hutool.core.thread.ThreadFactoryBuilder; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.ThreadFactory; 9 | import java.util.concurrent.ThreadPoolExecutor; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | /** 13 | * @Author: lty 14 | * @Date: 2021/4/7 13:53 15 | */ 16 | @Component 17 | public class ThreadPoolConfig { 18 | public static ExecutorService getThreadPool() { 19 | ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() 20 | .setNamePrefix("下载线程-").build(); 21 | return new ThreadPoolExecutor(32, 64, 22 | 0L, TimeUnit.MILLISECONDS, 23 | new LinkedBlockingQueue(255), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/controller/PageController.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.controller; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.liangtengyu.markdown.entity.MD; 5 | import com.liangtengyu.markdown.service.FilelistService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.web.bind.annotation.CrossOrigin; 9 | import org.springframework.web.bind.annotation.PathVariable; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.ResponseBody; 13 | 14 | @CrossOrigin 15 | @Controller 16 | public class PageController { 17 | @Autowired 18 | FilelistService filelistService; 19 | 20 | @RequestMapping("/") 21 | public String toFirstHtml(){ 22 | return "index"; 23 | } 24 | 25 | @RequestMapping("/filelist") 26 | @ResponseBody 27 | public JSONObject filelist(@RequestBody JSONObject data) { 28 | return filelistService.getFileList(data.getInteger("id")); 29 | } 30 | 31 | 32 | //delete 33 | @RequestMapping("/delete/{id}") 34 | @ResponseBody 35 | public Integer delete(@PathVariable("id")Integer id) { 36 | filelistService.delete(id); 37 | return 1; 38 | } 39 | 40 | 41 | //update 42 | @RequestMapping("/update") 43 | @ResponseBody 44 | public JSONObject update(@RequestBody JSONObject data) { 45 | return filelistService.update(data); 46 | } 47 | 48 | //select 49 | @RequestMapping("/select/{id}") 50 | @ResponseBody 51 | public MD select(@PathVariable("id")Integer id) { 52 | return filelistService.select(id); 53 | } 54 | 55 | 56 | 57 | 58 | //count 59 | @RequestMapping("/count") 60 | @ResponseBody 61 | public long count() { 62 | return filelistService.count(); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/controller/RequestController.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.controller; 2 | 3 | 4 | import cn.hutool.core.util.IdUtil; 5 | import com.liangtengyu.markdown.entity.MarkDown; 6 | import com.liangtengyu.markdown.service.ResolveService; 7 | import com.liangtengyu.markdown.service.SaveFileService; 8 | import com.liangtengyu.markdown.service.SettingService; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Controller; 12 | import org.springframework.web.bind.annotation.CrossOrigin; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.ResponseBody; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.UUID; 22 | 23 | @Controller 24 | @Slf4j 25 | @RequestMapping("/resolve") 26 | public class RequestController { 27 | 28 | 29 | 30 | 31 | @Autowired 32 | SaveFileService saveFileService; 33 | 34 | @Autowired 35 | SettingService settingService; 36 | 37 | /** 38 | * 获取文章 39 | * @param markDown 40 | * @return 41 | */ 42 | @PostMapping("/mark") 43 | @ResponseBody 44 | @CrossOrigin 45 | public Map mark(@RequestBody MarkDown markDown, HttpServletRequest request){ 46 | String id = IdUtil.simpleUUID(); 47 | fillUp(markDown,id); 48 | Map resultMap = new HashMap<>(); 49 | String result = null; 50 | try { 51 | log.info("开始解析 请求地址为: "+markDown.getBlogUrl()+" 请求ID: "+ id); 52 | result = ResolveService.get(markDown); 53 | resultMap.put("code","0"); 54 | resultMap.put("markdown",result); 55 | log.info(saveFileService.saveToFile(result,id,markDown)); 56 | log.info("解析完成 返回markdown结果 "+ id); 57 | log.info("-------------------------------------------------------------"); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | resultMap.put("code","-1"); 61 | resultMap.put("markdown",""); 62 | resultMap.put("message",e.getMessage()); 63 | } 64 | return resultMap; 65 | } 66 | 67 | private void fillUp(MarkDown markDown, String id) { 68 | Map settings = settingService.getSettings(); 69 | markDown.setImagePath(settings.get("Image_Save_Path")); 70 | markDown.setImageName(settings.get("Image_DEFAULT_NAME")); 71 | markDown.setImageUrl(settings.get("Image_Proxy_Path")); 72 | markDown.setId(id); 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/controller/SettingController.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.controller; 2 | 3 | 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.liangtengyu.markdown.entity.SETTING; 6 | import com.liangtengyu.markdown.entity.UserTemplate; 7 | import com.liangtengyu.markdown.service.SettingService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.ResponseBody; 16 | 17 | import java.util.Map; 18 | 19 | @Controller 20 | @Slf4j 21 | @CrossOrigin 22 | @RequestMapping("/setting") 23 | public class SettingController { 24 | 25 | @Autowired 26 | SettingService settingService; 27 | 28 | 29 | @PostMapping("/get") 30 | @ResponseBody 31 | public Map getConfig(){ 32 | return settingService.getSettings(); 33 | } 34 | 35 | 36 | 37 | @RequestMapping("/set") 38 | @ResponseBody 39 | public String set(@RequestBody JSONObject data) { 40 | settingService.setSettings(data); 41 | return "ok"; 42 | } 43 | 44 | 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/dao/MDDao.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.dao; 2 | 3 | import com.liangtengyu.markdown.entity.MD; 4 | import com.liangtengyu.markdown.entity.SETTING; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 7 | import org.springframework.data.jpa.repository.Modifying; 8 | import org.springframework.data.jpa.repository.Query; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | public interface MDDao extends JpaRepository, JpaSpecificationExecutor { 14 | 15 | @Query("select md from MD md where md.ID Between ?1 and ?1+10 ") 16 | List findbyid(Integer key); 17 | 18 | @Transactional 19 | @Modifying 20 | @Query(value = "update MD set CONTEXT = ?2 WHERE ID = ?1 ", nativeQuery = true) 21 | void update(Integer id, String context); 22 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/dao/PICDao.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.dao; 2 | 3 | import com.liangtengyu.markdown.entity.PIC; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | import java.util.List; 9 | 10 | public interface PICDao extends JpaRepository, JpaSpecificationExecutor { 11 | 12 | @Query("select pic.PATH from PIC pic where pic.PNAME = ?1 ") 13 | List findbyPname(String pname); 14 | @Query("select pic from PIC pic where pic.PNAME = ?1 ") 15 | List findPicbyPname(String pname); 16 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/dao/SETTINGDao.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.dao; 2 | 3 | import com.liangtengyu.markdown.entity.SETTING; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 6 | import org.springframework.data.jpa.repository.Modifying; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | public interface SETTINGDao extends JpaRepository, JpaSpecificationExecutor { 11 | 12 | @Query("select st from SETTING st where st.configName = ?1") 13 | SETTING findbyname(String key); 14 | 15 | @Transactional 16 | @Modifying 17 | @Query("update SETTING st set st.configValue = ?2 where st.configName = ?1") 18 | int updateByName(String key, String s); 19 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/dao/UserTemplateDao.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.dao; 2 | 3 | import com.liangtengyu.markdown.entity.UserTemplate; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 6 | import org.springframework.data.jpa.repository.Query; 7 | 8 | public interface UserTemplateDao extends JpaRepository, JpaSpecificationExecutor { 9 | 10 | 11 | 12 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/entity/MD.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Data; 5 | 6 | import javax.persistence.Column; 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.GenerationType; 10 | import javax.persistence.Id; 11 | import javax.persistence.Table; 12 | import java.io.Serializable; 13 | import java.util.Date; 14 | 15 | @Entity 16 | @Data 17 | @Table(name = "MD") 18 | @JsonIgnoreProperties(value = {"hibernateLazyInitializer","handler"}) 19 | public class MD implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | @Id 24 | @Column(name = "ID", nullable = false) 25 | @GeneratedValue(strategy = GenerationType.AUTO) 26 | private Integer ID; 27 | 28 | @Column(name = "TITLE") 29 | private String TITLE; 30 | 31 | @Column(name = "CONTEXT") 32 | private String CONTEXT; 33 | 34 | 35 | @Column(name = "PNAME") 36 | private String PNAME; 37 | 38 | 39 | 40 | @Column(name = "SAVE_PATH") 41 | private String savePath; 42 | 43 | @Column(name = "BLOG_URL") 44 | private String blogUrl; 45 | 46 | 47 | 48 | @Column(name = "CREATE_TIME") 49 | private Date createTime; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/entity/MarkDown.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.entity; 2 | 3 | import lombok.Data; 4 | 5 | 6 | 7 | @Data 8 | public class MarkDown { 9 | private String id; 10 | 11 | private String website; 12 | 13 | private String blogUrl; 14 | 15 | private String imagePath; 16 | 17 | private String imageUrl; 18 | 19 | private String imageName; 20 | 21 | 22 | public String getId() { 23 | return id; 24 | } 25 | 26 | public void setId(String id) { 27 | this.id = id; 28 | } 29 | 30 | public String getWebsite() { 31 | return website; 32 | } 33 | 34 | public void setWebsite(String website) { 35 | this.website = website; 36 | } 37 | 38 | public String getBlogUrl() { 39 | return blogUrl; 40 | } 41 | 42 | public void setBlogUrl(String blogUrl) { 43 | this.blogUrl = blogUrl; 44 | } 45 | 46 | public String getImagePath() { 47 | return imagePath; 48 | } 49 | 50 | public void setImagePath(String imagePath) { 51 | this.imagePath = imagePath; 52 | } 53 | 54 | public String getImageUrl() { 55 | return imageUrl; 56 | } 57 | 58 | public void setImageUrl(String imageUrl) { 59 | this.imageUrl = imageUrl; 60 | } 61 | 62 | public String getImageName() { 63 | return imageName; 64 | } 65 | 66 | public void setImageName(String imageName) { 67 | this.imageName = imageName; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/entity/PIC.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | @Entity 15 | @Table(name = "PIC") 16 | @Data 17 | public class PIC implements Serializable { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | @Id 22 | @Column(name = "ID", nullable = false) 23 | @GeneratedValue(strategy = GenerationType.AUTO) 24 | 25 | private Integer ID; 26 | 27 | @Column(name = "PNAME") 28 | private String PNAME; 29 | 30 | @Column(name = "PATH") 31 | private String PATH; 32 | 33 | @Column(name = "CREATE_TIME") 34 | private Date createTime; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/entity/SETTING.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.io.Serializable; 12 | import java.util.Date; 13 | 14 | @Entity 15 | @Table(name = "SETTING") 16 | @Data 17 | public class SETTING implements Serializable { 18 | 19 | private static final long serialVersionUID = 1L; 20 | 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.AUTO) 23 | @Column(name = "ID", nullable = false) 24 | private Integer ID; 25 | 26 | @Column(name = "CONFIG_NAME") 27 | private String configName; 28 | 29 | @Column(name = "CONFIG_VALUE") 30 | private String configValue; 31 | 32 | @Column(name = "CREATE_TIME") 33 | private Date createTime; 34 | 35 | @Column(name = "REMARK") 36 | private String REMARK; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/entity/UserTemplate.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.entity; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.Column; 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | import javax.persistence.Table; 11 | import java.io.Serializable; 12 | 13 | @Entity 14 | @Data 15 | @Table(name = "USER_TEMPLATE") 16 | public class UserTemplate implements Serializable { 17 | 18 | private static final long serialVersionUID = 1L; 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.AUTO) 22 | @Column(name = "ID", nullable = false) 23 | private Integer ID; 24 | 25 | @Column(name = "HEADER") 26 | private String HEADER; 27 | 28 | @Column(name = "BOTTOM") 29 | private String BOTTOM; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/FilelistService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.liangtengyu.markdown.entity.MD; 5 | import org.springframework.stereotype.Service; 6 | 7 | /** 8 | * @Author: lty 9 | * @Date: 2021/5/24 15:20 10 | */ 11 | @Service 12 | public interface FilelistService { 13 | JSONObject getFileList(Integer id); 14 | 15 | void delete(Integer id); 16 | 17 | JSONObject update(JSONObject data); 18 | 19 | MD select(Integer id); 20 | 21 | long count(); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/HandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service; 2 | 3 | 4 | import com.liangtengyu.markdown.entity.MarkDown; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.Map; 8 | 9 | public interface HandleService { 10 | 11 | /** 12 | * 获取博客内容 13 | * 14 | * @param markDown 15 | * @return 16 | */ 17 | String getBlogContent(MarkDown markDown); 18 | 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/CSDNHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import com.liangtengyu.markdown.utils.MarkDownUtil; 5 | import org.jsoup.Jsoup; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.Element; 8 | import org.jsoup.select.Elements; 9 | 10 | public class CSDNHandleService extends MarkDownService { 11 | 12 | 13 | @Override 14 | protected Document getHtmlContent(MarkDown markDown, Document document) { 15 | Element mainElement = document.getElementById("mainBox"); 16 | 17 | // 不是 Markdown,则获取 HTML 18 | if(mainElement == null){ 19 | mainElement = document.getElementById("htmledit_views"); 20 | } 21 | 22 | String htmlContent = mainElement.getElementById("content_views").html(); 23 | 24 | document = Jsoup.parse(htmlContent); 25 | 26 | // 去掉代码块中的行号 27 | Elements elements = document.getElementsByTag("pre"); 28 | if(MarkDownUtil.elementsNotEmpty(elements)){ 29 | 30 | Elements preNumbers = null; 31 | for(Element element : elements){ 32 | preNumbers = element.getElementsByClass("pre-numbering"); 33 | if(MarkDownUtil.elementsNotEmpty(preNumbers)){ 34 | for(Element preNumber : preNumbers){ 35 | // 删掉换行号 36 | preNumber.remove(); 37 | } 38 | } 39 | } 40 | } 41 | 42 | return document; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/CsdnBlogHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import com.liangtengyu.markdown.utils.MarkDownUtil; 5 | import org.jsoup.Jsoup; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.Element; 8 | import org.jsoup.select.Elements; 9 | 10 | public class CsdnBlogHandleService extends MarkDownService { 11 | 12 | 13 | @Override 14 | protected Document getHtmlContent(MarkDown markDown, Document document) { 15 | Element mainElement = document.getElementById("cnblogs_post_body"); 16 | 17 | 18 | String htmlContent = mainElement.html(); 19 | 20 | document = Jsoup.parse(htmlContent); 21 | 22 | // 去掉代码块中的行号 23 | Elements elements = document.getElementsByTag("pre"); 24 | if(MarkDownUtil.elementsNotEmpty(elements)){ 25 | 26 | Elements preNumbers = null; 27 | for(Element element : elements){ 28 | preNumbers = element.getElementsByClass("pre-numbering"); 29 | if(MarkDownUtil.elementsNotEmpty(preNumbers)){ 30 | for(Element preNumber : preNumbers){ 31 | // 删掉换行号 32 | preNumber.remove(); 33 | } 34 | } 35 | } 36 | } 37 | 38 | return document; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/FilelistServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.liangtengyu.markdown.dao.MDDao; 6 | import com.liangtengyu.markdown.dao.PICDao; 7 | import com.liangtengyu.markdown.entity.MD; 8 | import com.liangtengyu.markdown.entity.PIC; 9 | import com.liangtengyu.markdown.service.FilelistService; 10 | import com.liangtengyu.markdown.service.SettingService; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.data.domain.PageRequest; 14 | import org.springframework.data.domain.Sort; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.io.File; 18 | import java.io.FileOutputStream; 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Map; 23 | 24 | /** 25 | * @Author: lty 26 | * @Date: 2021/5/24 15:23 27 | */ 28 | @Service 29 | @Slf4j 30 | public class FilelistServiceImpl implements FilelistService { 31 | @Autowired 32 | PICDao picDao; 33 | @Autowired 34 | MDDao mdDao; 35 | @Autowired 36 | SettingService settingService; 37 | 38 | @Override 39 | public JSONObject getFileList(Integer id) { 40 | List orders = new ArrayList<>(); 41 | orders.add(new Sort.Order(Sort.Direction.DESC, "createTime"));//设置时间倒序 42 | Sort sort = new Sort(orders); 43 | List findbyid = mdDao.findAll(new PageRequest(id, 5,sort)).getContent(); 44 | JSONObject re = new JSONObject(); 45 | JSONArray jsonArray = new JSONArray(); 46 | 47 | 48 | for (MD md : findbyid) { 49 | JSONObject one_md = new JSONObject(); 50 | one_md.put("title", md.getTITLE()); 51 | one_md.put("pname", md.getPNAME()); 52 | one_md.put("id", md.getID()); 53 | List piclists = picDao.findbyPname(md.getPNAME()); 54 | one_md.put("pics", piclists); 55 | jsonArray.add(one_md); 56 | } 57 | re.put("data", jsonArray); 58 | return re; 59 | } 60 | 61 | @Override 62 | public void delete(Integer id) { 63 | 64 | MD one = mdDao.getOne(id); 65 | String pname = one.getPNAME(); 66 | List picbyPname = picDao.findPicbyPname(pname); 67 | for (PIC pic : picbyPname) { 68 | picDao.delete(pic); 69 | deleteLocalPic(pic.getPATH()); 70 | } 71 | deleteLocalmd(one.getSavePath()); 72 | mdDao.deleteById(id); 73 | 74 | log.info("删除文章 ID: " + id); 75 | 76 | } 77 | 78 | 79 | //删除本地的markdown文件. 80 | private void deleteLocalmd(String savePath) { 81 | File file = new File(savePath); 82 | if (file.isFile() && file.exists()) { 83 | boolean delete = file.delete(); 84 | log.info("删除markdown" + savePath +" "+ delete) ; 85 | } else { 86 | log.info("删除markdown" + savePath + "错误!!!文件不存在"); 87 | } 88 | } 89 | 90 | 91 | //删除本地的图片. 92 | private void deleteLocalPic(String path) { 93 | path = path.substring(41-12,41+4); 94 | Map settings = settingService.getSettings(); 95 | String image_save_path = settings.get("Image_Save_Path"); 96 | image_save_path = image_save_path +"/"+ path; 97 | System.out.println(image_save_path); 98 | File file = new File(image_save_path); 99 | if (file.isFile() && file.exists()) { 100 | boolean delete = file.delete(); 101 | log.info("删除图片" + path +" "+ delete) ; 102 | } else { 103 | log.info("删除图片" + path + "错误!!!文件不存在"); 104 | } 105 | } 106 | 107 | //写入本地文件 108 | public boolean writeTxtFile(String filePath, String content,JSONObject data) throws Exception { 109 | boolean flag = false; 110 | FileOutputStream fileOutputStream = null; 111 | File file = new File(filePath); 112 | try { 113 | fileOutputStream = new FileOutputStream(file); 114 | fileOutputStream.write(content.getBytes(StandardCharsets.UTF_8)); 115 | fileOutputStream.close(); 116 | flag = true; 117 | } catch (Exception e) { 118 | System.out.println("文件写入失败!" + e); 119 | } 120 | data.put("code", 0); 121 | return flag; 122 | } 123 | 124 | 125 | 126 | @Override 127 | public JSONObject update(JSONObject data) { 128 | Integer id = data.getInteger("id"); 129 | String context = data.getString("context"); 130 | String blogUrl = data.getString("blogUrl"); 131 | String title = data.getString("title"); 132 | MD one = mdDao.getOne(id); 133 | try { 134 | boolean b = writeTxtFile(one.getSavePath(), context, data); 135 | log.info("重新写文件 "+ one.getSavePath()+"返回:"+b); 136 | one.setBlogUrl(blogUrl); 137 | one.setCONTEXT(context); 138 | if (!"".equals(title) ) { 139 | one.setTITLE(title); 140 | } 141 | mdDao.saveAndFlush(one); 142 | } catch (Exception e) { 143 | e.printStackTrace(); 144 | } 145 | return data; 146 | } 147 | 148 | @Override 149 | public MD select(Integer id) { 150 | MD one = mdDao.getOne(id); 151 | return one; 152 | } 153 | 154 | @Override 155 | public long count() { 156 | long count = mdDao.count(); 157 | return count; 158 | } 159 | 160 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/JianshuHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.select.Elements; 7 | 8 | public class JianshuHandleService extends MarkDownService { 9 | 10 | @Override 11 | protected synchronized Document getHtmlContent(MarkDown markDown, Document document) { 12 | //默认为专栏 13 | Elements root = document.getElementsByTag("article"); 14 | return Jsoup.parse(root.html()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/JuejinHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.select.Elements; 7 | 8 | public class JuejinHandleService extends MarkDownService { 9 | 10 | @Override 11 | protected synchronized Document getHtmlContent(MarkDown markDown, Document document) { 12 | Elements article = document.getElementsByClass("article-content"); 13 | return Jsoup.parse(article.html().replaceAll("复制代码","")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/MarkDownService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import cn.hutool.http.HttpUtil; 4 | import com.liangtengyu.markdown.config.AppBean; 5 | import com.liangtengyu.markdown.config.ThreadPoolConfig; 6 | import com.liangtengyu.markdown.dao.PICDao; 7 | import com.liangtengyu.markdown.entity.MarkDown; 8 | import com.liangtengyu.markdown.entity.PIC; 9 | import com.liangtengyu.markdown.service.HandleService; 10 | import com.liangtengyu.markdown.utils.ImageUtil; 11 | import com.liangtengyu.markdown.utils.MarkDownUtil; 12 | import com.overzealous.remark.Remark; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.jsoup.Jsoup; 15 | import org.jsoup.nodes.Document; 16 | import org.jsoup.nodes.Element; 17 | import org.jsoup.select.Elements; 18 | import org.springframework.stereotype.Service; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | 23 | import java.util.Date; 24 | import java.util.UUID; 25 | import java.util.concurrent.CountDownLatch; 26 | import java.util.concurrent.ExecutorService; 27 | import java.util.concurrent.Semaphore; 28 | import java.util.concurrent.atomic.AtomicReference; 29 | 30 | @Slf4j 31 | @Service 32 | public abstract class MarkDownService implements HandleService { 33 | 34 | @Override 35 | public String getBlogContent(MarkDown markDown) { 36 | 37 | // 1. 获取 Document 38 | Document document = getDocument(markDown.getBlogUrl()); 39 | 40 | // 2.提取 Document 中的博文信息 41 | document = getHtmlContent(markDown,document); 42 | 43 | // 3.下载图片,并进行替换 44 | String htmlContent = convertHtml(markDown, document); 45 | 46 | // 转换为 markdown 47 | Remark remark = new Remark(); 48 | 49 | return remark.convert(htmlContent); 50 | } 51 | 52 | 53 | /** 54 | *

1、转换为 HTML 类型

55 | *

2、需要将文件中的图片下载到本地,且替换图片地址

56 | *

3、处理代码块,因为 markdown 转换时没有给代码块加 ```

57 | */ 58 | protected String convertHtml(MarkDown markDown, Document document) { 59 | if ((markDown.getImagePath() != null && !"".equals(markDown.getImagePath())) && 60 | (markDown.getImageUrl() != null && !"".equals(markDown.getImageUrl()))) { 61 | 62 | // 处理图片 63 | try { 64 | handleImg(markDown, document); 65 | } catch (InterruptedException e) { 66 | log.info("图片处理异常."); 67 | e.printStackTrace(); 68 | } 69 | } else { 70 | log.info("没有图片需要下载.图片处理结束."); 71 | } 72 | 73 | return document.html(); 74 | } 75 | 76 | /** 77 | * 处理代码块,主要是在代码块中的前面增加 `

```

` 即可 78 | * 79 | * @param document 80 | */ 81 | protected void handlePre(Document document) { 82 | 83 | } 84 | 85 | /** 86 | * 处理图片 87 | * 88 | * @param markDown 89 | * @param document 90 | */ 91 | private void handleImg(MarkDown markDown, Document document) throws InterruptedException { 92 | // 获取所有的 img 标签 93 | Elements elements = document.getElementsByTag("img"); 94 | 95 | if (elements != null && elements.size() > 0) { 96 | 97 | // 新建文件 98 | File pathFile = new File(markDown.getImagePath()); 99 | 100 | if (!pathFile.exists() && !pathFile.mkdirs()) { 101 | throw new RuntimeException("新建目录失败..."); 102 | } 103 | 104 | doHandleImg(elements, markDown); 105 | } 106 | } 107 | 108 | 109 | public void doHandleImg(Elements elements, MarkDown markDown) throws InterruptedException { 110 | String id = markDown.getId(); 111 | 112 | 113 | 114 | 115 | long l = System.currentTimeMillis(); 116 | CountDownLatch cdl = new CountDownLatch(elements.size()); 117 | Semaphore semaphore = new Semaphore(15); 118 | ExecutorService threadPool = ThreadPoolConfig.getThreadPool(); 119 | AtomicReference imageUrl = new AtomicReference<>(); 120 | String imageSrc = ""; 121 | 122 | for (Element element : elements) { 123 | threadPool.execute(() -> { 124 | //线号量获取 125 | try { 126 | semaphore.acquire(); 127 | String url = element.attr("src"); 128 | if (url.startsWith("data:image/svg+xml;utf8")) { 129 | semaphore.release(); 130 | return; 131 | } 132 | 133 | String name = UUID.randomUUID().toString().split("-")[0]; 134 | // 下载图片 135 | String fileName = downImage(markDown, element, name); 136 | 137 | 138 | String path = markDown.getImageUrl() + "/" + fileName; 139 | imageUrl.set(path); 140 | // 替换地址 141 | element.attr("src", imageUrl.get()); 142 | element.attr("alt", fileName); 143 | //下载完毕 保存数据库 144 | PIC pic = new PIC(); 145 | pic.setCreateTime(new Date()); 146 | pic.setPNAME(id); 147 | pic.setPATH(path); 148 | PICDao picdao = AppBean.getBean(PICDao.class); 149 | picdao.save(pic); 150 | 151 | 152 | semaphore.release(); 153 | } catch (IOException e) { 154 | System.out.println(imageSrc + "下载图片失败,cause by :" + e.getMessage()); 155 | e.printStackTrace(); 156 | } catch (InterruptedException e) { 157 | e.printStackTrace(); 158 | } finally { 159 | cdl.countDown(); 160 | } 161 | }); 162 | } 163 | cdl.await(); 164 | log.info("下载图片总耗时: "+(System.currentTimeMillis()-l)+"ms"); 165 | } 166 | 167 | /** 168 | * 下载图片 169 | * 170 | * @param markDown 171 | * @param element 172 | * @param name 173 | * @return 174 | * @throws IOException 175 | */ 176 | 177 | public String downImage(MarkDown markDown, Element element, String name) throws IOException { 178 | String fileName = markDown.getImageName() + "_" + name + ".png"; 179 | File imageFile = MarkDownUtil.getImageFile(markDown.getImagePath(), fileName); 180 | String imageSrc = element.attr("src"); 181 | 182 | 183 | imageSrc = handleLinkStyle(markDown, element, imageSrc); 184 | if (imageSrc == null) return ""; 185 | 186 | 187 | doDownloadImages(imageFile, imageSrc); 188 | return fileName; 189 | } 190 | 191 | 192 | public void doDownloadImages(File imageFile, String imageSrc) { 193 | //下载图片⬇️ 194 | log.info("catch picture :{}", imageSrc); 195 | byte[] bytes = HttpUtil.downloadBytes(imageSrc); 196 | ImageUtil.byte2image(bytes, imageFile.getPath()); 197 | } 198 | 199 | /** 200 | * 处理成统一的链接样式 如http://xxx.com/123/1.png 201 | * 202 | * @param markDown 203 | * @param element 204 | * @param imageSrc 205 | * @return 206 | */ 207 | private String handleLinkStyle(MarkDown markDown, Element element, String imageSrc) { 208 | if (imageSrc.startsWith("data:image/svg+xml;utf8")) { 209 | log.info("ignore svg type images"); 210 | return null; 211 | } 212 | 213 | 214 | // 如果不存在 src,则尝试获取 data-src 215 | if (imageSrc == null || "".equals(imageSrc.trim())) { 216 | imageSrc = element.attr("data-src"); 217 | } 218 | 219 | // 如果不存在 data-src,则尝试获取 data-original-src 220 | if (imageSrc == null || "".equals(imageSrc.trim())) { 221 | imageSrc = element.attr("data-original-src"); 222 | imageSrc = "https:" + imageSrc;//简书 Https 223 | } 224 | 225 | if (imageSrc.startsWith("/")) { 226 | imageSrc = markDown.getWebsite() + ".com" + imageSrc; 227 | } 228 | 229 | // 有些图片没有 http 230 | if (imageSrc.startsWith("//")) { 231 | imageSrc = "https:" + imageSrc; 232 | 233 | } 234 | 235 | return imageSrc; 236 | } 237 | 238 | // /** 239 | // * 获取 Document 对象 240 | // * 241 | // * @param blogUrl 242 | // * @return 243 | // */ 244 | // private Document getDocument(String blogUrl) { 245 | // try { 246 | // return Jsoup.connect(blogUrl).get(); 247 | // } catch (IOException e) { 248 | // throw new RuntimeException("解析地址,获取 Document 对象失败..", e); 249 | // } 250 | // } 251 | 252 | /** 253 | * 获取 Document 对象 254 | * 255 | * @param blogUrl 256 | * @return 257 | */ 258 | private Document getDocument(String blogUrl) { 259 | return Jsoup.parse(HttpUtil.downloadString(blogUrl, "utf-8")); 260 | } 261 | 262 | 263 | /** 264 | * 每个网站的结构不同,需要各个子类完成解析 265 | * 266 | * @param markDown 267 | * @param document 268 | * @return 269 | */ 270 | protected abstract Document getHtmlContent(MarkDown markDown, Document document); 271 | } 272 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/SaveFileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.liangtengyu.markdown.dao.MDDao; 5 | import com.liangtengyu.markdown.dao.SETTINGDao; 6 | import com.liangtengyu.markdown.entity.MD; 7 | import com.liangtengyu.markdown.entity.MarkDown; 8 | import com.liangtengyu.markdown.entity.SETTING; 9 | import com.liangtengyu.markdown.service.SaveFileService; 10 | import com.liangtengyu.markdown.utils.MarkDownUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.stereotype.Service; 14 | 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.IOException; 18 | import java.util.Date; 19 | 20 | /** 21 | * @Author: lty 22 | * @Date: 2020/12/28 14:40 23 | */ 24 | @Slf4j 25 | @Service 26 | public class SaveFileServiceImpl implements SaveFileService { 27 | 28 | @Autowired 29 | MDDao mdDao; 30 | 31 | @Autowired 32 | SETTINGDao settingDao; 33 | @Override 34 | public String saveToFile(String result, String id, MarkDown markDownm) throws IOException { 35 | SETTING mdSavePath = settingDao.findbyname("MD_Save_Path"); 36 | System.out.println(mdSavePath); 37 | //通过此接口,将markdown保存为文本 38 | File f = new File(mdSavePath.getConfigValue()); 39 | if (!f.exists()) { 40 | f.mkdirs(); 41 | } 42 | String filename = MarkDownUtil.generatorFileName(); 43 | File mdFile = new File(f, filename); 44 | if (!mdFile.exists()) { 45 | mdFile.createNewFile(); 46 | } 47 | FileOutputStream outputStream = new FileOutputStream(mdFile); 48 | outputStream.write(result.getBytes()); 49 | outputStream.close(); 50 | 51 | String savepath = mdSavePath.getConfigValue() + "/" + filename; 52 | 53 | saveToDatabase(result, id, savepath,markDownm); 54 | return "MD文件保存到:"+savepath; 55 | } 56 | 57 | @Override 58 | public void saveToDatabase(String result,String id,String savePath,MarkDown markdown) throws IOException { 59 | if (StrUtil.isBlank(result)) { 60 | return; 61 | } 62 | MD md = new MD(); 63 | md.setCreateTime(new Date()); 64 | md.setCONTEXT(result); 65 | md.setPNAME(id); 66 | md.setTITLE(getTitle(result)); 67 | md.setSavePath(savePath); 68 | md.setBlogUrl(markdown.getBlogUrl()); 69 | mdDao.save(md); 70 | log.info("保存到数据库成功!"); 71 | } 72 | 73 | private String getTitle(String result) { 74 | return result.substring(0, 100); 75 | } 76 | 77 | @Override 78 | public void saveImagePath(String path) { 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/SegmentFaultHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.select.Elements; 7 | 8 | public class SegmentFaultHandleService extends MarkDownService { 9 | 10 | @Override 11 | protected synchronized Document getHtmlContent(MarkDown markDown, Document document) { 12 | Elements article = document.getElementsByTag("article"); 13 | String html = article.html(); 14 | String data = html.replaceAll("src", ""); 15 | String s = data.replaceAll("/img/remote/", "https://segmentfault.com/img/remote/"); 16 | String src = s.replaceAll("data-", "src"); 17 | return Jsoup.parse(src); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/SettingServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.liangtengyu.markdown.config.ApplicationConfig; 5 | import com.liangtengyu.markdown.dao.SETTINGDao; 6 | import com.liangtengyu.markdown.dao.UserTemplateDao; 7 | import com.liangtengyu.markdown.entity.SETTING; 8 | import com.liangtengyu.markdown.entity.UserTemplate; 9 | import com.liangtengyu.markdown.service.SettingService; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | /** 20 | * @Author: lty 21 | * @Date: 2021/3/3 14:24 22 | */ 23 | @Service 24 | @Slf4j 25 | public class SettingServiceImpl implements SettingService { 26 | 27 | @Autowired 28 | ApplicationConfig applicationConfig; 29 | 30 | @Autowired 31 | SETTINGDao settingDao; 32 | 33 | @Autowired 34 | UserTemplateDao userTemplateDao; 35 | 36 | 37 | 38 | @Override 39 | public Map getSettings() { 40 | List list = settingDao.findAll(); 41 | HashMap kvMap = new HashMap<>(); 42 | for (SETTING setting : list) { 43 | String configName = setting.getConfigName(); 44 | String configValue = setting.getConfigValue(); 45 | kvMap.put(configName, configValue); 46 | } 47 | return kvMap; 48 | } 49 | 50 | 51 | 52 | 53 | @Override 54 | public int setSettings(JSONObject j) { 55 | 56 | // {"User_template_id":"1","MD_Save_Path":"./mds","Image_Save_Path":"./pics" 57 | // ,"Image_Proxy_Path":"http://localhost:9999/images","Image_DEFAULT_NAME":"pic"} 58 | 59 | Set> entries = j.entrySet(); 60 | for (Map.Entry entry : entries) { 61 | settingDao.updateByName(entry.getKey(),entry.getValue().toString()); 62 | } 63 | return 1; 64 | } 65 | 66 | @Override 67 | public UserTemplate getOneUserTemplate(Integer id) { 68 | return userTemplateDao.getOne(id); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/V2exHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.select.Elements; 7 | 8 | public class V2exHandleService extends MarkDownService { 9 | 10 | @Override 11 | protected Document getHtmlContent(MarkDown markDown, Document document) { 12 | Elements topic_content = document.getElementsByClass("topic_content"); 13 | return Jsoup.parse(topic_content.html()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/WeiXinHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.nodes.Element; 7 | 8 | public class WeiXinHandleService extends MarkDownService { 9 | 10 | @Override 11 | protected Document getHtmlContent(MarkDown markDown, Document document) { 12 | Element element = document.getElementById("js_content"); 13 | return Jsoup.parse(element.html()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/YuqueHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import com.liangtengyu.markdown.utils.MarkDownUtil; 5 | import org.jsoup.Jsoup; 6 | import org.jsoup.nodes.Document; 7 | import org.jsoup.nodes.Element; 8 | import org.jsoup.select.Elements; 9 | import org.openqa.selenium.*; 10 | import org.openqa.selenium.chrome.ChromeDriver; 11 | import org.openqa.selenium.chrome.ChromeOptions; 12 | import org.openqa.selenium.support.ui.ExpectedConditions; 13 | import org.openqa.selenium.support.ui.WebDriverWait; 14 | 15 | import java.net.URL; 16 | 17 | public class YuqueHandleService extends MarkDownService { 18 | 19 | 20 | @Override 21 | protected Document getHtmlContent(MarkDown markDown, Document document) { 22 | 23 | //启动chrome 24 | Element element = startChrome(markDown.getBlogUrl()); 25 | 26 | // 去掉代码块中的行号 27 | try { 28 | Elements elements = element.getElementsByClass("cm-gutters"); 29 | if (MarkDownUtil.elementsNotEmpty(elements)) { 30 | for (Element ele : elements) { 31 | ele.remove(); 32 | } 33 | } 34 | 35 | // 去掉返回文档按钮 36 | Elements elements2 = element.getElementsByClass("ne-viewer-header"); 37 | if (MarkDownUtil.elementsNotEmpty(elements2)) { 38 | for (Element ele : elements2) { 39 | ele.remove(); 40 | } 41 | } 42 | 43 | 44 | Elements lines = element.getElementsByClass("cm-line"); 45 | for (Element line : lines) { 46 | Element sp = new Element("span"); 47 | sp.html(line.html()); 48 | line.replaceWith(sp); 49 | } 50 | 51 | 52 | Elements codes = element.getElementsByClass("cm-content"); 53 | for (Element code : codes) { 54 | Element pre = new Element("pre"); 55 | pre.html(code.html()); 56 | code.replaceWith(pre); 57 | } 58 | } catch (NullPointerException e) { 59 | System.out.println("一些为空"); 60 | } 61 | //只要主要信息 62 | Elements elementsByClass = element.getElementsByClass("ne-viewer-body"); 63 | String html = elementsByClass.html(); 64 | 65 | return Jsoup.parse(html); 66 | } 67 | 68 | 69 | 70 | //"https://www.yuque.com/wangwangbunian-izczt/by3cic/vfw8xz6t003msaba#35c267ba" 71 | public static Element startChrome(String url) { 72 | System.setProperty("webdriver.chrome.driver", "chromeDriver/chromedriver_linux64/chromedriver"); 73 | ChromeOptions chromeOptions = new ChromeOptions(); 74 | chromeOptions.addArguments("--headless"); 75 | chromeOptions.addArguments("--disable-gpu"); 76 | chromeOptions.addArguments("--no-sandbox"); 77 | WebDriver driver = new ChromeDriver(chromeOptions); 78 | // 使用try-with-resources确保WebDriver在代码块结束后关闭 79 | try { 80 | // 打开网页 81 | driver.get(url); 82 | 83 | // 使用JavascriptExecutor滚动到页面底部 84 | JavascriptExecutor jsExecutor = (JavascriptExecutor) driver; 85 | // 模拟拖动滚动条从1%到100% 86 | for (int i = 1; i <= 100; i++) { 87 | jsExecutor.executeScript("window.scrollTo(0," + i / 100.0 + "* document.body.scrollHeight );"); 88 | Thread.sleep(50); 89 | } 90 | System.out.println("开始滚动代码块"); 91 | 92 | // 使用WebDriverWait等待页面加载完成 93 | WebDriverWait wait = new WebDriverWait(driver, 10); 94 | 95 | 96 | WebElement reactApp1 = wait.until(ExpectedConditions.presenceOfElementLocated(By.id("ReactApp"))); 97 | try { 98 | WebElement codeBlock = wait.until(ExpectedConditions.presenceOfElementLocated(By.className("cm-scroller"))); 99 | jsExecutor.executeScript("arguments[0].scrollTop = 0;", codeBlock); 100 | // 等待一段时间,确保内容加载(你可以根据实际情况调整等待时间) 101 | Thread.sleep(1000); 102 | // 使用JavascriptExecutor再滚动到页面底部 103 | jsExecutor.executeScript("arguments[0].scrollTop = arguments[0].scrollHeight;", codeBlock); 104 | } catch (TimeoutException e) { 105 | System.out.println("滚动条等超时"); 106 | } 107 | // 继续等待内部代码块组件的加载 108 | 109 | // 使用Jsoup解析页面 110 | String htmlCode = driver.getPageSource(); 111 | Document parse = Jsoup.parse(htmlCode); 112 | 113 | return parse.getElementById("ReactApp").getElementById("content"); 114 | 115 | } catch (Exception e) { 116 | e.printStackTrace(); 117 | return null; 118 | } finally { 119 | // 关闭浏览器 120 | driver.quit(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/Impl/ZhihuHandleService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service.Impl; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import org.jsoup.Jsoup; 5 | import org.jsoup.nodes.Document; 6 | import org.jsoup.nodes.Element; 7 | import org.jsoup.select.Elements; 8 | 9 | public class ZhihuHandleService extends MarkDownService { 10 | 11 | @Override 12 | protected synchronized Document getHtmlContent(MarkDown markDown, Document document) { 13 | //默认为专栏 14 | Element root = document.getElementById("root"); 15 | Elements elementsByClass = root.getElementsByClass("Post-RichText"); 16 | if (elementsByClass.size() == 0) { 17 | //是question 18 | Elements content = document.getElementsByClass("QuestionAnswer-content"); 19 | Document parse = Jsoup.parse(content.html()); 20 | Elements content_inner = parse.getElementsByClass("RichContent-inner"); 21 | if (content_inner.size() > 0) { 22 | return Jsoup.parse(content_inner.html()); 23 | } else { 24 | return Jsoup.parse(content.attr("error","解析知乎页面异常").html()); 25 | } 26 | } 27 | return Jsoup.parse(elementsByClass.html()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/ResolveService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | import com.liangtengyu.markdown.service.Impl.*; 5 | import com.liangtengyu.markdown.utils.MarkDownUtil; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.HashMap; 10 | 11 | /** 12 | * @Author: lty 13 | * @Date: 2020/8/21 16:30 14 | */ 15 | @Slf4j 16 | public class ResolveService { 17 | 18 | static HashMap serviceMap = new HashMap<>(); 19 | 20 | private static final String WEIXIN = "weixin"; 21 | private static final String CSDN = "csdn"; 22 | private static final String CSDN_BLOG = "cnblogs"; 23 | private static final String ZHIHU = "zhihu"; 24 | private static final String JUEJIN = "juejin"; 25 | private static final String SEGMENTFAULT = "segmentfault"; 26 | private static final String JIANSHU = "jianshu"; 27 | private static final String V_2_EX = "v2ex"; 28 | private static final String YU_QUE = "yuque"; 29 | 30 | 31 | 32 | public static String get (MarkDown markDown){ 33 | String website = MarkDownUtil.getUrlOrigin(markDown); 34 | initMap(website); 35 | return serviceMap.get(website) 36 | .getBlogContent(markDown); 37 | 38 | } 39 | 40 | private static void initMap(String website) { 41 | if (!serviceMap.containsKey(website)) { 42 | log.info("Init MarkdownService for {}" ,website); 43 | if (WEIXIN.equals(website)) { 44 | serviceMap.put(website, new WeiXinHandleService()); 45 | 46 | } else if (CSDN.equals(website)) { 47 | serviceMap.put(website, new CSDNHandleService()); 48 | 49 | } else if (CSDN_BLOG.equals(website)) { 50 | serviceMap.put(website, new CsdnBlogHandleService()); 51 | 52 | } else if (ZHIHU.equals(website)) { 53 | serviceMap.put(website,new ZhihuHandleService()); 54 | 55 | } else if (JUEJIN.equals(website)) { 56 | serviceMap.put(website,new JuejinHandleService()); 57 | 58 | } else if (SEGMENTFAULT.equals(website)) { 59 | serviceMap.put(website,new SegmentFaultHandleService()); 60 | 61 | } else if (JIANSHU.equals(website)) { 62 | serviceMap.put(website,new JianshuHandleService()); 63 | 64 | }else if (V_2_EX.equals(website)) { 65 | serviceMap.put(website,new V2exHandleService()); 66 | 67 | }else if (YU_QUE.equals(website)) { 68 | serviceMap.put(website,new YuqueHandleService()); 69 | 70 | } else { 71 | log.info("暂时还没有解决方案."); 72 | throw new RuntimeException("暂时还没有解决方案"); 73 | } 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/SaveFileService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service; 2 | 3 | import com.liangtengyu.markdown.entity.MarkDown; 4 | 5 | import java.io.FileNotFoundException; 6 | import java.io.IOException; 7 | 8 | /** 9 | * @Author: lty 10 | * @Date: 2020/12/28 14:39 11 | */ 12 | public interface SaveFileService { 13 | String saveToFile(String result, String id, MarkDown markDown) throws IOException; 14 | void saveToDatabase(String result,String id,String savePath,MarkDown markDown) throws IOException; 15 | void saveImagePath(String path); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/service/SettingService.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.service; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.liangtengyu.markdown.entity.SETTING; 5 | import com.liangtengyu.markdown.entity.UserTemplate; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * @Author: lty 12 | * @Date: 2021/3/3 14:23 13 | */ 14 | public interface SettingService { 15 | Map getSettings(); 16 | int setSettings(JSONObject j); 17 | UserTemplate getOneUserTemplate(Integer id); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/liangtengyu/markdown/utils/ImageUtil.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown.utils; 2 | 3 | import javax.imageio.stream.FileImageOutputStream; 4 | import java.io.File; 5 | 6 | /** 7 | * @Author: lty 8 | * @Date: 2021/4/7 09:50 9 | */ 10 | public class ImageUtil { 11 | 12 | 13 | public static void byte2image(byte[] data, String path){ 14 | if(data.length<3||path.equals("")) { 15 | return; 16 | } 17 | try{ 18 | FileImageOutputStream imageOutput = new FileImageOutputStream(new File(path)); 19 | imageOutput.write(data, 0, data.length); 20 | imageOutput.close(); 21 | } catch(Exception ex) { 22 | ex.printStackTrace(); 23 | } 24 | } 25 | 26 | /** 27 | * byte数组到16进制字符串 28 | * @param data 29 | * @return 30 | */ 31 | public String byte2string(byte[] data){ 32 | if(data==null||data.length<=1) { 33 | return "0x"; 34 | } 35 | if(data.length>200000) { 36 | return "0x"; 37 | } 38 | StringBuffer sb = new StringBuffer(); 39 | int buf[] = new int[data.length]; 40 | //byte数组转化成十进制 41 | for(int k=0;k 0){ 25 | return true; 26 | } 27 | 28 | return false; 29 | } 30 | 31 | /** 32 | * 获取 图片 file 33 | * 34 | * @param imageFilePath 35 | * @param imageFileName 36 | * @return 37 | */ 38 | public static File getImageFile(String imageFilePath,String imageFileName) throws IOException { 39 | File imageFile = new File(imageFilePath + File.separator + imageFileName); 40 | imageFile.createNewFile(); 41 | return imageFile; 42 | } 43 | 44 | 45 | public static String getUrlOrigin(MarkDown markDown) { 46 | String net = ""; 47 | String url = markDown.getBlogUrl(); 48 | String[] httpsSplit = url.split("://"); 49 | int i = httpsSplit[1].length() - httpsSplit[1].replaceAll("\\.", "").length(); 50 | if (i >= 2) { 51 | String[] split = httpsSplit[1].split("\\."); 52 | net = split[1]; 53 | } else { 54 | String[] split = httpsSplit[1].split("\\."); 55 | net = split[0]; 56 | } 57 | log.info("请求链接>>> {} 来源解析为:{}",url,net); 58 | markDown.setWebsite(net); 59 | return net; 60 | } 61 | public static String generatorFileName() { 62 | String filename = UUID.randomUUID().toString().split("-")[0]+".md"; 63 | return filename; 64 | } 65 | 66 | public static void main(String[] args) { 67 | String a = "https://zhuanlan.zhihu.com/p/52843516"; 68 | MarkDown markDown = new MarkDown(); 69 | markDown.setBlogUrl(a); 70 | String urlOrigin = getUrlOrigin(markDown); 71 | System.out.println(urlOrigin); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/Remark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 OverZealous Creations, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.overzealous.remark; 18 | 19 | import com.overzealous.remark.convert.DocumentConverter; 20 | import org.jsoup.Jsoup; 21 | import org.jsoup.nodes.Document; 22 | import org.jsoup.safety.Cleaner; 23 | import org.jsoup.safety.Whitelist; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.io.OutputStream; 28 | import java.io.Writer; 29 | import java.net.URL; 30 | import java.util.concurrent.locks.ReentrantLock; 31 | 32 | /** 33 | * The class that manages converting HTML to Markdown. 34 | * 35 | *

It is recommended that you saveToFile this class if it is going to be reused for better performance. This class 36 | * is thread-safe, but can only process a single document concurrently.

37 | * 38 | *

Usage:

39 | * 40 | *

Basic usage involves instantiating this class with a specific set of options, and calling one of the 41 | * {@code convert*} methods on some form of input.

42 | * 43 | *

Examples:

44 | * 45 | *
 46 |  * // Create a generic remark that converts to pure-Markdown spec. 
 47 |  * Remark remark = new Remark();
 48 |  * String cleanedUp = remark.convertFragment(inputString);
 49 |  * 
 50 |  * // Create a remark that converts to pegdown with all extensions enabled. 
 51 |  * Remark pegdownAll = new Remark(Options.pegdownAllExtensions());
 52 |  * cleanedUp = pegdownAll.convert(new URL("http://www.example.com"), 15000);
 53 |  * 
 54 |  * // stream the conversion
 55 |  * pegdownAll.withStream(System.out).convert(new URL("http://www.overzealous.com"), 15000);
 56 |  * 
57 | * 58 | * 59 | * @author Phil DeJarnett 60 | */ 61 | public class Remark { 62 | private final Cleaner cleaner; 63 | @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) 64 | private final Options options; 65 | private final DocumentConverter converter; 66 | private final ReentrantLock converterLock = new ReentrantLock(); 67 | private boolean cleanedHtmlEchoed = false; 68 | 69 | /** 70 | * Creates a default, pure Markdown-compatible Remark instance. 71 | */ 72 | public Remark() { 73 | this(Options.markdown()); 74 | } 75 | 76 | /** 77 | * Creates a Remark instance with the specified options. 78 | * 79 | * @param options Specified options to use on this instance. See the docs for the Options class for common options sets. 80 | */ 81 | public Remark(Options options) { 82 | this.options = options.getCopy(); 83 | Whitelist whitelist = Whitelist.basicWithImages() 84 | .addTags("div", 85 | "h1", "h2", "h3", "h4", "h5", "h6", 86 | "table", "tbody", "td", "tfoot", "th", "thead", "tr", 87 | "hr", 88 | "span", "font") 89 | .addAttributes("th", "colspan", "align", "style") 90 | .addAttributes("td", "colspan", "align", "style") 91 | .addAttributes(":all", "title", "style"); 92 | if(options.preserveRelativeLinks) { 93 | whitelist.preserveRelativeLinks(true); 94 | } 95 | if(options.abbreviations) { 96 | whitelist.addTags("abbr", "acronym"); 97 | } 98 | if(options.headerIds) { 99 | for(int i=1; i<=6; i++) { 100 | whitelist.addAttributes("h"+i, "id"); 101 | } 102 | } 103 | for(final IgnoredHtmlElement el : options.getIgnoredHtmlElements()) { 104 | whitelist.addTags(el.getTagName()); 105 | if(!el.getAttributes().isEmpty()) { 106 | whitelist.addAttributes(el.getTagName(), el.getAttributes().toArray(new String[el.getAttributes().size()])); 107 | } 108 | } 109 | cleaner = new Cleaner(whitelist); 110 | 111 | if(options.getTables().isLeftAsHtml()) { 112 | // we need to allow the table nodes to be ignored 113 | // since they are automatically ignored recursively, this is the only node we worry about. 114 | options.getIgnoredHtmlElements().add(IgnoredHtmlElement.create("table")); 115 | } 116 | 117 | converter = new DocumentConverter(options); 118 | } 119 | 120 | /** 121 | * Provides access to the DocumentConverter for customization. 122 | * 123 | * @return the configured DocumentConverter. 124 | */ 125 | @SuppressWarnings({"UnusedDeclaration"}) 126 | public DocumentConverter getConverter() { 127 | return converter; 128 | } 129 | 130 | /** 131 | * Returns true if the cleaned HTML document is echoed to {@code System.out}. 132 | * @return true if the cleaned HTML document is echoed 133 | */ 134 | @SuppressWarnings({"UnusedDeclaration"}) 135 | public boolean isCleanedHtmlEchoed() { 136 | return cleanedHtmlEchoed; 137 | } 138 | 139 | /** 140 | * To see the cleaned and processed HTML document, set this to true. It will 141 | * be rendered to {@code System.out} for debugging purposes. 142 | * @param cleanedHtmlEchoed true to echo out the cleaned HTML document 143 | */ 144 | public void setCleanedHtmlEchoed(boolean cleanedHtmlEchoed) { 145 | this.cleanedHtmlEchoed = cleanedHtmlEchoed; 146 | } 147 | 148 | /** 149 | * This class is used to handle conversions that convert directly to streams. 150 | */ 151 | private final class StreamRemark extends Remark { 152 | private final Remark remark; 153 | private final Writer writer; 154 | private final OutputStream os; 155 | 156 | private StreamRemark(Remark remark, Writer writer) { 157 | this.remark = remark; 158 | this.writer = writer; 159 | this.os = null; 160 | } 161 | private StreamRemark(Remark remark, OutputStream out) { 162 | this.remark = remark; 163 | this.writer = null; 164 | this.os = out; 165 | } 166 | 167 | @Override 168 | public Remark withWriter(Writer writer) { 169 | return remark.withWriter(writer); 170 | } 171 | 172 | @Override 173 | public Remark withOutputStream(OutputStream os) { 174 | return remark.withOutputStream(os); 175 | } 176 | 177 | @Override 178 | public String convert(Document doc) { 179 | return remark.processConvert(doc, writer, os); 180 | } 181 | } 182 | 183 | /** 184 | * Use this method in a chain to handle streaming the output to a Writer. 185 | * The returned class can be saved for repeated writing to the same streams. 186 | * 187 | *

Note: The convert methods on the returned class will always return {@code null}.

188 | * 189 | *

Note: It is up to the calling class to handle closing the writer!

190 | * 191 | *

Example:

192 | * 193 | *
{@code new Remark(options).withWriter(myWiter).convert(htmlText);}
194 | * 195 | * @param writer Writer to receive the converted output 196 | * @return A Remark that writes to streams. 197 | */ 198 | @SuppressWarnings({"WeakerAccess"}) 199 | public synchronized Remark withWriter(Writer writer) { 200 | if(writer == null) { 201 | throw new NullPointerException("Writer cannot be null."); 202 | } 203 | return new StreamRemark(this, writer); 204 | } 205 | 206 | /** 207 | * Use this method in a chain to handle streaming the output to an OutputStream. 208 | * The returned class can be saved for repeated writing to the same streams. 209 | * 210 | *

Note: The convert methods on the returned class will always return {@code null}.

211 | * 212 | *

Note: It is up to the calling class to handle closing the stream!

213 | * 214 | *

Example:

215 | * 216 | *
{@code new Remark(options).withOutputStream(myOut).convert(htmlText);}
217 | * 218 | * @param os OutputStream to receive the converted output 219 | * @return A Remark that writes to streams. 220 | */ 221 | @SuppressWarnings({"WeakerAccess"}) 222 | public synchronized Remark withOutputStream(OutputStream os) { 223 | if(os == null) { 224 | throw new NullPointerException("OutputStream cannot be null."); 225 | } 226 | return new StreamRemark(this, os); 227 | } 228 | 229 | /** 230 | * Converts an HTML document retrieved from a URL to Markdown. 231 | * @param url URL to connect to. 232 | * @param timeoutMillis Maximum time to wait before giving up on the connection. 233 | * @return Markdown text. 234 | * @throws IOException If an error occurs while retrieving the document. 235 | * @see org.jsoup.Jsoup#parse(URL, int) 236 | */ 237 | public String convert(URL url, int timeoutMillis) throws IOException { 238 | Document doc = Jsoup.parse(url, timeoutMillis); 239 | return convert(doc); 240 | } 241 | 242 | 243 | /** 244 | * Converts an HTML file to Markdown. 245 | * @param file The file to load. 246 | * @return Markdown text. 247 | * @throws IOException If an error occurs while loading the file. 248 | * @see org.jsoup.Jsoup#parse(File, String, String) 249 | */ 250 | public String convert(File file) throws IOException { 251 | return convert(file, null); 252 | } 253 | 254 | 255 | /** 256 | * Converts an HTML file to Markdown. 257 | * @param file The file to load. 258 | * @param charset The charset of the file (if not specified and not UTF-8). Set to {@code null} to determine from {@code http-equiv} meta tag, if present, or fall back to {@code UTF-8} (which is often safe to do). 259 | * @return Markdown text. 260 | * @throws IOException If an error occurs while loading the file. 261 | * @see org.jsoup.Jsoup#parse(File, String, String) 262 | */ 263 | @SuppressWarnings({"WeakerAccess", "SameParameterValue"}) 264 | public String convert(File file, String charset) throws IOException { 265 | return convert(file, charset, ""); 266 | } 267 | 268 | 269 | /** 270 | * Converts an HTML file to Markdown. 271 | * @param file The file to load. 272 | * @param charset The charset of the file (if not specified and not UTF-8). Set to {@code null} to determine from {@code http-equiv} meta tag, if present, or fall back to {@code UTF-8} (which is often safe to do). 273 | * @param baseUri The base URI for resolving relative links. 274 | * @return Markdown text. 275 | * @throws IOException If an error occurs while loading the file. 276 | * @see org.jsoup.Jsoup#parse(File, String, String) 277 | */ 278 | public String convert(File file, String charset, String baseUri) throws IOException { 279 | Document doc = Jsoup.parse(file, charset, baseUri); 280 | return convert(doc); 281 | } 282 | 283 | 284 | /** 285 | * Converts HTML in memory to Markdown. 286 | * @param html The string to processConvert from HTML 287 | * @return Markdown text. 288 | * @see org.jsoup.Jsoup#parse(String, String) 289 | */ 290 | public String convert(String html) { 291 | return convert(html, ""); 292 | } 293 | 294 | 295 | /** 296 | * Converts HTML in memory to Markdown. 297 | * @param html The string to processConvert from HTML 298 | * @param baseUri The base URI for resolving relative links. 299 | * @return Markdown text. 300 | * @see org.jsoup.Jsoup#parse(String, String) 301 | */ 302 | @SuppressWarnings({"WeakerAccess", "SameParameterValue"}) 303 | public String convert(String html, String baseUri) { 304 | Document doc = Jsoup.parse(html, baseUri); 305 | return convert(doc); 306 | } 307 | 308 | 309 | /** 310 | * Converts an HTML body fragment to Markdown. 311 | * @param body The fragment string to processConvert from HTML 312 | * @return Markdown text. 313 | * @see org.jsoup.Jsoup#parseBodyFragment(String, String) 314 | */ 315 | @SuppressWarnings({"UnusedDeclaration"}) 316 | public String convertFragment(String body) { 317 | return convertFragment(body, ""); 318 | } 319 | 320 | 321 | /** 322 | * Converts an HTML body fragment to Markdown. 323 | * @param body The fragment string to processConvert from HTML 324 | * @param baseUri The base URI for resolving relative links. 325 | * @return Markdown text. 326 | * @see org.jsoup.Jsoup#parseBodyFragment(String, String) 327 | */ 328 | public String convertFragment(String body, String baseUri) { 329 | Document doc = Jsoup.parseBodyFragment(body, baseUri); 330 | return convert(doc); 331 | } 332 | 333 | /** 334 | * Converts an already-loaded JSoup Document to Markdown. 335 | * 336 | * @param doc Document to be processed 337 | * @return Markdown text. 338 | */ 339 | @SuppressWarnings({"WeakerAccess"}) 340 | public String convert(Document doc) { 341 | // Note: all convert methods should end up going through this method! 342 | return processConvert(doc, null, null); 343 | } 344 | 345 | /** 346 | * Handles the actual conversion 347 | * @param doc document to convert 348 | * @param writer Optional Writer for output 349 | * @param os Optional OutputStream for output 350 | * @return String result if not using an output stream, else null 351 | */ 352 | private String processConvert(Document doc, Writer writer, OutputStream os) { 353 | String cleanString = Jsoup.clean(doc.html(), "https://www.baidu.com", Whitelist.relaxed().preserveRelativeLinks(true)); 354 | Document parse = Jsoup.parse(cleanString); 355 | doc = parse; 356 | if(cleanedHtmlEchoed) { 357 | System.out.println("Cleaned and processed HTML document:"); 358 | System.out.println(doc.toString()); 359 | System.out.println(); 360 | } 361 | String result = null; 362 | converterLock.lock(); 363 | try { 364 | if(writer != null) { 365 | converter.convert(doc, writer); 366 | } else if(os != null) { 367 | converter.convert(doc, os); 368 | } else { 369 | result = converter.convert(doc); 370 | } 371 | } finally { 372 | converterLock.unlock(); 373 | } 374 | return result; 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/convert/CmLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 OverZealous Creations, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.overzealous.remark.convert; 18 | 19 | import com.overzealous.remark.Options; 20 | import com.overzealous.remark.util.BlockWriter; 21 | import com.overzealous.remark.util.StringUtils; 22 | import org.jsoup.nodes.Element; 23 | 24 | /** 25 | * Handles preformatted sections (pre), renders them as code blocks. 26 | * 27 | * @author Phil DeJarnett 28 | */ 29 | public class CmLine extends AbstractNodeHandler { 30 | 31 | /** 32 | * Converts a pre-formatted block of code. 33 | * Depending on the options, this may render as a block with four spaces added to the beginning, 34 | * or as a fenced code block. 35 | * 36 | * @param parent The previous node walker, in case we just want to remove an element. 37 | * @param node Node to handle 38 | * @param converter Parent converter for this object. 39 | */ 40 | @Override 41 | public void handleNode(NodeHandler parent, Element node, DocumentConverter converter) { 42 | BlockWriter out; 43 | Options.FencedCodeBlocks fenced = converter.options.getFencedCodeBlocks(); 44 | if(fenced.isEnabled()) { 45 | String fence = StringUtils.multiply(fenced.getSeparatorCharacter(), 46 | converter.options.fencedCodeBlocksWidth); 47 | out = converter.output; 48 | converter.output.startBlock(); 49 | out.println(fence); 50 | out.write(converter.cleaner.cleanCode(node)); 51 | out.println(); 52 | out.print(fence); 53 | converter.output.endBlock(); 54 | } else { 55 | converter.output.startBlock(); 56 | out = new BlockWriter(converter.output); 57 | out.print("\r\n"); 58 | out.write(converter.cleaner.cleanCode(node)); 59 | out.print("\r\n"); 60 | converter.output.endBlock(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/convert/Codeblock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 OverZealous Creations, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.overzealous.remark.convert; 18 | 19 | import com.overzealous.remark.Options; 20 | import com.overzealous.remark.util.BlockWriter; 21 | import com.overzealous.remark.util.StringUtils; 22 | import org.jsoup.nodes.Element; 23 | 24 | /** 25 | * Handles preformatted sections (pre), renders them as code blocks. 26 | * 27 | * @author Phil DeJarnett 28 | */ 29 | public class Codeblock extends AbstractNodeHandler { 30 | 31 | /** 32 | * Converts a pre-formatted block of code. 33 | * Depending on the options, this may render as a block with four spaces added to the beginning, 34 | * or as a fenced code block. 35 | * 36 | * @param parent The previous node walker, in case we just want to remove an element. 37 | * @param node Node to handle 38 | * @param converter Parent converter for this object. 39 | */ 40 | @Override 41 | public void handleNode(NodeHandler parent, Element node, DocumentConverter converter) { 42 | BlockWriter out; 43 | Options.FencedCodeBlocks fenced = converter.options.getFencedCodeBlocks(); 44 | if(fenced.isEnabled()) { 45 | String fence = StringUtils.multiply(fenced.getSeparatorCharacter(), 46 | converter.options.fencedCodeBlocksWidth); 47 | out = converter.output; 48 | converter.output.startBlock(); 49 | out.println(fence); 50 | out.write(converter.cleaner.cleanCode(node)); 51 | out.println(); 52 | out.print(fence); 53 | converter.output.endBlock(); 54 | } else { 55 | converter.output.startBlock(); 56 | out = new BlockWriter(converter.output); 57 | out.print("```java"); 58 | out.print("\r\n"); 59 | out.write(converter.cleaner.cleanCode(node)); 60 | out.print("\r\n"); 61 | out.print("```"); 62 | converter.output.endBlock(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/convert/Header.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 OverZealous Creations, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.overzealous.remark.convert; 18 | 19 | import com.overzealous.remark.util.BlockWriter; 20 | import com.overzealous.remark.util.StringUtils; 21 | import org.jsoup.nodes.Element; 22 | 23 | /** 24 | * Handles header nodes (h1 through h6) 25 | * 26 | * @author Phil DeJarnett 27 | */ 28 | public class Header extends AbstractNodeHandler { 29 | 30 | /** 31 | * Renders a header node (h1..h6). If enabled, also handles the headerID attribute. 32 | * 33 | * @param parent The previous node walker, in case we just want to remove an element. 34 | * @param node Node to handle 35 | * @param converter Parent converter for this object. 36 | */ 37 | public void handleNode(NodeHandler parent, Element node, DocumentConverter converter) { 38 | int depth = Integer.parseInt(node.tagName().substring(1, 2)); 39 | BlockWriter out = converter.output; 40 | out.startBlock(); 41 | StringUtils.multiply(out, '#', depth); 42 | out.print(' '); 43 | out.print(converter.getInlineContent(this, node).replace("\n", " ")); 44 | out.print(' '); 45 | if(converter.options.headerIds && node.hasAttr("id")) { 46 | out.printf(" {#%s}", node.attr("id")); 47 | } 48 | out.endBlock(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/convert/Image.java: -------------------------------------------------------------------------------- 1 | package com.overzealous.remark.convert; 2 | 3 | 4 | import com.overzealous.remark.util.BlockWriter; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.jsoup.nodes.Element; 7 | 8 | /** 9 | * Handles img tags. 10 | * @author Phil DeJarnett 11 | */ 12 | public class Image extends AbstractNodeHandler { 13 | 14 | @Override 15 | public void handleNode(NodeHandler parent, Element node, DocumentConverter converter) { 16 | String url = converter.cleaner.cleanUrl(node.attr("src")); 17 | if (StringUtils.isNotBlank(url)) { 18 | String alt = node.attr("alt"); 19 | converter.output.printf("![%s](%s)", alt, url); 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/overzealous/remark/convert/InlineStyle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011 OverZealous Creations, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.overzealous.remark.convert; 18 | 19 | import com.overzealous.remark.Options; 20 | import org.jsoup.nodes.Element; 21 | import org.jsoup.nodes.Node; 22 | import org.jsoup.nodes.TextNode; 23 | 24 | import java.util.regex.Matcher; 25 | import java.util.regex.Pattern; 26 | 27 | /** 28 | * Handles various inline styling (italics and bold), such as em, i, strong, b, span, and font tags. 29 | * @author Phil DeJarnett 30 | */ 31 | public class InlineStyle extends AbstractNodeHandler { 32 | 33 | private static final char ITALICS_WRAPPER = '*'; 34 | private static final String BOLD_WRAPPER = "**"; 35 | 36 | private static final Pattern ITALICS_PATTERN = Pattern.compile("font-style:\\s*italic", Pattern.CASE_INSENSITIVE); 37 | private static final Pattern BOLD_PATTERN = Pattern.compile("font-weight:\\s*bold", Pattern.CASE_INSENSITIVE); 38 | 39 | private static final Pattern INWORD_CHARACTER = Pattern.compile("\\w"); 40 | 41 | private static final Pattern SPACE_CONTENT_SPACE = Pattern.compile("^(\\s*+)(.*?)(\\s*)$", Pattern.DOTALL); 42 | 43 | private int italicDepth = 0; 44 | private int boldDepth = 0; 45 | 46 | /** 47 | * Renders inline styling (bold, italics) for the given tag. It handles implicit styling ({@code em}, {@code strong}) as 48 | * well as explicit styling via the {@code style} attribute. 49 | *

This object keeps track of the depth of the styling, to prevent recursive situations like this:

50 | * 51 | *
{@code hello world}
52 | * 53 | *

A naive method would be render the example incorrectly (the output would be {@code *hello **world*})

54 | * 55 | * @param parent The previous node walker, in case we just want to remove an element. 56 | * @param node Node to handle 57 | * @param converter Parent converter for this object. 58 | */ 59 | public void handleNode(NodeHandler parent, Element node, DocumentConverter converter) { 60 | if(checkInnerBlock(node)) { 61 | // not valid to have an inline node around block nodes, so we have to 62 | // simply ignore them. 63 | // just recurse like it's not here. 64 | converter.walkNodes(parent, node); 65 | } else { 66 | Rules rules = checkInword(node, converter); 67 | if(rules.emphasisPreserved) { 68 | checkTag(node, rules); 69 | 70 | if(rules.bold || rules.italics) { 71 | handleStyled(parent, node, converter, rules); 72 | } else { 73 | converter.walkNodes(this, node, converter.inlineNodes); 74 | } 75 | } else { // emphasis has been disabled for this section 76 | // mark as if emphasis was already processed 77 | italicDepth++; 78 | boldDepth++; 79 | converter.walkNodes(this, node, converter.inlineNodes); 80 | italicDepth--; 81 | boldDepth--; 82 | } 83 | } 84 | } 85 | 86 | @Override 87 | public void handleTextNode(TextNode node, DocumentConverter converter) { 88 | // Override to provide special handling for ignoring 89 | // leading or trailing all-space nodes. 90 | if((node.previousSibling() != null && node.nextSibling() != null) || 91 | node.text().trim().length() != 0) { 92 | super.handleTextNode(node, converter); 93 | } 94 | } 95 | 96 | /** 97 | * Minor class to hold onto the styling rules for this class. 98 | */ 99 | private class Rules { 100 | boolean emphasisPreserved = true; 101 | boolean addSpacing = false; 102 | boolean italics = false; 103 | boolean bold = false; 104 | } 105 | 106 | /** 107 | * Handles dealing with a styled node (one that has markers on either side). 108 | * 109 | *

It's unique because we have to deal with leading and trailing spaces, among other issues.

110 | * 111 | * @param parent The previous node walker, in case we just want to remove an element. 112 | * @param node Node to handle 113 | * @param converter Parent converter for this object. 114 | * @param rules The styling rules that are active 115 | */ 116 | private void handleStyled(NodeHandler parent, Element node, DocumentConverter converter, Rules rules) { 117 | // prevent double styling 118 | if(rules.bold) { boldDepth++; } 119 | if(rules.italics) { italicDepth++; } 120 | String content = converter.getInlineContent(this, node, true); 121 | if(rules.bold) { boldDepth--; } 122 | if(rules.italics) { italicDepth--; } 123 | 124 | // only proceed if we have content 125 | if(content.length() > 0) { 126 | 127 | 128 | Matcher parts = SPACE_CONTENT_SPACE.matcher(content); 129 | if(parts.find()) { 130 | // write any leading space 131 | converter.output.write(parts.group(1)); 132 | 133 | // don't write the markers if the content ends up empty 134 | if(parts.group(2).length() > 0) { 135 | 136 | // write content 137 | converter.output.write(parts.group(2)); 138 | 139 | 140 | } 141 | 142 | // write any trailing space 143 | converter.output.write(parts.group(3)); 144 | 145 | } // else, something weird happened, like (1 == 0) 146 | } 147 | } 148 | 149 | /** 150 | * Check to see if there is a block-level node somewhere inside this node. 151 | * 152 | * @param node Current node 153 | * @return True is there is a block inside this node (which would be invalid HTML) 154 | */ 155 | private boolean checkInnerBlock(Element node) { 156 | boolean blockExists = false; 157 | for(final Element child : node.children()) { 158 | blockExists = child.isBlock() || checkInnerBlock(child); 159 | if(blockExists) { 160 | break; 161 | } 162 | } 163 | return blockExists; 164 | } 165 | 166 | /** 167 | * Handles the situation where InWordEmphasis needs to be manipulated. 168 | * 169 | *

This isn't a terribly intelligent check - it merely looks for the 170 | * situation where a styled node is immediately followed by a 171 | * text node, and that text node starts with a word character.

172 | * 173 | * @param node The current node (should be an inline-styled node) 174 | * @param converter The current converter 175 | * @return flags for checking. 176 | */ 177 | private Rules checkInword(Element node, DocumentConverter converter) { 178 | Rules result = new Rules(); 179 | Options.InWordEmphasis iwe = converter.options.getInWordEmphasis(); 180 | if(!iwe.isEmphasisPreserved() || iwe.isAdditionalSpacingNeeded()) { 181 | // peek behind for inline styling 182 | Node n = node.previousSibling(); 183 | if(n != null && n instanceof TextNode) { 184 | TextNode tn = (TextNode)n; 185 | String text = tn.text(); 186 | if(INWORD_CHARACTER.matcher(text.substring(text.length()-1)).matches()) { 187 | result.emphasisPreserved = iwe.isEmphasisPreserved(); 188 | result.addSpacing = iwe.isAdditionalSpacingNeeded(); 189 | } 190 | } 191 | // peek ahead for inline styling 192 | n = node.nextSibling(); 193 | if(n != null && n instanceof TextNode) { 194 | TextNode tn = (TextNode)n; 195 | if(INWORD_CHARACTER.matcher(tn.text().substring(0,1)).matches()) { 196 | result.emphasisPreserved = iwe.isEmphasisPreserved(); 197 | result.addSpacing = iwe.isAdditionalSpacingNeeded(); 198 | } 199 | } 200 | } 201 | return result; 202 | } 203 | 204 | /** 205 | * Check the styling rules that may or may not apply to this tag. 206 | * @param node The node to look at 207 | * @param rules The rules object to hold the result 208 | */ 209 | private void checkTag(Element node, Rules rules) { 210 | String tn = node.tagName(); 211 | if(tn.equals("i") || tn.equals("em")) { 212 | rules.italics = (italicDepth == 0); 213 | } else if(tn.equals("b") || tn.equals("strong")) { 214 | rules.bold = (boldDepth == 0); 215 | } else { 216 | // check inline-style 217 | if(node.hasAttr("style")) { 218 | String style = node.attr("style"); 219 | if(ITALICS_PATTERN.matcher(style).find()) { 220 | rules.italics = (italicDepth == 0); 221 | } 222 | if(BOLD_PATTERN.matcher(style).find()) { 223 | rules.bold = (boldDepth == 0); 224 | } 225 | } 226 | } 227 | } 228 | 229 | /** 230 | * Render the starting styling tag as necessary. 231 | * 232 | * @param style Rules to render 233 | * @param leadingSpaces Leading spaces string (if any) 234 | * @param converter parent converter 235 | */ 236 | private void start(Rules style, String leadingSpaces, DocumentConverter converter) { 237 | if(style.addSpacing && 238 | (italicDepth == 0 || boldDepth == 0) && 239 | (leadingSpaces == null || leadingSpaces.length() == 0)) { 240 | converter.output.write(' '); 241 | } 242 | if(style.italics) { 243 | if(italicDepth == 0) { 244 | converter.output.write(ITALICS_WRAPPER); 245 | } 246 | } 247 | if(style.bold) { 248 | if(boldDepth == 0) { 249 | converter.output.write(BOLD_WRAPPER); 250 | } 251 | } 252 | } 253 | 254 | /** 255 | * Render the ending tag as necessary. 256 | * 257 | * @param style Rules to render 258 | * @param trailingSpaces Trailing spaces (if any) 259 | * @param converter parent converter 260 | */ 261 | private void end(Rules style, String trailingSpaces, DocumentConverter converter) { 262 | if(style.bold) { 263 | if(boldDepth == 0) { 264 | converter.output.write(BOLD_WRAPPER); 265 | } 266 | } 267 | if(style.italics) { 268 | if(italicDepth == 0) { 269 | converter.output.write(ITALICS_WRAPPER); 270 | } 271 | } 272 | if(style.addSpacing && 273 | (italicDepth == 0 || boldDepth == 0) && 274 | (trailingSpaces == null || trailingSpaces.length() == 0)) { 275 | converter.output.write(' '); 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9999 3 | 4 | spring: 5 | thymeleaf: 6 | prefix: classpath:/templates/ 7 | suffix: .html 8 | mode: HTML 9 | cache: true 10 | encoding: UTF-8 11 | 12 | datasource: 13 | url: jdbc:h2:file:~/markdown;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;mode=MySQL 14 | platform: h2 15 | driver-class-name: org.h2.Driver 16 | username: sa 17 | password: 18 | schema: classpath:schema.sql 19 | 20 | 21 | 22 | 23 | 24 | resources: 25 | chain: 26 | strategy: 27 | content: 28 | enabled: true 29 | paths: /** 30 | h2: 31 | console: 32 | enabled: true 33 | path: /db 34 | settings: 35 | web-allow-others: true 36 | jpa: 37 | hibernate: 38 | ddl-auto: update 39 | show-sql: true 40 | 41 | 42 | 43 | 44 | ## markdown存储位置配置区: 45 | markdown: 46 | filePath: ./mds/ 47 | -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | 服务开始启动...... -------------------------------------------------------------------------------- /src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | INSERT IGNORE INTO PUBLIC.SETTING (ID, CONFIG_NAME, CONFIG_VALUE, CREATE_TIME, REMARK) VALUES (1, 'Image_Proxy_Path', 'http://127.0.0.1:9999/images', null, '图片映射地址'); 2 | INSERT IGNORE INTO PUBLIC.SETTING (ID, CONFIG_NAME, CONFIG_VALUE, CREATE_TIME, REMARK) VALUES (2, 'MD_Save_Path', './mds', null, 'MD文件保存路径'); 3 | INSERT IGNORE INTO PUBLIC.SETTING (ID, CONFIG_NAME, CONFIG_VALUE, CREATE_TIME, REMARK) VALUES (3, 'Image_Save_Path', './pics', null, '图片保存路径'); 4 | INSERT IGNORE INTO PUBLIC.SETTING (ID, CONFIG_NAME, CONFIG_VALUE, CREATE_TIME, REMARK) VALUES (4, 'User_template_id', '1', null, '自定义文件头部和尾部'); 5 | INSERT IGNORE INTO PUBLIC.SETTING (ID, CONFIG_NAME, CONFIG_VALUE, CREATE_TIME, REMARK) VALUES (36, 'Image_DEFAULT_NAME', 'pic', null, '默认图片文件名'); -------------------------------------------------------------------------------- /src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table IF NOT EXISTS md( 2 | id int primary key auto_increment, 3 | title text, 4 | context longtext, 5 | pname varchar(100), 6 | save_path varchar(100), 7 | blog_url varchar(300), 8 | create_time datetime 9 | ); 10 | 11 | create table IF NOT EXISTS pic( 12 | id int primary key auto_increment, 13 | pname varchar(100), 14 | path varchar(255), 15 | create_time datetime 16 | ); 17 | create table IF NOT EXISTS setting( 18 | id int primary key auto_increment, 19 | config_name varchar(100), 20 | config_value varchar(100), 21 | create_time datetime, 22 | remark varchar(200) 23 | ); 24 | create table IF NOT EXISTS USER_TEMPLATE( 25 | id int primary key auto_increment, 26 | header longtext, 27 | bottom longtext, 28 | create_time datetime 29 | ); -------------------------------------------------------------------------------- /src/main/resources/static/css/about.ae075234.css: -------------------------------------------------------------------------------- 1 | .img-container[data-v-4b17ba9c]{width:210px;height:140px}.tr[data-v-4b17ba9c]{border-radius:6px;margin-bottom:11px;overflow:hidden}.if[data-v-eb84ce2c]{height:768px;width:1024px} -------------------------------------------------------------------------------- /src/main/resources/static/css/app.400b5e86.css: -------------------------------------------------------------------------------- 1 | #components-layout-demo-custom-trigger .trigger{font-size:18px;line-height:64px;padding:0 24px;cursor:pointer;transition:color .3s}#components-layout-demo-custom-trigger .trigger:hover{color:#1890ff}#components-layout-demo-custom-trigger .logo{height:32px;background:hsla(0,0%,100%,.2);margin:16px}.mde[data-v-0524ac7a]{height:850px} -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontello.068ca2b3.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/fonts/fontello.068ca2b3.ttf -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontello.8d4a4e6f.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/fonts/fontello.8d4a4e6f.woff2 -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontello.a782baa8.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/fonts/fontello.a782baa8.woff -------------------------------------------------------------------------------- /src/main/resources/static/fonts/fontello.e73a0647.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/fonts/fontello.e73a0647.eot -------------------------------------------------------------------------------- /src/main/resources/static/img/1614755729311.82e3994d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/src/main/resources/static/img/1614755729311.82e3994d.jpg -------------------------------------------------------------------------------- /src/main/resources/static/img/fontello.9354499c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2017 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | Vue App
-------------------------------------------------------------------------------- /src/main/resources/static/js/about.37e01380.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about"],{"0bbf":function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},i=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticStyle:{height:"850px"}},[n("p",[t._v(" 线上试用版本,为防止被恶意篡改配置,不提供此功能! ")])])}],o={name:"manage"},s=o,c=(n("ca74"),n("2877")),l=Object(c["a"])(s,a,i,!1,null,"eb84ce2c",null);e["default"]=l.exports},"126c":function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},i=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"upload",staticStyle:{height:"850px"}},[n("h1",[t._v("还在开发中>..")])])}],o=n("2877"),s={},c=Object(o["a"])(s,a,i,!1,null,null,null);e["default"]=c.exports},"2bfc":function(t,e,n){},"2eec":function(t,e,n){},"66b0":function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"Files",staticStyle:{height:"1500px",overflow:"hidden"}},[t._l(t.mdlist,(function(e){return n("div",{staticClass:"content-box"},[n("div",{staticClass:"opt_button"},[n("a-button",{attrs:{type:"dashed",icon:"edit",id:e.id},on:{click:function(n){return t.edit(e.id)}}},[t._v(" 编辑 ")]),n("a-button",{ref:"id",refInFor:!0,attrs:{type:"dashed",icon:"delete",id:e.id},on:{click:function(n){return t.showModal(e.id)}}},[t._v(" 删除 ")])],1),n("a-card",{staticClass:"tr",attrs:{title:e.title}},[n("section",t._l(e.pics,(function(t){return n("td",[n("img",{staticClass:"img-container",attrs:{src:t,alt:""}})])})),0)])],1)})),n("a-pagination",{attrs:{total:t.count,defaultPageSize:5,"show-less-items":""},on:{change:function(e){return t.getData(t.current-1)}},model:{value:t.current,callback:function(e){t.current=e},expression:"current"}}),n("a-modal",{attrs:{title:"确认删除吗?"},on:{ok:function(e){return t.handleOk(e)}},model:{value:t.visible,callback:function(e){t.visible=e},expression:"visible"}},[n("p",[t._v("注意:删除时数据库记录和本地文件会一并删除!")])])],2)},i=[],o={components:{},data:function(){return{mdlist:"",current:1,count:5,visible:!1,delete:-1}},methods:{edit:function(t){console.log("发送"+t),this.$router.push({path:"/Home",name:"Home",params:{id:t}})},showModal:function(t){this.visible=!0,this.delete=t},handleOk:function(t){var e=this;console.log(this.delete),this.$axios.post("/delete/"+this.delete).then((function(t){console.log("删除返回:",t.data),e.getData(0)})).catch((function(t){alert("请求数据出错:"+t)})),this.visible=!1},getData:function(t){var e=this;this.spinning=!0,this.$axios.post("/filelist",{id:t}).then((function(t){e.mdlist=t.data.data})).catch((function(t){alert("请求数据出错:"+t)})),this.$axios.post("/count").then((function(t){e.count=t.data})).catch((function(t){alert("请求分页数据出错:"+t)})),this.spinning=!1}},mounted:function(){this.getData(0)}},s=o,c=(n("f9c2"),n("2877")),l=Object(c["a"])(s,a,i,!1,null,"4b17ba9c",null);e["default"]=l.exports},"9a8b":function(t,e,n){},ca74:function(t,e,n){"use strict";var a=n("2bfc"),i=n.n(a);i.a},cca0:function(t,e,n){t.exports=n.p+"img/1614755729311.82e3994d.jpg"},ef3c:function(t,e,n){"use strict";var a=n("2eec"),i=n.n(a);i.a},f121:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticStyle:{height:"850px"}},[n("a-spin",{attrs:{spinning:t.spinning}},[n("a-form-model",{attrs:{model:t.configInfo,rules:t.rules,"label-col":t.labelCol,"wrapper-col":t.wrapperCol}},[n("a-collapse",{model:{value:t.activeKey,callback:function(e){t.activeKey=e},expression:"activeKey"}},[n("a-collapse-panel",{key:"1",attrs:{header:"抓取文章前-先进行配置"}},[n("a-form-model-item",{attrs:{label:"图片名称"}},[n("a-input",{attrs:{required:"",placeholder:"请输入图片名称"},model:{value:t.configInfo.Image_DEFAULT_NAME,callback:function(e){t.$set(t.configInfo,"Image_DEFAULT_NAME",e)},expression:"configInfo.Image_DEFAULT_NAME"}})],1),n("a-form-model-item",{attrs:{label:"保存图片到:",prop:"blogUrl"}},[n("a-input",{attrs:{placeholder:"请输入存图片到哪里"},model:{value:t.configInfo.Image_Save_Path,callback:function(e){t.$set(t.configInfo,"Image_Save_Path",e)},expression:"configInfo.Image_Save_Path"}})],1),n("a-form-model-item",{attrs:{label:"保存MD文件到:"}},[n("a-input",{attrs:{required:"",placeholder:"请输入保存MD文件到哪里"},model:{value:t.configInfo.MD_Save_Path,callback:function(e){t.$set(t.configInfo,"MD_Save_Path",e)},expression:"configInfo.MD_Save_Path"}})],1),n("a-form-model-item",{attrs:{label:"图片映射地址:",prop:"blogUrl"}},[n("a-input",{attrs:{placeholder:"请输入图片映射的地址"},model:{value:t.configInfo.Image_Proxy_Path,callback:function(e){t.$set(t.configInfo,"Image_Proxy_Path",e)},expression:"configInfo.Image_Proxy_Path"}})],1),n("a-form-model-item",{staticClass:"save_button",attrs:{"wrapper-col":{span:14,offset:4}}},[t._v(" 线上试用版本,为防止被恶意篡改配置,不提供此功能! "),n("p",{staticClass:"lb",staticStyle:{color:"red"}},[t._v(t._s(t.msg))])])],1)],1)],1)],1)],1)},i=[],o={components:{},data:function(){return{msg:"",data:"",spinning:!1,activeKey:[1],labelCol:{span:4},wrapperCol:{span:18},configInfo:{Image_DEFAULT_NAME:"default_name",Image_Save_Path:"./pics",MD_Save_Path:"./mds",Image_Proxy_Path:"http://localhost:9999/images"},rules:{blogUrl:[{min:10,message:"Length should be > 10",trigger:"blur"}]}}},methods:{onSubmit:function(){var t=this;this.msg="更改配置后请重启服务器,否则不生效!",this.spinning=!0,this.$axios.post("/setting/set",this.configInfo).then((function(e){t.data=e.data,t.spinning=!1,console.log(t.data)})).catch((function(e){alert(e),t.spinning=!1}))}},mounted:function(){var t=this;this.spinning=!0,this.$axios.post("/setting/get").then((function(e){t.data=e.data,t.configInfo=t.data,t.spinning=!1})).catch((function(e){alert(e),t.spinning=!1}))}},s=o,c=(n("ef3c"),n("2877")),l=Object(c["a"])(s,a,i,!1,null,"650eaede",null);e["default"]=l.exports},f820:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},i=[function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"about",staticStyle:{height:"850px"}},[a("h1",[t._v("关于本工具")]),a("p",[t._v("将HTTP页面 解析为Markdown格式 ")]),a("p",[t._v("目前支持: 微信公众号,知乎,知乎专栏,简书,知否(SegmentFault),掘金,CSDN,V2EX,博客园")]),a("p",[t._v("----------------------")]),a("h2",[t._v("关于我")]),t._v(" 欢迎浏览我的博客>> "),a("a",{attrs:{href:"http://liangtengyu.com",target:"_blank"}},[t._v(" 点击访问")]),a("br"),t._v(" 欢迎浏览我的Github>> "),a("a",{attrs:{href:"https://github.com/liangtengyu",target:"_blank"}},[t._v(" 点击访问")]),a("br"),a("p",[t._v("----------------------")]),a("h1",[t._v("扫码关注我")]),a("img",{attrs:{src:n("cca0"),height:"280",width:"800",alt:""}})])}],o=n("2877"),s={},c=Object(o["a"])(s,a,i,!1,null,null,null);e["default"]=c.exports},f9c2:function(t,e,n){"use strict";var a=n("9a8b"),i=n.n(a);i.a}}]); -------------------------------------------------------------------------------- /src/main/resources/static/js/app.c2a083e4.js: -------------------------------------------------------------------------------- 1 | (function(t){function e(e){for(var a,o,l=e[0],c=e[1],s=e[2],u=0,p=[];u 10",trigger:"blur"}]}}},methods:{onSubmit:function(){var t=this;this.configInfo.blogUrl.length>10?(this.spinning=!0,this.$axios.post("/resolve/mark",this.configInfo).then((function(e){t.data=e.data,t.spinning=!1})).catch((function(e){alert(e),t.spinning=!1}))):alert("需要输入文章地址")},update:function(){this.spinning=!0,this.$axios.post("/update",{id:this.data.id,context:this.data.markdown,blogUrl:this.configInfo.blogUrl,title:this.title}).then((function(t){0===t.data.code&&alert(" 保存到数据库成功! 本地md文件重写完成!")})).catch((function(t){alert(t)})),this.spinning=!1},initArticle:function(t){var e=this;this.saveButtonIsShow=!0,this.$axios.post("/select/"+t).then((function(t){e.data.markdown=t.data.context,e.data.id=t.data.id,e.configInfo.blogUrl=t.data.blogUrl})).catch((function(t){alert(t)})),this.spinning=!1}},mounted:function(){this.loadfile=this.$route.params.id,void 0!==this.loadfile&&this.initArticle(this.loadfile)}}),v=g,y=(n("7a4d"),Object(c["a"])(v,m,h,!1,null,"0524ac7a",null)),k=y.exports,w={name:"Home",components:{WebInfo:k}},x=w,_=Object(c["a"])(x,f,d,!1,null,null,null),S=_.exports;a["a"].use(p["a"]);var I=[{path:"/",name:"Home",component:S},{path:"/Home",name:"sy",component:S},{path:"/upload",name:"upload",component:function(){return n.e("about").then(n.bind(null,"126c"))}},{path:"/filelist",name:"filelist",component:function(){return n.e("about").then(n.bind(null,"66b0"))}},{path:"/about",name:"About",component:function(){return n.e("about").then(n.bind(null,"f820"))}},{path:"/config",name:"config",component:function(){return n.e("about").then(n.bind(null,"f121"))}},{path:"/manage",name:"manage",component:function(){return n.e("about").then(n.bind(null,"0bbf"))}}],C=new p["a"]({routes:I}),O=C,j=n("bc3a"),E=n.n(j),$=n("5efb"),N=n("98c5"),U=n("b558"),P=n("55f1"),A=n("dfae"),T=n("0c63"),B=n("ff57"),H=n("8592"),L=n("9839"),M=n("de1b"),q=n("cdeb"),D=n("ed3b"),W=n("f64c");n("202f");a["a"].prototype.$axios=E.a,a["a"].use($["a"]),a["a"].use(N["a"]),a["a"].use(U["a"]),a["a"].use(P["a"]),a["a"].use(A["a"]),a["a"].use(T["a"]),a["a"].use(B["a"]),a["a"].use(H["a"]),a["a"].use(L["b"]),a["a"].use(M["a"]),a["a"].use(q["a"]),a["a"].use(D["a"]),a["a"].prototype.$message=W["a"],a["a"].config.productionTip=!1,new a["a"]({router:O,render:function(t){return t(u)}}).$mount("#app")},"7a4d":function(t,e,n){"use strict";var a=n("3f42"),o=n.n(a);o.a},"85ec":function(t,e,n){}}); -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | Vue App
-------------------------------------------------------------------------------- /src/test/java/com/liangtengyu/markdown/MarkDownApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.liangtengyu.markdown; 2 | 3 | import com.liangtengyu.markdown.service.SaveFileService; 4 | import org.junit.Test; 5 | import org.junit.runner.RunWith; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | 10 | import java.io.IOException; 11 | 12 | @RunWith(SpringRunner.class) 13 | @SpringBootTest(classes = MarkDownApplication.class) 14 | public class MarkDownApplicationTests { 15 | 16 | @Autowired 17 | SaveFileService saveFileService; 18 | 19 | @Test 20 | public void testsave() throws IOException { } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /vue_project/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /vue_project/README.md: -------------------------------------------------------------------------------- 1 | # vue_project 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Customize configuration 19 | See [Configuration Reference](https://cli.vuejs.org/config/). 20 | -------------------------------------------------------------------------------- /vue_project/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /vue_project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ToMarkDown", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve", 7 | "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "ant-design-vue": "^1.6.4", 11 | "axios": "^0.19.2", 12 | "core-js": "^3.6.5", 13 | "mavon-editor": "^2.9.0", 14 | "vue": "^2.6.11", 15 | "vue-router": "^3.2.0" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "~4.5.0", 19 | "@vue/cli-plugin-router": "~4.5.0", 20 | "@vue/cli-service": "~4.5.0", 21 | "vue-template-compiler": "^2.6.11" 22 | }, 23 | "browserslist": [ 24 | "> 1%", 25 | "last 2 versions", 26 | "not dead" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /vue_project/src/App.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 68 | 87 | 88 | -------------------------------------------------------------------------------- /vue_project/src/assets/1614755729311.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/vue_project/src/assets/1614755729311.jpg -------------------------------------------------------------------------------- /vue_project/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/vue_project/src/assets/logo.png -------------------------------------------------------------------------------- /vue_project/src/components/WebInfo.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 115 | 116 | 117 | 122 | -------------------------------------------------------------------------------- /vue_project/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | import axios from 'axios' 6 | Vue.prototype.$axios = axios 7 | 8 | import { Button, message,Layout,Input,Menu,Icon,Collapse,FormModel,Spin,Select,Pagination,Card,Modal} from 'ant-design-vue'; 9 | import 'ant-design-vue/dist/antd.css'; 10 | Vue.use(Button); 11 | Vue.use(Layout); 12 | Vue.use(Input); 13 | Vue.use(Menu); 14 | Vue.use(Collapse); 15 | Vue.use(Icon); 16 | Vue.use(FormModel); 17 | Vue.use(Spin); 18 | Vue.use(Select); 19 | Vue.use(Pagination); 20 | Vue.use(Card); 21 | Vue.use(Modal); 22 | Vue.prototype.$message = message; 23 | 24 | 25 | Vue.config.productionTip = false 26 | 27 | new Vue({ 28 | router, 29 | render: h => h(App) 30 | }).$mount('#app') 31 | -------------------------------------------------------------------------------- /vue_project/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | Vue.use(VueRouter) 6 | 7 | const routes = [ 8 | { 9 | path: '/', 10 | name: 'Home', 11 | component: Home 12 | }, 13 | { 14 | path: '/Home', 15 | name: 'sy', 16 | component: Home 17 | }, 18 | { 19 | path: '/upload', 20 | name: 'upload', 21 | // route level code-splitting 22 | // this generates a separate chunk (about.[hash].js) for this route 23 | // which is lazy-loaded when the route is visited. 24 | component: () => import(/* webpackChunkName: "about" */ '../views/upload.vue') 25 | }, 26 | { 27 | path: '/filelist', 28 | name: 'filelist', 29 | // route level code-splitting 30 | // this generates a separate chunk (about.[hash].js) for this route 31 | // which is lazy-loaded when the route is visited. 32 | component: () => import(/* webpackChunkName: "about" */ '../views/Filelist.vue') 33 | }, 34 | { 35 | path: '/about', 36 | name: 'About', 37 | // route level code-splitting 38 | // this generates a separate chunk (about.[hash].js) for this route 39 | // which is lazy-loaded when the route is visited. 40 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 41 | }, 42 | { 43 | path: '/config', 44 | name: 'config', 45 | // route level code-splitting 46 | // this generates a separate chunk (about.[hash].js) for this route 47 | // which is lazy-loaded when the route is visited. 48 | component: () => import(/* webpackChunkName: "about" */ '../views/config.vue') 49 | }, 50 | { 51 | path: '/manage', 52 | name: 'manage', 53 | // route level code-splitting 54 | // this generates a separate chunk (about.[hash].js) for this route 55 | // which is lazy-loaded when the route is visited. 56 | component: () => import(/* webpackChunkName: "about" */ '../views/manage.vue') 57 | } 58 | ] 59 | 60 | const router = new VueRouter({ 61 | routes 62 | }) 63 | 64 | export default router 65 | -------------------------------------------------------------------------------- /vue_project/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /vue_project/src/views/Filelist.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 89 | 90 | 91 | 106 | -------------------------------------------------------------------------------- /vue_project/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /vue_project/src/views/config.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 104 | 105 | 106 | 111 | -------------------------------------------------------------------------------- /vue_project/src/views/manage.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 24 | -------------------------------------------------------------------------------- /vue_project/src/views/upload.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /vue_project/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productionSourceMap: false 3 | } 4 | -------------------------------------------------------------------------------- /windows/tomarkdown.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liangtengyu/to_markdown/2e8cfc5910b94e7d26aa22ba6fc1646f6d2be53b/windows/tomarkdown.rar --------------------------------------------------------------------------------