zenn-markdown-html 0.1.126-alpha.3 → 0.1.126
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/lib/utils/embed-helper.d.ts +10 -20
- package/lib/utils/embed-helper.js +111 -41
- package/lib/utils/md-renderer-fence.js +2 -2
- package/package.json +2 -2
- package/lib/utils/helper.d.ts +0 -6
- package/lib/utils/helper.js +0 -53
|
@@ -1,26 +1,16 @@
|
|
|
1
|
-
export declare type EmbedType =
|
|
2
|
-
/**
|
|
3
|
-
declare
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
codepen(str: string): string;
|
|
9
|
-
codesandbox(str: string): string;
|
|
10
|
-
stackblitz(str: string): string;
|
|
11
|
-
tweet(str: string): string;
|
|
12
|
-
blueprintue(str: string): string;
|
|
13
|
-
figma(str: string): string;
|
|
14
|
-
card(str: string): string;
|
|
15
|
-
gist(str: string): string;
|
|
16
|
-
github(str: string): string;
|
|
1
|
+
export declare type EmbedType = 'youtube' | 'slideshare' | 'speakerdeck' | 'jsfiddle' | 'codepen' | 'codesandbox' | 'stackblitz' | 'tweet' | 'blueprintue' | 'figma' | 'card' | 'gist' | 'github';
|
|
2
|
+
/** embedサーバーで表示する埋め込み要素の種別 */
|
|
3
|
+
export declare type ZennEmbedTypes = 'tweet' | 'link-card' | 'mermaid' | 'github' | 'gist';
|
|
4
|
+
/** 渡された埋め込みURLまたはTokenを検証する */
|
|
5
|
+
export declare const validateEmbedToken: (str: string, type?: "youtube" | "slideshare" | "speakerdeck" | "jsfiddle" | "codepen" | "codesandbox" | "stackblitz" | "tweet" | "blueprintue" | "figma" | "card" | "gist" | "github" | "link-card" | "mermaid" | undefined) => {
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
message: string;
|
|
17
8
|
};
|
|
9
|
+
/** Embedサーバーを使った埋め込み要素の文字列を生成する */
|
|
10
|
+
export declare function generateEmbedIframe(type: ZennEmbedTypes, src: string): string;
|
|
18
11
|
/** `EmbedType`か判定する */
|
|
19
12
|
export declare function isEmbedType(type: unknown): type is EmbedType;
|
|
20
|
-
/** 渡された埋め込みURLを検証する */
|
|
21
|
-
export declare const validateEbedUrl: (url: string) => boolean;
|
|
22
13
|
/** 渡された`type`の埋め込み要素のHTML文字列を返す */
|
|
23
|
-
export declare const generateEmbedHTML: (type: EmbedType,
|
|
14
|
+
export declare const generateEmbedHTML: (type: EmbedType, str: string) => string;
|
|
24
15
|
/** Linkifyな埋め込み要素のHTML生成する */
|
|
25
16
|
export declare const generateLinkifyEmbedHTML: (url: string) => string;
|
|
26
|
-
export {};
|
|
@@ -3,27 +3,105 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.generateEmbedHTML = void 0;
|
|
7
|
+
exports.generateEmbedIframe = generateEmbedIframe;
|
|
8
|
+
exports.generateLinkifyEmbedHTML = void 0;
|
|
7
9
|
exports.isEmbedType = isEmbedType;
|
|
8
|
-
exports.
|
|
10
|
+
exports.validateEmbedToken = void 0;
|
|
9
11
|
|
|
10
12
|
var _utils = require("markdown-it/lib/common/utils");
|
|
11
13
|
|
|
12
|
-
var _helper = require("./helper");
|
|
13
|
-
|
|
14
14
|
var _urlMatcher = require("./url-matcher");
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
const
|
|
16
|
+
/** 検証から除外する埋め込みの種別 */
|
|
17
|
+
const ignoredEmbedType = ['card', 'link-card', 'github'];
|
|
18
|
+
/** 埋め込みURLまたはTokenの最大文字数( excludeEmbedTypeは除く ) */
|
|
19
|
+
|
|
20
|
+
const MAX_EMBED_TOKEN_LENGTH = 300;
|
|
21
|
+
/** 渡された埋め込みURLまたはTokenを検証する */
|
|
22
|
+
|
|
23
|
+
const validateEmbedToken = (str, type) => {
|
|
24
|
+
if (type && ignoredEmbedType.includes(type)) {
|
|
25
|
+
return {
|
|
26
|
+
isValid: true,
|
|
27
|
+
message: ''
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (str.length > MAX_EMBED_TOKEN_LENGTH) {
|
|
32
|
+
return {
|
|
33
|
+
isValid: false,
|
|
34
|
+
message: `埋め込みURLは${MAX_EMBED_TOKEN_LENGTH}文字以内にする必要があります`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
isValid: true,
|
|
40
|
+
message: ''
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/** 渡された文字列をサニタイズする */
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
exports.validateEmbedToken = validateEmbedToken;
|
|
47
|
+
|
|
48
|
+
function sanitaizeEmbedToken(str) {
|
|
49
|
+
return str.replace(/"/g, '%22');
|
|
50
|
+
}
|
|
51
|
+
/** URL文字列か判定する */
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
function isValidHttpUrl(str) {
|
|
55
|
+
try {
|
|
56
|
+
const url = new URL(str);
|
|
57
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
58
|
+
} catch (_) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** `videoId`から Youtube の埋め込み要素の文字列を生成する */
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
function generateYoutubeHtmlFromVideoId(videoId, start) {
|
|
66
|
+
const escapedVideoId = (0, _utils.escapeHtml)(videoId);
|
|
67
|
+
const time = Math.min(Number(start || 0), 48 * 60 * 60); // 48時間以内
|
|
68
|
+
|
|
69
|
+
const startQuery = time ? `&start=${time}` : '';
|
|
70
|
+
return `<div class="embed-youtube"><iframe src="https://www.youtube.com/embed/${escapedVideoId}?loop=1&playlist=${escapedVideoId}${startQuery}" allowfullscreen loading="lazy"></iframe></div>`;
|
|
71
|
+
}
|
|
72
|
+
/** Youtube の埋め込み要素の文字列を生成する */
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
function generateYoutubeHtmlFromUrl(url) {
|
|
76
|
+
const params = (0, _urlMatcher.extractYoutubeVideoParameters)(url);
|
|
77
|
+
|
|
78
|
+
if (!params) {
|
|
79
|
+
return generateEmbedIframe('link-card', url);
|
|
80
|
+
} else {
|
|
81
|
+
return generateYoutubeHtmlFromVideoId(params.videoId, params.start);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Embedサーバーを使った埋め込み要素の文字列を生成する */
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function generateEmbedIframe(type, src) {
|
|
88
|
+
// ユーザーからの入力値が引数として渡されたときのために念のためencodeする
|
|
89
|
+
const encodedType = encodeURIComponent(type);
|
|
90
|
+
const encodedSrc = encodeURIComponent(src);
|
|
91
|
+
const id = `zenn-embedded__${Math.random().toString(16).slice(2)}`;
|
|
92
|
+
const iframeSrc = `https://embed.zenn.studio/${encodedType}#${id}`;
|
|
93
|
+
return `<div class="zenn-embedded zenn-embedded-${encodedType}"><iframe id="${id}" src="${iframeSrc}" data-content="${encodedSrc}" frameborder="0" scrolling="no" loading="lazy"></iframe></div>`;
|
|
94
|
+
}
|
|
18
95
|
/** 渡された文字列から埋め込み要素のHTMLを生成する関数をまとめたオブジェクト */
|
|
19
96
|
|
|
97
|
+
|
|
20
98
|
const embedGenerators = {
|
|
21
99
|
youtube(videoId) {
|
|
22
100
|
if (!(videoId !== null && videoId !== void 0 && videoId.match(/^[a-zA-Z0-9_-]+$/))) {
|
|
23
101
|
return 'YouTubeのvideoIDが不正です';
|
|
24
102
|
}
|
|
25
103
|
|
|
26
|
-
return
|
|
104
|
+
return generateYoutubeHtmlFromVideoId(videoId);
|
|
27
105
|
},
|
|
28
106
|
|
|
29
107
|
slideshare(key) {
|
|
@@ -55,7 +133,7 @@ const embedGenerators = {
|
|
|
55
133
|
url = url.endsWith('/') ? `${url}embedded/` : `${url}/embedded/`;
|
|
56
134
|
}
|
|
57
135
|
|
|
58
|
-
return `<div class="embed-jsfiddle"><iframe src="${
|
|
136
|
+
return `<div class="embed-jsfiddle"><iframe src="${sanitaizeEmbedToken(url)}" scrolling="no" frameborder="no" loading="lazy"></iframe></div>`;
|
|
59
137
|
},
|
|
60
138
|
|
|
61
139
|
codepen(str) {
|
|
@@ -65,7 +143,7 @@ const embedGenerators = {
|
|
|
65
143
|
|
|
66
144
|
const url = new URL(str.replace('/pen/', '/embed/'));
|
|
67
145
|
url.searchParams.set('embed-version', '2');
|
|
68
|
-
return `<div class="embed-codepen"><iframe src="${
|
|
146
|
+
return `<div class="embed-codepen"><iframe src="${sanitaizeEmbedToken(url.toString())}" scrolling="no" frameborder="no" loading="lazy"></iframe></div>`;
|
|
69
147
|
},
|
|
70
148
|
|
|
71
149
|
codesandbox(str) {
|
|
@@ -73,7 +151,7 @@ const embedGenerators = {
|
|
|
73
151
|
return '「https://codesandbox.io/embed/」から始まる正しいURLを入力してください';
|
|
74
152
|
}
|
|
75
153
|
|
|
76
|
-
return `<div class="embed-codesandbox"><iframe src="${
|
|
154
|
+
return `<div class="embed-codesandbox"><iframe src="${sanitaizeEmbedToken(str)}" style="width:100%;height:500px;border:none;overflow:hidden;" allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" loading="lazy" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"></iframe></div>`;
|
|
77
155
|
},
|
|
78
156
|
|
|
79
157
|
stackblitz(str) {
|
|
@@ -81,27 +159,27 @@ const embedGenerators = {
|
|
|
81
159
|
return 'StackBlitzのembed用のURLを指定してください';
|
|
82
160
|
}
|
|
83
161
|
|
|
84
|
-
return `<div class="embed-stackblitz"><iframe src="${
|
|
162
|
+
return `<div class="embed-stackblitz"><iframe src="${sanitaizeEmbedToken(str)}" scrolling="no" frameborder="no" loading="lazy"></iframe></div>`;
|
|
85
163
|
},
|
|
86
164
|
|
|
87
165
|
tweet(str) {
|
|
88
166
|
if (!(0, _urlMatcher.isTweetUrl)(str)) return 'ツイートページのURLを指定してください';
|
|
89
|
-
return
|
|
167
|
+
return generateEmbedIframe('tweet', str);
|
|
90
168
|
},
|
|
91
169
|
|
|
92
170
|
blueprintue(str) {
|
|
93
171
|
if (!(0, _urlMatcher.isBlueprintUEUrl)(str)) return '「https://blueprintue.com/render/」から始まる正しいURLを指定してください';
|
|
94
|
-
return `<div class="embed-blueprintue"><iframe src="${
|
|
172
|
+
return `<div class="embed-blueprintue"><iframe src="${sanitaizeEmbedToken(str)}" width="100%" style="aspect-ratio: 16/9" scrolling="no" frameborder="no" loading="lazy" allowfullscreen></iframe></div>`;
|
|
95
173
|
},
|
|
96
174
|
|
|
97
175
|
figma(str) {
|
|
98
176
|
if (!(0, _urlMatcher.isFigmaUrl)(str)) return 'ファイルまたはプロトタイプのFigma URLを指定してください';
|
|
99
|
-
return `<div class="embed-figma"><iframe src="https://www.figma.com/embed?embed_host=zenn&url=${
|
|
177
|
+
return `<div class="embed-figma"><iframe src="https://www.figma.com/embed?embed_host=zenn&url=${sanitaizeEmbedToken(str)}" width="100%" style="aspect-ratio: 16/9" scrolling="no" frameborder="no" loading="lazy" allowfullscreen></iframe></div>`;
|
|
100
178
|
},
|
|
101
179
|
|
|
102
180
|
card(str) {
|
|
103
|
-
if (!
|
|
104
|
-
return
|
|
181
|
+
if (!isValidHttpUrl(str)) return 'URLが不正です';
|
|
182
|
+
return generateEmbedIframe('link-card', str);
|
|
105
183
|
},
|
|
106
184
|
|
|
107
185
|
gist(str) {
|
|
@@ -113,42 +191,29 @@ const embedGenerators = {
|
|
|
113
191
|
* のような形式
|
|
114
192
|
*/
|
|
115
193
|
|
|
116
|
-
return
|
|
194
|
+
return generateEmbedIframe('gist', str);
|
|
117
195
|
},
|
|
118
196
|
|
|
119
197
|
github(str) {
|
|
120
198
|
if (!(0, _urlMatcher.isGithubUrl)(str)) return 'GitHub のファイルURLまたはパーマリンクを指定してください';
|
|
121
|
-
return
|
|
199
|
+
return generateEmbedIframe('github', str);
|
|
122
200
|
}
|
|
123
201
|
|
|
124
202
|
};
|
|
125
|
-
|
|
126
|
-
function encodeDoubleQuote(str) {
|
|
127
|
-
return str.replace(/"/g, '%22');
|
|
128
|
-
}
|
|
129
203
|
/** `EmbedType`か判定する */
|
|
130
204
|
|
|
131
|
-
|
|
132
205
|
function isEmbedType(type) {
|
|
133
206
|
return typeof type === 'string' && type in embedGenerators;
|
|
134
207
|
}
|
|
135
|
-
/** 渡された埋め込みURLを検証する */
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const validateEbedUrl = url => {
|
|
139
|
-
return url.length <= MAX_EMBED_URL_LENGTH;
|
|
140
|
-
};
|
|
141
208
|
/** 渡された`type`の埋め込み要素のHTML文字列を返す */
|
|
142
209
|
|
|
143
210
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
return embedGenerators[type](url);
|
|
211
|
+
const generateEmbedHTML = (type, str) => {
|
|
212
|
+
const {
|
|
213
|
+
isValid,
|
|
214
|
+
message
|
|
215
|
+
} = validateEmbedToken(str, type);
|
|
216
|
+
return isValid ? embedGenerators[type](str) : message;
|
|
152
217
|
};
|
|
153
218
|
/** Linkifyな埋め込み要素のHTML生成する */
|
|
154
219
|
|
|
@@ -156,10 +221,15 @@ const generateEmbedHTML = (type, url) => {
|
|
|
156
221
|
exports.generateEmbedHTML = generateEmbedHTML;
|
|
157
222
|
|
|
158
223
|
const generateLinkifyEmbedHTML = url => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
const {
|
|
225
|
+
isValid,
|
|
226
|
+
message: msg
|
|
227
|
+
} = validateEmbedToken(url);
|
|
228
|
+
if ((0, _urlMatcher.isTweetUrl)(url)) return isValid ? generateEmbedIframe('tweet', url) : msg;
|
|
229
|
+
if ((0, _urlMatcher.isYoutubeUrl)(url)) return isValid ? generateYoutubeHtmlFromUrl(url) : msg; // GitHub は URL が長くなりやすいためバリデーション(`validateEmbedToken`)から外す
|
|
230
|
+
|
|
231
|
+
if ((0, _urlMatcher.isGithubUrl)(url)) return generateEmbedIframe('github', url);
|
|
232
|
+
return generateEmbedIframe('link-card', url);
|
|
163
233
|
};
|
|
164
234
|
|
|
165
235
|
exports.generateLinkifyEmbedHTML = generateLinkifyEmbedHTML;
|
|
@@ -8,7 +8,7 @@ exports.parseInfo = parseInfo;
|
|
|
8
8
|
|
|
9
9
|
var _utils = require("markdown-it/lib/common/utils");
|
|
10
10
|
|
|
11
|
-
var
|
|
11
|
+
var _embedHelper = require("./embed-helper");
|
|
12
12
|
|
|
13
13
|
var _highlight = require("./highlight");
|
|
14
14
|
|
|
@@ -88,7 +88,7 @@ function mdRendererFence(md) {
|
|
|
88
88
|
} = parseInfo(info);
|
|
89
89
|
|
|
90
90
|
if (langName === 'mermaid') {
|
|
91
|
-
return (0,
|
|
91
|
+
return (0, _embedHelper.generateEmbedIframe)('mermaid', content.trim());
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const className = getClassName({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zenn-markdown-html",
|
|
3
|
-
"version": "0.1.126
|
|
3
|
+
"version": "0.1.126",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Convert markdown to zenn flavor html.",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"markdown-it-task-lists": "^2.1.1",
|
|
54
54
|
"prismjs": "^1.27.0"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "2068f91c365dff20abbfc8de7eed6580a81bdbc0",
|
|
57
57
|
"publishConfig": {
|
|
58
58
|
"access": "public"
|
|
59
59
|
}
|
package/lib/utils/helper.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare function generateYoutubeHtmlFromUrl(url: string): string;
|
|
2
|
-
export declare function generateYoutubeHtmlFromVideoId(videoId: string): string;
|
|
3
|
-
export declare function isValidHttpUrl(str: string): boolean;
|
|
4
|
-
declare type ZennEmbedTypes = 'tweet' | 'link-card' | 'mermaid' | 'github' | 'gist';
|
|
5
|
-
export declare function generateEmbedIframe(type: ZennEmbedTypes, src: string): string;
|
|
6
|
-
export {};
|
package/lib/utils/helper.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.generateEmbedIframe = generateEmbedIframe;
|
|
7
|
-
exports.generateYoutubeHtmlFromUrl = generateYoutubeHtmlFromUrl;
|
|
8
|
-
exports.generateYoutubeHtmlFromVideoId = generateYoutubeHtmlFromVideoId;
|
|
9
|
-
exports.isValidHttpUrl = isValidHttpUrl;
|
|
10
|
-
|
|
11
|
-
var _utils = require("markdown-it/lib/common/utils");
|
|
12
|
-
|
|
13
|
-
var _urlMatcher = require("./url-matcher");
|
|
14
|
-
|
|
15
|
-
function generateYoutubeHtml(videoId, start) {
|
|
16
|
-
const escapedVideoId = (0, _utils.escapeHtml)(videoId); // 48時間以内
|
|
17
|
-
|
|
18
|
-
const time = Math.min(Number(start || 0), 48 * 60 * 60);
|
|
19
|
-
const startQuery = time ? `&start=${time}` : '';
|
|
20
|
-
return `<div class="embed-youtube"><iframe src="https://www.youtube.com/embed/${escapedVideoId}?loop=1&playlist=${escapedVideoId}${startQuery}" allowfullscreen loading="lazy"></iframe></div>`;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function generateYoutubeHtmlFromUrl(url) {
|
|
24
|
-
const params = (0, _urlMatcher.extractYoutubeVideoParameters)(url);
|
|
25
|
-
|
|
26
|
-
if (!params) {
|
|
27
|
-
return generateEmbedIframe('link-card', url);
|
|
28
|
-
} else {
|
|
29
|
-
return generateYoutubeHtml(params.videoId, params.start);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function generateYoutubeHtmlFromVideoId(videoId) {
|
|
34
|
-
return generateYoutubeHtml(videoId);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isValidHttpUrl(str) {
|
|
38
|
-
try {
|
|
39
|
-
const url = new URL(str);
|
|
40
|
-
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
41
|
-
} catch (_) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function generateEmbedIframe(type, src) {
|
|
47
|
-
// ユーザーからの入力値が引数として渡されたときのために念のためencodeする
|
|
48
|
-
const encodedType = encodeURIComponent(type);
|
|
49
|
-
const encodedSrc = encodeURIComponent(src);
|
|
50
|
-
const id = `zenn-embedded__${Math.random().toString(16).slice(2)}`;
|
|
51
|
-
const iframeSrc = `https://embed.zenn.studio/${encodedType}#${id}`;
|
|
52
|
-
return `<div class="zenn-embedded zenn-embedded-${encodedType}"><iframe id="${id}" src="${iframeSrc}" data-content="${encodedSrc}" frameborder="0" scrolling="no" loading="lazy"></iframe></div>`;
|
|
53
|
-
}
|