tempest-react-sdk 0.76.0 → 0.77.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 +2 -2
- package/bin/create-tempest-app.e2e.test.mjs +112 -0
- package/bin/create-tempest-app.mjs +69 -28
- package/bin/lib/component/generate.mjs +191 -0
- package/bin/lib/component/generate.test.mjs +336 -0
- package/bin/lib/openapi/escape.mjs +210 -0
- package/bin/lib/openapi/generate.hostile.test.mjs +592 -0
- package/bin/lib/openapi/generate.mjs +93 -17
- package/bin/lib/openapi/schema-to-zod.mjs +41 -4
- package/bin/tempest.mjs +184 -3
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +127 -55
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/refresh-queue.cjs +1 -1
- package/dist/auth/refresh-queue.cjs.map +1 -1
- package/dist/auth/refresh-queue.js +13 -6
- package/dist/auth/refresh-queue.js.map +1 -1
- package/dist/br/pix.cjs +1 -1
- package/dist/br/pix.cjs.map +1 -1
- package/dist/br/pix.js +72 -62
- package/dist/br/pix.js.map +1 -1
- package/dist/br.d.ts +12 -4
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +46 -38
- package/dist/components/Chat/ChatAttachments.js.map +1 -1
- package/dist/components/Markdown/markdown-parse.cjs +7 -5
- package/dist/components/Markdown/markdown-parse.cjs.map +1 -1
- package/dist/components/Markdown/markdown-parse.js +151 -87
- package/dist/components/Markdown/markdown-parse.js.map +1 -1
- package/dist/components/Toast/Toast.module.cjs +1 -1
- package/dist/components/Toast/Toast.module.cjs.map +1 -1
- package/dist/components/Toast/Toast.module.js +7 -3
- package/dist/components/Toast/Toast.module.js.map +1 -1
- package/dist/components/Toast/ToastProvider.cjs +1 -1
- package/dist/components/Toast/ToastProvider.cjs.map +1 -1
- package/dist/components/Toast/ToastProvider.js +77 -51
- package/dist/components/Toast/ToastProvider.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +32 -21
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +81 -73
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/errors.cjs +1 -1
- package/dist/http/errors.cjs.map +1 -1
- package/dist/http/errors.js +39 -35
- package/dist/http/errors.js.map +1 -1
- package/dist/oauth/callback.cjs +2 -0
- package/dist/oauth/callback.cjs.map +1 -0
- package/dist/oauth/callback.js +42 -0
- package/dist/oauth/callback.js.map +1 -0
- package/dist/oauth/use-oauth-callback.cjs +1 -1
- package/dist/oauth/use-oauth-callback.cjs.map +1 -1
- package/dist/oauth/use-oauth-callback.js +13 -20
- package/dist/oauth/use-oauth-callback.js.map +1 -1
- package/dist/push/use-push-subscription.cjs +1 -1
- package/dist/push/use-push-subscription.cjs.map +1 -1
- package/dist/push/use-push-subscription.js +42 -42
- package/dist/push/use-push-subscription.js.map +1 -1
- package/dist/push/web-push-client.cjs +1 -1
- package/dist/push/web-push-client.cjs.map +1 -1
- package/dist/push/web-push-client.js +23 -3
- package/dist/push/web-push-client.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +68 -23
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/styles/component/Toast.css +6 -1
- package/dist/styles/feedback.css +6 -1
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +271 -60
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache-inspect.cjs.map +1 -1
- package/dist/sw/cache-inspect.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +91 -64
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/credential-headers.cjs +2 -0
- package/dist/sw/credential-headers.cjs.map +1 -0
- package/dist/sw/credential-headers.js +17 -0
- package/dist/sw/credential-headers.js.map +1 -0
- package/dist/sw/session-data.cjs +2 -0
- package/dist/sw/session-data.cjs.map +1 -0
- package/dist/sw/session-data.js +17 -0
- package/dist/sw/session-data.js.map +1 -0
- package/dist/sw.cjs +1 -1
- package/dist/sw.d.ts +271 -23
- package/dist/sw.js +7 -5
- package/dist/telemetry/app-error-owner.cjs +2 -0
- package/dist/telemetry/app-error-owner.cjs.map +1 -0
- package/dist/telemetry/app-error-owner.js +18 -0
- package/dist/telemetry/app-error-owner.js.map +1 -0
- package/dist/telemetry/app-error-queue.cjs +1 -1
- package/dist/telemetry/app-error-queue.cjs.map +1 -1
- package/dist/telemetry/app-error-queue.js +19 -14
- package/dist/telemetry/app-error-queue.js.map +1 -1
- package/dist/telemetry/app-error-reporter.cjs +1 -1
- package/dist/telemetry/app-error-reporter.cjs.map +1 -1
- package/dist/telemetry/app-error-reporter.js +75 -34
- package/dist/telemetry/app-error-reporter.js.map +1 -1
- package/dist/telemetry/app-error-transport.cjs +1 -1
- package/dist/telemetry/app-error-transport.cjs.map +1 -1
- package/dist/telemetry/app-error-transport.js +16 -3
- package/dist/telemetry/app-error-transport.js.map +1 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +979 -62
- package/dist/tempest-react-sdk.js +160 -156
- package/dist/utils/csv.cjs +3 -3
- package/dist/utils/csv.cjs.map +1 -1
- package/dist/utils/csv.js +16 -13
- package/dist/utils/csv.js.map +1 -1
- package/dist/utils/safe-url.cjs +2 -0
- package/dist/utils/safe-url.cjs.map +1 -0
- package/dist/utils/safe-url.js +39 -0
- package/dist/utils/safe-url.js.map +1 -0
- package/dist/vite/tempest-csp.cjs +2 -2
- package/dist/vite/tempest-csp.cjs.map +1 -1
- package/dist/vite/tempest-csp.js +118 -63
- package/dist/vite/tempest-csp.js.map +1 -1
- package/dist/vite.d.ts +21 -5
- package/dist/webrtc/peer-link.cjs +1 -1
- package/dist/webrtc/peer-link.cjs.map +1 -1
- package/dist/webrtc/peer-link.js +2 -10
- package/dist/webrtc/peer-link.js.map +1 -1
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +110 -72
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +22 -0
- package/template-pwa/src/sw.ts +27 -14
- package/dist/components/Markdown/markdown-url.cjs +0 -2
- package/dist/components/Markdown/markdown-url.cjs.map +0 -1
- package/dist/components/Markdown/markdown-url.js +0 -28
- package/dist/components/Markdown/markdown-url.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatAttachments.js","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n
|
|
1
|
+
{"version":3,"file":"ChatAttachments.js","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\nimport { safeAttachmentUrl } from \"@/utils/safe-url\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n voiceNote: string;\n download: string;\n}\n\nexport interface ChatAttachmentsProps {\n /** What this message carries. */\n attachments: readonly ChatAttachment[];\n /** Labels, from `chatStrings(locale)`. */\n strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n const total = Math.round(durationMs / 1000);\n const minutes = Math.floor(total / 60);\n const seconds = total % 60;\n return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * The file row is the one place a sender's URL becomes an `href`, so it goes\n * through {@link safeAttachmentUrl}: `http:`, `https:`, `blob:` and relative URLs\n * are linked, anything else renders the same row without a link (#447). Media\n * `src` is left as given — an `<img>`, `<video>` or `<audio>` does not run a\n * `javascript:` URL.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n const images = attachments.filter((attachment) => attachment.kind === \"image\");\n const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n return (\n <div className={styles.attachments}>\n {attachments.map((attachment, index) => {\n const key = `${attachment.kind}-${attachment.url}-${index}`;\n if (attachment.kind === \"image\") {\n const position = images.indexOf(attachment);\n return (\n <button\n key={key}\n type=\"button\"\n className={styles.imageAttachment}\n onClick={() => setLightboxIndex(position)}\n >\n <img\n src={attachment.thumbnailUrl ?? attachment.url}\n alt={attachment.alt ?? attachment.name ?? \"\"}\n loading=\"lazy\"\n />\n </button>\n );\n }\n if (attachment.kind === \"video\") {\n return (\n <VideoPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n poster={attachment.thumbnailUrl}\n className={styles.videoAttachment}\n />\n );\n }\n if (attachment.kind === \"voice\") {\n return (\n <div key={key} className={styles.voiceAttachment}>\n <div className={styles.waveform} aria-hidden=\"true\">\n {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n <span\n key={bar}\n style={{\n height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n }}\n />\n ))}\n </div>\n <AudioPlayer\n src={attachment.url}\n durationMs={attachment.durationMs}\n aria-label={strings.voiceNote}\n />\n </div>\n );\n }\n if (attachment.kind === \"audio\") {\n return (\n <AudioPlayer\n key={key}\n src={attachment.url}\n durationMs={attachment.durationMs}\n />\n );\n }\n const href = safeAttachmentUrl(attachment.url);\n const row = (\n <>\n <span className={styles.fileName}>\n {attachment.name ?? strings.download}\n </span>\n <span className={styles.fileMeta}>\n {[\n attachment.sizeBytes !== undefined\n ? formatBytes(attachment.sizeBytes)\n : null,\n attachment.durationMs !== undefined\n ? durationLabel(attachment.durationMs)\n : null,\n ]\n .filter(Boolean)\n .join(\" · \")}\n </span>\n </>\n );\n return href ? (\n <a\n key={key}\n className={styles.fileAttachment}\n href={href}\n download={attachment.name ?? true}\n >\n {row}\n </a>\n ) : (\n <div key={key} className={styles.fileAttachment}>\n {row}\n </div>\n );\n })}\n\n {images.length > 0 && (\n <Lightbox\n items={images.map((image) => ({\n src: image.url,\n alt: image.alt ?? image.name ?? \"\",\n thumbnail: image.thumbnailUrl,\n }))}\n open={lightboxIndex !== null}\n index={lightboxIndex ?? 0}\n onIndexChange={setLightboxIndex}\n onClose={() => setLightboxIndex(null)}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
const e=require("
|
|
1
|
+
const e=require("../../utils/safe-url.cjs");var t=/^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)$/,n=/^\s{0,3}(#{1,6})\s+/,r=/^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/,i=/^\s{0,3}>\s?(.*)$/,a=/^(\s*)([-*+])\s+(.*)$/,o=/^(\s*)(\d{1,9})[.)]\s+(.*)$/,s=/^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?$/,c=32;function l(e){let t=n.exec(e);if(!t)return null;let r=e.slice(t[0].length).trimEnd(),i=r.length;for(;i>0&&r[i-1]===`#`;)--i;return r=r.slice(0,i).trimEnd(),{level:t[1].length,text:r}}function u(e){return e.endsWith(` `)}function d(e){return f(e.replace(/\r\n?/g,`
|
|
2
2
|
`).split(`
|
|
3
|
-
`))}function
|
|
4
|
-
`)
|
|
5
|
-
`)}
|
|
6
|
-
`){
|
|
3
|
+
`),0)}function f(e,n){if(n>=c){let t=e.map(e=>e.trim()).filter(Boolean).join(`
|
|
4
|
+
`);return t?[{type:`paragraph`,children:y(t)}]:[]}let d=[],h=0;for(;h<e.length;){let c=e[h];if(c.trim()===``){h+=1;continue}let _=t.exec(c.trimEnd());if(_){let t=_[1][0],n=[];for(h+=1;h<e.length&&!RegExp(`^\\s{0,3}${t}{3,}\\s*$`).test(e[h]);)n.push(e[h]),h+=1;h+=1,d.push({type:`code`,language:_[2]||null,value:n.join(`
|
|
5
|
+
`)});continue}let v=l(c);if(v){d.push({type:`heading`,level:v.level,children:y(v.text)}),h+=1;continue}if(r.test(c)){d.push({type:`rule`}),h+=1;continue}if(i.test(c)){let t=[];for(;h<e.length;){let n=i.exec(e[h]);if(n){t.push(n[1]),h+=1;continue}if(e[h].trim()===``)break;t.push(e[h]),h+=1}d.push({type:`quote`,children:f(t,n+1)});continue}if(a.test(c)||o.test(c)){let[t,r]=m(e,h,n);d.push(t),h=r;continue}if(h+1<e.length&&c.includes(`|`)&&s.test(e[h+1].trimEnd())){let[t,n]=g(e,h);d.push(t),h=n;continue}let b=[],x=[];for(;h<e.length&&e[h].trim()!==``;){let n=e[h];if(l(n)||r.test(n)||t.test(n.trimEnd())||i.test(n)||a.test(n)||o.test(n))break;b.push(n.trim()),x.push(u(n)),h+=1}b.length>0&&d.push({type:`paragraph`,children:y(p(b,x))})}return d}function p(e,t){return e.map((n,r)=>t[r]&&r<e.length-1?`${n} `:n).join(`
|
|
6
|
+
`)}function m(e,t,n){let r=a.exec(e[t]),i=o.exec(e[t]),s=i!==null,c=((i??r)?.[1]??``).length,l=s?Number(i?.[2]??1):1,u=[],d=[],p=t,m=()=>{d.length!==0&&(u.push(f(d,n+1)),d=[])};for(;p<e.length;){let t=e[p];if(t.trim()===``){if(p+1<e.length&&e[p+1].trim()!==``){d.push(``),p+=1;continue}break}let n=a.exec(t),r=o.exec(t),i=n??r;if(i&&i[1].length<=c){if(!(s?r:n))break;m(),d.push(i[3]),p+=1;continue}if(i){d.push(t.slice(c)),p+=1;continue}if(t.search(/\S/)>c){d.push(t.trim()),p+=1;continue}break}return m(),[{type:`list`,ordered:s,start:l,items:u},p]}function h(e){let t=e.trim().replace(/^\|/,``).replace(/\|$/,``),n=[],r=``;for(let e=0;e<t.length;e+=1){if(t[e]===`\\`&&t[e+1]===`|`){r+=`|`,e+=1;continue}if(t[e]===`|`){n.push(r),r=``;continue}r+=t[e]}return n.push(r),n.map(e=>e.trim())}function g(e,t){let n=h(e[t]),r=h(e[t+1]).map(e=>{let t=e.startsWith(`:`),n=e.endsWith(`:`);return t&&n?`center`:n?`right`:t?`left`:null}),i=[],a=t+2;for(;a<e.length&&e[a].trim()!==``&&e[a].includes(`|`);)i.push(h(e[a]).map(y)),a+=1;return[{type:`table`,align:r,head:n.map(y),rows:i},a]}var _=[{guard:{kind:`link`,opener:`![`},first:`!`,pattern:/^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeImageUrl(t[2]);return n?{type:`image`,src:n,alt:t[1]}:{type:`text`,value:t[1]}}},{guard:{kind:`link`,opener:`[`},first:`[`,pattern:/^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,build:t=>{let n=e.safeLinkUrl(t[2]),r=y(t[1]);return n?{type:`link`,href:n,children:r}:{type:`text`,value:t[1]}}},{first:"`",pattern:/^`([^`]+)`/,build:e=>({type:`code`,value:e[1]})},{first:`*`,pattern:/^\*\*([\s\S]+?)\*\*/,build:e=>({type:`strong`,children:y(e[1])})},{first:`_`,pattern:/^__([\s\S]+?)__/,build:e=>({type:`strong`,children:y(e[1])})},{first:`~`,pattern:/^~~([\s\S]+?)~~/,build:e=>({type:`del`,children:y(e[1])})},{first:`*`,pattern:/^\*([^*\n]+)\*/,build:e=>({type:`em`,children:y(e[1])})},{first:`_`,pattern:/^_([^_\n]+)_/,build:e=>({type:`em`,children:y(e[1])})},{guard:{kind:`autolink`,opener:`<`},first:`<`,pattern:/^<((?:https?:\/\/|mailto:)[^>\s]+)>/,build:t=>{let n=e.safeLinkUrl(t[1]);return n?{type:`link`,href:n,children:[{type:`text`,value:t[1]}]}:{type:`text`,value:t[1]}}},{first:`\\`,pattern:/^\\\n/,build:()=>({type:`break`})}],v=new Set(_.map(e=>e.first));function y(e){let t=[],n=``,r=0,i=-1,a=-1,o=-1,s=/[>\s]/g,c=()=>{n&&t.push({type:`text`,value:n}),n=``},l=t=>{if(i<t){let n=e.indexOf(`]`,t);i=n===-1?e.length:n}return i},u=t=>(o<t&&(s.lastIndex=t,o=s.exec(e)?.index??e.length),o),d=t=>{if(!t)return!0;if(!e.startsWith(t.opener,r))return!1;if(t.kind===`autolink`)return e[u(r+1)]===`>`;let n=l(r+t.opener.length);return e[n+1]===`(`&&n!==a};for(;r<e.length;){if(e[r]===`\\`&&r+1<e.length&&e[r+1]!==`
|
|
7
|
+
`){n+=e[r+1],r+=2;continue}if(e[r]===` `){let i=r;for(;e[i]===` `;)i+=1;i-r>=2&&e[i]===`
|
|
8
|
+
`?(c(),t.push({type:`break`}),r=i+1):(n+=e.slice(r,i),r=i);continue}if(!v.has(e[r])){n+=e[r],r+=1;continue}let i=e.slice(r),o=!1;for(let n of _){if(n.first!==e[r]||!d(n.guard))continue;let s=n.pattern.exec(i);if(!s){n.guard?.kind===`link`&&(a=l(r+n.guard.opener.length));continue}let u=n.build(s);if(u){c(),t.push(u),r+=s[0].length,o=!0;break}}o||(n+=e[r],r+=1)}return c(),t}exports.parseInline=y,exports.parseMarkdown=d;
|
|
7
9
|
//# sourceMappingURL=markdown-parse.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-parse.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"./markdown-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)\\s*$/;\nconst HEADING = /^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\nconst HARD_BREAK = / {2,}$/;\n/**\n * A table's delimiter row.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two.\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?\\s*$/;\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines);\n}\n\n/** Parse a run of lines into blocks. */\nfunction parseBlocks(lines: string[]): MarkdownBlock[] {\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line);\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = HEADING.exec(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading[1].length,\n children: parseInline(heading[2]),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (i + 1 < lines.length && line.includes(\"|\") && TABLE_DELIMITER.test(lines[i + 1])) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n const hardBreaks: boolean[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n HEADING.test(current) ||\n RULE.test(current) ||\n FENCE.test(current) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n hardBreaks.push(HARD_BREAK.test(current));\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({\n type: \"paragraph\",\n children: parseInline(joinParagraph(paragraph, hardBreaks)),\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Re-join the lines of a paragraph, keeping the two-space hard break.\n *\n * Every line is trimmed before it is parsed, because leading indentation is not\n * content and a trailing space is invisible noise in almost every line. The one\n * exception is the CommonMark hard break: two or more trailing spaces mean\n * \"break the line here\", so trimming first would delete the only mark that says\n * so and the ` \\n` inline rule could never match. The flag is recorded before\n * the trim and the marker put back here, on every line but the last — a break\n * after the final line of a paragraph would break into nothing.\n *\n * @param lines - The trimmed lines, in order.\n * @param hardBreaks - Whether each line ended in two or more spaces.\n * @returns The paragraph source the inline parser reads.\n */\nfunction joinParagraph(lines: readonly string[], hardBreaks: readonly boolean[]): string {\n return lines\n .map((line, index) => (hardBreaks[index] && index < lines.length - 1 ? `${line} ` : line))\n .join(\"\\n\");\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n pattern: RegExp;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { pattern: /^ {2,}\\n/, build: () => ({ type: \"break\" }) },\n { pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n const match = rule.pattern.exec(rest);\n if (!match) continue;\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"markdown-parse.cjs","names":[],"sources":["../../../src/components/Markdown/markdown-parse.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines, function-lines — a Markdown block parser is a state\n * machine over line kinds: paragraph, heading, fence, list, quote, table and\n * thematic break each need to see the lines around them to know where they end.\n * parseBlocks is that loop, and cutting it into per-kind functions would hand each\n * one the same lookahead cursor.\n */\n// A small Markdown parser for the subset an app actually renders from untrusted\n// text: comments, ticket bodies, release notes, a product description.\n//\n// It produces a node tree, never an HTML string, and the renderer turns that into\n// React elements. No `dangerouslySetInnerHTML` exists anywhere in this component —\n// which is the property that makes rendering somebody else's Markdown safe, and it\n// is structural rather than a promise about escaping.\n//\n// Raw HTML in the input is therefore not \"sanitized\", it is **text**: `<script>` in\n// a comment renders as the four characters somebody typed. That is the whole point,\n// and it is also the documented limit — this is not a CommonMark implementation and\n// does not try to be.\n\nimport { safeImageUrl, safeLinkUrl } from \"@/utils/safe-url\";\n\n/** Inline content. */\nexport type MarkdownInline =\n | { type: \"text\"; value: string }\n | { type: \"strong\"; children: MarkdownInline[] }\n | { type: \"em\"; children: MarkdownInline[] }\n | { type: \"del\"; children: MarkdownInline[] }\n | { type: \"code\"; value: string }\n | { type: \"link\"; href: string; children: MarkdownInline[] }\n | { type: \"image\"; src: string; alt: string }\n | { type: \"break\" };\n\n/** Column alignment of a table, from the delimiter row. */\nexport type MarkdownAlign = \"left\" | \"center\" | \"right\" | null;\n\n/** Block content. */\nexport type MarkdownBlock =\n | { type: \"heading\"; level: number; children: MarkdownInline[] }\n | { type: \"paragraph\"; children: MarkdownInline[] }\n | { type: \"code\"; language: string | null; value: string }\n | { type: \"quote\"; children: MarkdownBlock[] }\n | { type: \"list\"; ordered: boolean; start: number; items: MarkdownBlock[][] }\n | {\n type: \"table\";\n align: MarkdownAlign[];\n head: MarkdownInline[][];\n rows: MarkdownInline[][][];\n }\n | { type: \"rule\" };\n\n/**\n * A fence opener, matched against the line **after** `trimEnd()`.\n *\n * The trailing `\\s*` lives in the `trimEnd()` rather than in the pattern: with it,\n * `\\s*([\\w+-]*)\\s*$` is two whitespace quantifiers separated by one that may be\n * empty, and a fence followed by 20 000 spaces and a stray `!` took 0.36 s (#437).\n */\nconst FENCE = /^\\s{0,3}(`{3,}|~{3,})\\s*([\\w+-]*)$/;\n/**\n * The opening marker of an ATX heading. The text and the optional closing `#` run\n * are cut by {@link matchHeading} with string operations, not by this pattern.\n */\nconst HEADING_OPEN = /^\\s{0,3}(#{1,6})\\s+/;\nconst RULE = /^\\s{0,3}([-*_])\\s*(?:\\1\\s*){2,}$/;\nconst QUOTE = /^\\s{0,3}>\\s?(.*)$/;\nconst BULLET = /^(\\s*)([-*+])\\s+(.*)$/;\nconst ORDERED = /^(\\s*)(\\d{1,9})[.)]\\s+(.*)$/;\n/**\n * A table's delimiter row, matched against the line **after** `trimEnd()`.\n *\n * The `|` is required somewhere in the line, and that is what tells a one-column\n * table (`| --- |`) apart from a thematic break (`---`) — a single column is a\n * legitimate table, and the first version of this regex demanded two. The trailing\n * whitespace is trimmed before the test for the same reason as {@link FENCE}: a\n * `\\s*\\|?\\s*$` tail backtracked quadratically on `- |` plus 50 000 spaces (#437).\n */\nconst TABLE_DELIMITER = /^\\s{0,3}\\|[\\s|:-]*$|^\\s{0,3}:?-+:?(\\s*\\|\\s*:?-+:?)+\\s*\\|?$/;\n\n/**\n * How deep quotes and lists may nest before the rest is read as a paragraph.\n *\n * Each level is one recursive {@link parseBlocks} call and one nested element in\n * the render, so `\"- \".repeat(20000)` used to throw `RangeError: Maximum call stack\n * size exceeded` out of the component (#437). Real documents stop at a handful of\n * levels; past this cap the words are kept, only the nesting is dropped.\n */\nconst MAX_NESTING_DEPTH = 32;\n\n/**\n * Match an ATX heading in linear time.\n *\n * The regex this replaces, `/^\\s{0,3}(#{1,6})\\s+(.*?)\\s*#*\\s*$/`, let a lazy `.*?`\n * and two `\\s*` race for the same run of spaces: `\"# a\" + \" \".repeat(4000) + \"b\"`\n * took about 4.7 s, cubic in the run (#437). Only the opening marker is a regex\n * now. The text is the rest of the line minus the longest suffix shaped like\n * `\\s*#*\\s*` — exactly what the lazy group left behind, so `# título #`,\n * `# título#` and `# #` read as before.\n *\n * @param line - One source line.\n * @returns The level and raw text, or `null` when the line is not a heading.\n */\nfunction matchHeading(line: string): { level: number; text: string } | null {\n const open = HEADING_OPEN.exec(line);\n if (!open) return null;\n let text = line.slice(open[0].length).trimEnd();\n let end = text.length;\n while (end > 0 && text[end - 1] === \"#\") end -= 1;\n text = text.slice(0, end).trimEnd();\n return { level: open[1].length, text };\n}\n\n/**\n * Whether a paragraph line ends in a hard break: two or more trailing spaces.\n *\n * `/ {2,}$/` was quadratic on a line with a long inner run of spaces — it retried\n * the run from every space and failed at the next word each time (0.57 s for\n * 50 000 spaces, #437).\n */\nfunction endsWithHardBreak(line: string): boolean {\n return line.endsWith(\" \");\n}\n\n/**\n * Parse a Markdown document into blocks.\n *\n * @param source - Raw Markdown.\n * @returns Block nodes in document order.\n */\nexport function parseMarkdown(source: string): MarkdownBlock[] {\n const lines = source.replace(/\\r\\n?/g, \"\\n\").split(\"\\n\");\n return parseBlocks(lines, 0);\n}\n\n/**\n * Parse a run of lines into blocks.\n *\n * @param lines - The lines of this container.\n * @param depth - How many quotes and lists enclose them. At\n * {@link MAX_NESTING_DEPTH} the lines become one paragraph instead of more\n * containers, so a hostile document cannot exhaust the stack.\n * @returns The blocks.\n */\nfunction parseBlocks(lines: string[], depth: number): MarkdownBlock[] {\n if (depth >= MAX_NESTING_DEPTH) {\n const source = lines\n .map((line) => line.trim())\n .filter(Boolean)\n .join(\"\\n\");\n return source ? [{ type: \"paragraph\", children: parseInline(source) }] : [];\n }\n const blocks: MarkdownBlock[] = [];\n let i = 0;\n\n while (i < lines.length) {\n const line = lines[i];\n\n if (line.trim() === \"\") {\n i += 1;\n continue;\n }\n\n const fence = FENCE.exec(line.trimEnd());\n if (fence) {\n const marker = fence[1][0];\n const body: string[] = [];\n i += 1;\n while (i < lines.length && !new RegExp(`^\\\\s{0,3}${marker}{3,}\\\\s*$`).test(lines[i])) {\n body.push(lines[i]);\n i += 1;\n }\n // An unterminated fence still yields a code block: the alternative is to\n // render the rest of the document as prose full of backticks.\n i += 1;\n blocks.push({ type: \"code\", language: fence[2] || null, value: body.join(\"\\n\") });\n continue;\n }\n\n const heading = matchHeading(line);\n if (heading) {\n blocks.push({\n type: \"heading\",\n level: heading.level,\n children: parseInline(heading.text),\n });\n i += 1;\n continue;\n }\n\n if (RULE.test(line)) {\n blocks.push({ type: \"rule\" });\n i += 1;\n continue;\n }\n\n if (QUOTE.test(line)) {\n const inner: string[] = [];\n while (i < lines.length) {\n const match = QUOTE.exec(lines[i]);\n if (match) {\n inner.push(match[1]);\n i += 1;\n continue;\n }\n // A blank line ends the quote; a plain line continues it (lazy\n // continuation), which is how people actually write quotes.\n if (lines[i].trim() === \"\") break;\n inner.push(lines[i]);\n i += 1;\n }\n blocks.push({ type: \"quote\", children: parseBlocks(inner, depth + 1) });\n continue;\n }\n\n if (BULLET.test(line) || ORDERED.test(line)) {\n const [list, next] = parseList(lines, i, depth);\n blocks.push(list);\n i = next;\n continue;\n }\n\n if (\n i + 1 < lines.length &&\n line.includes(\"|\") &&\n TABLE_DELIMITER.test(lines[i + 1].trimEnd())\n ) {\n const [table, next] = parseTable(lines, i);\n blocks.push(table);\n i = next;\n continue;\n }\n\n const paragraph: string[] = [];\n const hardBreaks: boolean[] = [];\n while (i < lines.length && lines[i].trim() !== \"\") {\n const current = lines[i];\n if (\n matchHeading(current) ||\n RULE.test(current) ||\n FENCE.test(current.trimEnd()) ||\n QUOTE.test(current) ||\n BULLET.test(current) ||\n ORDERED.test(current)\n ) {\n break;\n }\n paragraph.push(current.trim());\n hardBreaks.push(endsWithHardBreak(current));\n i += 1;\n }\n if (paragraph.length > 0) {\n blocks.push({\n type: \"paragraph\",\n children: parseInline(joinParagraph(paragraph, hardBreaks)),\n });\n }\n }\n\n return blocks;\n}\n\n/**\n * Re-join the lines of a paragraph, keeping the two-space hard break.\n *\n * Every line is trimmed before it is parsed, because leading indentation is not\n * content and a trailing space is invisible noise in almost every line. The one\n * exception is the CommonMark hard break: two or more trailing spaces mean\n * \"break the line here\", so trimming first would delete the only mark that says\n * so and the ` \\n` inline rule could never match. The flag is recorded before\n * the trim and the marker put back here, on every line but the last — a break\n * after the final line of a paragraph would break into nothing.\n *\n * @param lines - The trimmed lines, in order.\n * @param hardBreaks - Whether each line ended in two or more spaces.\n * @returns The paragraph source the inline parser reads.\n */\nfunction joinParagraph(lines: readonly string[], hardBreaks: readonly boolean[]): string {\n return lines\n .map((line, index) => (hardBreaks[index] && index < lines.length - 1 ? `${line} ` : line))\n .join(\"\\n\");\n}\n\n/**\n * Parse a list, including nested lists and multi-line items.\n *\n * Nesting is decided by indentation relative to the **first** item's marker, so a\n * list indented inside a quote or another list still reads correctly.\n *\n * @param lines - The lines of the enclosing container.\n * @param start - Index of the first item's line.\n * @param depth - Nesting depth of the enclosing container.\n * @returns The list node and the index of the first line after it.\n */\nfunction parseList(lines: string[], start: number, depth: number): [MarkdownBlock, number] {\n const bulletStart = BULLET.exec(lines[start]);\n const orderedStart = ORDERED.exec(lines[start]);\n // The two markers are mutually exclusive — `-*+` versus digits — so whichever\n // matched decides the list kind.\n const ordered = orderedStart !== null;\n const first = orderedStart ?? bulletStart;\n const baseIndent = (first?.[1] ?? \"\").length;\n const startNumber = ordered ? Number(orderedStart?.[2] ?? 1) : 1;\n\n const items: MarkdownBlock[][] = [];\n let buffer: string[] = [];\n let i = start;\n\n const flush = (): void => {\n if (buffer.length === 0) return;\n items.push(parseBlocks(buffer, depth + 1));\n buffer = [];\n };\n\n while (i < lines.length) {\n const line = lines[i];\n if (line.trim() === \"\") {\n // A single blank line inside a list is a loose item, not the end. Two in\n // a row end it, which is what a blank-then-paragraph document means.\n if (i + 1 < lines.length && lines[i + 1].trim() !== \"\") {\n buffer.push(\"\");\n i += 1;\n continue;\n }\n break;\n }\n const bullet = BULLET.exec(line);\n const numbered = ORDERED.exec(line);\n const match = bullet ?? numbered;\n\n if (match && match[1].length <= baseIndent) {\n const sameKind = ordered ? Boolean(numbered) : Boolean(bullet);\n if (!sameKind) break;\n flush();\n buffer.push(match[3]);\n i += 1;\n continue;\n }\n if (match) {\n // Deeper marker: keep the indentation so the recursive call sees a list.\n buffer.push(line.slice(baseIndent));\n i += 1;\n continue;\n }\n if (line.search(/\\S/) > baseIndent) {\n buffer.push(line.trim());\n i += 1;\n continue;\n }\n break;\n }\n flush();\n\n return [{ type: \"list\", ordered, start: startNumber, items }, i];\n}\n\n/** Split a table row on unescaped pipes. */\nfunction splitRow(line: string): string[] {\n const trimmed = line.trim().replace(/^\\|/, \"\").replace(/\\|$/, \"\");\n const cells: string[] = [];\n let current = \"\";\n for (let i = 0; i < trimmed.length; i += 1) {\n if (trimmed[i] === \"\\\\\" && trimmed[i + 1] === \"|\") {\n current += \"|\";\n i += 1;\n continue;\n }\n if (trimmed[i] === \"|\") {\n cells.push(current);\n current = \"\";\n continue;\n }\n current += trimmed[i];\n }\n cells.push(current);\n return cells.map((cell) => cell.trim());\n}\n\n/**\n * Parse a GFM pipe table.\n *\n * @returns The table node and the index of the first line after it.\n */\nfunction parseTable(lines: string[], start: number): [MarkdownBlock, number] {\n const head = splitRow(lines[start]);\n const align: MarkdownAlign[] = splitRow(lines[start + 1]).map((cell) => {\n const left = cell.startsWith(\":\");\n const right = cell.endsWith(\":\");\n if (left && right) return \"center\";\n if (right) return \"right\";\n if (left) return \"left\";\n return null;\n });\n\n const rows: MarkdownInline[][][] = [];\n let i = start + 2;\n while (i < lines.length && lines[i].trim() !== \"\" && lines[i].includes(\"|\")) {\n rows.push(splitRow(lines[i]).map(parseInline));\n i += 1;\n }\n\n return [{ type: \"table\", align, head: head.map(parseInline), rows }, i];\n}\n\n/**\n * The precheck a rule needs before its pattern may run, keyed by the opener the\n * pattern starts with.\n *\n * `link` covers links and images, whose label ends at the first `]`; `autolink`\n * covers `<scheme:...>`, which ends at the first `>` or whitespace. Without the\n * precheck each pattern scanned to the end of the text from every opener, and\n * `\"[\".repeat(50000)` took 0.6 s (#437).\n */\ntype InlineGuard = { kind: \"link\"; opener: \"[\" | \"![\" } | { kind: \"autolink\"; opener: \"<\" };\n\n/** Inline delimiters, longest marker first so `**` wins over `*`. */\nconst INLINE_RULES: Array<{\n first: string;\n pattern: RegExp;\n guard?: InlineGuard;\n build: (match: RegExpExecArray) => MarkdownInline | null;\n}> = [\n {\n guard: { kind: \"link\", opener: \"![\" },\n first: \"!\",\n pattern: /^!\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const src = safeImageUrl(match[2]);\n return src ? { type: \"image\", src, alt: match[1] } : { type: \"text\", value: match[1] };\n },\n },\n {\n guard: { kind: \"link\", opener: \"[\" },\n first: \"[\",\n pattern: /^\\[([^\\]]*)\\]\\(((?:[^()\\s]|\\([^()\\s]*\\))+)(?:\\s+\"[^\"]*\")?\\)/,\n build: (match) => {\n const href = safeLinkUrl(match[2]);\n const children = parseInline(match[1]);\n // A rejected URL keeps the label as plain text: dropping the text too\n // would silently delete words somebody wrote.\n return href ? { type: \"link\", href, children } : { type: \"text\", value: match[1] };\n },\n },\n { first: \"`\", pattern: /^`([^`]+)`/, build: (match) => ({ type: \"code\", value: match[1] }) },\n {\n first: \"*\",\n pattern: /^\\*\\*([\\s\\S]+?)\\*\\*/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^__([\\s\\S]+?)__/,\n build: (match) => ({ type: \"strong\", children: parseInline(match[1]) }),\n },\n {\n first: \"~\",\n pattern: /^~~([\\s\\S]+?)~~/,\n build: (match) => ({ type: \"del\", children: parseInline(match[1]) }),\n },\n {\n first: \"*\",\n pattern: /^\\*([^*\\n]+)\\*/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n first: \"_\",\n pattern: /^_([^_\\n]+)_/,\n build: (match) => ({ type: \"em\", children: parseInline(match[1]) }),\n },\n {\n guard: { kind: \"autolink\", opener: \"<\" },\n first: \"<\",\n pattern: /^<((?:https?:\\/\\/|mailto:)[^>\\s]+)>/,\n build: (match) => {\n const href = safeLinkUrl(match[1]);\n return href\n ? { type: \"link\", href, children: [{ type: \"text\", value: match[1] }] }\n : { type: \"text\", value: match[1] };\n },\n },\n { first: \"\\\\\", pattern: /^\\\\\\n/, build: () => ({ type: \"break\" }) },\n];\n\n/** Characters some inline rule can start with; any other character is text. */\nconst INLINE_FIRST = new Set(INLINE_RULES.map((rule) => rule.first));\n\n/**\n * Parse inline Markdown.\n *\n * A backslash escapes the next character, so `\\*not italic\\*` renders with the\n * asterisks. Anything that matches no rule is text — an unclosed `**` is two\n * asterisks, not a bold run to the end of the paragraph.\n *\n * The parse is linear in the text, which matters because the text is somebody\n * else's (#437):\n *\n * - A run of spaces is consumed whole: it is a hard break when two or more of them\n * end at a newline, and text otherwise. Matching `^ {2,}\\n` from every space\n * rescanned the run each time.\n * - A link or image label ends at the first `]` after the opener, so every opener\n * before the same `]` shares the same `](...)` tail. The position of that `]` is\n * cached, an opener whose `]` is not followed by `(` is skipped without running\n * the pattern, and once a tail has failed no later opener retries it.\n * - An autolink ends at the first `>` or whitespace, also cached; when that stop\n * is not `>` the pattern cannot match and is not run.\n * - A rule runs only at its own first character, so a run of `[` costs one set\n * lookup per character instead of every pattern in the table.\n *\n * @param source - One block's text.\n * @returns Inline nodes.\n */\nexport function parseInline(source: string): MarkdownInline[] {\n const nodes: MarkdownInline[] = [];\n let text = \"\";\n let i = 0;\n\n let bracketAt = -1;\n let deadTailAt = -1;\n let stopAt = -1;\n const stops = /[>\\s]/g;\n\n const flush = (): void => {\n if (text) nodes.push({ type: \"text\", value: text });\n text = \"\";\n };\n\n const closingBracket = (from: number): number => {\n if (bracketAt < from) {\n const found = source.indexOf(\"]\", from);\n bracketAt = found === -1 ? source.length : found;\n }\n return bracketAt;\n };\n\n const autolinkStop = (from: number): number => {\n if (stopAt < from) {\n stops.lastIndex = from;\n stopAt = stops.exec(source)?.index ?? source.length;\n }\n return stopAt;\n };\n\n const allowed = (guard: InlineGuard | undefined): boolean => {\n if (!guard) return true;\n if (!source.startsWith(guard.opener, i)) return false;\n if (guard.kind === \"autolink\") return source[autolinkStop(i + 1)] === \">\";\n const close = closingBracket(i + guard.opener.length);\n return source[close + 1] === \"(\" && close !== deadTailAt;\n };\n\n while (i < source.length) {\n if (source[i] === \"\\\\\" && i + 1 < source.length && source[i + 1] !== \"\\n\") {\n text += source[i + 1];\n i += 2;\n continue;\n }\n\n if (source[i] === \" \") {\n let end = i;\n while (source[end] === \" \") end += 1;\n if (end - i >= 2 && source[end] === \"\\n\") {\n flush();\n nodes.push({ type: \"break\" });\n i = end + 1;\n } else {\n text += source.slice(i, end);\n i = end;\n }\n continue;\n }\n\n if (!INLINE_FIRST.has(source[i])) {\n text += source[i];\n i += 1;\n continue;\n }\n\n const rest = source.slice(i);\n let matched = false;\n for (const rule of INLINE_RULES) {\n if (rule.first !== source[i] || !allowed(rule.guard)) continue;\n const match = rule.pattern.exec(rest);\n if (!match) {\n if (rule.guard?.kind === \"link\") {\n deadTailAt = closingBracket(i + rule.guard.opener.length);\n }\n continue;\n }\n const node = rule.build(match);\n if (!node) continue;\n flush();\n nodes.push(node);\n i += match[0].length;\n matched = true;\n break;\n }\n if (matched) continue;\n\n text += source[i];\n i += 1;\n }\n\n flush();\n return nodes;\n}\n"],"mappings":"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"}
|
|
@@ -1,121 +1,141 @@
|
|
|
1
|
-
import { safeImageUrl as e, safeLinkUrl as t } from "
|
|
1
|
+
import { safeImageUrl as e, safeLinkUrl as t } from "../../utils/safe-url.js";
|
|
2
2
|
//#region src/components/Markdown/markdown-parse.ts
|
|
3
|
-
var n = /^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)
|
|
3
|
+
var n = /^\s{0,3}(`{3,}|~{3,})\s*([\w+-]*)$/, r = /^\s{0,3}(#{1,6})\s+/, i = /^\s{0,3}([-*_])\s*(?:\1\s*){2,}$/, a = /^\s{0,3}>\s?(.*)$/, o = /^(\s*)([-*+])\s+(.*)$/, s = /^(\s*)(\d{1,9})[.)]\s+(.*)$/, c = /^\s{0,3}\|[\s|:-]*$|^\s{0,3}:?-+:?(\s*\|\s*:?-+:?)+\s*\|?$/, l = 32;
|
|
4
4
|
function u(e) {
|
|
5
|
-
|
|
5
|
+
let t = r.exec(e);
|
|
6
|
+
if (!t) return null;
|
|
7
|
+
let n = e.slice(t[0].length).trimEnd(), i = n.length;
|
|
8
|
+
for (; i > 0 && n[i - 1] === "#";) --i;
|
|
9
|
+
return n = n.slice(0, i).trimEnd(), {
|
|
10
|
+
level: t[1].length,
|
|
11
|
+
text: n
|
|
12
|
+
};
|
|
6
13
|
}
|
|
7
14
|
function d(e) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
15
|
+
return e.endsWith(" ");
|
|
16
|
+
}
|
|
17
|
+
function f(e) {
|
|
18
|
+
return p(e.replace(/\r\n?/g, "\n").split("\n"), 0);
|
|
19
|
+
}
|
|
20
|
+
function p(e, t) {
|
|
21
|
+
if (t >= l) {
|
|
22
|
+
let t = e.map((e) => e.trim()).filter(Boolean).join("\n");
|
|
23
|
+
return t ? [{
|
|
24
|
+
type: "paragraph",
|
|
25
|
+
children: b(t)
|
|
26
|
+
}] : [];
|
|
27
|
+
}
|
|
28
|
+
let r = [], f = 0;
|
|
29
|
+
for (; f < e.length;) {
|
|
30
|
+
let l = e[f];
|
|
31
|
+
if (l.trim() === "") {
|
|
32
|
+
f += 1;
|
|
13
33
|
continue;
|
|
14
34
|
}
|
|
15
|
-
let g = n.exec(
|
|
35
|
+
let g = n.exec(l.trimEnd());
|
|
16
36
|
if (g) {
|
|
17
|
-
let
|
|
18
|
-
for (
|
|
19
|
-
|
|
37
|
+
let t = g[1][0], n = [];
|
|
38
|
+
for (f += 1; f < e.length && !RegExp(`^\\s{0,3}${t}{3,}\\s*$`).test(e[f]);) n.push(e[f]), f += 1;
|
|
39
|
+
f += 1, r.push({
|
|
20
40
|
type: "code",
|
|
21
41
|
language: g[2] || null,
|
|
22
|
-
value:
|
|
42
|
+
value: n.join("\n")
|
|
23
43
|
});
|
|
24
44
|
continue;
|
|
25
45
|
}
|
|
26
|
-
let v =
|
|
46
|
+
let v = u(l);
|
|
27
47
|
if (v) {
|
|
28
|
-
|
|
48
|
+
r.push({
|
|
29
49
|
type: "heading",
|
|
30
|
-
level: v
|
|
31
|
-
children:
|
|
32
|
-
}),
|
|
50
|
+
level: v.level,
|
|
51
|
+
children: b(v.text)
|
|
52
|
+
}), f += 1;
|
|
33
53
|
continue;
|
|
34
54
|
}
|
|
35
|
-
if (i.test(
|
|
36
|
-
|
|
55
|
+
if (i.test(l)) {
|
|
56
|
+
r.push({ type: "rule" }), f += 1;
|
|
37
57
|
continue;
|
|
38
58
|
}
|
|
39
|
-
if (a.test(
|
|
59
|
+
if (a.test(l)) {
|
|
40
60
|
let n = [];
|
|
41
|
-
for (;
|
|
42
|
-
let t = a.exec(e[
|
|
61
|
+
for (; f < e.length;) {
|
|
62
|
+
let t = a.exec(e[f]);
|
|
43
63
|
if (t) {
|
|
44
|
-
n.push(t[1]),
|
|
64
|
+
n.push(t[1]), f += 1;
|
|
45
65
|
continue;
|
|
46
66
|
}
|
|
47
|
-
if (e[
|
|
48
|
-
n.push(e[
|
|
67
|
+
if (e[f].trim() === "") break;
|
|
68
|
+
n.push(e[f]), f += 1;
|
|
49
69
|
}
|
|
50
|
-
|
|
70
|
+
r.push({
|
|
51
71
|
type: "quote",
|
|
52
|
-
children:
|
|
72
|
+
children: p(n, t + 1)
|
|
53
73
|
});
|
|
54
74
|
continue;
|
|
55
75
|
}
|
|
56
|
-
if (o.test(
|
|
57
|
-
let [n,
|
|
58
|
-
|
|
76
|
+
if (o.test(l) || s.test(l)) {
|
|
77
|
+
let [n, i] = h(e, f, t);
|
|
78
|
+
r.push(n), f = i;
|
|
59
79
|
continue;
|
|
60
80
|
}
|
|
61
|
-
if (
|
|
62
|
-
let [
|
|
63
|
-
|
|
81
|
+
if (f + 1 < e.length && l.includes("|") && c.test(e[f + 1].trimEnd())) {
|
|
82
|
+
let [t, n] = _(e, f);
|
|
83
|
+
r.push(t), f = n;
|
|
64
84
|
continue;
|
|
65
85
|
}
|
|
66
|
-
let y = [],
|
|
67
|
-
for (;
|
|
68
|
-
let t = e[
|
|
69
|
-
if (
|
|
70
|
-
y.push(t.trim()),
|
|
86
|
+
let y = [], x = [];
|
|
87
|
+
for (; f < e.length && e[f].trim() !== "";) {
|
|
88
|
+
let t = e[f];
|
|
89
|
+
if (u(t) || i.test(t) || n.test(t.trimEnd()) || a.test(t) || o.test(t) || s.test(t)) break;
|
|
90
|
+
y.push(t.trim()), x.push(d(t)), f += 1;
|
|
71
91
|
}
|
|
72
|
-
y.length > 0 &&
|
|
92
|
+
y.length > 0 && r.push({
|
|
73
93
|
type: "paragraph",
|
|
74
|
-
children:
|
|
94
|
+
children: b(m(y, x))
|
|
75
95
|
});
|
|
76
96
|
}
|
|
77
|
-
return
|
|
97
|
+
return r;
|
|
78
98
|
}
|
|
79
|
-
function
|
|
99
|
+
function m(e, t) {
|
|
80
100
|
return e.map((n, r) => t[r] && r < e.length - 1 ? `${n} ` : n).join("\n");
|
|
81
101
|
}
|
|
82
|
-
function
|
|
83
|
-
let
|
|
84
|
-
|
|
102
|
+
function h(e, t, n) {
|
|
103
|
+
let r = o.exec(e[t]), i = s.exec(e[t]), a = i !== null, c = ((i ?? r)?.[1] ?? "").length, l = a ? Number(i?.[2] ?? 1) : 1, u = [], d = [], f = t, m = () => {
|
|
104
|
+
d.length !== 0 && (u.push(p(d, n + 1)), d = []);
|
|
85
105
|
};
|
|
86
106
|
for (; f < e.length;) {
|
|
87
107
|
let t = e[f];
|
|
88
108
|
if (t.trim() === "") {
|
|
89
109
|
if (f + 1 < e.length && e[f + 1].trim() !== "") {
|
|
90
|
-
|
|
110
|
+
d.push(""), f += 1;
|
|
91
111
|
continue;
|
|
92
112
|
}
|
|
93
113
|
break;
|
|
94
114
|
}
|
|
95
|
-
let n = o.exec(t), r = s.exec(t),
|
|
96
|
-
if (
|
|
97
|
-
if (!(
|
|
98
|
-
|
|
115
|
+
let n = o.exec(t), r = s.exec(t), i = n ?? r;
|
|
116
|
+
if (i && i[1].length <= c) {
|
|
117
|
+
if (!(a ? r : n)) break;
|
|
118
|
+
m(), d.push(i[3]), f += 1;
|
|
99
119
|
continue;
|
|
100
120
|
}
|
|
101
|
-
if (
|
|
102
|
-
|
|
121
|
+
if (i) {
|
|
122
|
+
d.push(t.slice(c)), f += 1;
|
|
103
123
|
continue;
|
|
104
124
|
}
|
|
105
|
-
if (t.search(/\S/) >
|
|
106
|
-
|
|
125
|
+
if (t.search(/\S/) > c) {
|
|
126
|
+
d.push(t.trim()), f += 1;
|
|
107
127
|
continue;
|
|
108
128
|
}
|
|
109
129
|
break;
|
|
110
130
|
}
|
|
111
|
-
return
|
|
131
|
+
return m(), [{
|
|
112
132
|
type: "list",
|
|
113
|
-
ordered:
|
|
114
|
-
start:
|
|
115
|
-
items:
|
|
133
|
+
ordered: a,
|
|
134
|
+
start: l,
|
|
135
|
+
items: u
|
|
116
136
|
}, f];
|
|
117
137
|
}
|
|
118
|
-
function
|
|
138
|
+
function g(e) {
|
|
119
139
|
let t = e.trim().replace(/^\|/, "").replace(/\|$/, ""), n = [], r = "";
|
|
120
140
|
for (let e = 0; e < t.length; e += 1) {
|
|
121
141
|
if (t[e] === "\\" && t[e + 1] === "|") {
|
|
@@ -130,21 +150,26 @@ function m(e) {
|
|
|
130
150
|
}
|
|
131
151
|
return n.push(r), n.map((e) => e.trim());
|
|
132
152
|
}
|
|
133
|
-
function
|
|
134
|
-
let n =
|
|
153
|
+
function _(e, t) {
|
|
154
|
+
let n = g(e[t]), r = g(e[t + 1]).map((e) => {
|
|
135
155
|
let t = e.startsWith(":"), n = e.endsWith(":");
|
|
136
156
|
return t && n ? "center" : n ? "right" : t ? "left" : null;
|
|
137
157
|
}), i = [], a = t + 2;
|
|
138
|
-
for (; a < e.length && e[a].trim() !== "" && e[a].includes("|");) i.push(
|
|
158
|
+
for (; a < e.length && e[a].trim() !== "" && e[a].includes("|");) i.push(g(e[a]).map(b)), a += 1;
|
|
139
159
|
return [{
|
|
140
160
|
type: "table",
|
|
141
161
|
align: r,
|
|
142
|
-
head: n.map(
|
|
162
|
+
head: n.map(b),
|
|
143
163
|
rows: i
|
|
144
164
|
}, a];
|
|
145
165
|
}
|
|
146
|
-
var
|
|
166
|
+
var v = [
|
|
147
167
|
{
|
|
168
|
+
guard: {
|
|
169
|
+
kind: "link",
|
|
170
|
+
opener: "!["
|
|
171
|
+
},
|
|
172
|
+
first: "!",
|
|
148
173
|
pattern: /^!\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
|
|
149
174
|
build: (t) => {
|
|
150
175
|
let n = e(t[2]);
|
|
@@ -159,9 +184,14 @@ var g = [
|
|
|
159
184
|
}
|
|
160
185
|
},
|
|
161
186
|
{
|
|
187
|
+
guard: {
|
|
188
|
+
kind: "link",
|
|
189
|
+
opener: "["
|
|
190
|
+
},
|
|
191
|
+
first: "[",
|
|
162
192
|
pattern: /^\[([^\]]*)\]\(((?:[^()\s]|\([^()\s]*\))+)(?:\s+"[^"]*")?\)/,
|
|
163
193
|
build: (e) => {
|
|
164
|
-
let n = t(e[2]), r =
|
|
194
|
+
let n = t(e[2]), r = b(e[1]);
|
|
165
195
|
return n ? {
|
|
166
196
|
type: "link",
|
|
167
197
|
href: n,
|
|
@@ -173,6 +203,7 @@ var g = [
|
|
|
173
203
|
}
|
|
174
204
|
},
|
|
175
205
|
{
|
|
206
|
+
first: "`",
|
|
176
207
|
pattern: /^`([^`]+)`/,
|
|
177
208
|
build: (e) => ({
|
|
178
209
|
type: "code",
|
|
@@ -180,41 +211,51 @@ var g = [
|
|
|
180
211
|
})
|
|
181
212
|
},
|
|
182
213
|
{
|
|
214
|
+
first: "*",
|
|
183
215
|
pattern: /^\*\*([\s\S]+?)\*\*/,
|
|
184
216
|
build: (e) => ({
|
|
185
217
|
type: "strong",
|
|
186
|
-
children:
|
|
218
|
+
children: b(e[1])
|
|
187
219
|
})
|
|
188
220
|
},
|
|
189
221
|
{
|
|
222
|
+
first: "_",
|
|
190
223
|
pattern: /^__([\s\S]+?)__/,
|
|
191
224
|
build: (e) => ({
|
|
192
225
|
type: "strong",
|
|
193
|
-
children:
|
|
226
|
+
children: b(e[1])
|
|
194
227
|
})
|
|
195
228
|
},
|
|
196
229
|
{
|
|
230
|
+
first: "~",
|
|
197
231
|
pattern: /^~~([\s\S]+?)~~/,
|
|
198
232
|
build: (e) => ({
|
|
199
233
|
type: "del",
|
|
200
|
-
children:
|
|
234
|
+
children: b(e[1])
|
|
201
235
|
})
|
|
202
236
|
},
|
|
203
237
|
{
|
|
238
|
+
first: "*",
|
|
204
239
|
pattern: /^\*([^*\n]+)\*/,
|
|
205
240
|
build: (e) => ({
|
|
206
241
|
type: "em",
|
|
207
|
-
children:
|
|
242
|
+
children: b(e[1])
|
|
208
243
|
})
|
|
209
244
|
},
|
|
210
245
|
{
|
|
246
|
+
first: "_",
|
|
211
247
|
pattern: /^_([^_\n]+)_/,
|
|
212
248
|
build: (e) => ({
|
|
213
249
|
type: "em",
|
|
214
|
-
children:
|
|
250
|
+
children: b(e[1])
|
|
215
251
|
})
|
|
216
252
|
},
|
|
217
253
|
{
|
|
254
|
+
guard: {
|
|
255
|
+
kind: "autolink",
|
|
256
|
+
opener: "<"
|
|
257
|
+
},
|
|
258
|
+
first: "<",
|
|
218
259
|
pattern: /^<((?:https?:\/\/|mailto:)[^>\s]+)>/,
|
|
219
260
|
build: (e) => {
|
|
220
261
|
let n = t(e[1]);
|
|
@@ -232,41 +273,64 @@ var g = [
|
|
|
232
273
|
}
|
|
233
274
|
},
|
|
234
275
|
{
|
|
235
|
-
|
|
236
|
-
build: () => ({ type: "break" })
|
|
237
|
-
},
|
|
238
|
-
{
|
|
276
|
+
first: "\\",
|
|
239
277
|
pattern: /^\\\n/,
|
|
240
278
|
build: () => ({ type: "break" })
|
|
241
279
|
}
|
|
242
|
-
];
|
|
243
|
-
function
|
|
244
|
-
let t = [], n = "", r = 0, i = () => {
|
|
280
|
+
], y = new Set(v.map((e) => e.first));
|
|
281
|
+
function b(e) {
|
|
282
|
+
let t = [], n = "", r = 0, i = -1, a = -1, o = -1, s = /[>\s]/g, c = () => {
|
|
245
283
|
n && t.push({
|
|
246
284
|
type: "text",
|
|
247
285
|
value: n
|
|
248
286
|
}), n = "";
|
|
287
|
+
}, l = (t) => {
|
|
288
|
+
if (i < t) {
|
|
289
|
+
let n = e.indexOf("]", t);
|
|
290
|
+
i = n === -1 ? e.length : n;
|
|
291
|
+
}
|
|
292
|
+
return i;
|
|
293
|
+
}, u = (t) => (o < t && (s.lastIndex = t, o = s.exec(e)?.index ?? e.length), o), d = (t) => {
|
|
294
|
+
if (!t) return !0;
|
|
295
|
+
if (!e.startsWith(t.opener, r)) return !1;
|
|
296
|
+
if (t.kind === "autolink") return e[u(r + 1)] === ">";
|
|
297
|
+
let n = l(r + t.opener.length);
|
|
298
|
+
return e[n + 1] === "(" && n !== a;
|
|
249
299
|
};
|
|
250
300
|
for (; r < e.length;) {
|
|
251
301
|
if (e[r] === "\\" && r + 1 < e.length && e[r + 1] !== "\n") {
|
|
252
302
|
n += e[r + 1], r += 2;
|
|
253
303
|
continue;
|
|
254
304
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
305
|
+
if (e[r] === " ") {
|
|
306
|
+
let i = r;
|
|
307
|
+
for (; e[i] === " ";) i += 1;
|
|
308
|
+
i - r >= 2 && e[i] === "\n" ? (c(), t.push({ type: "break" }), r = i + 1) : (n += e.slice(r, i), r = i);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (!y.has(e[r])) {
|
|
312
|
+
n += e[r], r += 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
let i = e.slice(r), o = !1;
|
|
316
|
+
for (let n of v) {
|
|
317
|
+
if (n.first !== e[r] || !d(n.guard)) continue;
|
|
318
|
+
let s = n.pattern.exec(i);
|
|
319
|
+
if (!s) {
|
|
320
|
+
n.guard?.kind === "link" && (a = l(r + n.guard.opener.length));
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
let u = n.build(s);
|
|
324
|
+
if (u) {
|
|
325
|
+
c(), t.push(u), r += s[0].length, o = !0;
|
|
262
326
|
break;
|
|
263
327
|
}
|
|
264
328
|
}
|
|
265
329
|
o || (n += e[r], r += 1);
|
|
266
330
|
}
|
|
267
|
-
return
|
|
331
|
+
return c(), t;
|
|
268
332
|
}
|
|
269
333
|
//#endregion
|
|
270
|
-
export {
|
|
334
|
+
export { b as parseInline, f as parseMarkdown };
|
|
271
335
|
|
|
272
336
|
//# sourceMappingURL=markdown-parse.js.map
|