@onozaty/growi-uploader 1.2.0 → 1.4.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/README.md +16 -0
- package/dist/index.mjs +48 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# growi-uploader
|
|
2
2
|
|
|
3
|
+
[](https://github.com/onozaty/growi-uploader/actions/workflows/test.yaml)
|
|
4
|
+
[](https://codecov.io/gh/onozaty/growi-uploader)
|
|
3
5
|
[](https://www.npmjs.com/package/@onozaty/growi-uploader)
|
|
4
6
|
[](https://opensource.org/licenses/MIT)
|
|
5
7
|
|
|
@@ -178,10 +180,24 @@ images/
|
|
|
178
180
|
All referenced files (`logo.png`, `screenshot.png`, and `banner.png`) will be uploaded as attachments to the `/guide` page, even though they don't follow the `_attachment_` naming convention.
|
|
179
181
|
|
|
180
182
|
**Path resolution:**
|
|
183
|
+
- Markdown escape sequences are unescaped (`\(` → `(`)
|
|
184
|
+
- URL encoding (percent-encoding) is decoded (`%20` → space, `%E7%94%BB%E5%83%8F` → `画像`)
|
|
181
185
|
- Relative paths (`./`, `../`, or no prefix): Resolved from the Markdown file's directory
|
|
182
186
|
- Absolute paths (starting with `/`): Resolved from the source directory root
|
|
183
187
|
- Example: `/assets/banner.png` → `<source-dir>/assets/banner.png`
|
|
184
188
|
|
|
189
|
+
**Supported link formats:**
|
|
190
|
+
```markdown
|
|
191
|
+
 # Standard relative path
|
|
192
|
+
 # Relative path without ./
|
|
193
|
+
 # URL-encoded Japanese filename
|
|
194
|
+
[File](./docs/my%20file.pdf) # URL-encoded space
|
|
195
|
+
[File](<./path/file (1).png>) # Special chars with angle brackets
|
|
196
|
+
.png) # Special chars with escaping
|
|
197
|
+
<img src="./images/logo.png" alt="Logo"> # HTML img tag (double quotes)
|
|
198
|
+
<img src='./images/logo.png' alt='Logo'> # HTML img tag (single quotes)
|
|
199
|
+
```
|
|
200
|
+
|
|
185
201
|
**Excluded from detection:**
|
|
186
202
|
- `.md` files (treated as page links)
|
|
187
203
|
- External URLs (`http://`, `https://`)
|
package/dist/index.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { lookup } from "mime-types";
|
|
|
8
8
|
import { glob } from "glob";
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
|
-
var version = "1.
|
|
11
|
+
var version = "1.4.0";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/config.ts
|
|
@@ -215,6 +215,37 @@ const uploadAttachment = async (attachment, pageId, sourceDir) => {
|
|
|
215
215
|
//#endregion
|
|
216
216
|
//#region src/scanner.ts
|
|
217
217
|
/**
|
|
218
|
+
* Process a link path and convert it to an AttachmentFile if it exists
|
|
219
|
+
*
|
|
220
|
+
* @param linkPath The link path from markdown
|
|
221
|
+
* @param originalLinkPath The original format of the link (for replacement)
|
|
222
|
+
* @param markdownFilePath Path to the markdown file (relative to sourceDir)
|
|
223
|
+
* @param sourceDir Source directory (absolute path)
|
|
224
|
+
* @returns AttachmentFile if the path resolves to an existing file, null otherwise
|
|
225
|
+
*/
|
|
226
|
+
const processLinkPath = (linkPath, originalLinkPath, markdownFilePath, sourceDir) => {
|
|
227
|
+
if (linkPath.startsWith("http://") || linkPath.startsWith("https://")) return null;
|
|
228
|
+
if (linkPath.endsWith(".md")) return null;
|
|
229
|
+
const unescapedPath = linkPath.replace(/\\([\\`*_{}[\]()#+\-.!])/g, "$1");
|
|
230
|
+
let decodedPath;
|
|
231
|
+
try {
|
|
232
|
+
decodedPath = decodeURIComponent(unescapedPath);
|
|
233
|
+
} catch {
|
|
234
|
+
decodedPath = unescapedPath;
|
|
235
|
+
}
|
|
236
|
+
let absolutePath;
|
|
237
|
+
if (linkPath.startsWith("/")) absolutePath = resolve(sourceDir, decodedPath.slice(1));
|
|
238
|
+
else absolutePath = resolve(dirname(join(sourceDir, markdownFilePath)), decodedPath);
|
|
239
|
+
if (!existsSync(absolutePath)) return null;
|
|
240
|
+
const normalizedPath = relative(sourceDir, absolutePath).replace(/\\/g, "/");
|
|
241
|
+
return {
|
|
242
|
+
localPath: normalizedPath,
|
|
243
|
+
fileName: basename(normalizedPath),
|
|
244
|
+
detectionPattern: "link",
|
|
245
|
+
originalLinkPaths: [originalLinkPath]
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
218
249
|
* Extract attachment files from markdown links
|
|
219
250
|
*
|
|
220
251
|
* Scans markdown content for image and link references, resolves their paths,
|
|
@@ -243,21 +274,15 @@ const extractLinkedAttachments = (content, markdownFilePath, sourceDir) => {
|
|
|
243
274
|
const { anglePath, regularPath } = match.groups;
|
|
244
275
|
const linkPath = anglePath || regularPath;
|
|
245
276
|
if (!linkPath) continue;
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
attachments.push({
|
|
256
|
-
localPath: normalizedPath,
|
|
257
|
-
fileName: basename(normalizedPath),
|
|
258
|
-
detectionPattern: "link",
|
|
259
|
-
originalLinkPaths: [originalLinkPath]
|
|
260
|
-
});
|
|
277
|
+
const attachment = processLinkPath(linkPath, anglePath ? `<${anglePath}>` : regularPath, markdownFilePath, sourceDir);
|
|
278
|
+
if (attachment) attachments.push(attachment);
|
|
279
|
+
}
|
|
280
|
+
const imgTagRegex = /<img\s+[^>]*src=(?<quote>["'])(?<src>.*?)\k<quote>[^>]*>/gi;
|
|
281
|
+
while ((match = imgTagRegex.exec(content)) !== null) {
|
|
282
|
+
const { src } = match.groups;
|
|
283
|
+
if (!src) continue;
|
|
284
|
+
const attachment = processLinkPath(src, src, markdownFilePath, sourceDir);
|
|
285
|
+
if (attachment) attachments.push(attachment);
|
|
261
286
|
}
|
|
262
287
|
return attachments;
|
|
263
288
|
};
|
|
@@ -343,13 +368,13 @@ const replaceAttachmentLinks = (markdown, attachments, pageName) => {
|
|
|
343
368
|
const escapedFileName = escapeRegex(`${pageName}_attachment_${attachment.fileName}`);
|
|
344
369
|
patterns.push(escapedFileName, `\\./${escapedFileName}`);
|
|
345
370
|
}
|
|
346
|
-
if (attachment.
|
|
371
|
+
if (attachment.originalLinkPaths) for (const linkPath of attachment.originalLinkPaths) {
|
|
347
372
|
const escapedPath = escapeRegex(linkPath);
|
|
348
373
|
patterns.push(escapedPath);
|
|
349
374
|
if (linkPath.startsWith("./")) {
|
|
350
375
|
const escapedWithoutDot = escapeRegex(linkPath.substring(2));
|
|
351
376
|
patterns.push(escapedWithoutDot);
|
|
352
|
-
} else if (!linkPath.startsWith("../")) patterns.push(`\\./${escapedPath}`);
|
|
377
|
+
} else if (!linkPath.startsWith("../") && !linkPath.startsWith("<")) patterns.push(`\\./${escapedPath}`);
|
|
353
378
|
}
|
|
354
379
|
for (const pattern of patterns) {
|
|
355
380
|
const imgRegex = new RegExp(`!\\[([^\\]]*)\\]\\(${pattern}\\)`, "g");
|
|
@@ -362,6 +387,11 @@ const replaceAttachmentLinks = (markdown, attachments, pageName) => {
|
|
|
362
387
|
replaced = true;
|
|
363
388
|
result = result.replace(linkRegex, `[$1](${growiPath})`);
|
|
364
389
|
}
|
|
390
|
+
const imgTagRegex = new RegExp(`(<img\\s+[^>]*src=)(["'])${pattern}\\2([^>]*>)`, "gi");
|
|
391
|
+
if (imgTagRegex.test(result)) {
|
|
392
|
+
replaced = true;
|
|
393
|
+
result = result.replace(imgTagRegex, `$1$2${growiPath}$2$3`);
|
|
394
|
+
}
|
|
365
395
|
}
|
|
366
396
|
}
|
|
367
397
|
return {
|