@aigne/doc-smith 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +46 -0
- package/.github/workflows/reviewer.yml +2 -1
- package/CHANGELOG.md +17 -0
- package/agents/chat.yaml +30 -0
- package/agents/check-detail-result.mjs +2 -1
- package/agents/check-detail.mjs +1 -0
- package/agents/check-structure-plan.mjs +1 -1
- package/agents/docs-fs.yaml +25 -0
- package/agents/exit.mjs +6 -0
- package/agents/feedback-refiner.yaml +5 -1
- package/agents/find-items-by-paths.mjs +10 -4
- package/agents/fs.mjs +60 -0
- package/agents/input-generator.mjs +159 -90
- package/agents/load-config.mjs +0 -5
- package/agents/load-sources.mjs +119 -12
- package/agents/publish-docs.mjs +28 -11
- package/agents/retranslate.yaml +1 -1
- package/agents/team-publish-docs.yaml +2 -2
- package/aigne.yaml +1 -0
- package/package.json +13 -10
- package/prompts/content-detail-generator.md +12 -4
- package/prompts/document/custom-components.md +80 -0
- package/prompts/document/d2-chart/diy-examples.md +44 -0
- package/prompts/document/d2-chart/official-examples.md +708 -0
- package/prompts/document/d2-chart/rules.md +48 -0
- package/prompts/document/detail-generator.md +13 -15
- package/prompts/document/structure-planning.md +1 -3
- package/prompts/feedback-refiner.md +81 -60
- package/prompts/structure-planning.md +20 -3
- package/tests/check-detail-result.test.mjs +50 -2
- package/tests/conflict-resolution.test.mjs +237 -0
- package/tests/input-generator.test.mjs +940 -0
- package/tests/load-sources.test.mjs +627 -3
- package/tests/preferences-utils.test.mjs +94 -0
- package/tests/save-value-to-config.test.mjs +182 -5
- package/tests/utils.test.mjs +49 -0
- package/utils/auth-utils.mjs +1 -1
- package/utils/conflict-detector.mjs +72 -1
- package/utils/constants.mjs +139 -126
- package/utils/kroki-utils.mjs +162 -0
- package/utils/markdown-checker.mjs +175 -67
- package/utils/utils.mjs +97 -29
package/agents/load-sources.mjs
CHANGED
|
@@ -2,7 +2,11 @@ import { access, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_INCLUDE_PATTERNS } from "../utils/constants.mjs";
|
|
4
4
|
import { getFilesWithGlob, loadGitignore } from "../utils/file-utils.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
getCurrentGitHead,
|
|
7
|
+
getModifiedFilesBetweenCommits,
|
|
8
|
+
isGlobPattern,
|
|
9
|
+
} from "../utils/utils.mjs";
|
|
6
10
|
|
|
7
11
|
export default async function loadSources({
|
|
8
12
|
sources = [],
|
|
@@ -24,7 +28,12 @@ export default async function loadSources({
|
|
|
24
28
|
|
|
25
29
|
for (const dir of paths) {
|
|
26
30
|
try {
|
|
27
|
-
|
|
31
|
+
if (typeof dir !== "string") {
|
|
32
|
+
console.warn(`Invalid source path: ${dir}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// First try to access as a file or directory
|
|
28
37
|
const stats = await stat(dir);
|
|
29
38
|
|
|
30
39
|
if (stats.isFile()) {
|
|
@@ -78,7 +87,31 @@ export default async function loadSources({
|
|
|
78
87
|
allFiles = allFiles.concat(filesInDir);
|
|
79
88
|
}
|
|
80
89
|
} catch (err) {
|
|
81
|
-
if (err.code
|
|
90
|
+
if (err.code === "ENOENT") {
|
|
91
|
+
// Path doesn't exist as file or directory, try as glob pattern
|
|
92
|
+
try {
|
|
93
|
+
// Check if it looks like a glob pattern
|
|
94
|
+
const isGlobPatternResult = isGlobPattern(dir);
|
|
95
|
+
|
|
96
|
+
if (isGlobPatternResult) {
|
|
97
|
+
// Use glob to find matching files from current working directory
|
|
98
|
+
const { glob } = await import("glob");
|
|
99
|
+
const matchedFiles = await glob(dir, {
|
|
100
|
+
absolute: true,
|
|
101
|
+
nodir: true, // Only files, not directories
|
|
102
|
+
dot: false, // Don't include hidden files
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (matchedFiles.length > 0) {
|
|
106
|
+
allFiles = allFiles.concat(matchedFiles);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (globErr) {
|
|
110
|
+
console.warn(`Failed to process glob pattern "${dir}": ${globErr.message}`);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
throw err;
|
|
114
|
+
}
|
|
82
115
|
}
|
|
83
116
|
}
|
|
84
117
|
|
|
@@ -86,17 +119,55 @@ export default async function loadSources({
|
|
|
86
119
|
}
|
|
87
120
|
|
|
88
121
|
files = [...new Set(files)];
|
|
122
|
+
|
|
123
|
+
// Define media file extensions
|
|
124
|
+
const mediaExtensions = [
|
|
125
|
+
".jpg",
|
|
126
|
+
".jpeg",
|
|
127
|
+
".png",
|
|
128
|
+
".gif",
|
|
129
|
+
".bmp",
|
|
130
|
+
".webp",
|
|
131
|
+
".svg",
|
|
132
|
+
".mp4",
|
|
133
|
+
".mov",
|
|
134
|
+
".avi",
|
|
135
|
+
".mkv",
|
|
136
|
+
".webm",
|
|
137
|
+
".m4v",
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
// Separate source files from media files
|
|
141
|
+
const sourceFiles = [];
|
|
142
|
+
const mediaFiles = [];
|
|
89
143
|
let allSources = "";
|
|
90
|
-
|
|
144
|
+
|
|
145
|
+
await Promise.all(
|
|
91
146
|
files.map(async (file) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
147
|
+
const ext = path.extname(file).toLowerCase();
|
|
148
|
+
|
|
149
|
+
if (mediaExtensions.includes(ext)) {
|
|
150
|
+
// This is a media file
|
|
151
|
+
const relativePath = path.relative(docsDir, file);
|
|
152
|
+
const fileName = path.basename(file);
|
|
153
|
+
const description = path.parse(fileName).name;
|
|
154
|
+
|
|
155
|
+
mediaFiles.push({
|
|
156
|
+
name: fileName,
|
|
157
|
+
path: relativePath,
|
|
158
|
+
description,
|
|
159
|
+
});
|
|
160
|
+
} else {
|
|
161
|
+
// This is a source file
|
|
162
|
+
const content = await readFile(file, "utf8");
|
|
163
|
+
const relativePath = path.relative(process.cwd(), file);
|
|
164
|
+
allSources += `// sourceId: ${relativePath}\n${content}\n`;
|
|
165
|
+
|
|
166
|
+
sourceFiles.push({
|
|
167
|
+
sourceId: relativePath,
|
|
168
|
+
content,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
100
171
|
}),
|
|
101
172
|
);
|
|
102
173
|
|
|
@@ -164,6 +235,37 @@ export default async function loadSources({
|
|
|
164
235
|
}
|
|
165
236
|
}
|
|
166
237
|
|
|
238
|
+
// Generate assets content from media files
|
|
239
|
+
let assetsContent = "# Available Media Assets for Documentation\n\n";
|
|
240
|
+
|
|
241
|
+
if (mediaFiles.length > 0) {
|
|
242
|
+
// Helper function to determine file type from extension
|
|
243
|
+
const getFileType = (filePath) => {
|
|
244
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
245
|
+
const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg"];
|
|
246
|
+
const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
|
|
247
|
+
|
|
248
|
+
if (imageExts.includes(ext)) return "image";
|
|
249
|
+
if (videoExts.includes(ext)) return "video";
|
|
250
|
+
return "media";
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const mediaYaml = mediaFiles.map((file) => ({
|
|
254
|
+
name: file.name,
|
|
255
|
+
path: file.path,
|
|
256
|
+
type: getFileType(file.path),
|
|
257
|
+
}));
|
|
258
|
+
|
|
259
|
+
assetsContent += "```yaml\n";
|
|
260
|
+
assetsContent += "assets:\n";
|
|
261
|
+
mediaYaml.forEach((asset) => {
|
|
262
|
+
assetsContent += ` - name: "${asset.name}"\n`;
|
|
263
|
+
assetsContent += ` path: "${asset.path}"\n`;
|
|
264
|
+
assetsContent += ` type: "${asset.type}"\n`;
|
|
265
|
+
});
|
|
266
|
+
assetsContent += "```\n";
|
|
267
|
+
}
|
|
268
|
+
|
|
167
269
|
// Count words and lines in allSources
|
|
168
270
|
let totalWords = 0;
|
|
169
271
|
let totalLines = 0;
|
|
@@ -188,6 +290,7 @@ export default async function loadSources({
|
|
|
188
290
|
modifiedFiles,
|
|
189
291
|
totalWords,
|
|
190
292
|
totalLines,
|
|
293
|
+
assetsContent,
|
|
191
294
|
};
|
|
192
295
|
}
|
|
193
296
|
|
|
@@ -257,6 +360,10 @@ loadSources.output_schema = {
|
|
|
257
360
|
items: { type: "string" },
|
|
258
361
|
description: "Array of modified files since last generation",
|
|
259
362
|
},
|
|
363
|
+
assetsContent: {
|
|
364
|
+
type: "string",
|
|
365
|
+
description: "Markdown content for available media assets",
|
|
366
|
+
},
|
|
260
367
|
},
|
|
261
368
|
};
|
|
262
369
|
|
package/agents/publish-docs.mjs
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { basename, join } from "node:path";
|
|
2
2
|
import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
|
|
4
6
|
import { getAccessToken } from "../utils/auth-utils.mjs";
|
|
5
|
-
import { DISCUSS_KIT_STORE_URL } from "../utils/constants.mjs";
|
|
7
|
+
import { DISCUSS_KIT_STORE_URL, TMP_DIR, TMP_DOCS_DIR } from "../utils/constants.mjs";
|
|
8
|
+
import { beforePublishHook, ensureTmpDir } from "../utils/kroki-utils.mjs";
|
|
6
9
|
import { getGithubRepoUrl, loadConfigFromFile, saveValueToConfig } from "../utils/utils.mjs";
|
|
7
10
|
|
|
8
11
|
const DEFAULT_APP_URL = "https://docsmith.aigne.io";
|
|
9
12
|
|
|
10
13
|
export default async function publishDocs(
|
|
11
|
-
{ docsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
|
|
14
|
+
{ docsDir: rawDocsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
|
|
12
15
|
options,
|
|
13
16
|
) {
|
|
17
|
+
// move work dir to tmp-dir
|
|
18
|
+
await ensureTmpDir();
|
|
19
|
+
|
|
20
|
+
const docsDir = join(".aigne", "doc-smith", TMP_DIR, TMP_DOCS_DIR);
|
|
21
|
+
await fs.rm(docsDir, { recursive: true, force: true });
|
|
22
|
+
await fs.mkdir(docsDir, {
|
|
23
|
+
recursive: true,
|
|
24
|
+
});
|
|
25
|
+
await fs.cp(rawDocsDir, docsDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
// ----------------- trigger beforePublishHook -----------------------------
|
|
28
|
+
await beforePublishHook({ docsDir });
|
|
29
|
+
|
|
30
|
+
// ----------------- main publish process flow -----------------------------
|
|
14
31
|
// Check if DOC_DISCUSS_KIT_URL is set in environment variables
|
|
15
32
|
const envAppUrl = process.env.DOC_DISCUSS_KIT_URL;
|
|
16
33
|
const useEnvAppUrl = !!envAppUrl;
|
|
@@ -87,6 +104,8 @@ export default async function publishDocs(
|
|
|
87
104
|
].filter((lang, index, arr) => arr.indexOf(lang) === index), // Remove duplicates
|
|
88
105
|
};
|
|
89
106
|
|
|
107
|
+
let message;
|
|
108
|
+
|
|
90
109
|
try {
|
|
91
110
|
const { success, boardId: newBoardId } = await publishDocsFn({
|
|
92
111
|
sidebarPath,
|
|
@@ -98,6 +117,8 @@ export default async function publishDocs(
|
|
|
98
117
|
boardName: projectInfo.name,
|
|
99
118
|
boardDesc: projectInfo.description,
|
|
100
119
|
boardCover: projectInfo.icon,
|
|
120
|
+
mediaFolder: rawDocsDir,
|
|
121
|
+
cacheFilePath: join(".aigne", "doc-smith", "upload-cache.yaml"),
|
|
101
122
|
boardMeta,
|
|
102
123
|
});
|
|
103
124
|
|
|
@@ -112,18 +133,14 @@ export default async function publishDocs(
|
|
|
112
133
|
if (boardId !== newBoardId) {
|
|
113
134
|
await saveValueToConfig("boardId", newBoardId);
|
|
114
135
|
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
message,
|
|
118
|
-
};
|
|
136
|
+
message = `✅ Documentation Published Successfully!`;
|
|
119
137
|
}
|
|
120
|
-
|
|
121
|
-
return {};
|
|
122
138
|
} catch (error) {
|
|
123
|
-
|
|
124
|
-
message: `❌ Failed to publish docs: ${error.message}`,
|
|
125
|
-
};
|
|
139
|
+
message = `❌ Failed to publish docs: ${error.message}`;
|
|
126
140
|
}
|
|
141
|
+
// clean up tmp work dir
|
|
142
|
+
await fs.rm(docsDir, { recursive: true, force: true });
|
|
143
|
+
return message ? { message } : {};
|
|
127
144
|
}
|
|
128
145
|
|
|
129
146
|
publishDocs.input_schema = {
|
package/agents/retranslate.yaml
CHANGED
|
@@ -58,7 +58,7 @@ input_schema:
|
|
|
58
58
|
type: array
|
|
59
59
|
items:
|
|
60
60
|
type: string
|
|
61
|
-
description: Languages to translate to
|
|
61
|
+
description: "Languages to translate to, available languages are: en, zh, zh-TW, ja, fr, de, es, it, ru, ko, pt, ar"
|
|
62
62
|
feedback:
|
|
63
63
|
type: string
|
|
64
64
|
description: Feedback for translation improvement
|
|
@@ -13,6 +13,6 @@ skills:
|
|
|
13
13
|
input_schema:
|
|
14
14
|
type: object
|
|
15
15
|
properties:
|
|
16
|
-
appUrl:
|
|
16
|
+
appUrl:
|
|
17
17
|
type: string
|
|
18
|
-
description: target website URL where the documentation will be published
|
|
18
|
+
description: target website URL where the documentation will be published (optional - if not provided, will prompt for interactive input)
|
package/aigne.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/doc-smith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -12,19 +12,21 @@
|
|
|
12
12
|
"author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@aigne/aigne-hub": "^0.
|
|
16
|
-
"@aigne/anthropic": "^0.11.
|
|
17
|
-
"@aigne/cli": "^1.
|
|
18
|
-
"@aigne/core": "^1.
|
|
19
|
-
"@aigne/gemini": "^0.
|
|
20
|
-
"@aigne/openai": "^0.
|
|
21
|
-
"@aigne/publish-docs": "^0.
|
|
15
|
+
"@aigne/aigne-hub": "^0.8.1",
|
|
16
|
+
"@aigne/anthropic": "^0.11.12",
|
|
17
|
+
"@aigne/cli": "^1.42.0",
|
|
18
|
+
"@aigne/core": "^1.57.0",
|
|
19
|
+
"@aigne/gemini": "^0.11.1",
|
|
20
|
+
"@aigne/openai": "^0.13.2",
|
|
21
|
+
"@aigne/publish-docs": "^0.8.0",
|
|
22
22
|
"chalk": "^5.5.0",
|
|
23
23
|
"dompurify": "^3.2.6",
|
|
24
|
+
"fs-extra": "^11.3.1",
|
|
24
25
|
"glob": "^11.0.3",
|
|
25
26
|
"jsdom": "^26.1.0",
|
|
26
27
|
"mermaid": "^11.9.0",
|
|
27
28
|
"open": "^10.2.0",
|
|
29
|
+
"p-map": "^7.0.3",
|
|
28
30
|
"remark-gfm": "^4.0.1",
|
|
29
31
|
"remark-lint": "^10.0.1",
|
|
30
32
|
"remark-parse": "^11.0.0",
|
|
@@ -40,10 +42,11 @@
|
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
44
|
"test": "bun test",
|
|
45
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
|
|
43
46
|
"test:watch": "bun test --watch",
|
|
44
|
-
"lint": "biome check
|
|
47
|
+
"lint": "biome check",
|
|
45
48
|
"update:deps": "npx -y taze major -r -w -f -n '/@abtnode|@aigne|@arcblock|@blocklet|@did-connect|@did-pay|@did-space|@nft-store|@nft-studio|@ocap/' && pnpm install && pnpm run deduplicate",
|
|
46
49
|
"deduplicate": "pnpm dedupe",
|
|
47
|
-
"lint:fix": "biome check --write
|
|
50
|
+
"lint:fix": "biome check --write"
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -16,6 +16,11 @@
|
|
|
16
16
|
{{ detailDataSources }}
|
|
17
17
|
|
|
18
18
|
{{ additionalInformation }}
|
|
19
|
+
|
|
20
|
+
<media_list>
|
|
21
|
+
{{ assetsContent }}
|
|
22
|
+
</media_list>
|
|
23
|
+
|
|
19
24
|
</datasources>
|
|
20
25
|
|
|
21
26
|
<terms>
|
|
@@ -93,18 +98,21 @@ parentId: {{parentId}}
|
|
|
93
98
|
- 媒体资源以 markdown 格式提供,示例:
|
|
94
99
|
- 在生成结果中以 markdown 格式展示图片
|
|
95
100
|
- 根据资源描述,在上下文相关的位置,合理的展示图片,让结果展示效果更丰富
|
|
96
|
-
- 只使用完整的远程图片URL(如 https://example.com/image.jpg),禁止使用相对路径(如 ./images/photo.png 或 ../assets/logo.png),确保发布后图片能正常访问
|
|
97
101
|
|
|
98
102
|
</media_rules>
|
|
99
103
|
|
|
100
|
-
{% include "
|
|
104
|
+
{% include "document/detail-generator.md" %}
|
|
105
|
+
|
|
106
|
+
{% include "document/custom-components.md" %}
|
|
107
|
+
|
|
101
108
|
</rules>
|
|
102
109
|
|
|
103
|
-
{% include "
|
|
110
|
+
{% include "document/detail-example.md" %}
|
|
104
111
|
|
|
105
112
|
<output_schema>
|
|
106
113
|
|
|
107
114
|
1. 输内容为{{nodeName}}的详细文本。
|
|
108
115
|
2. 直接输出{{nodeName}}内容,不要包含其他信息.
|
|
109
116
|
3. 仅参考示例中的风格,**以语言 {{locale}} 输出内容 **
|
|
110
|
-
|
|
117
|
+
|
|
118
|
+
</output_schema>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
When generating document details, you can use the following custom components at appropriate locations based on their descriptions and functionality to enhance document presentation:
|
|
2
|
+
- `<x-card>`
|
|
3
|
+
- `<x-cards>`
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 1. <x-card> Single Card Component
|
|
7
|
+
Suitable for displaying individual links with a richer and more visually appealing presentation format.
|
|
8
|
+
|
|
9
|
+
Example:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
<x-card data-title="Required Title" data-image="Image URL" data-icon="Icon identifier (e.g., lucide:rocket or material-symbols:rocket-outline)" data-href="Navigation link URL" data-horizontal="true/false" data-cta="Button text" >
|
|
13
|
+
Card body content
|
|
14
|
+
</x-card>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Attribute Rules:
|
|
18
|
+
|
|
19
|
+
- data-title (required): Card title.
|
|
20
|
+
- data-icon / data-image (choose one, at least one must be provided):
|
|
21
|
+
- It's recommended to always provide data-icon.
|
|
22
|
+
- Icons should prioritize Lucide (lucide:icon-name). If not available in Lucide, use Iconify (collection:icon-name, e.g., material-symbols:rocket-outline).
|
|
23
|
+
- data-image (optional): Image URL, can coexist with icon.
|
|
24
|
+
- data-href (optional): Navigation link for clicking the card or button.
|
|
25
|
+
- data-horizontal (optional): Whether to use horizontal layout.
|
|
26
|
+
- data-cta (optional): Button text (call to action).
|
|
27
|
+
- Body content: Must be written within <x-card>...</x-card> children.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### 2. `<x-cards>` Card List Component
|
|
31
|
+
|
|
32
|
+
Suitable for displaying multiple links using a card list format, providing a richer and more visually appealing presentation.
|
|
33
|
+
|
|
34
|
+
Syntax:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
<x-cards data-columns="Number of columns">
|
|
38
|
+
<x-card data-title="Title 1" data-icon="lucide:rocket">Content 1</x-card>
|
|
39
|
+
<x-card data-title="Title 2" data-icon="lucide:bolt">Content 2</x-card>
|
|
40
|
+
<x-card data-title="Title 3" data-icon="material-symbols:rocket-outline">Content 3</x-card>
|
|
41
|
+
</x-cards>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Attribute Rules:
|
|
45
|
+
- data-columns (optional): Number of columns, integer (e.g., 2, 3). Default is 2.
|
|
46
|
+
- Must contain multiple <x-card> elements internally.
|
|
47
|
+
- Consistency requirement: All <x-card> elements within the same <x-cards> must maintain visual consistency:
|
|
48
|
+
- Recommended to always provide data-icon for each card.
|
|
49
|
+
- Or all cards should have data-image.
|
|
50
|
+
- Avoid mixing (some with icons, some with only images).
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### 3. Examples
|
|
54
|
+
|
|
55
|
+
Single card:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
<x-card data-title="Horizontal card" data-icon="lucide:atom" data-horizontal="true">
|
|
59
|
+
This is an example of a horizontal card.
|
|
60
|
+
</x-card>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Card list (all using icons, recommended approach):
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
<x-cards data-columns="3">
|
|
67
|
+
<x-card data-title="Feature 1" data-icon="lucide:rocket">Description of Feature 1.</x-card>
|
|
68
|
+
<x-card data-title="Feature 2" data-icon="lucide:bolt">Description of Feature 2.</x-card>
|
|
69
|
+
<x-card data-title="Feature 3" data-icon="material-symbols:rocket-outline">Description of Feature 3.</x-card>
|
|
70
|
+
</x-cards>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Card list (all using images):
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
<x-cards data-columns="2">
|
|
77
|
+
<x-card data-title="Card A" data-image="https://picsum.photos/id/10/300/300">Content A</x-card>
|
|
78
|
+
<x-card data-title="Card B" data-image="https://picsum.photos/id/11/300/300">Content B</x-card>
|
|
79
|
+
</x-cards>
|
|
80
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
- 结构图示例:
|
|
2
|
+
```d2
|
|
3
|
+
App: Application
|
|
4
|
+
API: API Server
|
|
5
|
+
DB: Database
|
|
6
|
+
|
|
7
|
+
App -> API: 调用
|
|
8
|
+
API -> DB: 读写数据
|
|
9
|
+
```
|
|
10
|
+
- 流程图示例:
|
|
11
|
+
```d2
|
|
12
|
+
start: 开始
|
|
13
|
+
input: 用户输入
|
|
14
|
+
process: 处理数据
|
|
15
|
+
output: 输出结果
|
|
16
|
+
end: 结束
|
|
17
|
+
|
|
18
|
+
start -> input -> process -> output -> end
|
|
19
|
+
```
|
|
20
|
+
- 时序图示例:
|
|
21
|
+
```d2
|
|
22
|
+
User: 用户
|
|
23
|
+
Service: 服务
|
|
24
|
+
DB: 数据库
|
|
25
|
+
|
|
26
|
+
User -> Service: 请求
|
|
27
|
+
Service -> DB: 查询
|
|
28
|
+
DB -> Service: 返回数据
|
|
29
|
+
Service -> User: 响应
|
|
30
|
+
```
|
|
31
|
+
- 决策树示例:
|
|
32
|
+
```d2
|
|
33
|
+
start: 开始
|
|
34
|
+
check: 是否有效?
|
|
35
|
+
yes: 是
|
|
36
|
+
no: 否
|
|
37
|
+
end: 结束
|
|
38
|
+
|
|
39
|
+
start -> check
|
|
40
|
+
check -> yes: 有效
|
|
41
|
+
check -> no: 无效
|
|
42
|
+
yes -> end
|
|
43
|
+
no -> end
|
|
44
|
+
```
|