@@ -8,6 +8,11 @@ const SUB_PAGE_LIST_PATTERN =
88const SUB_PAGE_ENTRY_PATTERN = / < s u b - p a g e \b [ ^ > ] * \/ \s * > / gi
99const CITE_PATTERN = / < c i t e \b ( [ ^ > ] * ) > \s * < \/ c i t e > / gi
1010const TABLE_PATTERN = / < t a b l e \b [ ^ > ] * > [ \s \S ] * ?< \/ t a b l e > / gi
11+ const MARKDOWN_IMAGE_PATTERN =
12+ / ! \[ ( [ ^ \] \n ] * ) \] \( ( h t t p s : \/ \/ [ ^ ) \s ] + ) \) / g
13+ const FEISHU_MEDIA_HOSTS = new Set ( [
14+ 'internal-api-drive-stream.feishu.cn'
15+ ] )
1116const TABLE_TAG_PATTERN =
1217 / < \s * ( \/ ? ) \s * ( [ A - Z a - z ] [ A - Z a - z 0 - 9 _ - ] * ) (? = [ \s / > ] ) ( [ ^ > ] * ) > / g
1318const TABLE_TAGS = new Set ( [
@@ -65,6 +70,10 @@ export async function normalizeFeishuMarkdown(
6570 context,
6671 downloadAsset
6772 } )
73+ markdown = await replaceHostedFeishuImages ( markdown , {
74+ context,
75+ downloadAsset
76+ } )
6877 const protectedTables = protectSafeTables ( markdown , context )
6978 markdown = protectedTables . markdown
7079 markdown = convertSupportedXml ( markdown , { context, renderSubPageList } )
@@ -143,6 +152,46 @@ async function replaceMediaTags(markdown, options) {
143152 return output + markdown . slice ( cursor )
144153}
145154
155+ async function replaceHostedFeishuImages ( markdown , options ) {
156+ const matches = [ ...markdown . matchAll ( MARKDOWN_IMAGE_PATTERN ) ] . filter (
157+ ( match ) => isHostedFeishuMediaUrl ( match [ 2 ] )
158+ )
159+ if ( matches . length === 0 ) return markdown
160+ if ( typeof options . downloadAsset !== 'function' ) {
161+ throw new Error ( `${ options . context } contains media but no downloader` )
162+ }
163+
164+ let output = ''
165+ let cursor = 0
166+ for ( const match of matches ) {
167+ output += markdown . slice ( cursor , match . index )
168+ const label = match [ 1 ]
169+ const asset = await options . downloadAsset ( {
170+ sourceUrl : match [ 2 ] ,
171+ name : label || undefined ,
172+ kind : 'img'
173+ } )
174+ output += ``
175+ cursor = match . index + match [ 0 ] . length
176+ }
177+
178+ return output + markdown . slice ( cursor )
179+ }
180+
181+ function isHostedFeishuMediaUrl ( value ) {
182+ try {
183+ const url = new URL ( value )
184+ return (
185+ url . protocol === 'https:' &&
186+ ! url . username &&
187+ ! url . password &&
188+ FEISHU_MEDIA_HOSTS . has ( url . hostname )
189+ )
190+ } catch {
191+ return false
192+ }
193+ }
194+
146195function parseAttributes ( source ) {
147196 const attributes = { }
148197 for ( const match of source . matchAll ( ATTRIBUTE_PATTERN ) ) {
0 commit comments