├── .DS_Store
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── question.md
├── .gitignore
├── README.md
├── assets
├── .DS_Store
└── css
│ ├── element-variables.scss
│ └── reset.css
├── daitu.jpg
├── demo.png
├── layouts
└── default.vue
├── nuxt.config.js
├── package-lock.json
├── package.json
├── pages
└── index.vue
├── plugins
├── axios.js
├── element-ui.js
└── hw-editor.js
├── pm2system.config.js
├── server
└── index.js
├── static
├── .DS_Store
├── favicon.ico
└── img
│ ├── 404-full.png
│ ├── github.svg
│ └── logo.svg
├── wx-group.png
└── wx.png
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/.DS_Store
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | charset = utf-8
12 |
13 | [*.md]
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | components/hw-editor/**
2 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | parserOptions: {
8 | parser: 'babel-eslint'
9 | },
10 | extends: [
11 | '@nuxtjs',
12 | 'plugin:nuxt/recommended'
13 | ],
14 | plugins: [
15 |
16 | ],
17 | // add your custom rules here
18 | rules: {
19 | 'nuxt/no-cjs-in-config': 'off',
20 | 'generator-star-spacing': 'off',
21 | 'no-mixed-operators': 0,
22 | 'vue/max-attributes-per-line': [
23 | 2,
24 | {
25 | 'singleline': 5,
26 | 'multiline': {
27 | 'max': 1,
28 | 'allowFirstLine': false
29 | }
30 | }
31 | ],
32 | 'vue/attribute-hyphenation': 0,
33 | 'vue/html-self-closing': 0,
34 | 'vue/component-name-in-template-casing': 0,
35 | 'vue/html-closing-bracket-spacing': 0,
36 | 'vue/singleline-html-element-content-newline': 0,
37 | 'vue/no-unused-components': 0,
38 | 'vue/multiline-html-element-content-newline': 0,
39 | 'vue/no-use-v-if-with-v-for': 0,
40 | 'vue/html-closing-bracket-newline': 0,
41 | 'vue/no-parsing-error': 0,
42 | 'vue/comment-directive': 'off',
43 | 'no-console': 0,
44 | 'no-tabs': 0,
45 | 'quotes': [
46 | 2,
47 | 'single',
48 | {
49 | 'avoidEscape': true,
50 | 'allowTemplateLiterals': true
51 | }
52 | ],
53 | 'no-delete-var': 2,
54 | 'prefer-const': [
55 | 2,
56 | {
57 | 'ignoreReadBeforeAssign': false
58 | }
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report(报告问题)
3 | about: Create a report to help us improve
4 | ---
5 |
10 |
11 |
12 | ## Bug report(问题描述)
13 |
14 | #### Steps to reproduce(问题复现步骤)
15 |
20 |
21 | #### Screenshot or Gif(截图或动态图)
22 |
23 |
24 | #### Other relevant information(格外信息)
25 | - Your OS:
26 | - Your broswer version:
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request(新功能建议)
3 | about: Suggest an idea for this project
4 | ---
5 |
6 | ## Feature request(新功能建议)
7 |
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question(提问)
3 | about: Asking questions about use
4 | ---
5 |
6 | ## Question(提问)
7 |
8 |
15 |
16 | #### Steps to reproduce(问题复现步骤)
17 |
22 |
23 | #### Screenshot or Gif(截图或动态图)
24 |
25 |
26 | #### Other relevant information(格外信息)
27 | - Your OS:
28 | - Your broswer version:
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | /node_modules
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # parcel-bundler cache (https://parceljs.org/)
63 | .cache
64 |
65 | # next.js build output
66 | .next
67 |
68 | # nuxt.js build output
69 | .nuxt
70 |
71 | # Nuxt generate
72 | dist
73 |
74 | # vuepress build output
75 | .vuepress/dist
76 |
77 | # Serverless directories
78 | .serverless
79 |
80 | # IDE
81 | .idea
82 |
83 | # Service worker
84 | sw.*
85 |
86 | *.tar.gz
87 |
88 | .vscode
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
🎉 Html2md 🥳
2 |
3 |
4 |
5 | `html2md` 是由 [helloworld开发者社区](https://www.helloworld.net) 开源的一款轻量级功能强大的html转md工具💪🏻,纯前端开发,不需要后端接口( Node是我们前端的😗!),支持多平台,一键将文章链接转换为md,方便大家收藏和保存文章🤪。请勿做其它用途
6 |
7 | [html2md地址](https://www.helloworld.net/html2md) 👈🏻
8 |
9 | ### 教程,如下图
10 | 
11 |
12 |
13 |
14 |
15 |
16 | ## 技术栈
17 |
18 | - [vue](https://cn.vuejs.org/) 前端三剑客之一,主张最少,具有高度灵活性的渐进式框架
19 | - [nuxt](https://zh.nuxtjs.org/) 通过利用 Vue.js 和 Node.js最佳实践来构建高性能应用程序
20 | - [express](https://www.expressjs.com.cn/) 基于 Node.js 平台,快速、开放、极简的 Web 开发框架
21 | - [element-ui](https://element.eleme.cn/#/zh-CN) 宇宙第一 Vue 第三方组件库,有不服?
22 | - [js-dom](https://github.com/jsdom/jsdom) 一款可在 Node 环境下模拟浏览器的 API 的库
23 | - [turndown](https://github.com/domchristie/turndown) 使用 JavaScript 将 HTML 转换为 Markdown
24 | - [axios](http://www.axios-js.com/) 易用、简洁且高效的 http库,支持浏览器和 Node 环境。
25 | - [mavon-editor](https://github.com/hinesboy/mavonEditor) 一款基于 Vue 的 markdown 编辑器,支持所见即所得
26 | - [sass](https://www.sass.hk/) 强大的 Css 预处理器之一
27 | ## 使用
28 |
29 | > 注意:node 版本要求: v14.18.0
30 |
31 | ### 第一步:下载
32 | ```bash
33 | git clone git@github.com:helloworld-Co/html2md.git
34 | cd ./html2md
35 | ```
36 | ### 第二步:安装
37 | ```bash
38 | npm install
39 | 或者
40 | yarn install
41 | ```
42 | ### 第三步:启动
43 | ```bash
44 | npm run dev
45 | 或者
46 | yarn dev
47 | ```
48 |
49 | ## 使用备注
50 | - **防盗链**:由于各大网站的图片基本都会有防盗链机制,转换出来的 md 里的图片会无法正常预览,这个不慌 😳,把 md 内容复制到 [helloworld开发者社区](https://www.helloworld.net) 的写作文本框中就能正常显示了👌🏻。
51 | - **微信文章**:微信文章内容格式差异性比较大,不能保证 💯% 好使,如果一篇不能解决,那就换下一篇吧😏。
52 |
53 | ## 更新日志
54 | - **2021.02.07**
55 | 1. 新增 `a` 标签和 `img` 标签中的`相对路径`转换成`绝对路径`功能,方便以后查找源路径。
56 | - **2021.03.27**
57 | 1. 底部添加转载来源声明。
58 | - **2021.04.20**
59 | 1. 添加对知乎专栏文章的支持👌🏻。
60 | - **2021.05.01**
61 | 1. 添加 Markdown 内容的文件保存和下载⬇️。
62 | - **2021.06.27**
63 | 1. 添加 HTML 直接转 Markdown 功能,和之前的 URL 转 Markdown 区分开来,可切换使用。
64 | - **2021.07.10**
65 | 1. 添加对 learnku 文章的支持👌🏻。
66 |
67 |
68 | ## 技术交流群 / 官方公众号
69 | 微信号 **daitukeji**
70 |
71 |
72 |
73 |
74 | ## 支持环境
75 |
76 | 现代浏览器及 IE11。
77 |
78 | | [
](http://godban.github.io/browsers-support-badges/)IE / Edge | [
](http://godban.github.io/browsers-support-badges/)Firefox | [
](http://godban.github.io/browsers-support-badges/)Chrome | [
](http://godban.github.io/browsers-support-badges/)Safari | [
](http://godban.github.io/browsers-support-badges/)Opera |
79 | | --------- | --------- | --------- | --------- | --------- |
80 | | IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions
81 |
82 | ## 参与贡献
83 |
84 | 我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 😃
85 |
86 | - 通过 [Issue](https://github.com/helloworld-Co/html2md/issues) 报告 bug。
87 | - 提交 [Pull Request](https://github.com/helloworld-Co/html2md/pulls) 一起改进。
88 |
89 | ## 技术交流
90 |
91 |
--------------------------------------------------------------------------------
/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/assets/.DS_Store
--------------------------------------------------------------------------------
/assets/css/element-variables.scss:
--------------------------------------------------------------------------------
1 | /* 改变主题色变量 */
2 | $--color-primary: #00A6F4;
3 |
4 | /* 改变 icon 字体路径变量,必需 */
5 | $--font-path: '~element-ui/lib/theme-chalk/fonts';
6 |
7 | @import "~element-ui/packages/theme-chalk/src/index";
8 |
--------------------------------------------------------------------------------
/assets/css/reset.css:
--------------------------------------------------------------------------------
1 | /**
2 | * 重置样式
3 | * */
4 | html,
5 | body,
6 | div,
7 | span,
8 | applet,
9 | object,
10 | iframe,
11 | p,
12 | blockquote,
13 | pre,
14 | a,
15 | abbr,
16 | acronym,
17 | address,
18 | big,
19 | cite,
20 | code,
21 | del,
22 | dfn,
23 | em,
24 | img,
25 | ins,
26 | kbd,
27 | q,
28 | s,
29 | samp,
30 | small,
31 | strike,
32 | sub,
33 | sup,
34 | tt,
35 | var,
36 | b,
37 | u,
38 | i,
39 | center,
40 | dl,
41 | dt,
42 | dd,
43 | ol,
44 | ul,
45 | li,
46 | fieldset,
47 | form,
48 | label,
49 | legend,
50 | table,
51 | caption,
52 | tbody,
53 | tfoot,
54 | thead,
55 | tr,
56 | th,
57 | td,
58 | article,
59 | aside,
60 | canvas,
61 | details,
62 | embed,
63 | figure,
64 | figcaption,
65 | footer,
66 | header,
67 | hgroup,
68 | menu,
69 | nav,
70 | output,
71 | ruby,
72 | section,
73 | summary,
74 | time,
75 | mark,
76 | audio,
77 | video {
78 | margin: 0;
79 | padding: 0;
80 | border: 0;
81 | font-size: 100%;
82 | -webkit-margin-before: 0;
83 | -webkit-margin-after: 0;
84 | -webkit-margin-start: 0;
85 | -webkit-margin-end: 0;
86 | font-weight: normal;
87 | }
88 |
89 | h1,
90 | h2,
91 | h3,
92 | h4,
93 | h5,
94 | h6,
95 | strong {
96 | margin: 0;
97 | padding: 0;
98 | border: 0;
99 | font-size: 100%;
100 | -webkit-margin-before: 0;
101 | -webkit-margin-after: 0;
102 | -webkit-margin-start: 0;
103 | -webkit-margin-end: 0;
104 | }
105 | /* HTML5 display-role reset for older browsers */
106 |
107 | article,
108 | aside,
109 | details,
110 | figcaption,
111 | figure,
112 | footer,
113 | header,
114 | hgroup,
115 | menu,
116 | nav,
117 | section {
118 | display: block;
119 | }
120 |
121 | ol,
122 | ul,
123 | li {
124 | list-style: none;
125 | }
126 |
127 | blockquote,
128 | q {
129 | quotes: none;
130 | }
131 |
132 | blockquote:before,
133 | blockquote:after,
134 | q:before,
135 | q:after {
136 | content: "";
137 | content: none;
138 | }
139 |
140 | table {
141 | border-collapse: collapse;
142 | border-spacing: 0;
143 | }
144 |
145 | html,
146 | body {
147 | -webkit-tap-hightlight-color: transparent;
148 | font-family: -apple-system, SF UI Text, Arial, PingFang SC, Hiragino Sans GB,
149 | Microsoft YaHei, WenQuanYi Micro Hei, sans-serif;
150 | width: 100%;
151 | height: 100%;
152 | padding: 0;
153 | margin: 0;
154 | background: #f5f5f5;
155 | }
156 |
157 | * {
158 | -moz-box-sizing: border-box;
159 | -webkit-box-sizing: border-box;
160 | box-sizing: border-box;
161 | }
162 |
163 | ol,
164 | ul,
165 | li {
166 | margin: 0;
167 | padding: 0;
168 | list-style-type: none;
169 | }
170 |
171 | img {
172 | border: 0;
173 | vertical-align: middle;
174 | }
175 |
176 | a,
177 | a:hover,
178 | a:focus,
179 | a:active {
180 | text-decoration: none;
181 | outline: none;
182 | }
183 |
184 | p {
185 | font-size: 16px;
186 | }
187 |
188 | /* 文本被选择的背景色以及文本颜色 */
189 | ::selection {
190 | color: #fff;
191 | background-color: #00A6F4;
192 | }
193 |
194 | /* 文本被选择的背景色以及文本颜色 */
195 | ::-moz-selection {
196 | color: #fff;
197 | background-color: #00A6F4;
198 | }
199 |
200 | img {
201 | object-fit: cover;
202 | }
203 |
204 | /* 滚动条 */
205 |
206 | /*滚动条宽度*/
207 | ::-webkit-scrollbar {
208 | -webkit-appearance: none;
209 | width: 6px;
210 | height: 6px;
211 | }
212 |
213 | /* 轨道样式 */
214 | ::-webkit-scrollbar-track {}
215 |
216 |
217 | /* Handle样式 */
218 | ::-webkit-scrollbar-thumb {
219 | border-radius: 6px;
220 | background: rgba(194,194,194,0.4);
221 | box-shadow: 0 0 1px rgba(255, 255, 255, 0.5);
222 | }
223 |
224 | ::-webkit-scrollbar-thumb:hover {
225 | border-radius: 6px;
226 | background: rgba(194,194,194,0.8);
227 | }
228 |
229 | ::-webkit-scrollbar-thumb:vertical {
230 | background-color: rgb(194,194,194);
231 | }
232 |
233 | /*hover到滚动条上*/
234 | ::-webkit-scrollbar-thumb:vertical:hover {
235 | background-color: rgba(0, 0, 0, 0.3);
236 | }
237 |
238 | /*滚动条按下*/
239 | ::-webkit-scrollbar-thumb:vertical:active {
240 | background-color: rgba(0, 0, 0, 0.6);
241 | }
242 |
243 | .github-markdown-body ul li,
244 | .vuepress-markdown-body ul li{
245 | list-style: disc!important;
246 | margin-left: 30px!important;
247 | }
248 |
249 | .github-markdown-body ol li,
250 | .vuepress-markdown-body ol li{
251 | list-style: decimal!important;
252 | margin-left: 30px!important;
253 | }
254 |
--------------------------------------------------------------------------------
/daitu.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/daitu.jpg
--------------------------------------------------------------------------------
/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/demo.png
--------------------------------------------------------------------------------
/layouts/default.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
30 |
--------------------------------------------------------------------------------
/nuxt.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | telemetry: false,
3 | /*
4 | ** Headers of the page
5 | */
6 | head: {
7 | title: 'hello-html2md',
8 | meta: [
9 | { charset: 'utf-8' },
10 | { name: 'viewport', content: 'width=device-width, initial-scale=1' },
11 | { hid: 'description', name: 'description', content: 'helloworld社区开发的轻量强大的html一键转md工具,支持多平台一键转换。' },
12 | { hid: 'keywords', name: 'keywords', content: 'javscript,html,markdown,vue,html-to-markdown,md,helloworld,helloworld开发者社区' },
13 | { name: 'renderer', content: 'webkit' },
14 | { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge,chrome=1' }
15 | ],
16 | link: [
17 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
18 | ]
19 | },
20 |
21 | /*
22 | ** Customize the progress-bar color
23 | */
24 | loading: { color: '#00A6F4' },
25 |
26 | /*
27 | ** Global CSS
28 | */
29 | css: [
30 | 'assets/css/reset.css'
31 | ],
32 |
33 | /*
34 | ** Plugins to load before mounting the App
35 | */
36 | plugins: [
37 | '@/plugins/axios',
38 | '@/plugins/element-ui',
39 | { src: '@/plugins/hw-editor', ssr: false }
40 | ],
41 | /*
42 | ** Nuxt.js dev-modules
43 | */
44 | buildModules: [
45 | // Doc: https://github.com/nuxt-community/eslint-module
46 | '@nuxtjs/eslint-module'
47 | ],
48 | /*
49 | ** Nuxt.js modules
50 | */
51 | modules: [
52 | '@nuxtjs/axios',
53 | '@nuxtjs/component-cache'
54 | ],
55 | /*
56 | ** axios modules
57 | ** https://axios.nuxtjs.org/options
58 | */
59 | axios: {
60 | proxy: true, // Can be also an object with default options
61 | credentials: true
62 | },
63 | /*
64 | ** Build configuration
65 | */
66 | build: {
67 | // analyze: true, // 打包时用来分析依赖包包大小
68 | transpile: [],
69 | extractCSS: true, // 单独提取 css
70 | babel: {
71 | 'plugins': [
72 | [
73 | 'component',
74 | {
75 | 'libraryName': 'element-ui',
76 | 'styleLibraryName': 'theme-chalk'
77 | }
78 | ]
79 | ],
80 | 'comments': true
81 | },
82 | optimization: {
83 | splitChunks: {
84 | chunks: 'all',
85 | minSize: 30000, // 模块的最小体积
86 | // maxSize: 360000, // 模块的最大体积
87 | automaticNameDelimiter: '~', // 文件名的连接符
88 | name: true,
89 | cacheGroups: { // 缓存组
90 | styles: {
91 | test: /\.[css|less|scss|sass]/,
92 | chunks: 'all',
93 | priority: 30,
94 | enforce: true
95 | },
96 | 'element-ui': {
97 | test: /node_modules[\\/]element-ui/,
98 | chunks: 'initial',
99 | priority: 20,
100 | name: true,
101 | enforce: true
102 | },
103 | vendors: {
104 | test: /[\\/]node_modules[\\/]/,
105 | chunks: 'initial',
106 | priority: 10,
107 | enforce: true
108 | },
109 | default: {
110 | minChunks: 2,
111 | priority: -10,
112 | reuseExistingChunk: true
113 | }
114 | }
115 | }
116 | },
117 | /*
118 | ** You can extend webpack config here
119 | */
120 | extend (config, { isDev, isClient }) {
121 | if (isClient && !isDev) {
122 | config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
123 | }
124 | }
125 | },
126 | server: {
127 | port: 3031, // default: 3000
128 | host: 'localhost' // default: localhost
129 | },
130 | env: {
131 |
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-html2md",
3 | "version": "1.0.0",
4 | "description": "轻量强大的html一键转md工具,支持多平台一键转换",
5 | "author": "洪少利",
6 | "private": true,
7 | "scripts": {
8 | "dev": "cross-env ENV=dev node server --watch server",
9 | "build": "cross-env ACTION=build node server",
10 | "start": "node server",
11 | "lint": "eslint --fix --ext .js,.vue --ignore-path .gitignore ."
12 | },
13 | "lint-staged": {
14 | "*.{js,vue}": "eslint"
15 | },
16 | "dependencies": {
17 | "body-parser": "^1.19.0",
18 | "copy-to-clipboard": "^3.3.1",
19 | "cross-env": "^7.0.3",
20 | "element-ui": "^2.13.0",
21 | "jsdom": "^16.4.0",
22 | "mavon-editor": "^2.9.1",
23 | "nuxt": "2.14.12",
24 | "request": "^2.88.2",
25 | "turndown": "^7.0.0",
26 | "turndown-plugin-gfm": "^1.0.2"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "7.9.0",
30 | "@babel/plugin-proposal-optional-chaining": "^7.12.1",
31 | "@nuxtjs/axios": "^5.9.7",
32 | "@nuxtjs/component-cache": "^1.1.5",
33 | "@nuxtjs/eslint-config": "^1.0.1",
34 | "@nuxtjs/eslint-module": "^1.0.0",
35 | "babel-eslint": "^10.0.1",
36 | "babel-plugin-component": "^1.1.1",
37 | "eslint": "^6.1.0",
38 | "eslint-plugin-html": "^6.0.0",
39 | "eslint-plugin-nuxt": ">=0.4.2",
40 | "eslint-plugin-vue": "^5.2.3",
41 | "husky": "^4.2.3",
42 | "node-sass": "^4.14.1",
43 | "sass-loader": "^7.3.1",
44 | "vuepress": "^1.5.2"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
html 转 md
10 | 支持 csdn、掘金、简书、segmentfault、cnblogs、oschina、微信文章
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
👉一键转换
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
126 |
127 |
170 |
--------------------------------------------------------------------------------
/plugins/axios.js:
--------------------------------------------------------------------------------
1 | const codes = {
2 | success: 1
3 | }
4 |
5 | export default function ({ $axios, store, redirect, req }) {
6 | // 添加请求拦截器
7 | $axios.onRequest((req) => {
8 |
9 | })
10 |
11 | // 添加响应拦截器
12 | $axios.onResponse((res) => {
13 | return new Promise((resolve, reject) => {
14 | const { data = {} } = res
15 | if (data.code === codes.success) { // 成功
16 | resolve(data)
17 | return
18 | }
19 |
20 | reject(res)
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/plugins/element-ui.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import '@/assets/css/element-variables.scss'
3 |
4 | // 按需加载组件
5 | import {
6 | Button,
7 | Input
8 | } from 'element-ui'
9 | Vue.use(Button)
10 | Vue.use(Input)
11 |
--------------------------------------------------------------------------------
/plugins/hw-editor.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import mavonEditor from 'mavon-editor'
3 | import 'mavon-editor/dist/css/index.css'
4 |
5 | Vue.use(mavonEditor)
6 |
--------------------------------------------------------------------------------
/pm2system.config.js:
--------------------------------------------------------------------------------
1 | /* pm2system.config.js
2 | * pm2 start pm2system.config.js --only hello-html2md
3 | */
4 | module.exports = {
5 | apps: [
6 | {
7 | name: 'hello-html2md', // 应用名
8 | cwd: './', // 当前工作路径
9 | script: 'npm', // 实际启动脚本
10 | args: 'run start', // 参数
11 | autorestart: true,
12 | watch: true, // 监控变化的目录,一旦变化,自动重启
13 | watch_delay: 10000,
14 | ignore_watch: ['node_modules', 'static'], // 从监控目录中排除
15 | watch_options: {
16 | 'followSymlinks': false,
17 | 'usePolling': true
18 | }
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fs = require('fs')
3 | const bodyParser = require('body-parser')
4 | const express = require('express')
5 | const consola = require('consola')
6 | const request = require('request')
7 | const jsdom = require('jsdom')
8 | const { JSDOM } = jsdom
9 |
10 | const { Nuxt, Builder } = require('nuxt')
11 | const app = express()
12 |
13 | const isDev = process.env.ENV === 'dev'
14 | const isBuild = process.env.ACTION === 'build'
15 |
16 | // Import and Set Nuxt.js options
17 | const config = require('../nuxt.config.js')
18 | Object.assign(config, {
19 | dev: isDev
20 | })
21 |
22 | app.use(bodyParser.json({ limit: '50mb' }))
23 | app.use(bodyParser.urlencoded({ limit: '50mb', extended: true }))
24 |
25 | // 设置应用静态目录
26 | app.use(express.static(path.join(__dirname, 'static')))
27 |
28 | const html2md = {
29 | dom: '',
30 | qUrl: '',
31 | lazyAttrs: ['data-src', 'data-original-src', 'data-original', 'src'],
32 | dealLazyImg (img) {
33 | /**
34 | * 处理懒加载路径
35 | * 简书:data-original-src
36 | * 掘金:data-src
37 | * segmentfault:data-src
38 | * 知乎专栏:data-original
39 | **/
40 | for (let i = 0, len = this.lazyAttrs.length; i < len; i++) {
41 | const src = img.getAttribute(this.lazyAttrs[i])
42 | if (src) { return src }
43 | }
44 | return ''
45 | },
46 | getAbsoluteUrl (p) {
47 | // 获取图片、链接的绝对路径
48 | const qOrigin = new URL(this.qUrl).origin || ''
49 | return new URL(p, qOrigin).href
50 | },
51 | changeRelativeUrl () {
52 | // 转换图片、链接的相对路径
53 | if (!this.dom) { return '内容出错~
' }
54 | const copyDom = this.dom
55 | const imgs = copyDom.querySelectorAll('img')
56 | const links = copyDom.querySelectorAll('a')
57 | imgs.length > 0 && imgs.forEach((v) => {
58 | const src = this.dealLazyImg(v)
59 | v.src = this.getAbsoluteUrl(src)
60 | })
61 | links.length > 0 && links.forEach((v) => {
62 | const href = v.href || this.qUrl
63 | v.href = this.getAbsoluteUrl(href)
64 | })
65 |
66 | this.dom = copyDom
67 | return this
68 | },
69 | addOriginText () {
70 | // 底部添加转载来源声明
71 | const originDom = new JSDOM('').window.document.createElement('div')
72 | originDom.innerHTML = `
`
73 | this.dom.appendChild(originDom)
74 | return this
75 | },
76 | getInnerHtml () {
77 | return this.dom.innerHTML
78 | },
79 | returnFinalHtml () {
80 | return this.changeRelativeUrl().addOriginText().getInnerHtml()
81 | },
82 | getDom (html, selector) {
83 | // 获取准确的文章内容
84 | const dom = new JSDOM(html)
85 | const htmlContent = dom.window.document.querySelector(selector)
86 | return htmlContent
87 | },
88 | getTitle (content) {
89 | // 获取文章的 title
90 | const title = this.getDom(content, 'title')
91 | if (title) { return title.textContent }
92 | return '获取标题失败~'
93 | },
94 | getBody (content) {
95 | // 获取不同平台的文章内容
96 | const getBySelector = selector => this.getDom(content, selector)
97 |
98 | // 掘金
99 | if (this.qUrl.includes('juejin.cn')) {
100 | const htmlContent = getBySelector('.markdown-body')
101 | const extraDom = htmlContent.querySelector('style')
102 | const extraDomArr = htmlContent.querySelectorAll('.copy-code-btn')
103 | extraDom && extraDom.remove()
104 | extraDomArr.length > 0 && extraDomArr.forEach((v) => { v.remove() })
105 | html2md.dom = htmlContent
106 | return this.returnFinalHtml()
107 | }
108 | // oschina
109 | if (this.qUrl.includes('oschina.net')) {
110 | const htmlContent = getBySelector('.article-detail')
111 | const extraDom = htmlContent.querySelector('.ad-wrap')
112 | extraDom && extraDom.remove()
113 | html2md.dom = htmlContent
114 | return this.returnFinalHtml()
115 | }
116 | // cnblogs
117 | if (this.qUrl.includes('cnblogs.com')) {
118 | html2md.dom = getBySelector('#cnblogs_post_body')
119 | return this.returnFinalHtml()
120 | }
121 | // weixin
122 | if (this.qUrl.includes('weixin.qq.com')) {
123 | html2md.dom = getBySelector('#js_content')
124 | return this.returnFinalHtml()
125 | }
126 | // 知乎专栏
127 | if (this.qUrl.includes('zhuanlan.zhihu.com')) {
128 | const htmlContent = getBySelector('.RichText')
129 | const extraScript = htmlContent.querySelectorAll('noscript')
130 | extraScript.length > 0 && extraScript.forEach((v) => { v.remove() })
131 | html2md.dom = htmlContent
132 | return this.returnFinalHtml()
133 | }
134 | // 慕课专栏
135 | if (this.qUrl.includes('www.imooc.com')) {
136 | const htmlContent = getBySelector('.article-con')
137 | html2md.dom = htmlContent
138 | return this.returnFinalHtml()
139 | }
140 | // learnku
141 | if (this.qUrl.includes('learnku.com')) {
142 | const htmlContent = getBySelector('.markdown-body')
143 | const extraScript = htmlContent.querySelectorAll('.toc-wraper')
144 | extraScript.length > 0 && extraScript.forEach((v) => { v.remove() })
145 | const extraToc = htmlContent.querySelectorAll('.markdown-toc')
146 | extraToc.length > 0 && extraToc.forEach((v) => { v.remove() })
147 | const extraLink = htmlContent.querySelectorAll('.reference-link')
148 | extraLink.length > 0 && extraLink.forEach((v) => { v.remove() })
149 | html2md.dom = htmlContent
150 | return this.returnFinalHtml()
151 | }
152 |
153 | // 优先适配 article 标签,没有再用 body 标签
154 | const htmlArticle = getBySelector('article')
155 | if (htmlArticle) {
156 | html2md.dom = htmlArticle
157 | return this.returnFinalHtml()
158 | }
159 |
160 | const htmlBody = getBySelector('body')
161 | if (htmlBody) {
162 | html2md.dom = htmlBody
163 | return this.returnFinalHtml()
164 | }
165 |
166 | return content
167 | }
168 | }
169 |
170 | // 需要模拟请求头的网站
171 | const mockSiteUa = ['juejin.cn']
172 | // 通过文章地址获取文章 html内容
173 | app.all('/getUrlHtml', function (req, res, next) {
174 | const qUrl = req.query.url || ''
175 | html2md.qUrl = qUrl
176 |
177 | const isMock = mockSiteUa.find(item => qUrl.includes(item))
178 | const headers = isMock ? {
179 | 'User-Agent': 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)'
180 | } : {}
181 |
182 | // 通过请求获取链接的 html
183 | request({
184 | url: qUrl,
185 | headers
186 | }, (error, response, body) => {
187 | if (error) {
188 | res.status(404).send('Url Error')
189 | return
190 | }
191 | res.type('text/json')
192 | try {
193 | const json = {
194 | code: 1,
195 | title: html2md.getTitle(body),
196 | html: html2md.getBody(body)
197 | }
198 | res.status(200).send(json)
199 | } catch (error) {
200 | res.status(200).send({
201 | code: 0,
202 | msg: '程序异常了~'
203 | })
204 | }
205 | })
206 | })
207 |
208 | // 下载路径
209 | const folderName = 'download'
210 | const downLoadPath = path.join('./static', folderName)
211 | // 下载 md 文件
212 | app.post('/getMdFile', function (req, res, next) {
213 | const qMd = req.body.md || '## 空空如也'
214 | const qUrl = req.body.url || 'https://www.helloworld.net'
215 |
216 | // 写入md文件
217 | function writeFile () {
218 | const mdName = `${Date.now()}.md`
219 | try {
220 | fs.writeFileSync(`${downLoadPath}/${mdName}`, qMd)
221 | res.status(200).send({
222 | code: 1,
223 | path: `${qUrl}/${folderName}/${mdName}`
224 | })
225 | } catch (error) {
226 | res.status(200).send({
227 | code: 0,
228 | msg: '程序异常了~'
229 | })
230 | }
231 | }
232 |
233 | // 判断目录是否存在
234 | const isExist = fs.existsSync(downLoadPath)
235 | if (isExist) {
236 | // 文件夹存在
237 | writeFile()
238 | return
239 | }
240 |
241 | // 文件夹不存在,创建一个
242 | fs.mkdirSync(downLoadPath)
243 | writeFile()
244 | })
245 |
246 | // 全局错误抛出
247 | app.use((error, req, res, next) => {
248 | if (error) {
249 | console.log('全局错误抛出:', error)
250 | }
251 | })
252 |
253 | async function start () {
254 | // Init Nuxt.js
255 | const nuxt = new Nuxt(config)
256 |
257 | const { host, port } = nuxt.options.server
258 |
259 | if (isBuild) {
260 | const builder = new Builder(nuxt)
261 | await builder.build()
262 | consola.success({
263 | message: `🚀 Packaged! 🚀`,
264 | badge: true
265 | })
266 | return
267 | }
268 |
269 | // Build only in dev mode
270 | if (isDev) {
271 | const builder = new Builder(nuxt)
272 | await builder.build()
273 | } else {
274 | await nuxt.ready()
275 | }
276 |
277 | // Give nuxt middleware to express
278 | app.use(nuxt.render)
279 |
280 | // Listen the server
281 | app.listen(port, host)
282 |
283 | // 拦截“未捕获的异常”
284 | process.on('uncaughtException', function (err) {
285 | console.log('未捕获的异常:', err)
286 | })
287 |
288 | consola.ready({
289 | message: `Server listening on http://${host}:${port}`,
290 | badge: true
291 | })
292 | }
293 | start()
294 |
--------------------------------------------------------------------------------
/static/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/static/.DS_Store
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/static/favicon.ico
--------------------------------------------------------------------------------
/static/img/404-full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/static/img/404-full.png
--------------------------------------------------------------------------------
/static/img/github.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/wx-group.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/wx-group.png
--------------------------------------------------------------------------------
/wx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/helloworld-Co/html2md/ca08965af93e6565806a79087868daa439565ffc/wx.png
--------------------------------------------------------------------------------