Skip to content

Commit cc57ef3

Browse files
committed
下载知乎/掘金/简书文章时增加处理公式
1 parent 455ba5a commit cc57ef3

File tree

7 files changed

+220
-137
lines changed

7 files changed

+220
-137
lines changed

package-lock.json

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "markdown-downloader",
3-
"version": "1.0.2",
3+
"version": "1.0.3",
44
"description": "markdown文章下载",
55
"main": "dist/index.js",
66
"scripts": {
@@ -42,6 +42,7 @@
4242
"cross-env": "^7.0.3",
4343
"html-to-md": "^0.5.3",
4444
"jszip": "^3.7.1",
45+
"mathjax": "^3.2.2",
4546
"md5": "^2.3.0",
4647
"path-browserify": "^1.0.1",
4748
"webpack": "^5.63.0",

src/index.js

Lines changed: 39 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,22 @@
11
import md5 from 'md5'
2-
import path from 'path-browserify'
32
import html2markdown from 'html-to-md'
43
import { websites, hooks } from './websites'
54
import merge from 'webpack-merge'
5+
import 'mathjax/es5/tex-svg'
6+
import {
7+
isExtension,
8+
getExt,
9+
query,
10+
getText,
11+
getAttribute,
12+
queryAll,
13+
noop,
14+
sendMessage,
15+
formatDate,
16+
insertAfter,
17+
getUrl
18+
} from './utils'
619

7-
const isBroswer = typeof window !== 'undefined' && window instanceof Object
8-
const isExtension = isBroswer && window.chrome instanceof Object && window.chrome.runtime
9-
10-
const getExt = (fileName) => {
11-
return path.parse(fileName).ext.slice(1)
12-
}
13-
14-
const query = (selector, context = document) => {
15-
if (selector instanceof NodeList || selector instanceof Node) {
16-
return selector
17-
}
18-
return context.querySelector(selector)
19-
}
20-
21-
const getText = (selector, context = document) => {
22-
const el = query(selector, context) || {}
23-
return el.innerText || ''
24-
}
25-
26-
const getAttribute = (val, selector, context = document) => {
27-
const el = query(selector, context)
28-
return el ? el.getAttribute(val) || '' : ''
29-
}
30-
31-
const queryAll = (selector, context = document) => {
32-
return [].slice.apply(context.querySelectorAll(selector))
33-
}
34-
35-
const noop = (func, defaultFunc) => {
36-
return typeof func === 'function' ? func : typeof defaultFunc === 'function' ? defaultFunc : () => {}
37-
}
38-
39-
const encodeUrlData = (data) => {
40-
let body = ''
41-
for (let key in data) {
42-
body += key + '=' + encodeURIComponent(data[key]) + '&'
43-
}
44-
return body.slice(0, -1)
45-
}
46-
47-
const encodeOptionsData = (options) => {
48-
if (options.stringify !== false && typeof options.data === 'object') {
49-
options.data = encodeUrlData(options.data)
50-
}
51-
return options
52-
}
53-
54-
const sendMessage = (options, onsuccess, onerror, retry) => {
55-
if (isExtension) {
56-
retry = isNaN(retry) ? 3 : +retry
57-
encodeOptionsData(options)
58-
chrome.runtime.sendMessage(options, ([error, response, headers, xhr]) => {
59-
if (!error) {
60-
try {
61-
const result = noop(onsuccess)(response, headers, xhr)
62-
if (result === void 0) {
63-
return response
64-
}
65-
// onsuccess返回值不为undefined, 视为调用失败
66-
error = result
67-
} catch (err) {
68-
// 执行onsuccess代码出错
69-
error = err
70-
}
71-
}
72-
if (retry-- > 0) {
73-
sendMessage(options, onsuccess, onerror, retry)
74-
} else {
75-
noop(onerror)(error, headers, xhr)
76-
}
77-
})
78-
}
79-
}
80-
81-
const formatDate = (str, t) => {
82-
t = typeof t === 'string' || !isNaN(t) ? new Date(t) : t
83-
if (t instanceof Date === false) {
84-
t = new Date()
85-
}
86-
const obj = {
87-
yyyyyyyy: t.getFullYear(),
88-
yy: t.getFullYear(),
89-
MM: t.getMonth()+1,
90-
dd: t.getDate(),
91-
HH: t.getHours(),
92-
hh: t.getHours() % 12,
93-
mm: t.getMinutes(),
94-
ss: t.getSeconds(),
95-
ww: '日一二三四五六'.split('')[t.getDay()]
96-
};
97-
return str.replace(/([a-z]+)/ig, function ($1){
98-
return (obj[$1+$1] === 0 ? '0' : obj[$1+$1]) || ('0' + obj[$1]).slice(-2);
99-
});
100-
}
10120

10221
const setInfo = (data) => {
10322
data = Object.assign({
@@ -129,25 +48,6 @@ const getMarkdown = (markdownBody) => {
12948
// }[s1] || s))
13049
}
13150

132-
const insertAfter = (newElement, targetElement) => {
133-
const parent = targetElement.parentNode
134-
if(parent.lastChild === targetElement){
135-
parent.appendChild(newElement)
136-
}else{
137-
parent.insertBefore(newElement, targetElement.nextSibling)
138-
}
139-
}
140-
141-
const getUrl = (prefix, link) => {
142-
if (!link) return ''
143-
if (/^(http|https)/.test(link)) {
144-
return link
145-
}
146-
if (/^\/\//.test(link)) {
147-
return prefix.split('//')[0] + link
148-
}
149-
return prefix + link
150-
}
15151
const convert = async (options, customOptions) => {
15252
const context = {}
15353
const defaultOptions = {
@@ -189,6 +89,9 @@ const convert = async (options, customOptions) => {
18989
return result
19090
}
19191
const markdownBody = query(selectors.body, options.context).cloneNode(true)
92+
const fileName = (getText(selectors.title) || document.title)
93+
const realName = fileName.replace(/[\\\/\?<>:'\*\|]/g, '_')
94+
noop(hook.extract)(context, { markdownBody, fileName, realName })
19295
queryAll(selectors.copyBtn, markdownBody).map(item => item.parentElement.removeChild(item))
19396
queryAll('[data-id]', markdownBody).map(item => item.removeAttribute('data-id'))
19497
if (selectors.invalid) {
@@ -214,9 +117,17 @@ const convert = async (options, customOptions) => {
214117
})
215118
}
216119
const urls = []
217-
const fileName = (getText(selectors.title) || document.title)
218-
const realName = fileName.replace(/[\\\/\?<>:'\*\|]/g, '_')
219120
const files = queryAll('img', markdownBody).map(item => {
121+
const downloadName = item.getAttribute('downloadName')
122+
const downloadUrl = item.getAttribute('downloadUrl')
123+
if (downloadName && downloadUrl) {
124+
item.src = './' + downloadName
125+
options.urls !== false && urls.push(downloadUrl)
126+
return {
127+
name: downloadName,
128+
downloadUrl
129+
}
130+
}
220131
const src = item.getAttribute(options.lazyKey) || item.src
221132
const url = src.replace(/\?$/, '')
222133
const ext = getExt(url)
@@ -235,11 +146,12 @@ const convert = async (options, customOptions) => {
235146
home: getUrl(location.origin, getAttribute('href', selectors.userLink)),
236147
description: markdownBody.innerText.replace(/^([\n\s]+)/g, '').replace(/\n/g, ' ').slice(0, 50) + '...',
237148
})
238-
noop(hook.extract)(context)
239149
const markdwonDoc = html2markdown(info + getMarkdown(markdownBody), {})
150+
const copyright = '> 当前文档由 [markdown文档下载插件](https://github.com/kscript/markdown-download) 下载, 原文链接: [' + fileName + '](' + location.href + ') '
151+
const content = await noop(hook.formatContent)(context, { markdownBody, markdwonDoc })
240152
files.push({
241153
name: realName + '.md',
242-
content: markdwonDoc + '\n\n' + '> 当前文档由 [markdown文档下载插件](https://github.com/kscript/markdown-download) 下载, 原文链接: [' + fileName + '](' + location.href + ') '
154+
content: (content && typeof content === 'string' ? content: markdwonDoc )+ '\n\n' + copyright
243155
})
244156
files.push({
245157
name: realName + '/urls',
@@ -259,19 +171,17 @@ const extract = async (options, customOptions) => {
259171
return datas
260172
}
261173

262-
if (isBroswer) {
263-
if (isExtension) {
264-
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
265-
if (message instanceof Object) {
266-
if (message.type === 'download') {
267-
if (typeof websites[message.website] === 'function') {
268-
await websites[message.website](extract)
269-
}
174+
if (isExtension) {
175+
chrome.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
176+
if (message instanceof Object) {
177+
if (message.type === 'download') {
178+
if (typeof websites[message.website] === 'function') {
179+
await websites[message.website](extract)
270180
}
271181
}
272-
sendResponse('')
273-
})
274-
}
182+
}
183+
sendResponse('')
184+
})
275185
}
276186

277187
export default convert

src/utils.js

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import path from 'path-browserify'
2+
3+
export const isBroswer = typeof window !== 'undefined' && window instanceof Object
4+
export const isExtension = isBroswer && window.chrome instanceof Object && window.chrome.runtime
5+
export const getExt = (fileName) => {
6+
return path.parse(fileName).ext.slice(1)
7+
}
8+
export const query = (selector, context = document) => {
9+
if (selector instanceof NodeList || selector instanceof Node) {
10+
return selector
11+
}
12+
return context.querySelector(selector)
13+
}
14+
export const getText = (selector, context = document) => {
15+
const el = query(selector, context) || {}
16+
return el.innerText || ''
17+
}
18+
export const getAttribute = (val, selector, context = document) => {
19+
const el = query(selector, context)
20+
return el ? el.getAttribute(val) || '' : ''
21+
}
22+
export const queryAll = (selector, context = document) => {
23+
return [].slice.apply(context.querySelectorAll(selector))
24+
}
25+
export const noop = (func, defaultFunc) => {
26+
return typeof func === 'function' ? func : typeof defaultFunc === 'function' ? defaultFunc : () => {}
27+
}
28+
export const encodeUrlData = (data) => {
29+
let body = ''
30+
for (let key in data) {
31+
body += key + '=' + encodeURIComponent(data[key]) + '&'
32+
}
33+
return body.slice(0, -1)
34+
}
35+
export const encodeOptionsData = (options) => {
36+
if (options.stringify !== false && typeof options.data === 'object') {
37+
options.data = encodeUrlData(options.data)
38+
}
39+
return options
40+
}
41+
export const sendMessage = (options, onsuccess, onerror, retry) => {
42+
if (isExtension) {
43+
retry = isNaN(retry) ? 3 : +retry
44+
encodeOptionsData(options)
45+
chrome.runtime.sendMessage(options, ([error, response, headers, xhr]) => {
46+
if (!error) {
47+
try {
48+
const result = noop(onsuccess)(response, headers, xhr)
49+
if (result === void 0) {
50+
return response
51+
}
52+
// onsuccess返回值不为undefined, 视为调用失败
53+
error = result
54+
} catch (err) {
55+
// 执行onsuccess代码出错
56+
error = err
57+
}
58+
}
59+
if (retry-- > 0) {
60+
sendMessage(options, onsuccess, onerror, retry)
61+
} else {
62+
noop(onerror)(error, headers, xhr)
63+
}
64+
})
65+
}
66+
}
67+
export const formatDate = (str, t) => {
68+
t = typeof t === 'string' || !isNaN(t) ? new Date(t) : t
69+
if (t instanceof Date === false) {
70+
t = new Date()
71+
}
72+
const obj = {
73+
yyyyyyyy: t.getFullYear(),
74+
yy: t.getFullYear(),
75+
MM: t.getMonth()+1,
76+
dd: t.getDate(),
77+
HH: t.getHours(),
78+
hh: t.getHours() % 12,
79+
mm: t.getMinutes(),
80+
ss: t.getSeconds(),
81+
ww: '日一二三四五六'.split('')[t.getDay()]
82+
};
83+
return str.replace(/([a-z]+)/ig, function ($1){
84+
return (obj[$1+$1] === 0 ? '0' : obj[$1+$1]) || ('0' + obj[$1]).slice(-2);
85+
});
86+
}
87+
export const insertAfter = (newElement, targetElement) => {
88+
const parent = targetElement.parentNode
89+
if(parent.lastChild === targetElement){
90+
parent.appendChild(newElement)
91+
}else{
92+
parent.insertBefore(newElement, targetElement.nextSibling)
93+
}
94+
}
95+
export const getUrl = (prefix, link) => {
96+
if (!link) return ''
97+
if (/^(http|https)/.test(link)) {
98+
return link
99+
}
100+
if (/^\/\//.test(link)) {
101+
return prefix.split('//')[0] + link
102+
}
103+
return prefix + link
104+
}
105+
export const tex2svg = (markdwonDoc) => {
106+
return markdwonDoc.replace(/<ztext>(.*?)<\/ztext>/g, (s, s1) => {
107+
const tex = decodeURIComponent(s1)
108+
const svg = MathJax.tex2svg(tex)
109+
svg.setAttribute('data-tex', tex)
110+
svg.style.display = 'inline'
111+
return svg.outerHTML
112+
})
113+
}
114+
115+
export default {
116+
isBroswer,
117+
isExtension,
118+
getExt,
119+
query,
120+
getText,
121+
getAttribute,
122+
queryAll,
123+
noop,
124+
encodeUrlData,
125+
encodeOptionsData,
126+
sendMessage,
127+
formatDate,
128+
insertAfter,
129+
getUrl,
130+
tex2svg
131+
}

0 commit comments

Comments
 (0)