Skip to content
This repository was archived by the owner on Aug 8, 2024. It is now read-only.

Commit daf2039

Browse files
authored
Feature/anchor (#6)
* feat: anchor(todo) * fix: typo * fix: rename * fix: hash * feat: string hash * feat: anchor style * feat: anchor style * feat: anchor * feat: issue * feat: highlight * feat: babel
1 parent 9013e9b commit daf2039

19 files changed

+295
-70
lines changed

.umirc.js

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export default {
77
`${__dirname}/lib`,
88
{
99
wrapper: 'div',
10+
className: 'markdown-body',
11+
style: {
12+
padding: '30px',
13+
},
1014
},
1115
],
1216
],

README.md

+19-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ export default {
2929

3030
option | intro | type | default
3131
-|-|-|-
32-
wrapper | HTMLElementTagName |string | section |
32+
anchor | Auto Anchor `^0.2.0` | string[], falsy to disable | ['h1', 'h2', 'h3'] |
33+
wrapper | HTMLElementTagName | string | section |
3334
className | React className | string | |
3435
style | React style | object | |
3536
html | markdown-it option | boolean | true |
@@ -43,13 +44,29 @@ highlight | markdown-it option | function | highlight.js |
4344

4445
e.g, `index.md` will cover `index.jsx`.
4546

46-
## Stylize
47+
## Anchor
48+
49+
![Example](https://raw.githubusercontent.com/chiaweilee/umi-plugin-md/master/Screenshot%202019-07-16%20at%2022.56.20.png)
50+
51+
#### Anchor Stylize
52+
53+
```css
54+
// global.css
55+
@import "~umi-plugin-md/anchor.css";
56+
```
57+
58+
Or, write yourself.
59+
60+
tips: we do not support `scroll to anchor on did mount` this moment.
61+
62+
## Markdown Stylize
4763

4864
[github-markdown-css](https://www.npmjs.com/package/github-markdown-css)
4965

5066
```css
5167
// global.css
5268
@import "~github-markdown-css/github-markdown.css";
69+
@import "~highlight.js/styles/github.css";
5370
```
5471

5572
```js

Screenshot 2019-07-16 at 22.56.20.png

22.7 KB
Loading

anchor.less

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
h1,
2+
h2,
3+
h3,
4+
h4,
5+
h5 {
6+
.anchor {
7+
display: none;
8+
position: absolute;
9+
transform: translateX(-100%);
10+
font-family: serif;
11+
margin: 0 !important;
12+
padding: 0 !important;
13+
line-height: inherit !important;
14+
&:after {
15+
content: '#';
16+
}
17+
}
18+
}
19+
20+
h1:hover,
21+
h2:hover,
22+
h3:hover,
23+
h4:hover,
24+
h5:hover {
25+
.anchor {
26+
display: block;
27+
position: absolute;
28+
}
29+
}

package.json

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "umi-plugin-md",
3-
"version": "0.1.0-beta.1",
3+
"version": "0.2.0",
44
"description": "Markdown(*.md) component plugin for umi.",
55
"scripts": {
66
"prepublishOnly": "npm run build",
@@ -10,21 +10,26 @@
1010
"prestart": "npm run build",
1111
"start": "umi dev",
1212
"test": "umi test --coverage",
13-
"build": "npm run build:babel",
13+
"build": "npm run build:babel && npm run build:less",
1414
"build:babel": "npx babel src --out-dir lib --extensions \".ts\"",
15+
"build:less": "lessc anchor.less anchor.css",
1516
"precommit": "lint-staged"
1617
},
1718
"main": "lib/index.js",
1819
"files": [
19-
"lib"
20+
"lib",
21+
"anchor.css"
2022
],
2123
"dependencies": {
2224
"@babel/core": "^7.3.4",
2325
"@babel/plugin-transform-react-jsx": "^7.3.0",
26+
"cheerio": "^1.0.0-rc.3",
27+
"github-markdown-css": "^3.0.1",
2428
"highlight.js": "^9.15.8",
2529
"loader-utils": "^1.2.3",
2630
"lodash": "^4.17.14",
2731
"markdown-it": "^8.4.2",
32+
"shorthash": "^0.0.2",
2833
"umi-utils": "^1.5.1",
2934
"xss": "^1.0.6"
3035
},
@@ -50,6 +55,7 @@
5055
"eslint-plugin-react": "^7.14.2",
5156
"husky": "^0.14.3",
5257
"jest": "^24.8.0",
58+
"lessc": "^1.0.2",
5359
"lint-staged": "^7.2.2",
5460
"np": "^3.0.4",
5561
"prettier": "^1.18.2",

src/global.less

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@import "~github-markdown-css/github-markdown.css";
2+
@import "~highlight.js/styles/github.css";
3+
@import "../anchor";

src/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IApi } from 'umi-plugin-types';
2-
import getRouteConfigFromDir from './getRouteConfigFromDir';
3-
import mergeRoute from './mergeRoute';
2+
import getRouteConfigFromDir from './lib/getRouteConfigFromDir';
3+
import mergeRoute from './lib/mergeRoute';
44

55
const path = require('path');
66
const loader = path.join(__dirname, './loader');
@@ -9,6 +9,7 @@ export interface IOption {
99
wrapper?: string;
1010
className?: string;
1111
style?: object;
12+
anchor?: string[] | boolean;
1213
}
1314

1415
interface ExIApi extends IApi {

src/lib/buildWrapper.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const isPlainObject = require('lodash/isPlainObject');
2+
3+
interface Ext {
4+
className?: string;
5+
style?: object;
6+
}
7+
8+
export default function(source: string, wrapper: string, extend: Ext): string {
9+
let extendString = '';
10+
11+
if (typeof extend.className === 'string') {
12+
extendString += ' className="'.concat(extend.className, '"');
13+
}
14+
15+
if (isPlainObject(extend.style)) {
16+
// @ts-ignore
17+
extendString += ' style={'.concat(JSON.stringify(extend.style), '}');
18+
}
19+
20+
return `<${wrapper.concat(extendString)}>${source}</${wrapper}>`;
21+
}
File renamed without changes.

src/lib/loadContent.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import markdown from './markdown';
2+
import replaceArchor from './replaceArchor';
3+
import buildWrapper from './buildWrapper';
4+
5+
function replaceJSX(source: string): string {
6+
return source.replace(/([{}])/g, "{'$1'}");
7+
}
8+
9+
interface Opt {
10+
markdown: object;
11+
anchor: string[] | boolean;
12+
wrapper: string;
13+
className: string;
14+
style: object;
15+
}
16+
17+
export default function(source: string, options: Opt): string {
18+
source = markdown(source, options.markdown);
19+
source = replaceArchor(source, options.anchor);
20+
source = replaceJSX(source);
21+
source = buildWrapper(source, options.wrapper, {
22+
className: options.className,
23+
style: options.style,
24+
});
25+
26+
return source;
27+
}

src/lib/markdown.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import renderHighlight from './renderHighlight';
2+
3+
export default function(source: string, options = {} as object): string {
4+
return require('markdown-it')(
5+
'default',
6+
Object.assign(
7+
{
8+
html: true,
9+
xhtmlOut: true,
10+
breaks: true,
11+
linkify: true,
12+
typographer: true,
13+
highlight: renderHighlight,
14+
},
15+
options,
16+
),
17+
).render(source);
18+
}

src/mergeRoute.ts src/lib/mergeRoute.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ interface Routes {
88
}
99

1010
export default function mergeRoute(source: Routes[], target: Routes[]): Routes[] {
11-
if (!Array.isArray(source)) { source = []; }
12-
if (!Array.isArray(target)) { target = []; }
11+
if (!Array.isArray(source)) {
12+
source = [];
13+
}
14+
if (!Array.isArray(target)) {
15+
target = [];
16+
}
1317

1418
forEach(source, s => {
1519
const max = target.length;
1620
every(target, (t, i) => {
17-
if ((t.path === s.path)) {
21+
if (t.path === s.path) {
1822
target[i] = s;
1923
if (Array.isArray(t.routes) || Array.isArray(s.routes)) {
2024
// @ts-ignore

src/lib/renderHighlight.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const highlight = require('highlight.js');
2+
3+
export default function(source: string, lang): string {
4+
if (!(lang && highlight.getLanguage(lang))) {
5+
return '';
6+
}
7+
return highlight.highlight(lang, source, true).value;
8+
}

src/lib/replaceArchor.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import selfClose from './selfClose';
2+
3+
const cheerio = require('cheerio');
4+
const sh = require('shorthash');
5+
6+
export default function(html: string, anchor: string[] | boolean): string {
7+
if (!html || !Array.isArray(anchor)) {
8+
return html;
9+
}
10+
11+
const $ = cheerio.load(html);
12+
13+
anchor.forEach(_ => {
14+
$(_).each(function() {
15+
const hash = sh.unique($(this).text());
16+
$(this).attr('id', hash);
17+
$(this).prepend(`<a class="anchor" href="#${hash}" />`);
18+
});
19+
});
20+
21+
return selfClose($.html());
22+
}

src/lib/selfClose.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const tags = [
2+
'area',
3+
'base',
4+
'br',
5+
'col',
6+
'command',
7+
'embed',
8+
'hr',
9+
'img',
10+
'input',
11+
'keygen',
12+
'link',
13+
'meta',
14+
'param',
15+
'source',
16+
'track',
17+
'wbr',
18+
];
19+
20+
export default function(html: string): string {
21+
const reg = new RegExp(`<s*(${tags.join('|')})s*>`, 'g');
22+
return html.replace(reg, '<$1 />');
23+
}

src/loader.ts

+14-35
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,11 @@
11
import { IOption } from './index';
2-
3-
const highlight = require('highlight.js');
4-
5-
const renderHighlight = function(source: string, lang): string {
6-
if (!(lang && highlight.getLanguage(lang))) {
7-
return '';
8-
}
9-
return highlight.highlight(lang, source, true).value;
10-
};
11-
12-
const markdown = function(source: string, options = {} as object): string {
13-
return require('markdown-it')(
14-
'default',
15-
Object.assign(
16-
{
17-
html: true,
18-
xhtmlOut: true,
19-
breaks: true,
20-
linkify: true,
21-
typographer: true,
22-
highlight: renderHighlight,
23-
},
24-
options,
25-
),
26-
).render(source);
27-
};
28-
29-
const replace = function(source: string): string {
30-
return source.replace(/([{}])/g, "{'$1'}");
31-
};
2+
import loadContent from './lib/loadContent';
323

334
export default function loader(source: string) {
345
const opts: IOption = {
356
// default opts
367
wrapper: 'section',
8+
anchor: ['h1', 'h2', 'h3'],
379
};
3810

3911
if (this) {
@@ -44,16 +16,23 @@ export default function loader(source: string) {
4416
Object.assign(opts, require('loader-utils').getOptions(this));
4517
}
4618

47-
const { wrapper, className, style, ...options } = opts;
48-
49-
const code: string = require('xss')(replace(markdown(source, options)));
19+
const { anchor, wrapper, className, style, ...options } = opts;
20+
const rawHtml = loadContent(source, {
21+
markdown: options,
22+
anchor,
23+
wrapper,
24+
className,
25+
style,
26+
});
5027

5128
const component = `import React from 'react';
5229
export default function() {
53-
return (<${opts.wrapper} className="${className}" style={${style}}>${code}</${wrapper}>);
30+
return (${rawHtml});
5431
}`;
5532

56-
return require('@babel/core').transform(component, {
33+
return require('@babel/core').transformSync(component, {
5734
plugins: ['@babel/plugin-transform-react-jsx'],
35+
babelrc: false,
36+
configFile: false,
5837
}).code;
5938
}

0 commit comments

Comments
 (0)