docnest 0.1.6 → 0.1.8
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/README.md +1 -0
- package/dist/attachments.d.ts +6 -0
- package/dist/attachments.js +36 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,6 +90,7 @@ startServer(3000)
|
|
|
90
90
|
`collectDocumentAttachmentPaths` 和 `copyDocumentAttachments`。它们只复制
|
|
91
91
|
Markdown 或 HTML 中明确引用的本地附件,不按文件扩展名设置业务白名单;外链、文档链接、隐藏路径、路径穿越和代码块中的伪引用会被忽略,缺失的附件默认会让构建失败。
|
|
92
92
|
常见凭据文件(如 `.env`、私钥和证书容器)属于公共安全阻断项,即使被误写进文档链接也不会进入静态产物。
|
|
93
|
+
静态生成 HTML 时,可再调用 `rewriteDocumentAttachmentUrls`,将正文中的附件链接统一改写到 `/doc-asset/` 端点。
|
|
93
94
|
|
|
94
95
|
## 开发
|
|
95
96
|
|
package/dist/attachments.d.ts
CHANGED
|
@@ -5,7 +5,13 @@ export type DocumentAttachmentOptions = {
|
|
|
5
5
|
*/
|
|
6
6
|
failOnMissing?: boolean;
|
|
7
7
|
};
|
|
8
|
+
export type DocumentAttachmentUrlOptions = {
|
|
9
|
+
assetPaths?: readonly string[];
|
|
10
|
+
assetPrefix?: string;
|
|
11
|
+
};
|
|
8
12
|
/** Collect every existing, explicitly referenced local document attachment. */
|
|
9
13
|
export declare function collectDocumentAttachmentPaths(docsRoot: string, options?: DocumentAttachmentOptions): string[];
|
|
10
14
|
/** Copy collected attachments while preserving their paths relative to docsRoot. */
|
|
11
15
|
export declare function copyDocumentAttachments(sourceRoot: string, destinationRoot: string, options?: DocumentAttachmentOptions): string[];
|
|
16
|
+
/** Rewrite rendered local attachment links to the static document asset endpoint. */
|
|
17
|
+
export declare function rewriteDocumentAttachmentUrls(html: string, documentPath: string, options?: DocumentAttachmentUrlOptions): string;
|
package/dist/attachments.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.collectDocumentAttachmentPaths = collectDocumentAttachmentPaths;
|
|
7
7
|
exports.copyDocumentAttachments = copyDocumentAttachments;
|
|
8
|
+
exports.rewriteDocumentAttachmentUrls = rewriteDocumentAttachmentUrls;
|
|
8
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
11
|
const MARKDOWN_SOURCE_EXTENSIONS = new Set(['.md', '.markdown']);
|
|
@@ -85,6 +86,8 @@ function resolveAttachmentPath(docsRoot, realDocsRoot, markdownPath, rawTarget,
|
|
|
85
86
|
}
|
|
86
87
|
if (!decodedTarget)
|
|
87
88
|
return null;
|
|
89
|
+
if (decodedTarget.endsWith('/'))
|
|
90
|
+
return null;
|
|
88
91
|
const extension = node_path_1.default.extname(decodedTarget).toLowerCase();
|
|
89
92
|
if (MARKDOWN_SOURCE_EXTENSIONS.has(extension))
|
|
90
93
|
return null;
|
|
@@ -146,3 +149,36 @@ function copyDocumentAttachments(sourceRoot, destinationRoot, options = {}) {
|
|
|
146
149
|
}
|
|
147
150
|
return attachmentPaths;
|
|
148
151
|
}
|
|
152
|
+
function encodeAssetPath(assetPath) {
|
|
153
|
+
return assetPath.split('/').map((segment) => encodeURIComponent(segment)).join('/');
|
|
154
|
+
}
|
|
155
|
+
/** Rewrite rendered local attachment links to the static document asset endpoint. */
|
|
156
|
+
function rewriteDocumentAttachmentUrls(html, documentPath, options = {}) {
|
|
157
|
+
const assetPrefix = (options.assetPrefix || '/doc-asset').replace(/\/+$/, '');
|
|
158
|
+
const assetPaths = options.assetPaths ? new Set(options.assetPaths) : null;
|
|
159
|
+
const documentDir = node_path_1.default.posix.dirname(documentPath.replace(/\\/g, '/'));
|
|
160
|
+
const attributePattern = /(<(?:a|audio|embed|iframe|img|object|source|video)\b[^>]*\s(?:href|src)=["'])([^"']+)(["'])/gi;
|
|
161
|
+
return html.replace(attributePattern, (match, prefix, rawTarget, suffix) => {
|
|
162
|
+
if (!rawTarget || /^(?:[a-z][a-z0-9+.-]*:|\/\/|#|\/|data:)/i.test(rawTarget))
|
|
163
|
+
return match;
|
|
164
|
+
const targetWithoutSuffix = stripTargetSuffix(rawTarget);
|
|
165
|
+
let decodedTarget;
|
|
166
|
+
try {
|
|
167
|
+
decodedTarget = decodeURIComponent(targetWithoutSuffix);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return match;
|
|
171
|
+
}
|
|
172
|
+
if (!decodedTarget || MARKDOWN_SOURCE_EXTENSIONS.has(node_path_1.default.extname(decodedTarget).toLowerCase())) {
|
|
173
|
+
return match;
|
|
174
|
+
}
|
|
175
|
+
const resolved = node_path_1.default.posix.normalize(node_path_1.default.posix.join(documentDir, decodedTarget));
|
|
176
|
+
if (!resolved || resolved === '..' || resolved.startsWith('../') || resolved.startsWith('.'))
|
|
177
|
+
return match;
|
|
178
|
+
if (assetPaths && !assetPaths.has(resolved))
|
|
179
|
+
return match;
|
|
180
|
+
const suffixStart = targetWithoutSuffix.length;
|
|
181
|
+
const queryAndHash = rawTarget.slice(suffixStart);
|
|
182
|
+
return `${prefix}${assetPrefix}/${encodeAssetPath(resolved)}${queryAndHash}${suffix}`;
|
|
183
|
+
});
|
|
184
|
+
}
|