@optilogic/core 1.0.0-beta.15 → 1.0.0-beta.17

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/index.cjs CHANGED
@@ -23,6 +23,10 @@ var sonner = require('sonner');
23
23
  var reactVirtual = require('@tanstack/react-virtual');
24
24
  var dateFns = require('date-fns');
25
25
  var reactDayPicker = require('react-day-picker');
26
+ var ReactMarkdownImport = require('react-markdown');
27
+ var remarkGfmImport = require('remark-gfm');
28
+
29
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
26
30
 
27
31
  function _interopNamespace(e) {
28
32
  if (e && e.__esModule) return e;
@@ -55,6 +59,8 @@ var TooltipPrimitive__namespace = /*#__PURE__*/_interopNamespace(TooltipPrimitiv
55
59
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
56
60
  var DropdownMenuPrimitive__namespace = /*#__PURE__*/_interopNamespace(DropdownMenuPrimitive);
57
61
  var AlertDialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(AlertDialogPrimitive);
62
+ var ReactMarkdownImport__default = /*#__PURE__*/_interopDefault(ReactMarkdownImport);
63
+ var remarkGfmImport__default = /*#__PURE__*/_interopDefault(remarkGfmImport);
58
64
 
59
65
  // src/utils/cn.ts
60
66
  function cn(...inputs) {
@@ -6801,6 +6807,848 @@ function useContextMenu() {
6801
6807
  };
6802
6808
  }
6803
6809
 
6810
+ // src/components/file-view/utils/contentTypeDetection.ts
6811
+ var EXTENSION_MAP = {
6812
+ // Code / programming languages
6813
+ ts: "code",
6814
+ tsx: "code",
6815
+ js: "code",
6816
+ jsx: "code",
6817
+ mjs: "code",
6818
+ cjs: "code",
6819
+ py: "code",
6820
+ rb: "code",
6821
+ go: "code",
6822
+ rs: "code",
6823
+ java: "code",
6824
+ c: "code",
6825
+ cpp: "code",
6826
+ h: "code",
6827
+ hpp: "code",
6828
+ cs: "code",
6829
+ php: "code",
6830
+ swift: "code",
6831
+ kt: "code",
6832
+ scala: "code",
6833
+ r: "code",
6834
+ sql: "code",
6835
+ sh: "code",
6836
+ bash: "code",
6837
+ zsh: "code",
6838
+ ps1: "code",
6839
+ bat: "code",
6840
+ lua: "code",
6841
+ perl: "code",
6842
+ pl: "code",
6843
+ // Config / data (code-rendered)
6844
+ json: "code",
6845
+ yaml: "code",
6846
+ yml: "code",
6847
+ toml: "code",
6848
+ xml: "code",
6849
+ html: "html",
6850
+ htm: "html",
6851
+ css: "code",
6852
+ scss: "code",
6853
+ sass: "code",
6854
+ less: "code",
6855
+ graphql: "code",
6856
+ gql: "code",
6857
+ // Optimization / domain-specific
6858
+ lp: "code",
6859
+ dat: "code",
6860
+ // Markdown
6861
+ md: "markdown",
6862
+ mdx: "markdown",
6863
+ // Images
6864
+ png: "image",
6865
+ jpg: "image",
6866
+ jpeg: "image",
6867
+ gif: "image",
6868
+ svg: "image",
6869
+ webp: "image",
6870
+ ico: "image",
6871
+ bmp: "image",
6872
+ // PDF
6873
+ pdf: "pdf",
6874
+ // CSV
6875
+ csv: "csv",
6876
+ tsv: "csv",
6877
+ // Plain text
6878
+ txt: "plaintext",
6879
+ log: "plaintext",
6880
+ env: "plaintext"
6881
+ };
6882
+ var SPECIAL_FILES = {
6883
+ dockerfile: "code",
6884
+ makefile: "code",
6885
+ rakefile: "code",
6886
+ gemfile: "code",
6887
+ procfile: "code"
6888
+ };
6889
+ function getFileExtension(fileName) {
6890
+ if (!fileName) return null;
6891
+ const baseName = fileName.split("/").pop() || fileName;
6892
+ if (baseName.startsWith(".") && !baseName.slice(1).includes(".")) {
6893
+ return baseName.slice(1).toLowerCase();
6894
+ }
6895
+ const lastDot = baseName.lastIndexOf(".");
6896
+ if (lastDot === -1 || lastDot === baseName.length - 1) return null;
6897
+ return baseName.slice(lastDot + 1).toLowerCase();
6898
+ }
6899
+ function detectContentType(fileName) {
6900
+ const baseName = (fileName.split("/").pop() || fileName).toLowerCase();
6901
+ const specialType = SPECIAL_FILES[baseName];
6902
+ if (specialType) {
6903
+ return { type: specialType, extension: baseName };
6904
+ }
6905
+ const extension = getFileExtension(fileName);
6906
+ if (!extension) {
6907
+ return { type: "unknown", extension: null };
6908
+ }
6909
+ const type = EXTENSION_MAP[extension] ?? "unknown";
6910
+ return { type, extension };
6911
+ }
6912
+ function isTextContentType(contentType) {
6913
+ return contentType === "code" || contentType === "markdown" || contentType === "plaintext" || contentType === "csv" || contentType === "html" || contentType === "unknown";
6914
+ }
6915
+ function isUrlContentType(contentType) {
6916
+ return contentType === "image" || contentType === "pdf";
6917
+ }
6918
+
6919
+ // src/components/file-view/hooks/useContentType.ts
6920
+ function useContentType({
6921
+ fileName,
6922
+ contentTypeOverride
6923
+ }) {
6924
+ return React20.useMemo(() => {
6925
+ if (contentTypeOverride) {
6926
+ return {
6927
+ type: contentTypeOverride,
6928
+ extension: null,
6929
+ isOverridden: true
6930
+ };
6931
+ }
6932
+ const detected = detectContentType(fileName);
6933
+ return { ...detected, isOverridden: false };
6934
+ }, [fileName, contentTypeOverride]);
6935
+ }
6936
+ var globalHighlighter = null;
6937
+ var initPromise = null;
6938
+ async function getOrCreateHighlighter() {
6939
+ if (globalHighlighter) return globalHighlighter;
6940
+ if (!initPromise) {
6941
+ initPromise = (async () => {
6942
+ try {
6943
+ const { createHighlighter } = await import('shiki');
6944
+ globalHighlighter = await createHighlighter({
6945
+ themes: ["github-light", "github-dark"],
6946
+ langs: []
6947
+ });
6948
+ return globalHighlighter;
6949
+ } catch {
6950
+ return null;
6951
+ }
6952
+ })();
6953
+ }
6954
+ return initPromise;
6955
+ }
6956
+ function useShikiHighlighter() {
6957
+ const [state, setState] = React20.useState({
6958
+ highlighter: globalHighlighter,
6959
+ isReady: globalHighlighter !== null
6960
+ });
6961
+ React20.useEffect(() => {
6962
+ if (globalHighlighter) {
6963
+ setState({ highlighter: globalHighlighter, isReady: true });
6964
+ return;
6965
+ }
6966
+ let cancelled = false;
6967
+ getOrCreateHighlighter().then((h) => {
6968
+ if (!cancelled) {
6969
+ setState({ highlighter: h, isReady: true });
6970
+ }
6971
+ });
6972
+ return () => {
6973
+ cancelled = true;
6974
+ };
6975
+ }, []);
6976
+ return state;
6977
+ }
6978
+ function getBackgroundLightness(el) {
6979
+ const value = getComputedStyle(el).getPropertyValue("--background").trim();
6980
+ if (!value) return null;
6981
+ const match = value.match(/(\d+(?:\.\d+)?)%\s*$/);
6982
+ return match ? parseFloat(match[1]) : null;
6983
+ }
6984
+ function useDarkMode() {
6985
+ const checkDark = React20.useCallback(() => {
6986
+ if (typeof document === "undefined") return false;
6987
+ const lightness = getBackgroundLightness(document.documentElement);
6988
+ if (lightness === null) {
6989
+ return document.documentElement.classList.contains("dark");
6990
+ }
6991
+ return lightness < 50;
6992
+ }, []);
6993
+ const [isDark, setIsDark] = React20.useState(checkDark);
6994
+ React20.useEffect(() => {
6995
+ const el = document.documentElement;
6996
+ const update = () => {
6997
+ setIsDark(checkDark());
6998
+ };
6999
+ const observer = new MutationObserver(update);
7000
+ observer.observe(el, {
7001
+ attributes: true,
7002
+ attributeFilter: ["class", "style"]
7003
+ });
7004
+ update();
7005
+ return () => observer.disconnect();
7006
+ }, [checkDark]);
7007
+ return isDark;
7008
+ }
7009
+
7010
+ // src/components/file-view/hooks/useHighlightedTokens.ts
7011
+ function useHighlightedTokens(code, language) {
7012
+ const { highlighter, isReady } = useShikiHighlighter();
7013
+ const isDark = useDarkMode();
7014
+ const [lines, setLines] = React20.useState(null);
7015
+ React20.useEffect(() => {
7016
+ if (!isReady || !highlighter || !code) {
7017
+ setLines(null);
7018
+ return;
7019
+ }
7020
+ let cancelled = false;
7021
+ const theme = isDark ? "github-dark" : "github-light";
7022
+ (async () => {
7023
+ try {
7024
+ if (language !== "text") {
7025
+ const loadedLangs = highlighter.getLoadedLanguages();
7026
+ if (!loadedLangs.includes(language)) {
7027
+ await highlighter.loadLanguage(language);
7028
+ }
7029
+ }
7030
+ } catch {
7031
+ if (!cancelled) setLines(null);
7032
+ return;
7033
+ }
7034
+ if (cancelled) return;
7035
+ try {
7036
+ const result = highlighter.codeToTokens(code, {
7037
+ lang: language,
7038
+ theme
7039
+ });
7040
+ if (!cancelled) {
7041
+ setLines(
7042
+ result.tokens.map(
7043
+ (line) => line.map((token) => ({
7044
+ content: token.content,
7045
+ color: token.color
7046
+ }))
7047
+ )
7048
+ );
7049
+ }
7050
+ } catch {
7051
+ if (!cancelled) setLines(null);
7052
+ }
7053
+ })();
7054
+ return () => {
7055
+ cancelled = true;
7056
+ };
7057
+ }, [highlighter, isReady, code, language, isDark]);
7058
+ return { lines };
7059
+ }
7060
+
7061
+ // src/components/file-view/utils/languageMapping.ts
7062
+ var EXTENSION_OVERRIDES = {
7063
+ yml: "yaml",
7064
+ htm: "html",
7065
+ cjs: "javascript",
7066
+ mjs: "javascript",
7067
+ jsx: "jsx",
7068
+ tsx: "tsx",
7069
+ h: "c",
7070
+ hpp: "cpp",
7071
+ cs: "csharp",
7072
+ rb: "ruby",
7073
+ sh: "shellscript",
7074
+ bash: "shellscript",
7075
+ zsh: "shellscript",
7076
+ ps1: "powershell",
7077
+ bat: "bat",
7078
+ kt: "kotlin",
7079
+ rs: "rust",
7080
+ gql: "graphql",
7081
+ pl: "perl",
7082
+ sass: "sass",
7083
+ scss: "scss",
7084
+ less: "less",
7085
+ // Domain-specific / data formats without shiki support
7086
+ lp: "text",
7087
+ dat: "text",
7088
+ env: "shellscript"
7089
+ };
7090
+ var SPECIAL_FILE_LANGUAGES = {
7091
+ dockerfile: "dockerfile",
7092
+ makefile: "makefile",
7093
+ rakefile: "ruby",
7094
+ gemfile: "ruby",
7095
+ procfile: "yaml"
7096
+ };
7097
+ function getLanguageFromFileName(fileName) {
7098
+ const baseName = (fileName.split("/").pop() || fileName).toLowerCase();
7099
+ const specialLang = SPECIAL_FILE_LANGUAGES[baseName];
7100
+ if (specialLang) return specialLang;
7101
+ let ext = null;
7102
+ if (baseName.startsWith(".") && !baseName.slice(1).includes(".")) {
7103
+ ext = baseName.slice(1);
7104
+ } else {
7105
+ const lastDot = baseName.lastIndexOf(".");
7106
+ if (lastDot !== -1 && lastDot !== baseName.length - 1) {
7107
+ ext = baseName.slice(lastDot + 1);
7108
+ }
7109
+ }
7110
+ if (!ext) return "text";
7111
+ return EXTENSION_OVERRIDES[ext] ?? ext;
7112
+ }
7113
+ function CodeRenderer({
7114
+ content,
7115
+ fileName,
7116
+ className
7117
+ }) {
7118
+ const language = React20__namespace.useMemo(
7119
+ () => getLanguageFromFileName(fileName),
7120
+ [fileName]
7121
+ );
7122
+ const plainLines = React20__namespace.useMemo(
7123
+ () => (content ?? "").split("\n"),
7124
+ [content]
7125
+ );
7126
+ const { lines: highlightedLines } = useHighlightedTokens(
7127
+ content ?? "",
7128
+ language
7129
+ );
7130
+ const gutterWidth = React20__namespace.useMemo(
7131
+ () => `${Math.max(String(plainLines.length).length, 2) + 2}ch`,
7132
+ [plainLines.length]
7133
+ );
7134
+ return /* @__PURE__ */ jsxRuntime.jsx(
7135
+ "div",
7136
+ {
7137
+ className: cn(
7138
+ "relative h-full w-full overflow-auto rounded-md border border-border bg-background",
7139
+ "scrollbar-thin",
7140
+ className
7141
+ ),
7142
+ children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "m-0 p-0", children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "block font-mono text-sm leading-relaxed", children: highlightedLines ? highlightedLines.map((tokens, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
7143
+ /* @__PURE__ */ jsxRuntime.jsx(
7144
+ "span",
7145
+ {
7146
+ className: "sticky left-0 shrink-0 select-none border-r border-border bg-muted px-3 text-right text-muted-foreground",
7147
+ style: { minWidth: gutterWidth },
7148
+ children: index + 1
7149
+ }
7150
+ ),
7151
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre px-4", children: tokens.length === 0 || tokens.length === 1 && tokens[0].content === "" ? " " : tokens.map((token, ti) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: token.color }, children: token.content }, ti)) })
7152
+ ] }, index)) : plainLines.map((line, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex", children: [
7153
+ /* @__PURE__ */ jsxRuntime.jsx(
7154
+ "span",
7155
+ {
7156
+ className: "sticky left-0 shrink-0 select-none border-r border-border bg-muted px-3 text-right text-muted-foreground",
7157
+ style: { minWidth: gutterWidth },
7158
+ children: index + 1
7159
+ }
7160
+ ),
7161
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "whitespace-pre px-4 text-foreground", children: line || " " })
7162
+ ] }, index)) }) })
7163
+ }
7164
+ );
7165
+ }
7166
+ CodeRenderer.displayName = "CodeRenderer";
7167
+ function HighlightedCodeBlock({
7168
+ children,
7169
+ className: codeClassName,
7170
+ ...props
7171
+ }) {
7172
+ const language = React20__namespace.useMemo(() => {
7173
+ if (!codeClassName) return "text";
7174
+ const match = codeClassName.match(/language-(\S+)/);
7175
+ return match ? match[1] : "text";
7176
+ }, [codeClassName]);
7177
+ const code = React20__namespace.useMemo(() => {
7178
+ const raw = String(children);
7179
+ return raw.endsWith("\n") ? raw.slice(0, -1) : raw;
7180
+ }, [children]);
7181
+ const { lines } = useHighlightedTokens(code, language);
7182
+ if (lines) {
7183
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { className: cn("bg-transparent p-0", codeClassName), ...props, children: lines.map((tokens, i) => /* @__PURE__ */ jsxRuntime.jsxs(React20__namespace.Fragment, { children: [
7184
+ i > 0 && "\n",
7185
+ tokens.map((token, ti) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: token.color }, children: token.content }, ti))
7186
+ ] }, i)) });
7187
+ }
7188
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { className: cn("bg-transparent p-0", codeClassName), ...props, children });
7189
+ }
7190
+ function MarkdownImage({
7191
+ src,
7192
+ alt,
7193
+ resolveImageUrl,
7194
+ ...props
7195
+ }) {
7196
+ const needsResolution = !!src && !!resolveImageUrl && !src.startsWith("http://") && !src.startsWith("https://") && !src.startsWith("data:");
7197
+ const [resolvedUrl, setResolvedUrl] = React20__namespace.useState(null);
7198
+ const [error, setError] = React20__namespace.useState(false);
7199
+ const [loading, setLoading] = React20__namespace.useState(needsResolution);
7200
+ React20__namespace.useEffect(() => {
7201
+ if (!needsResolution || !src || !resolveImageUrl) return;
7202
+ let cancelled = false;
7203
+ let blobUrl = null;
7204
+ setLoading(true);
7205
+ setError(false);
7206
+ setResolvedUrl(null);
7207
+ Promise.resolve(resolveImageUrl(src)).then((url) => {
7208
+ if (cancelled) {
7209
+ if (url.startsWith("blob:")) URL.revokeObjectURL(url);
7210
+ return;
7211
+ }
7212
+ if (url.startsWith("blob:")) blobUrl = url;
7213
+ setResolvedUrl(url);
7214
+ setLoading(false);
7215
+ }).catch(() => {
7216
+ if (!cancelled) {
7217
+ setError(true);
7218
+ setLoading(false);
7219
+ }
7220
+ });
7221
+ return () => {
7222
+ cancelled = true;
7223
+ if (blobUrl) URL.revokeObjectURL(blobUrl);
7224
+ };
7225
+ }, [needsResolution, src, resolveImageUrl]);
7226
+ if (!needsResolution) {
7227
+ return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className: "max-w-full rounded", ...props });
7228
+ }
7229
+ if (loading) {
7230
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "my-2 inline-block h-24 w-40 animate-pulse rounded bg-muted" });
7231
+ }
7232
+ if (error || !resolvedUrl) {
7233
+ return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt, className: "max-w-full rounded", ...props });
7234
+ }
7235
+ return /* @__PURE__ */ jsxRuntime.jsx(
7236
+ "img",
7237
+ {
7238
+ src: resolvedUrl,
7239
+ alt,
7240
+ className: "max-w-full rounded",
7241
+ ...props
7242
+ }
7243
+ );
7244
+ }
7245
+ function MarkdownRenderer({
7246
+ content,
7247
+ className,
7248
+ resolveImageUrl
7249
+ }) {
7250
+ const markdownComponents = React20__namespace.useMemo(
7251
+ () => ({
7252
+ code: ({
7253
+ children,
7254
+ className: codeClassName,
7255
+ ...props
7256
+ }) => {
7257
+ const isInline = !codeClassName;
7258
+ if (isInline) {
7259
+ return /* @__PURE__ */ jsxRuntime.jsx(
7260
+ "code",
7261
+ {
7262
+ className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[85%]",
7263
+ ...props,
7264
+ children
7265
+ }
7266
+ );
7267
+ }
7268
+ return /* @__PURE__ */ jsxRuntime.jsx(HighlightedCodeBlock, { className: codeClassName, ...props, children });
7269
+ },
7270
+ pre: ({ children, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
7271
+ "pre",
7272
+ {
7273
+ className: "my-4 overflow-auto rounded-md border border-border bg-muted p-4 font-mono text-sm leading-relaxed",
7274
+ ...props,
7275
+ children
7276
+ }
7277
+ ),
7278
+ blockquote: ({
7279
+ children,
7280
+ ...props
7281
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
7282
+ "blockquote",
7283
+ {
7284
+ className: "my-4 border-l-4 border-border pl-4 text-muted-foreground",
7285
+ ...props,
7286
+ children
7287
+ }
7288
+ ),
7289
+ a: ({
7290
+ children,
7291
+ href,
7292
+ ...props
7293
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
7294
+ "a",
7295
+ {
7296
+ href,
7297
+ target: "_blank",
7298
+ rel: "noopener noreferrer",
7299
+ className: "text-primary hover:underline",
7300
+ ...props,
7301
+ children
7302
+ }
7303
+ ),
7304
+ table: ({
7305
+ children,
7306
+ ...props
7307
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "my-4 overflow-auto", children: /* @__PURE__ */ jsxRuntime.jsx(
7308
+ "table",
7309
+ {
7310
+ className: "w-full border-collapse border border-border text-sm",
7311
+ ...props,
7312
+ children
7313
+ }
7314
+ ) }),
7315
+ th: ({
7316
+ children,
7317
+ ...props
7318
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
7319
+ "th",
7320
+ {
7321
+ className: "border border-border bg-muted px-3 py-2 text-left font-semibold",
7322
+ ...props,
7323
+ children
7324
+ }
7325
+ ),
7326
+ td: ({
7327
+ children,
7328
+ ...props
7329
+ }) => /* @__PURE__ */ jsxRuntime.jsx("td", { className: "border border-border px-3 py-2", ...props, children }),
7330
+ img: ({
7331
+ src,
7332
+ alt,
7333
+ ...props
7334
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
7335
+ MarkdownImage,
7336
+ {
7337
+ src,
7338
+ alt,
7339
+ resolveImageUrl,
7340
+ ...props
7341
+ }
7342
+ )
7343
+ }),
7344
+ [resolveImageUrl]
7345
+ );
7346
+ return /* @__PURE__ */ jsxRuntime.jsx(
7347
+ "div",
7348
+ {
7349
+ className: cn(
7350
+ "h-full w-full overflow-auto bg-background p-6 text-sm leading-relaxed text-foreground",
7351
+ // Heading styles
7352
+ "[&_h1]:mb-4 [&_h1]:mt-6 [&_h1]:border-b [&_h1]:border-border [&_h1]:pb-2 [&_h1]:text-2xl [&_h1]:font-bold",
7353
+ "[&_h2]:mb-4 [&_h2]:mt-6 [&_h2]:border-b [&_h2]:border-border [&_h2]:pb-2 [&_h2]:text-xl [&_h2]:font-semibold",
7354
+ "[&_h3]:mb-3 [&_h3]:mt-5 [&_h3]:text-lg [&_h3]:font-semibold",
7355
+ "[&_h4]:mb-3 [&_h4]:mt-4 [&_h4]:text-base [&_h4]:font-semibold",
7356
+ // Paragraph and list styles
7357
+ "[&_p]:mb-4 [&_p]:leading-relaxed",
7358
+ "[&_ul]:mb-4 [&_ul]:list-disc [&_ul]:pl-6",
7359
+ "[&_ol]:mb-4 [&_ol]:list-decimal [&_ol]:pl-6",
7360
+ "[&_li]:mb-1",
7361
+ // Horizontal rule
7362
+ "[&_hr]:my-6 [&_hr]:border-border",
7363
+ "scrollbar-thin",
7364
+ className
7365
+ ),
7366
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7367
+ ReactMarkdownImport__default.default,
7368
+ {
7369
+ remarkPlugins: [remarkGfmImport__default.default],
7370
+ components: markdownComponents,
7371
+ children: content ?? ""
7372
+ }
7373
+ )
7374
+ }
7375
+ );
7376
+ }
7377
+ MarkdownRenderer.displayName = "MarkdownRenderer";
7378
+ function ImageRenderer({
7379
+ url,
7380
+ fileName,
7381
+ className
7382
+ }) {
7383
+ const [hasError, setHasError] = React20__namespace.useState(false);
7384
+ React20__namespace.useEffect(() => {
7385
+ setHasError(false);
7386
+ }, [url]);
7387
+ if (!url) {
7388
+ return /* @__PURE__ */ jsxRuntime.jsx(
7389
+ "div",
7390
+ {
7391
+ className: cn(
7392
+ "flex h-full w-full items-center justify-center text-sm text-muted-foreground",
7393
+ className
7394
+ ),
7395
+ children: "No image URL provided"
7396
+ }
7397
+ );
7398
+ }
7399
+ if (hasError) {
7400
+ return /* @__PURE__ */ jsxRuntime.jsxs(
7401
+ "div",
7402
+ {
7403
+ className: cn(
7404
+ "flex h-full w-full flex-col items-center justify-center gap-2 text-sm text-muted-foreground",
7405
+ className
7406
+ ),
7407
+ children: [
7408
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Failed to load image" }),
7409
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: fileName })
7410
+ ]
7411
+ }
7412
+ );
7413
+ }
7414
+ return /* @__PURE__ */ jsxRuntime.jsx(
7415
+ "div",
7416
+ {
7417
+ className: cn(
7418
+ "flex h-full w-full items-center justify-center overflow-auto bg-background p-4",
7419
+ "scrollbar-thin",
7420
+ className
7421
+ ),
7422
+ children: /* @__PURE__ */ jsxRuntime.jsx(
7423
+ "img",
7424
+ {
7425
+ src: url,
7426
+ alt: fileName,
7427
+ onError: () => setHasError(true),
7428
+ className: "max-h-full max-w-full object-contain"
7429
+ }
7430
+ )
7431
+ }
7432
+ );
7433
+ }
7434
+ ImageRenderer.displayName = "ImageRenderer";
7435
+ function PlainTextRenderer({ content, className }) {
7436
+ return /* @__PURE__ */ jsxRuntime.jsx(
7437
+ "div",
7438
+ {
7439
+ className: cn(
7440
+ "h-full w-full overflow-auto rounded-md border border-border bg-background p-4",
7441
+ "scrollbar-thin",
7442
+ className
7443
+ ),
7444
+ children: /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "m-0 whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-foreground", children: content ?? "" })
7445
+ }
7446
+ );
7447
+ }
7448
+ PlainTextRenderer.displayName = "PlainTextRenderer";
7449
+ function parseCSV(text) {
7450
+ const lines = text.split("\n");
7451
+ if (lines.length === 0) return { headers: [], rows: [] };
7452
+ const firstLine = lines[0] ?? "";
7453
+ const delimiter = firstLine.includes(" ") ? " " : ",";
7454
+ function parseLine(line) {
7455
+ const fields = [];
7456
+ let current = "";
7457
+ let inQuotes = false;
7458
+ let i = 0;
7459
+ while (i < line.length) {
7460
+ const char = line[i];
7461
+ if (inQuotes) {
7462
+ if (char === '"') {
7463
+ if (i + 1 < line.length && line[i + 1] === '"') {
7464
+ current += '"';
7465
+ i += 2;
7466
+ } else {
7467
+ inQuotes = false;
7468
+ i++;
7469
+ }
7470
+ } else {
7471
+ current += char;
7472
+ i++;
7473
+ }
7474
+ } else {
7475
+ if (char === '"') {
7476
+ inQuotes = true;
7477
+ i++;
7478
+ } else if (char === delimiter) {
7479
+ fields.push(current.trim());
7480
+ current = "";
7481
+ i++;
7482
+ } else {
7483
+ current += char;
7484
+ i++;
7485
+ }
7486
+ }
7487
+ }
7488
+ fields.push(current.trim());
7489
+ return fields;
7490
+ }
7491
+ const headers = parseLine(firstLine);
7492
+ const rows = [];
7493
+ for (let i = 1; i < lines.length; i++) {
7494
+ const line = lines[i];
7495
+ if (line.trim() === "") continue;
7496
+ const values = parseLine(line);
7497
+ const row = {};
7498
+ for (let j = 0; j < headers.length; j++) {
7499
+ row[headers[j]] = values[j] ?? "";
7500
+ }
7501
+ rows.push(row);
7502
+ }
7503
+ return { headers, rows };
7504
+ }
7505
+ function CsvRenderer({ content, className }) {
7506
+ const { headers, rows } = React20__namespace.useMemo(
7507
+ () => parseCSV(content ?? ""),
7508
+ [content]
7509
+ );
7510
+ const columns = React20__namespace.useMemo(
7511
+ () => headers.map((header) => ({
7512
+ key: header,
7513
+ header,
7514
+ sortable: true,
7515
+ filterable: true,
7516
+ filterType: "text"
7517
+ })),
7518
+ [headers]
7519
+ );
7520
+ if (headers.length === 0) {
7521
+ return /* @__PURE__ */ jsxRuntime.jsx(
7522
+ "div",
7523
+ {
7524
+ className: cn(
7525
+ "flex h-full w-full items-center justify-center text-sm text-muted-foreground",
7526
+ className
7527
+ ),
7528
+ children: "No CSV data to display"
7529
+ }
7530
+ );
7531
+ }
7532
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("h-full w-full overflow-auto", className), children: /* @__PURE__ */ jsxRuntime.jsx(
7533
+ DataGrid,
7534
+ {
7535
+ data: rows,
7536
+ columns,
7537
+ getRowKey: (_, index) => String(index),
7538
+ resizableColumns: true
7539
+ }
7540
+ ) });
7541
+ }
7542
+ CsvRenderer.displayName = "CsvRenderer";
7543
+ function HtmlRenderer({
7544
+ content,
7545
+ fileName,
7546
+ className
7547
+ }) {
7548
+ return /* @__PURE__ */ jsxRuntime.jsx(
7549
+ "iframe",
7550
+ {
7551
+ srcDoc: content ?? "",
7552
+ sandbox: "allow-scripts",
7553
+ title: fileName,
7554
+ className: cn("h-full w-full border-0", className)
7555
+ }
7556
+ );
7557
+ }
7558
+ HtmlRenderer.displayName = "HtmlRenderer";
7559
+
7560
+ // src/components/file-view/utils/rendererRegistry.ts
7561
+ var DEFAULT_RENDERERS = {
7562
+ code: CodeRenderer,
7563
+ markdown: MarkdownRenderer,
7564
+ image: ImageRenderer,
7565
+ plaintext: PlainTextRenderer,
7566
+ csv: CsvRenderer,
7567
+ html: HtmlRenderer
7568
+ };
7569
+ function mergeRenderers(userRenderers) {
7570
+ if (!userRenderers) return DEFAULT_RENDERERS;
7571
+ return { ...DEFAULT_RENDERERS, ...userRenderers };
7572
+ }
7573
+ function resolveRenderer(registry, contentType) {
7574
+ return registry[contentType] ?? registry["plaintext"] ?? PlainTextRenderer;
7575
+ }
7576
+ function DefaultEmptyState({ message }) {
7577
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center text-sm text-muted-foreground", children: message });
7578
+ }
7579
+ function DefaultLoadingState() {
7580
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3", children: [
7581
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 w-8 animate-spin rounded-full border-2 border-primary/20 border-t-primary" }),
7582
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: "Loading file..." })
7583
+ ] }) });
7584
+ }
7585
+ function DefaultErrorState({ error }) {
7586
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex h-full w-full items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex max-w-sm flex-col items-center gap-3 text-center", children: [
7587
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-destructive", children: error.message }),
7588
+ error.onRetry && /* @__PURE__ */ jsxRuntime.jsx(
7589
+ "button",
7590
+ {
7591
+ onClick: error.onRetry,
7592
+ className: cn(
7593
+ "rounded-md border border-border bg-background px-3 py-1.5 text-sm text-foreground",
7594
+ "transition-colors hover:bg-muted"
7595
+ ),
7596
+ children: "Retry"
7597
+ }
7598
+ )
7599
+ ] }) });
7600
+ }
7601
+ function FileView({
7602
+ content,
7603
+ url = null,
7604
+ fileName,
7605
+ contentType: contentTypeOverride,
7606
+ renderers: userRenderers,
7607
+ loading = false,
7608
+ error = null,
7609
+ loadingComponent,
7610
+ emptyComponent,
7611
+ emptyMessage = "No content to display",
7612
+ errorComponent: ErrorComponent,
7613
+ className,
7614
+ rendererClassName,
7615
+ resolveImageUrl
7616
+ }) {
7617
+ const { type: resolvedType } = useContentType({
7618
+ fileName,
7619
+ contentTypeOverride
7620
+ });
7621
+ const registry = React20__namespace.useMemo(
7622
+ () => mergeRenderers(userRenderers),
7623
+ [userRenderers]
7624
+ );
7625
+ const Renderer = React20__namespace.useMemo(
7626
+ () => resolveRenderer(registry, resolvedType),
7627
+ [registry, resolvedType]
7628
+ );
7629
+ if (loading) {
7630
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("relative h-full w-full", className), children: loadingComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultLoadingState, {}) });
7631
+ }
7632
+ if (error) {
7633
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("relative h-full w-full", className), children: ErrorComponent ? /* @__PURE__ */ jsxRuntime.jsx(ErrorComponent, { error }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultErrorState, { error }) });
7634
+ }
7635
+ if ((content == null || content === "") && url == null) {
7636
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("relative h-full w-full", className), children: emptyComponent ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultEmptyState, { message: emptyMessage }) });
7637
+ }
7638
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("relative h-full w-full", className), children: /* @__PURE__ */ jsxRuntime.jsx(
7639
+ Renderer,
7640
+ {
7641
+ content,
7642
+ url: url ?? null,
7643
+ fileName,
7644
+ contentType: resolvedType,
7645
+ className: rendererClassName,
7646
+ resolveImageUrl
7647
+ }
7648
+ ) });
7649
+ }
7650
+ FileView.displayName = "FileView";
7651
+
6804
7652
  exports.ALL_THEMES = ALL_THEMES;
6805
7653
  exports.Accordion = Accordion;
6806
7654
  exports.AccordionContent = AccordionContent;
@@ -6838,10 +7686,13 @@ exports.CardTitle = CardTitle;
6838
7686
  exports.CellEditor = CellEditor;
6839
7687
  exports.Checkbox = Checkbox;
6840
7688
  exports.Chip = Chip;
7689
+ exports.CodeRenderer = CodeRenderer;
6841
7690
  exports.ConfirmationModal = ConfirmationModal;
6842
7691
  exports.ContextMenu = ContextMenu;
6843
7692
  exports.CopyButton = CopyButton;
7693
+ exports.CsvRenderer = CsvRenderer;
6844
7694
  exports.DARK_ELEGANT_THEME = DARK_ELEGANT_THEME;
7695
+ exports.DEFAULT_RENDERERS = DEFAULT_RENDERERS;
6845
7696
  exports.DataGrid = DataGrid;
6846
7697
  exports.DataTable = DataTable;
6847
7698
  exports.DatePicker = DatePicker;
@@ -6863,20 +7714,25 @@ exports.DropdownMenuSubTrigger = DropdownMenuSubTrigger;
6863
7714
  exports.DropdownMenuTrigger = DropdownMenuTrigger;
6864
7715
  exports.FOREST_THEME = FOREST_THEME;
6865
7716
  exports.FUTURISTIC_THEME = FUTURISTIC_THEME;
7717
+ exports.FileView = FileView;
6866
7718
  exports.FilterPopover = FilterPopover;
6867
7719
  exports.GREEN_THEME = GREEN_THEME;
6868
7720
  exports.HeaderCell = HeaderCell;
7721
+ exports.HtmlRenderer = HtmlRenderer;
6869
7722
  exports.IconButton = IconButton;
7723
+ exports.ImageRenderer = ImageRenderer;
6870
7724
  exports.Input = Input;
6871
7725
  exports.Label = Label;
6872
7726
  exports.LoadingSpinner = LoadingSpinner;
6873
7727
  exports.MINIMALIST_LIGHT_THEME = MINIMALIST_LIGHT_THEME;
7728
+ exports.MarkdownRenderer = MarkdownRenderer;
6874
7729
  exports.Modal = Modal;
6875
7730
  exports.ModalButton = ModalButton;
6876
7731
  exports.NATURE_THEME = NATURE_THEME;
6877
7732
  exports.OCEAN_THEME = OCEAN_THEME;
6878
7733
  exports.OPTILOGIC_LEGACY_THEME = OPTILOGIC_LEGACY_THEME;
6879
7734
  exports.PRESET_THEMES = PRESET_THEMES;
7735
+ exports.PlainTextRenderer = PlainTextRenderer;
6880
7736
  exports.Popover = Popover;
6881
7737
  exports.PopoverAnchor = PopoverAnchor;
6882
7738
  exports.PopoverContent = PopoverContent;
@@ -6940,21 +7796,28 @@ exports.cardListVariants = cardListVariants;
6940
7796
  exports.cardVariants = cardVariants;
6941
7797
  exports.cloneTheme = cloneTheme;
6942
7798
  exports.cn = cn;
7799
+ exports.detectContentType = detectContentType;
6943
7800
  exports.exportTheme = exportTheme;
6944
7801
  exports.getCellValue = getCellValue;
6945
7802
  exports.getCurrentTheme = getCurrentTheme;
6946
7803
  exports.getDefaultTheme = getDefaultTheme;
7804
+ exports.getFileExtension = getFileExtension;
6947
7805
  exports.getPresetTheme = getPresetTheme;
6948
7806
  exports.hexToHsl = hexToHsl;
6949
7807
  exports.iconButtonVariants = iconButtonVariants;
6950
7808
  exports.importTheme = importTheme;
6951
7809
  exports.isPresetTheme = isPresetTheme;
7810
+ exports.isTextContentType = isTextContentType;
7811
+ exports.isUrlContentType = isUrlContentType;
6952
7812
  exports.labelVariants = labelVariants;
6953
7813
  exports.loadingSpinnerVariants = loadingSpinnerVariants;
7814
+ exports.mergeRenderers = mergeRenderers;
7815
+ exports.resolveRenderer = resolveRenderer;
6954
7816
  exports.themeToHsl = themeToHsl;
6955
7817
  exports.useColumnResize = useColumnResize;
6956
7818
  exports.useColumnResizeManager = useColumnResizeManager;
6957
7819
  exports.useConfirmation = useConfirmation;
7820
+ exports.useContentType = useContentType;
6958
7821
  exports.useContextMenu = useContextMenu;
6959
7822
  exports.useDataGridState = useDataGridState;
6960
7823
  exports.useKeyboardNavigation = useKeyboardNavigation;