Skip to content

Commit 9095750

Browse files
committed
支持 grid 扩展
1 parent 30c14eb commit 9095750

File tree

6 files changed

+287
-75
lines changed

6 files changed

+287
-75
lines changed

docs/content/usage/grid.md

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
---
2+
title: Grid
3+
icon: grid
4+
---
5+
6+
PageForge 支持网格布局语法。
7+
8+
!!! danger "注意"
9+
10+
该功能需要在配置文件中启用,可以在 `pageforge.yaml` 中配置 `feature.grid.enable: true`
11+
12+
!!!
13+
14+
## 基本语法
15+
16+
---
17+
18+
- 源代码
19+
20+
```markdown
21+
::: grid
22+
- 第一个网格项
23+
- 第二个网格项
24+
- 第三个网格项
25+
- 第四个网格项
26+
:::
27+
```
28+
29+
- 显示效果
30+
31+
::: grid
32+
- 第一个网格项
33+
- 第二个网格项
34+
- 第三个网格项
35+
- 第四个网格项
36+
:::
37+
38+
## 设置列数
39+
40+
---
41+
42+
- 源代码
43+
44+
```markdown
45+
::: grid cols-3
46+
- 第一列内容
47+
- 第二列内容
48+
- 第三列内容
49+
- 第四列内容(自动换行)
50+
- 第五列内容
51+
- 第六列内容
52+
:::
53+
```
54+
55+
- 显示效果
56+
57+
::: grid cols-3
58+
- 第一列内容
59+
- 第二列内容
60+
- 第三列内容
61+
- 第四列内容(自动换行)
62+
- 第五列内容
63+
- 第六列内容
64+
:::
65+
66+
## 设置间距
67+
68+
---
69+
70+
- 源代码
71+
72+
```markdown
73+
::: grid cols-2 gap-8
74+
- 这是第一个网格项
75+
可以包含多行内容
76+
- 这是第二个网格项
77+
间距设置为 8 (2rem)
78+
- 这是第三个网格项
79+
- 这是第四个网格项
80+
:::
81+
```
82+
83+
- 显示效果
84+
85+
::: grid cols-2 gap-8
86+
- 这是第一个网格项
87+
可以包含多行内容
88+
- 这是第二个网格项
89+
间距设置为 8 (2rem)
90+
- 这是第三个网格项
91+
- 这是第四个网格项
92+
:::
93+
94+
## 禁用响应式
95+
96+
---
97+
98+
默认情况下,网格布局是响应式的:
99+
- 移动设备:1列
100+
- 平板设备:最多2列
101+
- 桌面设备:完整列数
102+
103+
使用 `no-responsive` 可以禁用响应式布局:
104+
105+
```markdown
106+
::: grid cols-4 no-responsive
107+
- 第一列
108+
- 第二列
109+
- 第三列
110+
- 第四列
111+
:::
112+
```
113+
114+
## 完整选项
115+
116+
---
117+
118+
网格布局支持以下选项:
119+
120+
1. `cols-{number}`: 设置列数(默认:2)
121+
- 例如:`cols-3``cols-4``cols-6`
122+
123+
2. `gap-{number}`: 设置网格间距(默认:4)
124+
- 例如:`gap-4``gap-6``gap-8`
125+
126+
3. `no-responsive`: 禁用响应式布局
127+
- 默认启用响应式
128+
129+
可以组合使用这些选项:
130+
131+
```markdown
132+
::: grid cols-3 gap-6 no-responsive
133+
- 内容1
134+
- 内容2
135+
- 内容3
136+
:::
137+
```
138+
139+
## 实际应用示例
140+
141+
---
142+
143+
创建特色卡片布局:
144+
145+
```markdown
146+
::: grid cols-3 gap-6
147+
- ### 🚀 快速上手
148+
零配置,开箱即用,
149+
支持 Markdown 所有基础语法。
150+
151+
- ### 📦 功能丰富
152+
内置大量扩展组件,
153+
支持自定义扩展。
154+
155+
- ### 🎨 主题系统
156+
提供多套主题,
157+
支持自定义主题。
158+
:::
159+
```
160+
161+
::: grid cols-3 gap-6
162+
- __🚀 快速上手__
163+
零配置,开箱即用,
164+
支持 Markdown 所有基础语法。
165+
166+
- ### 📦 功能丰富
167+
内置大量扩展组件,
168+
支持自定义扩展。
169+
170+
- ### 🎨 主题系统
171+
提供多套主题,
172+
支持自定义主题。
173+
:::

docs/pageforge.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ feature:
9393
enable: true
9494
tabs:
9595
enable: true
96+
grid:
97+
enable: true
9698

9799
i18n:
98100
default: zh-CN
@@ -191,6 +193,7 @@ nav:
191193
- /usage/diff
192194
- /usage/button
193195
- /usage/icon
196+
- /usage/grid
194197
- Template:
195198
- /template/home
196199
- /template/team
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
const {loadComponent} = require("../../component-loader");
2+
const ConfigManager = require("../../config-manager");
3+
const config = new ConfigManager(process.cwd()).getConfig();
4+
5+
const PageForgeGridExtension = {
6+
name: 'pageforgeGrid',
7+
level: 'block',
8+
9+
start(src) {
10+
if (!config.feature?.grid?.enable) {
11+
return -1;
12+
}
13+
const index = src.match(/^:::\s*grid(?:\s+[\w-]+)*\s*\n/m)?.index ?? -1;
14+
return index;
15+
},
16+
17+
tokenizer(src, tokens) {
18+
if (!config.feature?.grid?.enable) {
19+
return false;
20+
}
21+
22+
const headerMatch = /^:::\s*grid((?:\s+[\w-]+)*)\s*\n/.exec(src);
23+
if (!headerMatch) {
24+
return false;
25+
}
26+
27+
const endIndex = src.indexOf('\n:::');
28+
if (endIndex === -1) {
29+
return false;
30+
}
31+
32+
const gridOptions = {
33+
cols: 2,
34+
gap: 4,
35+
responsive: true
36+
};
37+
38+
const options = headerMatch[1];
39+
if (options) {
40+
options.trim().split(/\s+/).forEach(opt => {
41+
if (opt.startsWith('cols-')) {
42+
gridOptions.cols = parseInt(opt.slice(5));
43+
}
44+
else if (opt.startsWith('gap-')) {
45+
gridOptions.gap = parseInt(opt.slice(4));
46+
}
47+
else if (opt === 'no-responsive') {
48+
gridOptions.responsive = false;
49+
}
50+
});
51+
}
52+
53+
const headerLength = headerMatch[0].length;
54+
const content = src.slice(headerLength, endIndex);
55+
const raw = src.slice(0, endIndex + 4);
56+
57+
const items = content.split(/\n(?=\s*[-*+]|\s*\d+\.)/g)
58+
.map(item => item.trim())
59+
.filter(item => item)
60+
.map(item => {
61+
return item.replace(/^[-*+]\s+|^\d+\.\s+/, '');
62+
});
63+
64+
return {
65+
type: 'pageforgeGrid',
66+
raw,
67+
content: items,
68+
options: gridOptions,
69+
tokens: []
70+
};
71+
},
72+
73+
renderer(token) {
74+
return loadComponent('grid', {
75+
content: token.content,
76+
options: token.options,
77+
config: config.feature?.grid?.options || {}
78+
});
79+
}
80+
};
81+
82+
module.exports = PageForgeGridExtension;

lib/extension/marked/pageforge-marked.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ const PageForgeMermaidExtension = require("./pageforge-mermaid");
1515
const PageForgeDiffExtension = require("./pageforge-diff");
1616
const PageForgeButtonExtension = require("./pageforge-button");
1717
const PageForgeIconExtension = require("./pageforge-icon");
18+
const PageForgeGridExtension = require('./pageforge-grid')
19+
const {unescape} = require('./utils')
1820

1921
const renderer = {
2022
paragraph({tokens}) {
@@ -96,7 +98,8 @@ marked.use({
9698
PageForgeTooltipsExtension,
9799
PageForgeDiffExtension,
98100
PageForgeButtonExtension,
99-
PageForgeIconExtension
101+
PageForgeIconExtension,
102+
PageForgeGridExtension
100103
],
101104
renderer,
102105
breaks: false

lib/extension/marked/utils.js

Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,7 @@
1-
const other = {
2-
codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm,
3-
outputLinkReplace: /\\([\[\]])/g,
4-
indentCodeCompensation: /^(\s+)(?:```)/,
5-
beginningSpace: /^\s+/,
6-
endingHash: /#$/,
7-
startingSpaceChar: /^ /,
8-
endingSpaceChar: / $/,
9-
nonSpaceChar: /[^ ]/,
10-
newLineCharGlobal: /\n/g,
11-
tabCharGlobal: /\t/g,
12-
multipleSpaceGlobal: /\s+/g,
13-
blankLine: /^[ \t]*$/,
14-
doubleBlankLine: /\n[ \t]*\n[ \t]*$/,
15-
blockquoteStart: /^ {0,3}>/,
16-
blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g,
17-
blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm,
18-
listReplaceTabs: /^\t+/,
19-
listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g,
20-
listIsTask: /^\[[ xX]\] /,
21-
listReplaceTask: /^\[[ xX]\] +/,
22-
anyLine: /\n.*\n/,
23-
hrefBrackets: /^<(.*)>$/,
24-
tableDelimiter: /[:|]/,
25-
tableAlignChars: /^\||\| *$/g,
26-
tableRowBlankLine: /\n[ \t]*$/,
27-
tableAlignRight: /^ *-+: *$/,
28-
tableAlignCenter: /^ *:-+: *$/,
29-
tableAlignLeft: /^ *:-+ *$/,
30-
startATag: /^<a /i,
31-
endATag: /^<\/a>/i,
32-
startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i,
33-
endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i,
34-
startAngleBracket: /^</,
35-
endAngleBracket: />$/,
36-
pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/,
37-
unicodeAlphaNumeric: /[\p{L}\p{N}]/u,
38-
escapeTest: /[&<>"']/,
39-
escapeReplace: /[&<>"']/g,
40-
escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
41-
escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
42-
unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,
43-
caret: /(^|[^\[])\^/g,
44-
percentDecode: /%25/g,
45-
findPipe: /\|/g,
46-
splitPipe: / \|/,
47-
slashPipe: /\\\|/g,
48-
carriageReturn: /\r\n|\r/g,
49-
spaceLine: /^ +$/gm,
50-
notSpaceStart: /^\S*/,
51-
endingNewline: /\n$/,
52-
listItemRegex: (bull: string) => new RegExp(`^( {0,3}${bull})((?:[\t ][^\\n]*)?(?:\\n|$))`),
53-
nextBulletRegex: (indent: number) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),
54-
hrRegex: (indent: number) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),
55-
fencesBeginRegex: (indent: number) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`),
56-
headingBeginRegex: (indent: number) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`),
57-
htmlBeginRegex: (indent: number) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, 'i'),
1+
const unescape = (text) => {
2+
return text.replace(/\\([\\`*{}[\]()#+\-.!_>])/g, '$1');
583
};
594

60-
function edit(regex: string | RegExp, opt = '') {
61-
let source = typeof regex === 'string' ? regex : regex.source
62-
const obj = {
63-
replace: (name: string | RegExp, val: string | RegExp) => {
64-
let valSource = typeof val === 'string' ? val : val.source
65-
valSource = valSource.replace(other.caret, '$1')
66-
source = source.replace(name, valSource)
67-
return obj
68-
},
69-
getRegex: () => {
70-
return new RegExp(source, opt)
71-
}
72-
}
73-
return obj
74-
}
75-
76-
module.exports = edit
5+
module.exports = {
6+
unescape
7+
};

0 commit comments

Comments
 (0)