@k2b/ui 0.17.0 → 0.18.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/dist/browser/chat/ChatComposer.js +261 -274
- package/dist/browser/chat/ChatComposer.js.map +1 -1
- package/dist/browser/chat/ChatPrimitives.js +9 -6
- package/dist/browser/chat/ChatPrimitives.js.map +1 -1
- package/dist/browser/content/FileView.js +62 -36
- package/dist/browser/content/FileView.js.map +1 -1
- package/dist/browser/content/MarkdownView.js +9 -3
- package/dist/browser/content/MarkdownView.js.map +1 -1
- package/dist/browser/content/VideoPlayer.js +279 -0
- package/dist/browser/content/VideoPlayer.js.map +1 -0
- package/dist/browser/content/file-view-preview.js +2 -2
- package/dist/browser/content/file-view-preview.js.map +1 -1
- package/dist/browser/content/index.js +2 -0
- package/dist/browser/content/index.js.map +1 -1
- package/dist/browser/content/markdown-links.js +178 -0
- package/dist/browser/content/markdown-links.js.map +1 -0
- package/dist/browser/inputs/FileDropTarget.js +129 -0
- package/dist/browser/inputs/FileDropTarget.js.map +1 -0
- package/dist/browser/inputs/FileInputs.js +37 -11
- package/dist/browser/inputs/FileInputs.js.map +1 -1
- package/dist/browser/inputs/file-drop.js +338 -0
- package/dist/browser/inputs/file-drop.js.map +1 -0
- package/dist/browser/inputs/index.js +1 -0
- package/dist/browser/inputs/index.js.map +1 -1
- package/dist/browser/inputs/markdown/MarkdownEditor.js +72 -78
- package/dist/browser/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/browser/intl/messages.js +97 -4
- package/dist/browser/intl/messages.js.map +1 -1
- package/dist/browser/layout/AppWorkspace.js +210 -44
- package/dist/browser/layout/AppWorkspace.js.map +1 -1
- package/dist/browser/layout/Navigation.js +274 -197
- package/dist/browser/layout/Navigation.js.map +1 -1
- package/dist/browser/layout/index.js.map +1 -1
- package/dist/browser/layout/navigation-model.js +2 -0
- package/dist/browser/layout/navigation-model.js.map +1 -1
- package/dist/browser/surfaces/NoticeCard.js +47 -44
- package/dist/browser/surfaces/NoticeCard.js.map +1 -1
- package/dist/ssr/chat/ChatComposer.js +59 -48
- package/dist/ssr/chat/ChatComposer.js.map +1 -1
- package/dist/ssr/chat/ChatPrimitives.js +9 -6
- package/dist/ssr/chat/ChatPrimitives.js.map +1 -1
- package/dist/ssr/content/FileView.js +30 -6
- package/dist/ssr/content/FileView.js.map +1 -1
- package/dist/ssr/content/MarkdownView.js +9 -3
- package/dist/ssr/content/MarkdownView.js.map +1 -1
- package/dist/ssr/content/VideoPlayer.js +208 -0
- package/dist/ssr/content/VideoPlayer.js.map +1 -0
- package/dist/ssr/content/file-view-preview.js +2 -2
- package/dist/ssr/content/file-view-preview.js.map +1 -1
- package/dist/ssr/content/index.js +2 -0
- package/dist/ssr/content/index.js.map +1 -1
- package/dist/ssr/content/markdown-links.js +178 -0
- package/dist/ssr/content/markdown-links.js.map +1 -0
- package/dist/ssr/inputs/FileDropTarget.js +103 -0
- package/dist/ssr/inputs/FileDropTarget.js.map +1 -0
- package/dist/ssr/inputs/FileInputs.js +32 -10
- package/dist/ssr/inputs/FileInputs.js.map +1 -1
- package/dist/ssr/inputs/file-drop.js +338 -0
- package/dist/ssr/inputs/file-drop.js.map +1 -0
- package/dist/ssr/inputs/index.js +1 -0
- package/dist/ssr/inputs/index.js.map +1 -1
- package/dist/ssr/inputs/markdown/MarkdownEditor.js +14 -2
- package/dist/ssr/inputs/markdown/MarkdownEditor.js.map +1 -1
- package/dist/ssr/intl/messages.js +97 -4
- package/dist/ssr/intl/messages.js.map +1 -1
- package/dist/ssr/layout/AppWorkspace.js +173 -27
- package/dist/ssr/layout/AppWorkspace.js.map +1 -1
- package/dist/ssr/layout/Navigation.js +169 -122
- package/dist/ssr/layout/Navigation.js.map +1 -1
- package/dist/ssr/layout/index.js.map +1 -1
- package/dist/ssr/layout/navigation-model.js +2 -0
- package/dist/ssr/layout/navigation-model.js.map +1 -1
- package/dist/ssr/surfaces/NoticeCard.js +17 -12
- package/dist/ssr/surfaces/NoticeCard.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/chat/ChatPrimitives.d.ts +1 -1
- package/dist/types/content/VideoPlayer.d.ts +41 -0
- package/dist/types/content/index.d.ts +2 -0
- package/dist/types/content/markdown-links.d.ts +66 -0
- package/dist/types/inputs/FileDropTarget.d.ts +23 -0
- package/dist/types/inputs/FileInputs.d.ts +2 -0
- package/dist/types/inputs/file-drop.d.ts +80 -0
- package/dist/types/inputs/index.d.ts +2 -0
- package/dist/types/intl/messages.d.ts +47 -0
- package/dist/types/layout/AppWorkspace.d.ts +21 -0
- package/dist/types/layout/index.d.ts +2 -2
- package/dist/types/layout/navigation-model.d.ts +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/VideoPlayer.tsx"],"sourcesContent":["/**\n * VideoPlayer — one video with the browser's own controls inside a frame whose\n * size never depends on the video. The frame fills the height its host gives\n * it, or keeps a ratio where the host leaves the height open. The picture fits\n * inside uncropped, so a portrait reel and a wide recording both show whole and\n * nothing moves while the video loads.\n */\n\nimport { createEffect, createSignal, type JSX, Match, Switch } from \"solid-js\";\nimport { useUiMessages } from \"../intl/messages\";\nimport Placeholder from \"../surfaces/Placeholder\";\n\nexport type VideoPlayerProps = {\n /**\n * Inline address of the video. Seeking needs a server that answers `Range` requests; Safari plays nothing without.\n * Null while the host still fetches it, for example a signed URL: the frame already shows at its final size.\n */\n src: string | null;\n /** Names the video for assistive technology, for example its file name. */\n label: string;\n /** Picture shown before playback starts. Without one, the player shows the first frame. */\n poster?: string;\n /** Width divided by height of the frame where the host leaves its height open. Defaults to 16 / 9. */\n ratio?: number;\n /** CORS mode for a video on another origin. Omit to keep the browser default. */\n crossOrigin?: \"anonymous\" | \"use-credentials\";\n /**\n * Resolves a fresh address of the same video, for example a renewed signed URL. When playback fails, the player asks\n * for one and continues at the same time, playing if it was. A renewed address that fails before it shows that point,\n * or within ten seconds after, shows the fallback, so a renewed address must stay valid for longer.\n */\n renew?: () => Promise<string>;\n /** Offered with the fallback when the browser cannot play the video, typically a download. */\n fallbackAction?: JSX.Element;\n /**\n * Shown in the frame instead of the video while set, for example the host's own error with a retry when it could not\n * fetch the address. The frame keeps its size.\n */\n error?: JSX.Element;\n /** Called when the player gives up on the video and shows its fallback. */\n onFallback?: () => void;\n class?: string;\n};\n\n/** How far the arrow keys move playback, in seconds. */\nconst SEEK_STEP = 5;\n\n/**\n * How long a renewed address has to hold at the point where its predecessor failed before a failure there counts as\n * new. A video that cannot play at a point fails again within moments, also where the engine reads or decodes ahead of\n * playback; an expired address fails only after its lifetime, which for a signed URL is typically a minute or more.\n */\nconst RENEWED_GRACE_MS = 10_000;\n\n/** Safari on iOS shows no frame before playback without a poster; a media fragment makes it load and show the first one. */\nconst withFirstFrame = (src: string) => (src.includes(\"#\") ? src : `${src}#t=0.001`);\n\n/** Where focus goes when the fallback replaces the focused video. */\nconst FALLBACK_FOCUS = \".k2b-video-player__fallback :is(a[href], button:not(:disabled))\";\n\nexport function VideoPlayer(props: VideoPlayerProps) {\n const messages = useUiMessages();\n const [source, setSource] = createSignal<string | null>(props.src);\n const [failed, setFailed] = createSignal(false);\n let frame: HTMLDivElement | undefined;\n let video: HTMLVideoElement | undefined;\n /**\n * The last renewal: where to continue once the renewed address has loaded, and since when it shows that point. Until\n * it has held there for a while, a failure means the video itself does not play, not that the address expired.\n */\n let renewal: { time: number; rate: number; playing: boolean; loaded: boolean; readyAt: number | null } | null = null;\n /** Playing as the viewer meant it; a failing source may stop without a pause event. */\n let playing = false;\n\n // A new address from the host is another video: start over without resuming. It counts as new against the address\n // the player started with, because the host may change it before this effect first runs, for example on mount.\n let hostSrc = props.src;\n createEffect(() => {\n const src = props.src;\n if (src === hostSrc) return;\n hostSrc = src;\n renewal = null;\n playing = false;\n setFailed(false);\n setSource(src);\n });\n\n const videoSource = () => {\n const src = source();\n return src && !props.poster ? withFirstFrame(src) : (src ?? undefined);\n };\n\n const giveUp = () => {\n // The fallback replaces the video, so focus on the video would drop to the page; it moves into the fallback.\n const focused = video !== undefined && document.activeElement === video;\n setFailed(true);\n if (focused) (frame?.querySelector<HTMLElement>(FALLBACK_FOCUS) ?? frame)?.focus();\n props.onFallback?.();\n };\n\n const fail = async () => {\n const element = video;\n if (!element || failed()) return;\n const recent = renewal && (renewal.readyAt === null || performance.now() - renewal.readyAt < RENEWED_GRACE_MS);\n if (!props.renew || recent) return giveUp();\n const requested = props.src;\n renewal = { time: element.currentTime, rate: element.playbackRate, playing, loaded: false, readyAt: null };\n try {\n const renewed = await props.renew();\n if (requested !== props.src) return;\n if (renewed === source()) element.load();\n else setSource(renewed);\n } catch {\n if (requested === props.src) giveUp();\n }\n };\n\n const loaded = () => {\n const element = video;\n if (!element) return;\n // Sound in an empty frame: the browser cannot decode the picture, for example HEVC outside Safari. A fresh address\n // does not change that.\n if (element.videoWidth === 0 && element.videoHeight === 0) return giveUp();\n if (!renewal || renewal.loaded) return;\n renewal.loaded = true;\n if (renewal.time > 0) element.currentTime = renewal.time;\n element.playbackRate = renewal.rate;\n if (renewal.playing) void element.play().catch(() => undefined);\n };\n\n /** The renewed address holds once the video shows the point where it continues. */\n const ready = (event: Event & { currentTarget: HTMLVideoElement }) => {\n const element = event.currentTarget;\n if (renewal?.loaded && renewal.readyAt === null && !element.seeking && element.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA)\n renewal.readyAt = performance.now();\n };\n\n const fullscreen = (element: HTMLVideoElement) => {\n if (document.fullscreenElement) return void document.exitFullscreen().catch(() => undefined);\n if (element.requestFullscreen) return void element.requestFullscreen().catch(() => undefined);\n // Safari on iOS has no element fullscreen for video, only its own player.\n if (\"webkitEnterFullscreen\" in element && typeof element.webkitEnterFullscreen === \"function\") element.webkitEnterFullscreen();\n };\n\n const toggle = (element: HTMLVideoElement) => {\n if (element.paused) void element.play().catch(() => undefined);\n else element.pause();\n };\n\n /** Whether the video was paused when Space went down; null while no Space press is open. */\n let spacePaused: boolean | null = null;\n\n // Native controls differ per engine: WebKit has no keyboard playback at all, so the player gives every engine the\n // same keys. The keys reach the player only while the video itself has focus, not one of the browser's buttons.\n const onKeyDown = (event: KeyboardEvent & { currentTarget: HTMLVideoElement }) => {\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n const element = event.currentTarget;\n switch (event.key) {\n case \" \":\n // Chromium toggles on release whatever the page does, so Space waits for it and toggles only if it did not.\n if (!event.repeat) spacePaused = element.paused;\n break;\n case \"k\":\n toggle(element);\n break;\n case \"ArrowLeft\":\n element.currentTime = Math.max(0, element.currentTime - SEEK_STEP);\n break;\n case \"ArrowRight\":\n element.currentTime = Number.isFinite(element.duration)\n ? Math.min(element.duration, element.currentTime + SEEK_STEP)\n : element.currentTime + SEEK_STEP;\n break;\n case \"m\":\n element.muted = !element.muted;\n break;\n case \"f\":\n fullscreen(element);\n break;\n default:\n return;\n }\n event.preventDefault();\n };\n const onKeyUp = (event: KeyboardEvent & { currentTarget: HTMLVideoElement }) => {\n if (event.key !== \" \" || spacePaused === null) return;\n const element = event.currentTarget;\n const paused = spacePaused;\n spacePaused = null;\n setTimeout(() => {\n if (element.paused === paused) toggle(element);\n });\n };\n\n return (\n <div\n ref={frame}\n class={`k2b-video-player ${props.class ?? \"\"}`}\n data-state={failed() ? \"failed\" : undefined}\n tabIndex={failed() ? -1 : undefined}\n style={{ \"aspect-ratio\": String(props.ratio && props.ratio > 0 ? props.ratio : 16 / 9) }}\n >\n <Switch fallback={<Placeholder class=\"k2b-video-player__fallback\" state=\"loading\" />}>\n <Match when={props.error}>{(error) => <div class=\"k2b-video-player__fallback\">{error()}</div>}</Match>\n <Match when={failed()}>\n <Placeholder\n class=\"k2b-video-player__fallback\"\n state=\"error\"\n icon=\"ti ti-video-off\"\n title={messages().videoCannotPlay}\n description={messages().videoCannotPlayDescription}\n action={props.fallbackAction}\n />\n </Match>\n <Match when={source()}>\n <video\n ref={video}\n class=\"k2b-video-player__video\"\n src={videoSource()}\n poster={props.poster}\n crossOrigin={props.crossOrigin}\n controls\n playsinline\n preload=\"metadata\"\n tabIndex={0}\n aria-label={props.label}\n onKeyDown={onKeyDown}\n onKeyUp={onKeyUp}\n onPlay={() => {\n playing = true;\n }}\n onPause={(event) => {\n // A video that stops because its source failed has not been paused by the viewer.\n if (!event.currentTarget.error) playing = false;\n }}\n onEnded={() => {\n playing = false;\n }}\n onLoadedMetadata={loaded}\n onLoadedData={ready}\n onSeeked={ready}\n onCanPlay={ready}\n onError={() => void fail()}\n >\n {messages().videoUnsupported}\n </video>\n </Match>\n </Switch>\n </div>\n );\n}\n\nexport default VideoPlayer;\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -13,11 +13,11 @@ const maxPreviewBytes = {
|
|
|
13
13
|
// SVG remains inert because FileView renders every image through <img>; never inject its markup into the DOM.
|
|
14
14
|
const imageMediaTypes = new Set(["image/avif", "image/bmp", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "image/x-icon"]);
|
|
15
15
|
const audioMediaTypes = new Set(["audio/aac", "audio/flac", "audio/mp4", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "audio/x-wav"]);
|
|
16
|
-
const videoMediaTypes = new Set(["video/mp4", "video/ogg", "video/webm"]);
|
|
16
|
+
const videoMediaTypes = new Set(["video/mp4", "video/ogg", "video/quicktime", "video/webm", "video/x-m4v"]);
|
|
17
17
|
const markdownExtensions = new Set(["md", "markdown"]);
|
|
18
18
|
const imageExtensions = new Set(["avif", "bmp", "gif", "ico", "jpeg", "jpg", "png", "svg", "webp"]);
|
|
19
19
|
const audioExtensions = new Set(["aac", "flac", "m4a", "mp3", "oga", "ogg", "wav", "weba"]);
|
|
20
|
-
const videoExtensions = new Set(["m4v", "mp4", "ogv", "webm"]);
|
|
20
|
+
const videoExtensions = new Set(["m4v", "mov", "mp4", "ogv", "webm"]);
|
|
21
21
|
const textExtensions = new Set(["c", "conf", "cpp", "css", "env", "go", "h", "html", "ini", "java", "js", "jsx", "log", "mjs", "php", "py", "rb", "rs", "sh", "sql", "toml", "ts", "tsx", "txt", "xml", "yaml", "yml"]);
|
|
22
22
|
export const fileViewExtension = path => {
|
|
23
23
|
const cleanPath = path.split(/[?#]/, 1)[0] ?? path;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["content/file-view-preview.ts"],"sourcesContent":["export type FileViewFile = {\n path: string;\n mediaType?: string;\n size?: number;\n};\n\nexport type FileViewPreviewKind = \"markdown\" | \"image\" | \"pdf\" | \"json\" | \"delimited-text\" | \"audio\" | \"video\" | \"text\";\n\nconst MEBIBYTE = 1024 * 1024;\n\nconst maxPreviewBytes: Record<FileViewPreviewKind, number> = {\n markdown: 2 * MEBIBYTE,\n image: 25 * MEBIBYTE,\n pdf: 50 * MEBIBYTE,\n json: 2 * MEBIBYTE,\n \"delimited-text\": 2 * MEBIBYTE,\n audio: 50 * MEBIBYTE,\n video: 50 * MEBIBYTE,\n text: 2 * MEBIBYTE,\n};\n\n// SVG remains inert because FileView renders every image through <img>; never inject its markup into the DOM.\nconst imageMediaTypes = new Set([\n \"image/avif\",\n \"image/bmp\",\n \"image/gif\",\n \"image/jpeg\",\n \"image/png\",\n \"image/svg+xml\",\n \"image/webp\",\n \"image/x-icon\",\n]);\nconst audioMediaTypes = new Set([\n \"audio/aac\",\n \"audio/flac\",\n \"audio/mp4\",\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/x-wav\",\n]);\nconst videoMediaTypes = new Set([\"video/mp4\", \"video/ogg\", \"video/webm\"]);\n\nconst markdownExtensions = new Set([\"md\", \"markdown\"]);\nconst imageExtensions = new Set([\"avif\", \"bmp\", \"gif\", \"ico\", \"jpeg\", \"jpg\", \"png\", \"svg\", \"webp\"]);\nconst audioExtensions = new Set([\"aac\", \"flac\", \"m4a\", \"mp3\", \"oga\", \"ogg\", \"wav\", \"weba\"]);\nconst videoExtensions = new Set([\"m4v\", \"mp4\", \"ogv\", \"webm\"]);\nconst textExtensions = new Set([\n \"c\",\n \"conf\",\n \"cpp\",\n \"css\",\n \"env\",\n \"go\",\n \"h\",\n \"html\",\n \"ini\",\n \"java\",\n \"js\",\n \"jsx\",\n \"log\",\n \"mjs\",\n \"php\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"txt\",\n \"xml\",\n \"yaml\",\n \"yml\",\n]);\n\nexport const fileViewExtension = (path: string): string => {\n const cleanPath = path.split(/[?#]/, 1)[0] ?? path;\n const name = cleanPath.slice(cleanPath.lastIndexOf(\"/\") + 1);\n const dot = name.lastIndexOf(\".\");\n return dot > 0 ? name.slice(dot + 1).toLowerCase() : \"\";\n};\n\nconst normalizedMediaType = (mediaType?: string): string => mediaType?.split(\";\", 1)[0]?.trim().toLowerCase() ?? \"\";\n\nconst previewKindByType = (file: FileViewFile): FileViewPreviewKind | null => {\n const extension = fileViewExtension(file.path);\n const mediaType = normalizedMediaType(file.mediaType);\n\n if (mediaType === \"text/markdown\" || markdownExtensions.has(extension)) return \"markdown\";\n if (mediaType === \"application/json\" || mediaType.endsWith(\"+json\") || extension === \"json\") return \"json\";\n if (mediaType === \"text/csv\" || mediaType === \"text/tab-separated-values\" || extension === \"csv\" || extension === \"tsv\") {\n return \"delimited-text\";\n }\n if (mediaType === \"application/pdf\" || extension === \"pdf\") return \"pdf\";\n if (imageMediaTypes.has(mediaType) || imageExtensions.has(extension)) return \"image\";\n if (audioMediaTypes.has(mediaType) || audioExtensions.has(extension)) return \"audio\";\n if (videoMediaTypes.has(mediaType) || videoExtensions.has(extension)) return \"video\";\n if (\n mediaType.startsWith(\"text/\") ||\n mediaType === \"application/javascript\" ||\n mediaType === \"application/sql\" ||\n mediaType === \"application/xml\" ||\n textExtensions.has(extension)\n ) {\n return \"text\";\n }\n return null;\n};\n\n/**\n * Returns the built-in preview kind when FileView can render the file within\n * its bounded client-side limits. Custom renderers remain registry-driven.\n */\nexport const getFileViewPreviewKind = (file: FileViewFile): FileViewPreviewKind | null => {\n const kind = previewKindByType(file);\n if (!kind) return null;\n if (file.size !== undefined && (!Number.isFinite(file.size) || file.size < 0 || file.size > maxPreviewBytes[kind])) {\n return null;\n }\n return kind;\n};\n\n/** Use this to expose preview actions only for files FileView can render. */\nexport const canPreviewFile = (file: FileViewFile): boolean => getFileViewPreviewKind(file) !== null;\n\nexport type DelimitedTextPreview = {\n rows: string[][];\n truncated: boolean;\n totalRows: number;\n columnsTruncated: boolean;\n};\n\nexport const parseDelimitedText = (\n input: string,\n delimiter: string,\n limits: { rows?: number; columns?: number; offset?: number } = {},\n): DelimitedTextPreview => {\n const maxRows = Math.max(1, limits.rows ?? 201);\n const maxColumns = Math.max(1, limits.columns ?? 50);\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let quoted = false;\n let truncated = false;\n let totalRows = 0;\n let columnsTruncated = false;\n\n const pushField = () => {\n if (row.length < maxColumns) row.push(field);\n else {\n truncated = true;\n columnsTruncated = true;\n }\n field = \"\";\n };\n const pushRow = () => {\n if (rows.length < maxRows && (totalRows === 0 || totalRows > (limits.offset ?? 0))) rows.push(row);\n else truncated = true;\n totalRows++;\n row = [];\n };\n\n for (let index = 0; index < input.length; index += 1) {\n const character = input[index]!;\n if (quoted) {\n if (character === '\"' && input[index + 1] === '\"') {\n field += '\"';\n index += 1;\n } else if (character === '\"') {\n quoted = false;\n } else {\n field += character;\n }\n continue;\n }\n\n if (character === '\"' && field.length === 0) quoted = true;\n else if (character === delimiter) pushField();\n else if (character === \"\\n\" || character === \"\\r\") {\n pushField();\n pushRow();\n if (character === \"\\r\" && input[index + 1] === \"\\n\") index += 1;\n } else {\n field += character;\n }\n }\n\n if (field.length > 0 || row.length > 0) {\n pushField();\n pushRow();\n }\n\n return { rows, truncated, totalRows, columnsTruncated };\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sources":["content/file-view-preview.ts"],"sourcesContent":["export type FileViewFile = {\n path: string;\n mediaType?: string;\n size?: number;\n};\n\nexport type FileViewPreviewKind = \"markdown\" | \"image\" | \"pdf\" | \"json\" | \"delimited-text\" | \"audio\" | \"video\" | \"text\";\n\nconst MEBIBYTE = 1024 * 1024;\n\nconst maxPreviewBytes: Record<FileViewPreviewKind, number> = {\n markdown: 2 * MEBIBYTE,\n image: 25 * MEBIBYTE,\n pdf: 50 * MEBIBYTE,\n json: 2 * MEBIBYTE,\n \"delimited-text\": 2 * MEBIBYTE,\n audio: 50 * MEBIBYTE,\n video: 50 * MEBIBYTE,\n text: 2 * MEBIBYTE,\n};\n\n// SVG remains inert because FileView renders every image through <img>; never inject its markup into the DOM.\nconst imageMediaTypes = new Set([\n \"image/avif\",\n \"image/bmp\",\n \"image/gif\",\n \"image/jpeg\",\n \"image/png\",\n \"image/svg+xml\",\n \"image/webp\",\n \"image/x-icon\",\n]);\nconst audioMediaTypes = new Set([\n \"audio/aac\",\n \"audio/flac\",\n \"audio/mp4\",\n \"audio/mpeg\",\n \"audio/ogg\",\n \"audio/wav\",\n \"audio/webm\",\n \"audio/x-wav\",\n]);\nconst videoMediaTypes = new Set([\"video/mp4\", \"video/ogg\", \"video/quicktime\", \"video/webm\", \"video/x-m4v\"]);\n\nconst markdownExtensions = new Set([\"md\", \"markdown\"]);\nconst imageExtensions = new Set([\"avif\", \"bmp\", \"gif\", \"ico\", \"jpeg\", \"jpg\", \"png\", \"svg\", \"webp\"]);\nconst audioExtensions = new Set([\"aac\", \"flac\", \"m4a\", \"mp3\", \"oga\", \"ogg\", \"wav\", \"weba\"]);\nconst videoExtensions = new Set([\"m4v\", \"mov\", \"mp4\", \"ogv\", \"webm\"]);\nconst textExtensions = new Set([\n \"c\",\n \"conf\",\n \"cpp\",\n \"css\",\n \"env\",\n \"go\",\n \"h\",\n \"html\",\n \"ini\",\n \"java\",\n \"js\",\n \"jsx\",\n \"log\",\n \"mjs\",\n \"php\",\n \"py\",\n \"rb\",\n \"rs\",\n \"sh\",\n \"sql\",\n \"toml\",\n \"ts\",\n \"tsx\",\n \"txt\",\n \"xml\",\n \"yaml\",\n \"yml\",\n]);\n\nexport const fileViewExtension = (path: string): string => {\n const cleanPath = path.split(/[?#]/, 1)[0] ?? path;\n const name = cleanPath.slice(cleanPath.lastIndexOf(\"/\") + 1);\n const dot = name.lastIndexOf(\".\");\n return dot > 0 ? name.slice(dot + 1).toLowerCase() : \"\";\n};\n\nconst normalizedMediaType = (mediaType?: string): string => mediaType?.split(\";\", 1)[0]?.trim().toLowerCase() ?? \"\";\n\nconst previewKindByType = (file: FileViewFile): FileViewPreviewKind | null => {\n const extension = fileViewExtension(file.path);\n const mediaType = normalizedMediaType(file.mediaType);\n\n if (mediaType === \"text/markdown\" || markdownExtensions.has(extension)) return \"markdown\";\n if (mediaType === \"application/json\" || mediaType.endsWith(\"+json\") || extension === \"json\") return \"json\";\n if (mediaType === \"text/csv\" || mediaType === \"text/tab-separated-values\" || extension === \"csv\" || extension === \"tsv\") {\n return \"delimited-text\";\n }\n if (mediaType === \"application/pdf\" || extension === \"pdf\") return \"pdf\";\n if (imageMediaTypes.has(mediaType) || imageExtensions.has(extension)) return \"image\";\n if (audioMediaTypes.has(mediaType) || audioExtensions.has(extension)) return \"audio\";\n if (videoMediaTypes.has(mediaType) || videoExtensions.has(extension)) return \"video\";\n if (\n mediaType.startsWith(\"text/\") ||\n mediaType === \"application/javascript\" ||\n mediaType === \"application/sql\" ||\n mediaType === \"application/xml\" ||\n textExtensions.has(extension)\n ) {\n return \"text\";\n }\n return null;\n};\n\n/**\n * Returns the built-in preview kind when FileView can render the file within\n * its bounded client-side limits. Custom renderers remain registry-driven.\n */\nexport const getFileViewPreviewKind = (file: FileViewFile): FileViewPreviewKind | null => {\n const kind = previewKindByType(file);\n if (!kind) return null;\n if (file.size !== undefined && (!Number.isFinite(file.size) || file.size < 0 || file.size > maxPreviewBytes[kind])) {\n return null;\n }\n return kind;\n};\n\n/** Use this to expose preview actions only for files FileView can render. */\nexport const canPreviewFile = (file: FileViewFile): boolean => getFileViewPreviewKind(file) !== null;\n\nexport type DelimitedTextPreview = {\n rows: string[][];\n truncated: boolean;\n totalRows: number;\n columnsTruncated: boolean;\n};\n\nexport const parseDelimitedText = (\n input: string,\n delimiter: string,\n limits: { rows?: number; columns?: number; offset?: number } = {},\n): DelimitedTextPreview => {\n const maxRows = Math.max(1, limits.rows ?? 201);\n const maxColumns = Math.max(1, limits.columns ?? 50);\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let quoted = false;\n let truncated = false;\n let totalRows = 0;\n let columnsTruncated = false;\n\n const pushField = () => {\n if (row.length < maxColumns) row.push(field);\n else {\n truncated = true;\n columnsTruncated = true;\n }\n field = \"\";\n };\n const pushRow = () => {\n if (rows.length < maxRows && (totalRows === 0 || totalRows > (limits.offset ?? 0))) rows.push(row);\n else truncated = true;\n totalRows++;\n row = [];\n };\n\n for (let index = 0; index < input.length; index += 1) {\n const character = input[index]!;\n if (quoted) {\n if (character === '\"' && input[index + 1] === '\"') {\n field += '\"';\n index += 1;\n } else if (character === '\"') {\n quoted = false;\n } else {\n field += character;\n }\n continue;\n }\n\n if (character === '\"' && field.length === 0) quoted = true;\n else if (character === delimiter) pushField();\n else if (character === \"\\n\" || character === \"\\r\") {\n pushField();\n pushRow();\n if (character === \"\\r\" && input[index + 1] === \"\\n\") index += 1;\n } else {\n field += character;\n }\n }\n\n if (field.length > 0 || row.length > 0) {\n pushField();\n pushRow();\n }\n\n return { rows, truncated, totalRows, columnsTruncated };\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -20,10 +20,12 @@ export { default as LogEntriesTable } from "./LogEntriesTable";
|
|
|
20
20
|
export { default as MarkdownView, renderSafeMarkdown } from "./MarkdownView";
|
|
21
21
|
export { MessageRow, MessageSystemRow, startsMessageGroup } from "./MessageRow";
|
|
22
22
|
export { markdownInfoBlocks, renderMarkdownInfoBlock, scanMarkdownInfoBlock } from "./markdown-info-blocks";
|
|
23
|
+
export { markdownLinkReference, markdownReferenceIcon, markdownReferenceType, markdownReferenceTypeLabel, markStandaloneLinks, renderMarkdownLink } from "./markdown-links";
|
|
23
24
|
export { Pagination } from "./Pagination";
|
|
24
25
|
export { default as PdfPreview } from "./PdfPreview";
|
|
25
26
|
export { default as RangePicker } from "./RangePicker";
|
|
26
27
|
export { default as StructuredDataPreview, isStructuredDataValue } from "./StructuredDataPreview";
|
|
28
|
+
export { VideoPlayer } from "./VideoPlayer";
|
|
27
29
|
export { VirtualFeed } from "./VirtualFeed";
|
|
28
30
|
export { ZoomPanViewport } from "./ZoomPanViewport";
|
|
29
31
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["content/index.ts"],"sourcesContent":["export type {\n CalendarAttendee,\n CalendarDayBadge,\n CalendarEvent,\n CalendarEventColor,\n CalendarEventRenderContext,\n CalendarEventTimeChange,\n CalendarLabels,\n CalendarProps,\n CalendarRecurrence,\n CalendarResource,\n CalendarView,\n} from \"./Calendar\";\nexport { default as Calendar } from \"./Calendar\";\n\nexport type { ChartKind, ChartLabels, ChartProps, ChartRenderOptions } from \"./Chart\";\nexport { default as Chart } from \"./Chart\";\nexport type { ChartExplorerColumn, ChartExplorerProps } from \"./ChartExplorer\";\nexport { ChartExplorer } from \"./ChartExplorer\";\nexport type { ChartExplorerControlsProps } from \"./ChartExplorerControls\";\nexport { ChartExplorerControls } from \"./ChartExplorerControls\";\nexport { ChartFilterControls, type ChartFilterControlsProps } from \"./ChartFilterControls\";\nexport { ChartSnapshotView } from \"./ChartSnapshotView\";\nexport type { CodeDisplayLanguage, CodeDisplayProps } from \"./CodeDisplay\";\nexport { default as CodeDisplay } from \"./CodeDisplay\";\nexport { type ChartCursor, createChartCursor } from \"./chart-cursor\";\nexport type {\n ChartExplorerCharts,\n ChartExplorerController,\n ChartExplorerData,\n ChartExplorerOptions,\n ChartExplorerRequest,\n ChartExplorerRow,\n ChartExplorerSnapshot,\n} from \"./chart-explorer\";\nexport { createChartExplorer } from \"./chart-explorer\";\nexport type { ChartDatumRef, ChartSelection, ChartTooltip, ChartTooltipFormatter } from \"./chart-inspection\";\nexport type { ChartSnapshot } from \"./chart-snapshot\";\nexport { prepareChartSnapshot } from \"./chart-snapshot\";\nexport type {\n StateTimelineChartOptions,\n StateTimelineDomain,\n StateTimelineInterval,\n StateTimelineRow,\n StateTimelineState,\n} from \"./chart-state-timeline\";\nexport { type CollectionSelection, createCollectionSelection, type SelectionModifiers } from \"./collection-selection\";\nexport type {\n DataTableColumn,\n DataTableControlsProps,\n DataTableFooter,\n DataTableHeaderProps,\n DataTablePanelFooterProps,\n DataTablePanelProps,\n DataTableProps,\n DataTableRenderCell,\n DataTableRenderHeader,\n DataTableSort,\n} from \"./DataTable\";\nexport { default as DataTable } from \"./DataTable\";\nexport type { DocCodeHighlighter, DocCodeProps, DocConcept, DocNoteVariant, DocRow } from \"./Docs\";\nexport { DocCode, DocConceptGrid, DocInlineCode, DocLead, DocNote, DocPage, DocRows, DocSection } from \"./Docs\";\nexport { FileBrowserPanel, type FileBrowserPanelProps, type FileSource, openFileBrowser } from \"./FileBrowser\";\nexport { FileGrid, type FileGridProps } from \"./FileGrid\";\nexport { default as FileTree, type FileTreeActions, type FileTreeEntry, type FileTreeProps } from \"./FileTree\";\nexport {\n canPreviewFile,\n default as FileView,\n type FileViewContent,\n type FileViewFile,\n type FileViewPreviewKind,\n type FileViewProps,\n type FileViewRenderer,\n type FileViewRendererProps,\n formatFileViewSize,\n getFileViewPreviewKind,\n} from \"./FileView\";\nexport type { LightboxImage } from \"./Lightbox\";\nexport { default as Lightbox } from \"./Lightbox\";\nexport type { LogTableEntry } from \"./LogEntriesTable\";\nexport { default as LogEntriesTable } from \"./LogEntriesTable\";\nexport { default as MarkdownView, type MarkdownRenderOptions, type MarkdownViewProps, renderSafeMarkdown } from \"./MarkdownView\";\nexport type {\n MessageGroupEntry,\n MessageRowAction,\n MessageRowAttachment,\n MessageRowAuthor,\n MessageRowFile,\n MessageRowMedia,\n MessageRowProgress,\n MessageRowProps,\n MessageRowQuote,\n MessageRowReaction,\n MessageRowStatus,\n MessageRowThread,\n MessageSystemRowProps,\n} from \"./MessageRow\";\nexport { MessageRow, MessageSystemRow, startsMessageGroup } from \"./MessageRow\";\nexport {\n type MarkdownInfoBlock,\n type MarkdownInfoBlockRenderInput,\n type MarkdownInfoBlockType,\n markdownInfoBlocks,\n renderMarkdownInfoBlock,\n scanMarkdownInfoBlock,\n} from \"./markdown-info-blocks\";\nexport { Pagination, type PaginationProps } from \"./Pagination\";\nexport type { PdfPreviewProps, PdfPreviewRequest } from \"./PdfPreview\";\nexport { default as PdfPreview } from \"./PdfPreview\";\nexport type { RangeOption, RangePickerProps } from \"./RangePicker\";\nexport { default as RangePicker } from \"./RangePicker\";\nexport type { StructuredDataPreviewMode, StructuredDataPreviewProps, StructuredDataValue } from \"./StructuredDataPreview\";\nexport { default as StructuredDataPreview, isStructuredDataValue } from \"./StructuredDataPreview\";\nexport type { VirtualFeedController, VirtualFeedProps, VirtualFeedScrollOptions } from \"./VirtualFeed\";\nexport { VirtualFeed } from \"./VirtualFeed\";\nexport type { ZoomPanAction, ZoomPanFullscreen, ZoomPanViewportProps } from \"./ZoomPanViewport\";\nexport { ZoomPanViewport } from \"./ZoomPanViewport\";\n"],"mappings":"AAaA,SAAS,OAAO,IAAI,QAAQ,QAAQ,YAAY;AAGhD,SAAS,OAAO,IAAI,KAAK,QAAQ,SAAS;AAE1C,SAAS,aAAa,QAAQ,iBAAiB;AAE/C,SAAS,qBAAqB,QAAQ,yBAAyB;AAC/D,SAAS,mBAAmB,QAAuC,uBAAuB;AAC1F,SAAS,iBAAiB,QAAQ,qBAAqB;AAEvD,SAAS,OAAO,IAAI,WAAW,QAAQ,eAAe;AACtD,SAA2B,iBAAiB,QAAQ,gBAAgB;AAUpE,SAAS,mBAAmB,QAAQ,kBAAkB;AAGtD,SAAS,oBAAoB,QAAQ,kBAAkB;AAQvD,SAAmC,yBAAyB,QAAiC,wBAAwB;AAarH,SAAS,OAAO,IAAI,SAAS,QAAQ,aAAa;AAElD,SAAS,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,QAAQ,QAAQ;AAC/G,SAAS,gBAAgB,EAA+C,eAAe,QAAQ,eAAe;AAC9G,SAAS,QAAQ,QAA4B,YAAY;AACzD,SAAS,OAAO,IAAI,QAAQ,QAAsE,YAAY;AAC9G,SACE,cAAc,EACd,OAAO,IAAI,QAAQ,EAOnB,kBAAkB,EAClB,sBAAsB,QACjB,YAAY;AAEnB,SAAS,OAAO,IAAI,QAAQ,QAAQ,YAAY;AAEhD,SAAS,OAAO,IAAI,eAAe,QAAQ,mBAAmB;AAC9D,SAAS,OAAO,IAAI,YAAY,EAAsD,kBAAkB,QAAQ,gBAAgB;AAgBhI,SAAS,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,QAAQ,cAAc;AAC/E,SAIE,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,QAChB,wBAAwB;AAC/B,SAAS,UAAU,QAA8B,cAAc;AAE/D,SAAS,OAAO,IAAI,UAAU,QAAQ,cAAc;AAEpD,SAAS,OAAO,IAAI,WAAW,QAAQ,eAAe;AAEtD,SAAS,OAAO,IAAI,qBAAqB,EAAE,qBAAqB,QAAQ,yBAAyB;
|
|
1
|
+
{"version":3,"names":[],"sources":["content/index.ts"],"sourcesContent":["export type {\n CalendarAttendee,\n CalendarDayBadge,\n CalendarEvent,\n CalendarEventColor,\n CalendarEventRenderContext,\n CalendarEventTimeChange,\n CalendarLabels,\n CalendarProps,\n CalendarRecurrence,\n CalendarResource,\n CalendarView,\n} from \"./Calendar\";\nexport { default as Calendar } from \"./Calendar\";\n\nexport type { ChartKind, ChartLabels, ChartProps, ChartRenderOptions } from \"./Chart\";\nexport { default as Chart } from \"./Chart\";\nexport type { ChartExplorerColumn, ChartExplorerProps } from \"./ChartExplorer\";\nexport { ChartExplorer } from \"./ChartExplorer\";\nexport type { ChartExplorerControlsProps } from \"./ChartExplorerControls\";\nexport { ChartExplorerControls } from \"./ChartExplorerControls\";\nexport { ChartFilterControls, type ChartFilterControlsProps } from \"./ChartFilterControls\";\nexport { ChartSnapshotView } from \"./ChartSnapshotView\";\nexport type { CodeDisplayLanguage, CodeDisplayProps } from \"./CodeDisplay\";\nexport { default as CodeDisplay } from \"./CodeDisplay\";\nexport { type ChartCursor, createChartCursor } from \"./chart-cursor\";\nexport type {\n ChartExplorerCharts,\n ChartExplorerController,\n ChartExplorerData,\n ChartExplorerOptions,\n ChartExplorerRequest,\n ChartExplorerRow,\n ChartExplorerSnapshot,\n} from \"./chart-explorer\";\nexport { createChartExplorer } from \"./chart-explorer\";\nexport type { ChartDatumRef, ChartSelection, ChartTooltip, ChartTooltipFormatter } from \"./chart-inspection\";\nexport type { ChartSnapshot } from \"./chart-snapshot\";\nexport { prepareChartSnapshot } from \"./chart-snapshot\";\nexport type {\n StateTimelineChartOptions,\n StateTimelineDomain,\n StateTimelineInterval,\n StateTimelineRow,\n StateTimelineState,\n} from \"./chart-state-timeline\";\nexport { type CollectionSelection, createCollectionSelection, type SelectionModifiers } from \"./collection-selection\";\nexport type {\n DataTableColumn,\n DataTableControlsProps,\n DataTableFooter,\n DataTableHeaderProps,\n DataTablePanelFooterProps,\n DataTablePanelProps,\n DataTableProps,\n DataTableRenderCell,\n DataTableRenderHeader,\n DataTableSort,\n} from \"./DataTable\";\nexport { default as DataTable } from \"./DataTable\";\nexport type { DocCodeHighlighter, DocCodeProps, DocConcept, DocNoteVariant, DocRow } from \"./Docs\";\nexport { DocCode, DocConceptGrid, DocInlineCode, DocLead, DocNote, DocPage, DocRows, DocSection } from \"./Docs\";\nexport { FileBrowserPanel, type FileBrowserPanelProps, type FileSource, openFileBrowser } from \"./FileBrowser\";\nexport { FileGrid, type FileGridProps } from \"./FileGrid\";\nexport { default as FileTree, type FileTreeActions, type FileTreeEntry, type FileTreeProps } from \"./FileTree\";\nexport {\n canPreviewFile,\n default as FileView,\n type FileViewContent,\n type FileViewFile,\n type FileViewPreviewKind,\n type FileViewProps,\n type FileViewRenderer,\n type FileViewRendererProps,\n formatFileViewSize,\n getFileViewPreviewKind,\n} from \"./FileView\";\nexport type { LightboxImage } from \"./Lightbox\";\nexport { default as Lightbox } from \"./Lightbox\";\nexport type { LogTableEntry } from \"./LogEntriesTable\";\nexport { default as LogEntriesTable } from \"./LogEntriesTable\";\nexport { default as MarkdownView, type MarkdownRenderOptions, type MarkdownViewProps, renderSafeMarkdown } from \"./MarkdownView\";\nexport type {\n MessageGroupEntry,\n MessageRowAction,\n MessageRowAttachment,\n MessageRowAuthor,\n MessageRowFile,\n MessageRowMedia,\n MessageRowProgress,\n MessageRowProps,\n MessageRowQuote,\n MessageRowReaction,\n MessageRowStatus,\n MessageRowThread,\n MessageSystemRowProps,\n} from \"./MessageRow\";\nexport { MessageRow, MessageSystemRow, startsMessageGroup } from \"./MessageRow\";\nexport {\n type MarkdownInfoBlock,\n type MarkdownInfoBlockRenderInput,\n type MarkdownInfoBlockType,\n markdownInfoBlocks,\n renderMarkdownInfoBlock,\n scanMarkdownInfoBlock,\n} from \"./markdown-info-blocks\";\nexport {\n type MarkdownLinkInput,\n type MarkdownReference,\n type MarkdownReferenceKind,\n type MarkdownReferenceType,\n markdownLinkReference,\n markdownReferenceIcon,\n markdownReferenceType,\n markdownReferenceTypeLabel,\n markStandaloneLinks,\n renderMarkdownLink,\n} from \"./markdown-links\";\nexport { Pagination, type PaginationProps } from \"./Pagination\";\nexport type { PdfPreviewProps, PdfPreviewRequest } from \"./PdfPreview\";\nexport { default as PdfPreview } from \"./PdfPreview\";\nexport type { RangeOption, RangePickerProps } from \"./RangePicker\";\nexport { default as RangePicker } from \"./RangePicker\";\nexport type { StructuredDataPreviewMode, StructuredDataPreviewProps, StructuredDataValue } from \"./StructuredDataPreview\";\nexport { default as StructuredDataPreview, isStructuredDataValue } from \"./StructuredDataPreview\";\nexport { VideoPlayer, type VideoPlayerProps } from \"./VideoPlayer\";\nexport type { VirtualFeedController, VirtualFeedProps, VirtualFeedScrollOptions } from \"./VirtualFeed\";\nexport { VirtualFeed } from \"./VirtualFeed\";\nexport type { ZoomPanAction, ZoomPanFullscreen, ZoomPanViewportProps } from \"./ZoomPanViewport\";\nexport { ZoomPanViewport } from \"./ZoomPanViewport\";\n"],"mappings":"AAaA,SAAS,OAAO,IAAI,QAAQ,QAAQ,YAAY;AAGhD,SAAS,OAAO,IAAI,KAAK,QAAQ,SAAS;AAE1C,SAAS,aAAa,QAAQ,iBAAiB;AAE/C,SAAS,qBAAqB,QAAQ,yBAAyB;AAC/D,SAAS,mBAAmB,QAAuC,uBAAuB;AAC1F,SAAS,iBAAiB,QAAQ,qBAAqB;AAEvD,SAAS,OAAO,IAAI,WAAW,QAAQ,eAAe;AACtD,SAA2B,iBAAiB,QAAQ,gBAAgB;AAUpE,SAAS,mBAAmB,QAAQ,kBAAkB;AAGtD,SAAS,oBAAoB,QAAQ,kBAAkB;AAQvD,SAAmC,yBAAyB,QAAiC,wBAAwB;AAarH,SAAS,OAAO,IAAI,SAAS,QAAQ,aAAa;AAElD,SAAS,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,QAAQ,QAAQ;AAC/G,SAAS,gBAAgB,EAA+C,eAAe,QAAQ,eAAe;AAC9G,SAAS,QAAQ,QAA4B,YAAY;AACzD,SAAS,OAAO,IAAI,QAAQ,QAAsE,YAAY;AAC9G,SACE,cAAc,EACd,OAAO,IAAI,QAAQ,EAOnB,kBAAkB,EAClB,sBAAsB,QACjB,YAAY;AAEnB,SAAS,OAAO,IAAI,QAAQ,QAAQ,YAAY;AAEhD,SAAS,OAAO,IAAI,eAAe,QAAQ,mBAAmB;AAC9D,SAAS,OAAO,IAAI,YAAY,EAAsD,kBAAkB,QAAQ,gBAAgB;AAgBhI,SAAS,UAAU,EAAE,gBAAgB,EAAE,kBAAkB,QAAQ,cAAc;AAC/E,SAIE,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,QAChB,wBAAwB;AAC/B,SAKE,qBAAqB,EACrB,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,mBAAmB,EACnB,kBAAkB,QACb,kBAAkB;AACzB,SAAS,UAAU,QAA8B,cAAc;AAE/D,SAAS,OAAO,IAAI,UAAU,QAAQ,cAAc;AAEpD,SAAS,OAAO,IAAI,WAAW,QAAQ,eAAe;AAEtD,SAAS,OAAO,IAAI,qBAAqB,EAAE,qBAAqB,QAAQ,yBAAyB;AACjG,SAAS,WAAW,QAA+B,eAAe;AAElE,SAAS,WAAW,QAAQ,eAAe;AAE3C,SAAS,eAAe,QAAQ,mBAAmB","ignoreList":[]}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { fileIcons } from "@k2b/stdlib";
|
|
2
|
+
import { resolveUiMessages } from "../intl/messages";
|
|
3
|
+
|
|
4
|
+
/** What a reference points to inside the host application. */
|
|
5
|
+
|
|
6
|
+
/** The type a reference shows: a file's type from its extension, or the kind of the referenced content. */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A link to content inside the host application, as opposed to a web or mail link.
|
|
10
|
+
* References render as a calm pill whose icon names the type.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const escapeHtml = value => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
14
|
+
const IMAGE_EXTENSIONS = new Set(["png", "jpg", "jpeg", "gif", "webp", "avif", "bmp", "ico", "svg", "heic", "heif", "tif", "tiff"]);
|
|
15
|
+
const DESIGN_EXTENSIONS = new Set(["afdesign", "afphoto", "afpub", "psd", "psb", "ai", "eps", "indd", "idml", "fig", "sketch", "xd"]);
|
|
16
|
+
const extensionOf = name => {
|
|
17
|
+
const match = /\.([a-z0-9]{1,10})$/i.exec(name.trim());
|
|
18
|
+
return match && /[a-z]/i.test(match[1]) ? match[1].toLowerCase() : null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** The type of a file reference, from the extension of its name. */
|
|
22
|
+
export const markdownFileType = fileName => {
|
|
23
|
+
const extension = extensionOf(fileName);
|
|
24
|
+
if (extension === "pdf") return "pdf";
|
|
25
|
+
if (extension && IMAGE_EXTENSIONS.has(extension)) return "image";
|
|
26
|
+
if (extension && DESIGN_EXTENSIONS.has(extension)) return "design";
|
|
27
|
+
return "file";
|
|
28
|
+
};
|
|
29
|
+
const REFERENCE_BASE = "https://reference.invalid/";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The reference a link destination names on its own: `#anchor` is a heading in the same document, and any other
|
|
33
|
+
* relative URL is a page of the host, or a file when its last path segment has a file extension. Absolute URLs,
|
|
34
|
+
* every scheme, and relative URLs that a browser resolves to another host, such as `//host` or `/\host`, are web
|
|
35
|
+
* or mail links; the result is then `null`.
|
|
36
|
+
*/
|
|
37
|
+
export const markdownLinkReference = href => {
|
|
38
|
+
const url = href.trim();
|
|
39
|
+
if (!url || /^[a-z][a-z\d+.-]*:/i.test(url)) return null;
|
|
40
|
+
try {
|
|
41
|
+
if (new URL(url, REFERENCE_BASE).origin !== new URL(REFERENCE_BASE).origin) return null;
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
if (url.startsWith("#")) return {
|
|
46
|
+
kind: "heading"
|
|
47
|
+
};
|
|
48
|
+
const segment = url.split(/[?#]/, 1)[0].split("/").pop() ?? "";
|
|
49
|
+
let fileName = segment;
|
|
50
|
+
try {
|
|
51
|
+
fileName = decodeURIComponent(segment);
|
|
52
|
+
} catch {
|
|
53
|
+
/* A malformed escape stays literal text. */
|
|
54
|
+
}
|
|
55
|
+
return extensionOf(fileName) ? {
|
|
56
|
+
kind: "file",
|
|
57
|
+
fileName
|
|
58
|
+
} : {
|
|
59
|
+
kind: "page"
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** The type a reference shows. */
|
|
64
|
+
export const markdownReferenceType = (reference, text) => reference.kind === "file" ? markdownFileType(reference.fileName ?? text) : reference.kind;
|
|
65
|
+
const TYPE_ICONS = {
|
|
66
|
+
pdf: "ti-file-type-pdf",
|
|
67
|
+
image: "ti-photo",
|
|
68
|
+
design: "ti-vector-bezier-2",
|
|
69
|
+
note: "ti-file-text",
|
|
70
|
+
heading: "ti-hash",
|
|
71
|
+
task: "ti-circle-check",
|
|
72
|
+
page: "ti-link"
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** The Tabler glyph of a reference type; any other file keeps the glyph of its file family. */
|
|
76
|
+
export const markdownReferenceIcon = (type, fileName = "") => type === "file" ? fileIcons.getFileIcon({
|
|
77
|
+
name: fileName,
|
|
78
|
+
type: "file"
|
|
79
|
+
}).split(/\s+/)[0] ?? "ti-file" : TYPE_ICONS[type];
|
|
80
|
+
const TYPE_LABELS = {
|
|
81
|
+
pdf: "referencePdf",
|
|
82
|
+
image: "referenceImage",
|
|
83
|
+
design: "referenceDesign",
|
|
84
|
+
file: "referenceFile",
|
|
85
|
+
note: "referenceNote",
|
|
86
|
+
heading: "referenceHeading",
|
|
87
|
+
task: "referenceTask",
|
|
88
|
+
page: "referencePage"
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/** The localized name of a reference type, such as "PDF" or "Überschrift". */
|
|
92
|
+
export const markdownReferenceTypeLabel = (type, locale) => String(resolveUiMessages(locale)[TYPE_LABELS[type]]);
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The text of rendered inline HTML, still as HTML: tags go, character references such as `©` stay. One pass,
|
|
96
|
+
* so a label full of `<` stays linear. The result only ever reaches `textAttribute`, which escapes what is left.
|
|
97
|
+
*/
|
|
98
|
+
const htmlText = html => {
|
|
99
|
+
let text = "";
|
|
100
|
+
let index = 0;
|
|
101
|
+
for (let open = html.indexOf("<"); open !== -1; open = html.indexOf("<", index)) {
|
|
102
|
+
const close = html.indexOf(">", open + 1);
|
|
103
|
+
if (close === -1) break;
|
|
104
|
+
text += html.slice(index, open);
|
|
105
|
+
index = close + 1;
|
|
106
|
+
}
|
|
107
|
+
return text + html.slice(index);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** HTML text as an attribute value. Character references stay for the browser to decode; nothing can end the value. */
|
|
111
|
+
const textAttribute = text => text.replace(/&(?!#\d+;|#x[\da-f]+;|[a-z][a-z\d]*;)/gi, "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
112
|
+
const isMailHref = href => /^(?:mailto|tel):/i.test(href.trim());
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* One link of rendered Markdown. A reference to content of the host renders as a calm pill: a neutral fill, the
|
|
116
|
+
* text in the prose colour, and an icon whose colour names the type. Its accessible name starts with the type, as
|
|
117
|
+
* in "PDF: Brand-2026.pdf". A heading in another document shows that document first ("Notes › Heading"). A web
|
|
118
|
+
* link stays prose text with a thin underline in the link accent and a small ↗; a mail link has no arrow. A link
|
|
119
|
+
* around an image is the image: a plain anchor that the image's own text names.
|
|
120
|
+
*/
|
|
121
|
+
export const renderMarkdownLink = input => {
|
|
122
|
+
const titleAttribute = input.title ? ` title="${escapeHtml(input.title)}"` : "";
|
|
123
|
+
const targetAttributes = input.target === "_blank" ? ' target="_blank" rel="noopener noreferrer"' : input.rel ? ` rel="${escapeHtml(input.rel)}"` : "";
|
|
124
|
+
const href = escapeHtml(input.href);
|
|
125
|
+
if (/<img\b/i.test(input.html)) return `<a href="${href}"${titleAttribute}${targetAttributes}>${input.html}</a>`;
|
|
126
|
+
const reference = input.reference;
|
|
127
|
+
if (!reference) {
|
|
128
|
+
const mail = isMailHref(input.href);
|
|
129
|
+
const arrow = mail ? "" : '<i class="k2b-text-link__external ti ti-arrow-up-right" aria-hidden="true"></i>';
|
|
130
|
+
return `<a href="${href}" class="k2b-text-link" data-link="${mail ? "mail" : "web"}"${titleAttribute}${targetAttributes}>${input.html}${arrow}</a>`;
|
|
131
|
+
}
|
|
132
|
+
const text = (input.text ?? htmlText(input.html)).trim();
|
|
133
|
+
const type = markdownReferenceType(reference, text);
|
|
134
|
+
const icon = markdownReferenceIcon(type, reference.fileName ?? text);
|
|
135
|
+
const size = input.standalone && reference.kind === "file" && reference.size ? reference.size : "";
|
|
136
|
+
const document = reference.kind === "heading" ? reference.document?.trim() ?? "" : "";
|
|
137
|
+
const name = escapeHtml(`${markdownReferenceTypeLabel(type, input.locale)}: ${document ? `${document} › ` : ""}`) + (input.text === undefined ? textAttribute(text) : escapeHtml(text)) + (size ? escapeHtml(`, ${size}`) : "");
|
|
138
|
+
return `<a href="${href}" class="k2b-reference" data-reference="${type}" aria-label="${name}"${titleAttribute}${targetAttributes}>` + `<i class="k2b-reference__icon ti ${icon}" aria-hidden="true"></i>` + (document ? `<span class="k2b-reference__document">${escapeHtml(document)} ›</span> ` : "") + input.html + (size ? `<span class="k2b-reference__size">${escapeHtml(size)}</span>` : "") + `</a>`;
|
|
139
|
+
};
|
|
140
|
+
const isBlank = token => (token.type === "text" || token.type === "space") && !token.raw.trim();
|
|
141
|
+
const isLink = token => token?.type === "link";
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The links among `tokens` that stand alone on their line: the only content of a paragraph or list item line,
|
|
145
|
+
* between line breaks. Pass the result's check as `standalone` to `renderMarkdownLink`, so a file shows its size
|
|
146
|
+
* in a list of files but not inside a sentence. Call it from `walkTokens` of a renderer with GFM line breaks
|
|
147
|
+
* (`breaks: true`), where every source line is a visual line.
|
|
148
|
+
*/
|
|
149
|
+
export const markStandaloneLinks = (token, standalone) => {
|
|
150
|
+
if (token.type !== "paragraph" && token.type !== "text" || !("tokens" in token) || !token.tokens) return;
|
|
151
|
+
let line = [];
|
|
152
|
+
const close = () => {
|
|
153
|
+
const content = line.filter(candidate => !isBlank(candidate));
|
|
154
|
+
if (content.length === 1 && isLink(content[0])) standalone.add(content[0]);
|
|
155
|
+
line = [];
|
|
156
|
+
};
|
|
157
|
+
for (const child of token.tokens) {
|
|
158
|
+
if (child.type === "br") {
|
|
159
|
+
close();
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const plain = child.type === "text" && !("tokens" in child && child.tokens?.length) ? child.raw : "";
|
|
163
|
+
if (plain.includes("\n")) {
|
|
164
|
+
for (const [index, part] of plain.split("\n").entries()) {
|
|
165
|
+
if (index > 0) close();
|
|
166
|
+
if (part.trim()) line.push({
|
|
167
|
+
type: "text",
|
|
168
|
+
raw: part,
|
|
169
|
+
text: part
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
line.push(child);
|
|
175
|
+
}
|
|
176
|
+
close();
|
|
177
|
+
};
|
|
178
|
+
//# sourceMappingURL=markdown-links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["content/markdown-links.ts"],"sourcesContent":["import { fileIcons } from \"@k2b/stdlib\";\nimport type { Token, Tokens } from \"marked\";\nimport { resolveUiMessages, type UiMessages } from \"../intl/messages\";\n\n/** What a reference points to inside the host application. */\nexport type MarkdownReferenceKind = \"file\" | \"note\" | \"heading\" | \"task\" | \"page\";\n\n/** The type a reference shows: a file's type from its extension, or the kind of the referenced content. */\nexport type MarkdownReferenceType = \"pdf\" | \"image\" | \"design\" | \"file\" | \"note\" | \"heading\" | \"task\" | \"page\";\n\n/**\n * A link to content inside the host application, as opposed to a web or mail link.\n * References render as a calm pill whose icon names the type.\n */\nexport type MarkdownReference = {\n kind: MarkdownReferenceKind;\n /** The file's name, which decides its type and icon. Defaults to the link text. */\n fileName?: string;\n /** The file's formatted size. It shows only when the link stands alone on its line. */\n size?: string;\n /** Plain-text title of the document that holds a heading, for a heading in another document. */\n document?: string;\n};\n\nexport type MarkdownLinkInput = {\n /** The final, already validated link destination. */\n href: string;\n /** The rendered link text. It crosses a trust boundary: the caller sanitizes it. */\n html: string;\n /** The link text as plain text, for the accessible name. Defaults to the text of `html`. */\n text?: string;\n title?: string;\n target?: \"_blank\";\n /** Link relations without a new tab, such as `noreferrer`. `target: \"_blank\"` always sets `noopener noreferrer`. */\n rel?: string;\n /** A reference inside the host. Without one, the link is a web link, or a mail link for `mailto:` and `tel:`. */\n reference?: MarkdownReference | null;\n /** The link is the only content of its line. Only then does a file show its size. */\n standalone?: boolean;\n /** Locale of the type name in the accessible name. Defaults to English on the server and the page language in the browser. */\n locale?: string;\n};\n\nconst escapeHtml = (value: string): string =>\n value.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\").replaceAll('\"', \""\").replaceAll(\"'\", \"'\");\n\nconst IMAGE_EXTENSIONS = new Set([\"png\", \"jpg\", \"jpeg\", \"gif\", \"webp\", \"avif\", \"bmp\", \"ico\", \"svg\", \"heic\", \"heif\", \"tif\", \"tiff\"]);\nconst DESIGN_EXTENSIONS = new Set([\"afdesign\", \"afphoto\", \"afpub\", \"psd\", \"psb\", \"ai\", \"eps\", \"indd\", \"idml\", \"fig\", \"sketch\", \"xd\"]);\n\nconst extensionOf = (name: string): string | null => {\n const match = /\\.([a-z0-9]{1,10})$/i.exec(name.trim());\n return match && /[a-z]/i.test(match[1]!) ? match[1]!.toLowerCase() : null;\n};\n\n/** The type of a file reference, from the extension of its name. */\nexport const markdownFileType = (fileName: string): \"pdf\" | \"image\" | \"design\" | \"file\" => {\n const extension = extensionOf(fileName);\n if (extension === \"pdf\") return \"pdf\";\n if (extension && IMAGE_EXTENSIONS.has(extension)) return \"image\";\n if (extension && DESIGN_EXTENSIONS.has(extension)) return \"design\";\n return \"file\";\n};\n\nconst REFERENCE_BASE = \"https://reference.invalid/\";\n\n/**\n * The reference a link destination names on its own: `#anchor` is a heading in the same document, and any other\n * relative URL is a page of the host, or a file when its last path segment has a file extension. Absolute URLs,\n * every scheme, and relative URLs that a browser resolves to another host, such as `//host` or `/\\host`, are web\n * or mail links; the result is then `null`.\n */\nexport const markdownLinkReference = (href: string): MarkdownReference | null => {\n const url = href.trim();\n if (!url || /^[a-z][a-z\\d+.-]*:/i.test(url)) return null;\n try {\n if (new URL(url, REFERENCE_BASE).origin !== new URL(REFERENCE_BASE).origin) return null;\n } catch {\n return null;\n }\n if (url.startsWith(\"#\")) return { kind: \"heading\" };\n const segment = url.split(/[?#]/, 1)[0]!.split(\"/\").pop() ?? \"\";\n let fileName = segment;\n try {\n fileName = decodeURIComponent(segment);\n } catch {\n /* A malformed escape stays literal text. */\n }\n return extensionOf(fileName) ? { kind: \"file\", fileName } : { kind: \"page\" };\n};\n\n/** The type a reference shows. */\nexport const markdownReferenceType = (reference: MarkdownReference, text: string): MarkdownReferenceType =>\n reference.kind === \"file\" ? markdownFileType(reference.fileName ?? text) : reference.kind;\n\nconst TYPE_ICONS: Record<Exclude<MarkdownReferenceType, \"file\">, string> = {\n pdf: \"ti-file-type-pdf\",\n image: \"ti-photo\",\n design: \"ti-vector-bezier-2\",\n note: \"ti-file-text\",\n heading: \"ti-hash\",\n task: \"ti-circle-check\",\n page: \"ti-link\",\n};\n\n/** The Tabler glyph of a reference type; any other file keeps the glyph of its file family. */\nexport const markdownReferenceIcon = (type: MarkdownReferenceType, fileName = \"\"): string =>\n type === \"file\" ? (fileIcons.getFileIcon({ name: fileName, type: \"file\" }).split(/\\s+/)[0] ?? \"ti-file\") : TYPE_ICONS[type];\n\nconst TYPE_LABELS: Record<MarkdownReferenceType, keyof UiMessages> = {\n pdf: \"referencePdf\",\n image: \"referenceImage\",\n design: \"referenceDesign\",\n file: \"referenceFile\",\n note: \"referenceNote\",\n heading: \"referenceHeading\",\n task: \"referenceTask\",\n page: \"referencePage\",\n};\n\n/** The localized name of a reference type, such as \"PDF\" or \"Überschrift\". */\nexport const markdownReferenceTypeLabel = (type: MarkdownReferenceType, locale?: string): string =>\n String(resolveUiMessages(locale)[TYPE_LABELS[type]]);\n\n/**\n * The text of rendered inline HTML, still as HTML: tags go, character references such as `©` stay. One pass,\n * so a label full of `<` stays linear. The result only ever reaches `textAttribute`, which escapes what is left.\n */\nconst htmlText = (html: string): string => {\n let text = \"\";\n let index = 0;\n for (let open = html.indexOf(\"<\"); open !== -1; open = html.indexOf(\"<\", index)) {\n const close = html.indexOf(\">\", open + 1);\n if (close === -1) break;\n text += html.slice(index, open);\n index = close + 1;\n }\n return text + html.slice(index);\n};\n\n/** HTML text as an attribute value. Character references stay for the browser to decode; nothing can end the value. */\nconst textAttribute = (text: string): string =>\n text\n .replace(/&(?!#\\d+;|#x[\\da-f]+;|[a-z][a-z\\d]*;)/gi, \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n\nconst isMailHref = (href: string) => /^(?:mailto|tel):/i.test(href.trim());\n\n/**\n * One link of rendered Markdown. A reference to content of the host renders as a calm pill: a neutral fill, the\n * text in the prose colour, and an icon whose colour names the type. Its accessible name starts with the type, as\n * in \"PDF: Brand-2026.pdf\". A heading in another document shows that document first (\"Notes › Heading\"). A web\n * link stays prose text with a thin underline in the link accent and a small ↗; a mail link has no arrow. A link\n * around an image is the image: a plain anchor that the image's own text names.\n */\nexport const renderMarkdownLink = (input: MarkdownLinkInput): string => {\n const titleAttribute = input.title ? ` title=\"${escapeHtml(input.title)}\"` : \"\";\n const targetAttributes =\n input.target === \"_blank\" ? ' target=\"_blank\" rel=\"noopener noreferrer\"' : input.rel ? ` rel=\"${escapeHtml(input.rel)}\"` : \"\";\n const href = escapeHtml(input.href);\n if (/<img\\b/i.test(input.html)) return `<a href=\"${href}\"${titleAttribute}${targetAttributes}>${input.html}</a>`;\n const reference = input.reference;\n if (!reference) {\n const mail = isMailHref(input.href);\n const arrow = mail ? \"\" : '<i class=\"k2b-text-link__external ti ti-arrow-up-right\" aria-hidden=\"true\"></i>';\n return `<a href=\"${href}\" class=\"k2b-text-link\" data-link=\"${mail ? \"mail\" : \"web\"}\"${titleAttribute}${targetAttributes}>${input.html}${arrow}</a>`;\n }\n const text = (input.text ?? htmlText(input.html)).trim();\n const type = markdownReferenceType(reference, text);\n const icon = markdownReferenceIcon(type, reference.fileName ?? text);\n const size = input.standalone && reference.kind === \"file\" && reference.size ? reference.size : \"\";\n const document = reference.kind === \"heading\" ? (reference.document?.trim() ?? \"\") : \"\";\n const name =\n escapeHtml(`${markdownReferenceTypeLabel(type, input.locale)}: ${document ? `${document} › ` : \"\"}`) +\n (input.text === undefined ? textAttribute(text) : escapeHtml(text)) +\n (size ? escapeHtml(`, ${size}`) : \"\");\n return (\n `<a href=\"${href}\" class=\"k2b-reference\" data-reference=\"${type}\" aria-label=\"${name}\"${titleAttribute}${targetAttributes}>` +\n `<i class=\"k2b-reference__icon ti ${icon}\" aria-hidden=\"true\"></i>` +\n (document ? `<span class=\"k2b-reference__document\">${escapeHtml(document)} ›</span> ` : \"\") +\n input.html +\n (size ? `<span class=\"k2b-reference__size\">${escapeHtml(size)}</span>` : \"\") +\n `</a>`\n );\n};\n\nconst isBlank = (token: Token) => (token.type === \"text\" || token.type === \"space\") && !token.raw.trim();\nconst isLink = (token: Token | undefined): token is Tokens.Link => token?.type === \"link\";\n\n/**\n * The links among `tokens` that stand alone on their line: the only content of a paragraph or list item line,\n * between line breaks. Pass the result's check as `standalone` to `renderMarkdownLink`, so a file shows its size\n * in a list of files but not inside a sentence. Call it from `walkTokens` of a renderer with GFM line breaks\n * (`breaks: true`), where every source line is a visual line.\n */\nexport const markStandaloneLinks = (token: Token, standalone: WeakSet<Tokens.Link>): void => {\n if ((token.type !== \"paragraph\" && token.type !== \"text\") || !(\"tokens\" in token) || !token.tokens) return;\n let line: Token[] = [];\n const close = () => {\n const content = line.filter((candidate) => !isBlank(candidate));\n if (content.length === 1 && isLink(content[0])) standalone.add(content[0]);\n line = [];\n };\n for (const child of token.tokens) {\n if (child.type === \"br\") {\n close();\n continue;\n }\n const plain = child.type === \"text\" && !(\"tokens\" in child && child.tokens?.length) ? child.raw : \"\";\n if (plain.includes(\"\\n\")) {\n for (const [index, part] of plain.split(\"\\n\").entries()) {\n if (index > 0) close();\n if (part.trim()) line.push({ type: \"text\", raw: part, text: part });\n }\n continue;\n }\n line.push(child);\n }\n close();\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ssr as _$ssr } from "solid-js/web";
|
|
2
|
+
import { ssrStyle as _$ssrStyle } from "solid-js/web";
|
|
3
|
+
import { ssrAttribute as _$ssrAttribute } from "solid-js/web";
|
|
4
|
+
import { escape as _$escape } from "solid-js/web";
|
|
5
|
+
var _tmpl$ = ["<div popover=\"manual\" class=\"k2b-file-drop\"", " aria-hidden=\"true\" style=\"", "\"><span class=\"k2b-file-drop__message\"><i", "></i><span>", "</span></span></div>"];
|
|
6
|
+
import { createEffect, createMemo, createSignal, onCleanup, onMount } from "solid-js";
|
|
7
|
+
import { useLocale } from "../intl/locale";
|
|
8
|
+
import { useUiMessages } from "../intl/messages";
|
|
9
|
+
import { FILE_DROP_DEFAULT_REGION, fileDropEngine, liveDocument } from "./file-drop";
|
|
10
|
+
/**
|
|
11
|
+
* Turns an area into a drop target while files from outside the page are dragged over the window. A calm overlay
|
|
12
|
+
* covers the area without moving anything and says what dropping does; a more specific target inside it, such as a
|
|
13
|
+
* folder row or a `FileDropzone`, takes the drop and its sentence while the pointer is over it. Text, links, and
|
|
14
|
+
* elements dragged within the page never show it. Keep a visible upload button for keyboard and touch users.
|
|
15
|
+
*/
|
|
16
|
+
export function FileDropTarget(props) {
|
|
17
|
+
const messages = useUiMessages();
|
|
18
|
+
const locale = useLocale();
|
|
19
|
+
let overlay;
|
|
20
|
+
const [rect, setRect] = createSignal();
|
|
21
|
+
const region = () => props.for ?? overlay?.parentElement?.closest(FILE_DROP_DEFAULT_REGION) ?? overlay?.ownerDocument.documentElement;
|
|
22
|
+
const entry = {
|
|
23
|
+
options: props,
|
|
24
|
+
messages,
|
|
25
|
+
locale,
|
|
26
|
+
region,
|
|
27
|
+
host: () => overlay?.parentElement ?? undefined
|
|
28
|
+
};
|
|
29
|
+
const [engine, setEngine] = createSignal();
|
|
30
|
+
onMount(() => {
|
|
31
|
+
if (!overlay) return;
|
|
32
|
+
const drop = fileDropEngine(liveDocument(overlay));
|
|
33
|
+
onCleanup(drop.addRegion(entry));
|
|
34
|
+
setEngine(drop);
|
|
35
|
+
});
|
|
36
|
+
const shown = createMemo(() => engine()?.session()?.shown.includes(entry) ?? false);
|
|
37
|
+
const hover = createMemo(() => {
|
|
38
|
+
const current = engine()?.session()?.hover;
|
|
39
|
+
const element = region();
|
|
40
|
+
return current && element && (current.element === element || element.contains(current.element)) ? current : null;
|
|
41
|
+
});
|
|
42
|
+
const state = () => hover() ? hover().invalid ? "invalid" : "over" : "available";
|
|
43
|
+
const sentence = () => {
|
|
44
|
+
const current = hover();
|
|
45
|
+
if (current?.invalid) return messages().fileTypeNotAccepted;
|
|
46
|
+
return current ? current.entry.options.label : props.label;
|
|
47
|
+
};
|
|
48
|
+
createEffect(() => {
|
|
49
|
+
if (!shown()) return;
|
|
50
|
+
engine()?.frame();
|
|
51
|
+
const element = region();
|
|
52
|
+
const view = overlay?.ownerDocument.defaultView;
|
|
53
|
+
if (!element || !view) return;
|
|
54
|
+
// The overlay keeps to the visible part of its area, so its sentence stays on screen.
|
|
55
|
+
const bounds = element === element.ownerDocument.documentElement ? new DOMRect(0, 0, view.innerWidth, view.innerHeight) : element.getBoundingClientRect();
|
|
56
|
+
const top = Math.max(0, bounds.top);
|
|
57
|
+
const left = Math.max(0, bounds.left);
|
|
58
|
+
const next = {
|
|
59
|
+
top,
|
|
60
|
+
left,
|
|
61
|
+
width: Math.max(0, Math.min(view.innerWidth, bounds.right) - left),
|
|
62
|
+
height: Math.max(0, Math.min(view.innerHeight, bounds.bottom) - top),
|
|
63
|
+
radius: view.getComputedStyle(element).borderRadius
|
|
64
|
+
};
|
|
65
|
+
const previous = rect();
|
|
66
|
+
if (!previous || previous.top !== next.top || previous.left !== next.left || previous.width !== next.width || previous.height !== next.height || previous.radius !== next.radius) setRect(next);
|
|
67
|
+
});
|
|
68
|
+
createEffect(() => {
|
|
69
|
+
if (!overlay?.showPopover) return;
|
|
70
|
+
const open = overlay.matches(":popover-open");
|
|
71
|
+
if (shown() && !open) overlay.showPopover();else if (!shown() && open) overlay.hidePopover();
|
|
72
|
+
});
|
|
73
|
+
onCleanup(() => {
|
|
74
|
+
if (overlay?.matches?.(":popover-open")) overlay.hidePopover();
|
|
75
|
+
});
|
|
76
|
+
return _$ssr(_tmpl$, _$ssrAttribute("data-state", _$escape(state(), true), false), _$ssrStyle(rect() ? {
|
|
77
|
+
top: `${rect().top}px`,
|
|
78
|
+
left: `${rect().left}px`,
|
|
79
|
+
width: `${rect().width}px`,
|
|
80
|
+
height: `${rect().height}px`,
|
|
81
|
+
"border-radius": rect().radius
|
|
82
|
+
} : undefined), _$ssrAttribute("class", state() === "invalid" ? "ti ti-file-x" : "ti ti-upload", false), _$escape(sentence()));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Makes one element a specific drop target, such as a folder row inside a `FileDropTarget` area: pass the result as
|
|
87
|
+
* the element's `ref`. It wins over the area around it while the pointer is over it, and the area's overlay shows its
|
|
88
|
+
* sentence. Calling the ref again with new options replaces them.
|
|
89
|
+
*/
|
|
90
|
+
export function fileDropTarget(options) {
|
|
91
|
+
const messages = useUiMessages();
|
|
92
|
+
const locale = useLocale();
|
|
93
|
+
return element => {
|
|
94
|
+
const entry = {
|
|
95
|
+
options,
|
|
96
|
+
messages,
|
|
97
|
+
locale
|
|
98
|
+
};
|
|
99
|
+
// A ref runs before its element is inserted; registering after insertion finds the document it ends up in.
|
|
100
|
+
queueMicrotask(() => fileDropEngine(liveDocument(element)).setTarget(element, entry));
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=FileDropTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sources":["inputs/FileDropTarget.tsx"],"sourcesContent":["import { createEffect, createMemo, createSignal, type JSX, onCleanup, onMount } from \"solid-js\";\nimport { useLocale } from \"../intl/locale\";\nimport { useUiMessages } from \"../intl/messages\";\nimport { FILE_DROP_DEFAULT_REGION, type FileDropOptions, type FileDropTargetEntry, fileDropEngine, liveDocument } from \"./file-drop\";\n\nexport type { FileDropDetails, FileDropOptions, FileDropRejection } from \"./file-drop\";\n\nexport type FileDropTargetProps = FileDropOptions & {\n /**\n * The area that takes the files. Defaults to the nearest dialog, `AppWorkspace.Detail`, or `AppWorkspace.Main`\n * around the component, and to the whole window outside of them.\n */\n for?: Element;\n};\n\ntype Rect = { top: number; left: number; width: number; height: number; radius: string };\n\n/**\n * Turns an area into a drop target while files from outside the page are dragged over the window. A calm overlay\n * covers the area without moving anything and says what dropping does; a more specific target inside it, such as a\n * folder row or a `FileDropzone`, takes the drop and its sentence while the pointer is over it. Text, links, and\n * elements dragged within the page never show it. Keep a visible upload button for keyboard and touch users.\n */\nexport function FileDropTarget(props: FileDropTargetProps): JSX.Element {\n const messages = useUiMessages();\n const locale = useLocale();\n let overlay: HTMLDivElement | undefined;\n const [rect, setRect] = createSignal<Rect>();\n const region = () => props.for ?? overlay?.parentElement?.closest(FILE_DROP_DEFAULT_REGION) ?? overlay?.ownerDocument.documentElement;\n const entry: FileDropTargetEntry = { options: props, messages, locale, region, host: () => overlay?.parentElement ?? undefined };\n const [engine, setEngine] = createSignal<ReturnType<typeof fileDropEngine>>();\n\n onMount(() => {\n if (!overlay) return;\n const drop = fileDropEngine(liveDocument(overlay));\n onCleanup(drop.addRegion(entry));\n setEngine(drop);\n });\n\n const shown = createMemo(() => engine()?.session()?.shown.includes(entry) ?? false);\n const hover = createMemo(() => {\n const current = engine()?.session()?.hover;\n const element = region();\n return current && element && (current.element === element || element.contains(current.element)) ? current : null;\n });\n const state = () => (hover() ? (hover()!.invalid ? \"invalid\" : \"over\") : \"available\");\n const sentence = () => {\n const current = hover();\n if (current?.invalid) return messages().fileTypeNotAccepted;\n return current ? current.entry.options.label : props.label;\n };\n\n createEffect(() => {\n if (!shown()) return;\n engine()?.frame();\n const element = region();\n const view = overlay?.ownerDocument.defaultView;\n if (!element || !view) return;\n // The overlay keeps to the visible part of its area, so its sentence stays on screen.\n const bounds =\n element === element.ownerDocument.documentElement\n ? new DOMRect(0, 0, view.innerWidth, view.innerHeight)\n : element.getBoundingClientRect();\n const top = Math.max(0, bounds.top);\n const left = Math.max(0, bounds.left);\n const next = {\n top,\n left,\n width: Math.max(0, Math.min(view.innerWidth, bounds.right) - left),\n height: Math.max(0, Math.min(view.innerHeight, bounds.bottom) - top),\n radius: view.getComputedStyle(element).borderRadius,\n };\n const previous = rect();\n if (\n !previous ||\n previous.top !== next.top ||\n previous.left !== next.left ||\n previous.width !== next.width ||\n previous.height !== next.height ||\n previous.radius !== next.radius\n )\n setRect(next);\n });\n\n createEffect(() => {\n if (!overlay?.showPopover) return;\n const open = overlay.matches(\":popover-open\");\n if (shown() && !open) overlay.showPopover();\n else if (!shown() && open) overlay.hidePopover();\n });\n onCleanup(() => {\n if (overlay?.matches?.(\":popover-open\")) overlay.hidePopover();\n });\n\n return (\n <div\n ref={overlay}\n popover=\"manual\"\n class=\"k2b-file-drop\"\n data-state={state()}\n aria-hidden=\"true\"\n style={\n rect()\n ? {\n top: `${rect()!.top}px`,\n left: `${rect()!.left}px`,\n width: `${rect()!.width}px`,\n height: `${rect()!.height}px`,\n \"border-radius\": rect()!.radius,\n }\n : undefined\n }\n >\n <span class=\"k2b-file-drop__message\">\n <i class={state() === \"invalid\" ? \"ti ti-file-x\" : \"ti ti-upload\"} />\n <span>{sentence()}</span>\n </span>\n </div>\n );\n}\n\n/**\n * Makes one element a specific drop target, such as a folder row inside a `FileDropTarget` area: pass the result as\n * the element's `ref`. It wins over the area around it while the pointer is over it, and the area's overlay shows its\n * sentence. Calling the ref again with new options replaces them.\n */\nexport function fileDropTarget(options: FileDropOptions): (element: Element) => void {\n const messages = useUiMessages();\n const locale = useLocale();\n return (element) => {\n const entry = { options, messages, locale };\n // A ref runs before its element is inserted; registering after insertion finds the document it ends up in.\n queueMicrotask(() => fileDropEngine(liveDocument(element)).setTarget(element, entry));\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -22,13 +22,14 @@ var _tmpl$ = ["<span class=\"k2b-dropzone__icon\" aria-hidden=\"true\"><i", "></
|
|
|
22
22
|
_tmpl$12 = ["<small>", "</small>"],
|
|
23
23
|
_tmpl$13 = ["<small class=\"k2b-image-cropper__error\">", "</small>"],
|
|
24
24
|
_tmpl$14 = ["<button type=\"button\" class=\"k2b-image-cropper__handle\"", "", "></button>"];
|
|
25
|
-
import {
|
|
26
|
-
import { createEffect, createSignal, For, onCleanup, Show } from "solid-js";
|
|
25
|
+
import { createEffect, createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
|
27
26
|
import { Button, IconButton } from "../actions/Button";
|
|
28
27
|
import { Tooltip } from "../feedback/Tooltip";
|
|
29
28
|
import { createFieldMeta, Field, fieldControlAria } from "../internal/field";
|
|
30
29
|
import { useUiMessages } from "../intl/messages";
|
|
30
|
+
import { fileDropTarget } from "./FileDropTarget";
|
|
31
31
|
import { commitFieldValue, resolveMaybeAccessor } from "./field-contract";
|
|
32
|
+
import { fileDropEngine, liveDocument } from "./file-drop";
|
|
32
33
|
import { clampImageCropRect, getInitialImageCropRect, resizeImageCropFromCorner, rotateImageCropRight } from "./image-crop";
|
|
33
34
|
export function FileDropzone(props) {
|
|
34
35
|
const messages = useUiMessages();
|
|
@@ -36,17 +37,41 @@ export function FileDropzone(props) {
|
|
|
36
37
|
const disabled = () => Boolean(props.disabled || props.busy);
|
|
37
38
|
const error = () => resolveMaybeAccessor(props.error);
|
|
38
39
|
let input;
|
|
40
|
+
const [button, setButton] = createSignal();
|
|
39
41
|
const emit = files => {
|
|
40
42
|
if (disabled() || files.length === 0) return;
|
|
41
43
|
void props.onDrop(props.multiple === false ? files.slice(0, 1) : files);
|
|
42
44
|
};
|
|
43
|
-
|
|
45
|
+
// Drops take the same path as every other file drop target: files only, the most specific target wins, and files
|
|
46
|
+
// that do not fit `accept` are left out with a message.
|
|
47
|
+
const dropLabel = () => props.dropLabel ?? messages().dropToUpload;
|
|
48
|
+
const dropRef = fileDropTarget({
|
|
49
|
+
get label() {
|
|
50
|
+
return dropLabel();
|
|
51
|
+
},
|
|
44
52
|
get accept() {
|
|
45
53
|
return props.accept;
|
|
46
54
|
},
|
|
55
|
+
get multiple() {
|
|
56
|
+
return props.multiple;
|
|
57
|
+
},
|
|
58
|
+
get disabled() {
|
|
59
|
+
return disabled();
|
|
60
|
+
},
|
|
47
61
|
onDrop: emit
|
|
48
62
|
});
|
|
49
|
-
const
|
|
63
|
+
const [engine, setEngine] = createSignal();
|
|
64
|
+
onMount(() => {
|
|
65
|
+
const element = button();
|
|
66
|
+
if (element) setEngine(fileDropEngine(liveDocument(element)));
|
|
67
|
+
});
|
|
68
|
+
const dragState = createMemo(() => {
|
|
69
|
+
const element = button();
|
|
70
|
+
const hover = engine()?.session()?.hover;
|
|
71
|
+
if (!hover || hover.element !== element) return undefined;
|
|
72
|
+
return hover.invalid ? "invalid" : "over";
|
|
73
|
+
});
|
|
74
|
+
const title = () => props.busy ? messages().uploading : dragState() === "invalid" ? messages().fileTypeNotAccepted : dragState() === "over" ? dropLabel() : props.title ?? messages().dropFiles;
|
|
50
75
|
return _$createComponent(Field, {
|
|
51
76
|
get ["class"]() {
|
|
52
77
|
return props.class;
|
|
@@ -74,18 +99,15 @@ export function FileDropzone(props) {
|
|
|
74
99
|
},
|
|
75
100
|
type: "button",
|
|
76
101
|
"class": "k2b-dropzone",
|
|
77
|
-
get ["data-dragging"]() {
|
|
78
|
-
return zone.isDragging() ? "true" : undefined;
|
|
79
|
-
},
|
|
80
102
|
get ["data-invalid"]() {
|
|
81
|
-
return
|
|
103
|
+
return error() ? "true" : undefined;
|
|
82
104
|
},
|
|
83
105
|
get disabled() {
|
|
84
106
|
return disabled();
|
|
85
107
|
}
|
|
86
|
-
}, () => fieldControlAria(meta, props)
|
|
108
|
+
}, () => fieldControlAria(meta, props)), [_$ssr(_tmpl$, _$ssrAttribute("class", props.busy ? "ti ti-loader-2 k2b-spin" : _$escape(props.icon ?? "ti ti-cloud-upload", true), false)), _$ssr(_tmpl$4, _$escape(title()), _$escape(_$createComponent(Show, {
|
|
87
109
|
get when() {
|
|
88
|
-
return
|
|
110
|
+
return dragState() === "invalid";
|
|
89
111
|
},
|
|
90
112
|
get fallback() {
|
|
91
113
|
return _$createComponent(Show, {
|