diff --git a/package.json b/package.json index a3603bf2..5cd5ca6b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "surmon.me", - "version": "4.33.0", + "version": "4.33.1", "description": "Surmon.me blog", "author": "Surmon", "license": "MIT", diff --git a/src/components/comment/index.vue b/src/components/comment/index.vue index 6e8403fb..b08dc981 100755 --- a/src/components/comment/index.vue +++ b/src/components/comment/index.vue @@ -16,7 +16,7 @@ import { useCommentStore, CommentFetchParams } from '/@/stores/comment' import { GAEventCategories } from '/@/constants/gtag' import * as ANCHORS from '/@/constants/anchor' - import * as URL_HASHS from '/@/constants/url-hash' + import * as URL_HASHS from '/@/constants/anchor' import { UNDEFINED } from '/@/constants/value' import { SortType } from '/@/constants/state' import { Author } from '/@/interfaces/comment' diff --git a/src/components/comment/list/item.vue b/src/components/comment/list/item.vue index d74b838a..dbff4330 100644 --- a/src/components/comment/list/item.vue +++ b/src/components/comment/list/item.vue @@ -4,7 +4,7 @@ import { useCommentStore } from '/@/stores/comment' import { useIdentityStore, UserType } from '/@/stores/identity' import { getCommentItemElementId } from '/@/constants/anchor' - import { getCommentUrlHashById } from '/@/constants/url-hash' + import { getCommentUrlHashById } from '/@/constants/anchor' import { LanguageKey } from '/@/language' import { UNDEFINED } from '/@/constants/value' import { Comment } from '/@/interfaces/comment' diff --git a/src/components/common/markdown.vue b/src/components/common/markdown.vue index 0bc2588f..78bddbdd 100644 --- a/src/components/common/markdown.vue +++ b/src/components/common/markdown.vue @@ -88,16 +88,20 @@ font-weight: 700; text-indent: 0; &:hover { - .anchor { + .anchor.static { + color: $text; + } + .anchor.link { color: $primary; + cursor: pointer; } } .anchor { margin-right: $xs-gap; color: $text-secondary; + text-decoration: none; user-select: none; - cursor: pointer; } } diff --git a/src/constants/anchor.ts b/src/constants/anchor.ts index a3560b73..cedb0862 100644 --- a/src/constants/anchor.ts +++ b/src/constants/anchor.ts @@ -18,8 +18,15 @@ export const ARTICLE_SHARE_ELEMENT_ID = 'A_article_share' export const ARTICLE_RELATED_ELEMENT_ID = 'A_article_related' export const ARTICLE_CONTENT_HEADING_ELEMENT_ID_PREFIX = 'A_article_content_heading' -export const getArticleContentHeadingElementId = (level: number, heading: string) => { - return `${ARTICLE_CONTENT_HEADING_ELEMENT_ID_PREFIX}_${level}_${heading}` +export const getArticleContentHeadingElementId = (level: number, anchor: string) => { + return `${ARTICLE_CONTENT_HEADING_ELEMENT_ID_PREFIX}_${level}_${anchor}` +} + +export const getArticleHeadingUrlHash = (heading: string) => { + return heading + .replace(/[^\p{L}\d\s\-_]/gu, '') + .toLowerCase() + .replace(/\s+/g, '-') } export const COMMENT_ELEMENT_ID = 'A_comment_warpper' @@ -30,3 +37,14 @@ export const COMMENT_ITEM_ELEMENT_ID_PREFIX = 'A_comment_content_item' export const getCommentItemElementId = (commentId: string | number) => { return `${COMMENT_ITEM_ELEMENT_ID_PREFIX}_${commentId}` } + +export const COMMENTS_URL_HASH = 'comments' +export const COMMENT_ITEM_URL_HASH_PREFIX = 'comment-' + +export const getCommentIdByUrlHash = (hash: string) => { + return hash.replace(COMMENT_ITEM_URL_HASH_PREFIX, '') +} + +export const getCommentUrlHashById = (commentId: string | number) => { + return `${COMMENT_ITEM_URL_HASH_PREFIX}${commentId}` +} diff --git a/src/constants/url-hash.ts b/src/constants/url-hash.ts deleted file mode 100644 index e98737b1..00000000 --- a/src/constants/url-hash.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * @file URL Hash constant - * @module constant.url-hash - * @author Surmon - */ - -export const COMMENTS_URL_HASH = 'comments' -export const COMMENT_ITEM_URL_HASH_PREFIX = 'comment-' - -export const getCommentIdByUrlHash = (hash: string) => { - return hash.replace(COMMENT_ITEM_URL_HASH_PREFIX, '') -} - -export const getCommentUrlHashById = (commentId: string | number) => { - return `${COMMENT_ITEM_URL_HASH_PREFIX}${commentId}` -} diff --git a/src/pages/article/index.vue b/src/pages/article/index.vue index b988ebfd..83a8d0ed 100755 --- a/src/pages/article/index.vue +++ b/src/pages/article/index.vue @@ -5,7 +5,7 @@ import { useUniversalFetch } from '/@/universal' import { useStores } from '/@/stores' import * as ANCHORS from '/@/constants/anchor' - import * as URL_HASHS from '/@/constants/url-hash' + import * as URL_HASHS from '/@/constants/anchor' import { GAEventCategories } from '/@/constants/gtag' import { LanguageKey } from '/@/language' import { CUSTOM_ELEMENTS } from '/@/effects/elements' @@ -129,7 +129,7 @@ const elementID = urlHash === URL_HASHS.COMMENTS_URL_HASH ? ANCHORS.COMMENT_ELEMENT_ID - : articleHeadings.find(({ text }) => text === urlHash)?.id + : articleHeadings.find(({ anchor }) => anchor === urlHash)?.id if (elementID && document.getElementById(elementID)) { // Allow a certain amount of time to ensure that the browser is rendered. diff --git a/src/stores/_fetch.ts b/src/stores/_fetch.ts index eba7e61a..cb5e15f2 100644 --- a/src/stores/_fetch.ts +++ b/src/stores/_fetch.ts @@ -15,7 +15,7 @@ export interface FetchStoreOptions { shallow?: boolean } -export const useFetchStore = (options: FetchStoreOptions) => { +export const createFetchStore = (options: FetchStoreOptions) => { // default: shallow const isShallow = isUndefined(options.shallow) ? true : options.shallow const refWrapper = isShallow ? shallowRef : ref diff --git a/src/stores/announcement.ts b/src/stores/announcement.ts index 35667237..f4b7c7cb 100755 --- a/src/stores/announcement.ts +++ b/src/stores/announcement.ts @@ -5,13 +5,13 @@ */ import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { Announcement } from '/@/interfaces/announcement' import { PaginationList } from '/@/interfaces/common' import nodepress from '/@/services/nodepress' export const useAnnouncementStore = defineStore('announcement', () => { - return useFetchStore({ + return createFetchStore({ data: [], preclean: true, async fetcher(params?: any) { diff --git a/src/stores/archive.ts b/src/stores/archive.ts index 093a5296..a59ae587 100755 --- a/src/stores/archive.ts +++ b/src/stores/archive.ts @@ -6,7 +6,7 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { Archive } from '/@/interfaces/archive' import { Article } from '/@/interfaces/article' import { dateToHuman, HumanDate } from '/@/transforms/moment' @@ -21,7 +21,7 @@ export type ArchiveTreeList = Array<{ }> export const useArchiveStore = defineStore('archive', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ data: null, once: true, async fetcher() { diff --git a/src/stores/article.ts b/src/stores/article.ts index 0d5b9b0c..c087eea8 100755 --- a/src/stores/article.ts +++ b/src/stores/article.ts @@ -6,15 +6,15 @@ import { ref, shallowRef, computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { useIdentityStore } from './identity' import { useCDNDomain } from '/@/app/context' +import { SortType } from '/@/constants/state' import { Article } from '/@/interfaces/article' import { Pagination, PaginationList } from '/@/interfaces/common' -import { SortType } from '/@/constants/state' -import { getArticleContentHeadingElementId } from '/@/constants/anchor' -import { getStaticURL, getStaticPath, isOriginalStaticURL } from '/@/transforms/url' +import { getArticleContentHeadingElementId, getArticleHeadingUrlHash } from '/@/constants/anchor' import { markdownToHTML, getMarkdownSplitIndex, MarkdownRenderOption } from '/@/transforms/markdown' +import { getStaticURL, getStaticPath, isOriginalStaticURL } from '/@/transforms/url' import { delayPromise } from '/@/utils/delayer' import { isClient } from '/@/app/environment' import { RENDER_LONG_ARTICLE_THRESHOLD } from '/@/config/app.config' @@ -23,7 +23,7 @@ import nodepress from '/@/services/nodepress' export const ARTICLE_API_PATH = '/article' const createSpecialArticleListStore = (_params: Record, perPage: number = 8) => { - return useFetchStore({ + return createFetchStore({ once: true, data: [], async fetcher() { @@ -86,8 +86,9 @@ export const useArticleListStore = defineStore('articleList', () => { }) interface ArticleHeading { - text: string level: number + text: string + anchor: string id: string } @@ -97,11 +98,11 @@ const renderArticleMarkdown = (markdown: string, imageSourceGetter: MarkdownRend const html = markdownToHTML(markdown, { sanitize: false, imageSourceGetter, - headingIdGetter: (_, level, raw) => { - const escaped = raw.toLowerCase().replace(/[^a-zA-Z0-9\u4E00-\u9FA5]+/g, '-') - const id = getArticleContentHeadingElementId(level, escaped) - headings.push({ level, id, text: raw }) - return id + headingIdentifierGetter: (_, level, raw) => { + const anchor = getArticleHeadingUrlHash(raw) + const id = getArticleContentHeadingElementId(level, anchor) + headings.push({ level, text: raw, id, anchor }) + return { id, anchor } } }) @@ -143,7 +144,6 @@ export const useArticleDetailStore = defineStore('articleDetail', () => { if (!isOriginalStaticURL(src)) { return src } - const cdnDomain = useCDNDomain() const path = getStaticPath(src) return getStaticURL(cdnDomain, path) diff --git a/src/stores/basic.ts b/src/stores/basic.ts index 8ceb08bb..ccedd1be 100755 --- a/src/stores/basic.ts +++ b/src/stores/basic.ts @@ -6,7 +6,7 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { CommentPostId } from '/@/constants/state' import { UNDEFINED } from '/@/constants/value' import { AdminInfo, AppOption, AppAdConfig } from '/@/interfaces/option' @@ -14,7 +14,7 @@ import { useIdentityStore, UserType } from './identity' import nodepress from '/@/services/nodepress' export const useAdminInfoStore = defineStore('adminInfo', () => { - return useFetchStore({ + return createFetchStore({ data: null, async fetcher() { const response = await nodepress.get('/auth/admin') @@ -24,7 +24,7 @@ export const useAdminInfoStore = defineStore('adminInfo', () => { }) export const useAppOptionStore = defineStore('appOption', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ shallow: false, data: null, async fetcher() { diff --git a/src/stores/calendar.ts b/src/stores/calendar.ts index 9d10b87c..0c129be7 100755 --- a/src/stores/calendar.ts +++ b/src/stores/calendar.ts @@ -6,7 +6,7 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { TunnelModule } from '/@/constants/tunnel' import nodepress from '/@/services/nodepress' import tunnel from '/@/services/tunnel' @@ -14,7 +14,7 @@ import tunnel from '/@/services/tunnel' type CalendarDay = { date: string; count: number } export const useArticleCalendarStore = defineStore('articleCalendar', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: [], async fetcher() { @@ -27,7 +27,7 @@ export const useArticleCalendarStore = defineStore('articleCalendar', () => { }) export const useInstagramCalendarStore = defineStore('instagramCalendar', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: [], fetcher: () => { @@ -37,7 +37,7 @@ export const useInstagramCalendarStore = defineStore('instagramCalendar', () => }) export const useGitHubCalendarStore = defineStore('githubContributionsCalendar', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ once: true, data: null, fetcher: () => tunnel.dispatch(TunnelModule.GitHubContributions) diff --git a/src/stores/category.ts b/src/stores/category.ts index ea27c169..fd265e46 100755 --- a/src/stores/category.ts +++ b/src/stores/category.ts @@ -5,13 +5,13 @@ */ import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { Category } from '/@/interfaces/category' import { PaginationList } from '/@/interfaces/common' import nodepress from '/@/services/nodepress' export const useCategoryStore = defineStore('category', () => { - return useFetchStore({ + return createFetchStore({ data: [], once: true, fetcher() { diff --git a/src/stores/media.ts b/src/stores/media.ts index f79941a8..c34bd4cf 100755 --- a/src/stores/media.ts +++ b/src/stores/media.ts @@ -5,7 +5,7 @@ */ import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { TunnelModule } from '/@/constants/tunnel' import { isClient } from '/@/app/environment' import { delayPromise } from '/@/utils/delayer' @@ -15,7 +15,7 @@ import tunnel from '/@/services/tunnel' // Douban movies export const useDoubanMoviesStore = defineStore('doubanMovies', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: null, fetcher: () => tunnel.dispatch(TunnelModule.DoubanMovies) @@ -24,7 +24,7 @@ export const useDoubanMoviesStore = defineStore('doubanMovies', () => { // Instagram timeline export const useInstagramTimelineStore = defineStore('instagramTimeline', () => { - return useFetchStore({ + return createFetchStore({ data: null, fetcher: () => { const request = tunnel.dispatch(TunnelModule.InstagramMedias) @@ -35,7 +35,7 @@ export const useInstagramTimelineStore = defineStore('instagramTimeline', () => // Instagram profile export const useInstagramProfileStore = defineStore('instagramProfile', () => { - return useFetchStore({ + return createFetchStore({ data: null, fetcher: () => tunnel.dispatch(TunnelModule.InstagramProfile) }) @@ -43,7 +43,7 @@ export const useInstagramProfileStore = defineStore('instagramProfile', () => { // YouTube playlist export const useYouTubePlayListStore = defineStore('youtubePlaylist', () => { - return useFetchStore>({ + return createFetchStore>({ data: [], async fetcher() { const response = await tunnel.dispatch>(TunnelModule.YouTubePlaylist) @@ -55,7 +55,7 @@ export const useYouTubePlayListStore = defineStore('youtubePlaylist', () => { // Twitter userinfo export const useTwitterStore = defineStore('twitterAggregate', () => { - return useFetchStore({ + return createFetchStore({ data: null, fetcher: () => { return tunnel.dispatch(TunnelModule.TwitterAggregate) @@ -65,7 +65,7 @@ export const useTwitterStore = defineStore('twitterAggregate', () => { // My Google map export const useMyGoogleMapStore = defineStore('myGoogleMap', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: null, fetcher: () => tunnel.dispatch(TunnelModule.MyGoogleMap) diff --git a/src/stores/sponsor.ts b/src/stores/sponsor.ts index 66b22bcd..c32ff592 100644 --- a/src/stores/sponsor.ts +++ b/src/stores/sponsor.ts @@ -6,13 +6,13 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import type { GitHubSponsorsResponse, GitHubSponsorUser } from '/@/server/getters/github' import { TunnelModule } from '/@/constants/tunnel' import tunnel from '/@/services/tunnel' export const useSponsorStore = defineStore('githubSponsor', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ fetcher: () => tunnel.dispatch(TunnelModule.GitHubSponsors), once: true, data: null diff --git a/src/stores/statistic.ts b/src/stores/statistic.ts index f5ca7585..e3dadca1 100755 --- a/src/stores/statistic.ts +++ b/src/stores/statistic.ts @@ -5,7 +5,7 @@ */ import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { TunnelModule } from '/@/constants/tunnel' import type { GitHubStatistic, NpmStatistic } from '/@/server/getters/open-srouce' import nodepress from '/@/services/nodepress' @@ -22,7 +22,7 @@ export interface NodePressStatistic { } export const useNodepressStatisticStore = defineStore('nodepressStatistic', () => { - return useFetchStore({ + return createFetchStore({ data: null, fetcher: async () => { const response = await nodepress.get('/expansion/statistic') @@ -32,7 +32,7 @@ export const useNodepressStatisticStore = defineStore('nodepressStatistic', () = }) export const useGitHubStatisticStore = defineStore('githubStatistic', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: null, fetcher: () => tunnel.dispatch(TunnelModule.OpenSourceGitHubStatistic) @@ -40,7 +40,7 @@ export const useGitHubStatisticStore = defineStore('githubStatistic', () => { }) export const useNpmStatisticStore = defineStore('npmStatistic', () => { - return useFetchStore({ + return createFetchStore({ once: true, data: null, fetcher: () => tunnel.dispatch(TunnelModule.OpenSourceNPMStatistic) diff --git a/src/stores/tag.ts b/src/stores/tag.ts index 0c0e8984..3861e0f0 100755 --- a/src/stores/tag.ts +++ b/src/stores/tag.ts @@ -6,7 +6,7 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { firstUpperCase } from '/@/transforms/text' import { getExtendValue } from '/@/transforms/state' import { Tag } from '/@/interfaces/tag' @@ -31,7 +31,7 @@ export const getTagEnName = (tag: Tag) => { export type TagMap = Map export const useTagStore = defineStore('tag', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ once: true, data: [], fetcher: async () => { diff --git a/src/stores/wallpaper.ts b/src/stores/wallpaper.ts index 8a73a20e..a7a615a0 100755 --- a/src/stores/wallpaper.ts +++ b/src/stores/wallpaper.ts @@ -6,7 +6,7 @@ import { computed } from 'vue' import { defineStore } from 'pinia' -import { useFetchStore } from './_fetch' +import { createFetchStore } from './_fetch' import { Language } from '/@/language' import { TunnelModule } from '/@/constants/tunnel' import tunnel from '/@/services/tunnel' @@ -14,7 +14,7 @@ import tunnel from '/@/services/tunnel' export type Wallpaper = Record> export const useWallpaperStore = defineStore('wallpaper', () => { - const fetchStore = useFetchStore({ + const fetchStore = createFetchStore({ fetcher: () => tunnel.dispatch(TunnelModule.BingWallpaper), once: true, data: null diff --git a/src/transforms/markdown.ts b/src/transforms/markdown.ts index d2536385..e47314ca 100644 --- a/src/transforms/markdown.ts +++ b/src/transforms/markdown.ts @@ -6,9 +6,9 @@ import highlight from '/@/effects/highlight' import { Marked, Renderer } from 'marked' -import { markedHighlight } from 'marked-highlight' -import { markedXhtml } from 'marked-xhtml' import { mangle } from 'marked-mangle' +import { markedXhtml } from 'marked-xhtml' +import { markedHighlight } from 'marked-highlight' import { sanitizeUrl } from '@braintree/sanitize-url' import { CUSTOM_ELEMENT_LIST } from '/@/effects/elements' import { LOZAD_CLASS_NAME } from '/@/composables/lozad' @@ -16,7 +16,6 @@ import { getLoadingIndicatorHTML } from '/@/components/common/loading-indicator' import { getOriginalProxyURL } from '/@/transforms/url' import { escape } from '/@/transforms/text' import { META } from '/@/config/app.config' -import API_CONFIG from '/@/config/api.config' // https://marked.js.org const highlightLangPrefix = 'language-' @@ -68,19 +67,13 @@ const trimHTML = (html: string) => html.replace(/\s+/g, ' ').replace(/\n/g, ' ') interface RendererCreatorOptions { sanitize: boolean lazyLoadImage: boolean - text: (text: string) => string - headingId: (html: string, level: number, raw: string) => string - imageSource: (src: string) => string | { src: string; sources: Array<{ srcset: string; type: string }> } + headingIdentifierGetter(html: string, level: number, raw: string): { anchor?: string; id?: string } + imageSourceGetter(src: string): string | { src: string; sources: Array<{ srcset: string; type: string }> } } const createRenderer = (options?: Partial): Renderer => { const renderer = new Renderer() - // text - renderer.text = (text) => { - return options?.text ? options.text(text) : text - } - // html: escape > sanitize renderer.html = (html) => { const trimmed = html.trim() @@ -91,11 +84,21 @@ const createRenderer = (options?: Partial): Renderer => // heading renderer.heading = (html, level, raw) => { - const idText = options?.headingId ? `id="${options.headingId(html, level, raw)}"` : '' - const url = `'${API_CONFIG.FE}' + window.location.pathname + '#${encodeURIComponent(raw)}'` - const copy = `window.navigator.clipboard?.writeText(${url})` - const anchor = `#` - return `${anchor}${html}` + const getAnchorWithLink = (anchor: string) => { + const preventDefault = `event.preventDefault()` + const copy = `window.navigator.clipboard?.writeText(this.href)` + const onclick = `onclick="${preventDefault};${copy}"` + const href = `href="#${anchor}"` + return `#` + } + + const identifier = options?.headingIdentifierGetter?.(html, level, raw) + const idAttr = identifier?.id ? `id="${identifier.id}"` : '' + const titleAttr = `title="${escape(raw)}"` + const anchorElement = identifier?.anchor + ? getAnchorWithLink(identifier.anchor) + : `#` + return `${anchorElement}${html}` } // paragraph @@ -141,7 +144,7 @@ const createRenderer = (options?: Partial): Renderer => const altValue = sanitizeHTML(escape(alt!)) const sanitized = sanitizeUrl(src!) const original = sanitized.startsWith('http://') ? getOriginalProxyURL(sanitized) : sanitized - const parsed = options?.imageSource ? options.imageSource(original) : original + const parsed = options?.imageSourceGetter ? options.imageSourceGetter(original) : original // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture // https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/currentSrc const srcValue = typeof parsed === 'object' ? parsed.src : parsed @@ -187,12 +190,12 @@ const createRenderer = (options?: Partial): Renderer => .map((_, i) => `
  • ${i + 1}
  • `.replace(/\s+/g, ' ')) .join('') - const readOnlyAttrs = ` - contenteditable="true" - oncut="return false" - onpaste="return false" - onkeydown="if(event.metaKey) return true; return false;" - ` + const readOnlyAttrs = [ + `contenteditable="true"`, + `oncut="return false"`, + `onpaste="return false"`, + `onkeydown="if(event.metaKey) return true; return false;"` + ].join(' ') return lang ? ` @@ -214,23 +217,16 @@ const createRenderer = (options?: Partial): Renderer => return renderer } -export interface MarkdownRenderOption { - sanitize?: boolean - lazyLoadImage?: boolean - headingIdGetter?: RendererCreatorOptions['headingId'] - imageSourceGetter?: RendererCreatorOptions['imageSource'] -} - +export interface MarkdownRenderOption extends Partial {} export const markdownToHTML = (markdown: string, options?: MarkdownRenderOption) => { if (!markdown || typeof markdown !== 'string') { return '' } const renderOptions: Partial = { + ...options, sanitize: options?.sanitize ?? false, - lazyLoadImage: options?.lazyLoadImage ?? true, - headingId: options?.headingIdGetter, - imageSource: options?.imageSourceGetter + lazyLoadImage: options?.lazyLoadImage ?? true } return marked.parse(markdown, { renderer: createRenderer(renderOptions) }) as string