@particle-academy/fancy-code 0.12.1 → 0.14.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/CHANGELOG.md +43 -0
- package/dist/index.cjs +76 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -2
- package/dist/index.d.ts +27 -2
- package/dist/index.js +77 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
3
3
|
import { ThemeColors, DiffAnnotations, LineChangeType, Token, Tokenizer } from '@particle-academy/fancy-file-commons';
|
|
4
4
|
export { ThemeColors, Token, TokenType, Tokenizer, languageFromFilename } from '@particle-academy/fancy-file-commons';
|
|
5
|
-
import { MediaKind, MediaViewerProps, InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
5
|
+
import { MediaKind, MediaViewerProps, FauxClientProps, InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
6
6
|
|
|
7
7
|
interface ThemeDefinition {
|
|
8
8
|
/** Unique theme name */
|
|
@@ -281,7 +281,20 @@ type FileKind = {
|
|
|
281
281
|
} | {
|
|
282
282
|
kind: "media";
|
|
283
283
|
mediaKind: MediaKind;
|
|
284
|
+
}
|
|
285
|
+
/** Read as a reader sees it (rendered), not as source. `language` is for the source view. */
|
|
286
|
+
| {
|
|
287
|
+
kind: "document";
|
|
288
|
+
format: DocumentFormat;
|
|
289
|
+
language: string;
|
|
290
|
+
}
|
|
291
|
+
/** An Office file: binary, so never text. */
|
|
292
|
+
| {
|
|
293
|
+
kind: "office";
|
|
294
|
+
officeKind: OfficeKind;
|
|
284
295
|
};
|
|
296
|
+
type DocumentFormat = "markdown" | "html" | "text";
|
|
297
|
+
type OfficeKind = "docx" | "xlsx" | "pptx";
|
|
285
298
|
interface ResolveFileKindInput {
|
|
286
299
|
/** File name or path — sniffed for an extension (and used for the media check). */
|
|
287
300
|
filename?: string;
|
|
@@ -354,6 +367,18 @@ interface FileViewerProps {
|
|
|
354
367
|
maxHeight?: number;
|
|
355
368
|
/** Extra props forwarded to `<MediaViewer>` for media files. */
|
|
356
369
|
mediaProps?: Partial<Omit<MediaViewerProps, "src" | "mime" | "alt">>;
|
|
370
|
+
/**
|
|
371
|
+
* How a **document** (Markdown, HTML, plain text) is shown. `"rendered"`
|
|
372
|
+
* (default): as a reader sees it, through react-fancy's `ContentRenderer`
|
|
373
|
+
* (sanitised; a file is untrusted). `"source"`: in the code editor. Editing
|
|
374
|
+
* (`readOnly={false}`) and landing on a `line` always show the source.
|
|
375
|
+
*/
|
|
376
|
+
view?: "rendered" | "source";
|
|
377
|
+
/**
|
|
378
|
+
* Passed to the react-fancy `FauxClient` frame a rendered file sits in (zoom
|
|
379
|
+
* and full screen, revealed on hover): `variant`, a controlled `zoom`, …
|
|
380
|
+
*/
|
|
381
|
+
frameProps?: Partial<FauxClientProps>;
|
|
357
382
|
className?: string;
|
|
358
383
|
style?: CSSProperties;
|
|
359
384
|
}
|
|
@@ -374,7 +399,7 @@ interface FileViewerProps {
|
|
|
374
399
|
* <FileViewer filename="logo.png" src={url} style={{ height: 360 }} />
|
|
375
400
|
* ```
|
|
376
401
|
*/
|
|
377
|
-
declare function FileViewer({ filename, mime, src, value, onChange, readOnly, theme, fileKind, lineNumbers, wordWrap, line, column, highlightLines, minHeight, maxHeight, mediaProps, className, style, }: FileViewerProps): react.JSX.Element;
|
|
402
|
+
declare function FileViewer({ filename, mime, src, value, onChange, readOnly, theme, fileKind, lineNumbers, wordWrap, line, column, highlightLines, minHeight, maxHeight, mediaProps, view, frameProps, className, style, }: FileViewerProps): react.JSX.Element;
|
|
378
403
|
declare namespace FileViewer {
|
|
379
404
|
var displayName: string;
|
|
380
405
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as react from 'react';
|
|
|
2
2
|
import { RefObject, ReactNode, CSSProperties } from 'react';
|
|
3
3
|
import { ThemeColors, DiffAnnotations, LineChangeType, Token, Tokenizer } from '@particle-academy/fancy-file-commons';
|
|
4
4
|
export { ThemeColors, Token, TokenType, Tokenizer, languageFromFilename } from '@particle-academy/fancy-file-commons';
|
|
5
|
-
import { MediaKind, MediaViewerProps, InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
5
|
+
import { MediaKind, MediaViewerProps, FauxClientProps, InputTagProps, InputTagTriggers, InputTagAdapter } from '@particle-academy/react-fancy';
|
|
6
6
|
|
|
7
7
|
interface ThemeDefinition {
|
|
8
8
|
/** Unique theme name */
|
|
@@ -281,7 +281,20 @@ type FileKind = {
|
|
|
281
281
|
} | {
|
|
282
282
|
kind: "media";
|
|
283
283
|
mediaKind: MediaKind;
|
|
284
|
+
}
|
|
285
|
+
/** Read as a reader sees it (rendered), not as source. `language` is for the source view. */
|
|
286
|
+
| {
|
|
287
|
+
kind: "document";
|
|
288
|
+
format: DocumentFormat;
|
|
289
|
+
language: string;
|
|
290
|
+
}
|
|
291
|
+
/** An Office file: binary, so never text. */
|
|
292
|
+
| {
|
|
293
|
+
kind: "office";
|
|
294
|
+
officeKind: OfficeKind;
|
|
284
295
|
};
|
|
296
|
+
type DocumentFormat = "markdown" | "html" | "text";
|
|
297
|
+
type OfficeKind = "docx" | "xlsx" | "pptx";
|
|
285
298
|
interface ResolveFileKindInput {
|
|
286
299
|
/** File name or path — sniffed for an extension (and used for the media check). */
|
|
287
300
|
filename?: string;
|
|
@@ -354,6 +367,18 @@ interface FileViewerProps {
|
|
|
354
367
|
maxHeight?: number;
|
|
355
368
|
/** Extra props forwarded to `<MediaViewer>` for media files. */
|
|
356
369
|
mediaProps?: Partial<Omit<MediaViewerProps, "src" | "mime" | "alt">>;
|
|
370
|
+
/**
|
|
371
|
+
* How a **document** (Markdown, HTML, plain text) is shown. `"rendered"`
|
|
372
|
+
* (default): as a reader sees it, through react-fancy's `ContentRenderer`
|
|
373
|
+
* (sanitised; a file is untrusted). `"source"`: in the code editor. Editing
|
|
374
|
+
* (`readOnly={false}`) and landing on a `line` always show the source.
|
|
375
|
+
*/
|
|
376
|
+
view?: "rendered" | "source";
|
|
377
|
+
/**
|
|
378
|
+
* Passed to the react-fancy `FauxClient` frame a rendered file sits in (zoom
|
|
379
|
+
* and full screen, revealed on hover): `variant`, a controlled `zoom`, …
|
|
380
|
+
*/
|
|
381
|
+
frameProps?: Partial<FauxClientProps>;
|
|
357
382
|
className?: string;
|
|
358
383
|
style?: CSSProperties;
|
|
359
384
|
}
|
|
@@ -374,7 +399,7 @@ interface FileViewerProps {
|
|
|
374
399
|
* <FileViewer filename="logo.png" src={url} style={{ height: 360 }} />
|
|
375
400
|
* ```
|
|
376
401
|
*/
|
|
377
|
-
declare function FileViewer({ filename, mime, src, value, onChange, readOnly, theme, fileKind, lineNumbers, wordWrap, line, column, highlightLines, minHeight, maxHeight, mediaProps, className, style, }: FileViewerProps): react.JSX.Element;
|
|
402
|
+
declare function FileViewer({ filename, mime, src, value, onChange, readOnly, theme, fileKind, lineNumbers, wordWrap, line, column, highlightLines, minHeight, maxHeight, mediaProps, view, frameProps, className, style, }: FileViewerProps): react.JSX.Element;
|
|
378
403
|
declare namespace FileViewer {
|
|
379
404
|
var displayName: string;
|
|
380
405
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext, useMemo, useState, useCallback, useRef, useEffect, useSyncExternalStore, useContext } from 'react';
|
|
2
|
-
import { cn, useControllableState, ContentRenderer, resolveMediaType, MediaViewer, textareaAdapter, InputTag } from '@particle-academy/react-fancy';
|
|
2
|
+
import { cn, useControllableState, ContentRenderer, resolveMediaType, FauxClient, MediaViewer, textareaAdapter, InputTag } from '@particle-academy/react-fancy';
|
|
3
3
|
import { annotateLines, highlightCode, tok, tokenizeHtml, LIGHT_COLORS, DARK_COLORS, languageFromFilename } from '@particle-academy/fancy-file-commons';
|
|
4
4
|
export { languageFromFilename } from '@particle-academy/fancy-file-commons';
|
|
5
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -1720,10 +1720,45 @@ function MarkdownEditor({
|
|
|
1720
1720
|
);
|
|
1721
1721
|
}
|
|
1722
1722
|
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1723
|
+
var OFFICE_EXT = { docx: "docx", xlsx: "xlsx", xlsm: "xlsx", pptx: "pptx" };
|
|
1724
|
+
var OFFICE_MIME = {
|
|
1725
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
1726
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
1727
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx"
|
|
1728
|
+
};
|
|
1729
|
+
var DOCUMENT_EXT = {
|
|
1730
|
+
md: "markdown",
|
|
1731
|
+
markdown: "markdown",
|
|
1732
|
+
html: "html",
|
|
1733
|
+
htm: "html",
|
|
1734
|
+
txt: "text",
|
|
1735
|
+
text: "text"
|
|
1736
|
+
};
|
|
1737
|
+
var DOCUMENT_MIME = {
|
|
1738
|
+
"text/markdown": "markdown",
|
|
1739
|
+
"text/x-markdown": "markdown",
|
|
1740
|
+
"text/html": "html",
|
|
1741
|
+
"text/plain": "text"
|
|
1742
|
+
};
|
|
1743
|
+
function extensionOf(filename) {
|
|
1744
|
+
const name = (filename ?? "").split(/[?#]/)[0].split("/").pop() ?? "";
|
|
1745
|
+
const dot = name.lastIndexOf(".");
|
|
1746
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
1747
|
+
}
|
|
1723
1748
|
function resolveFileKind({ filename, mime }) {
|
|
1724
1749
|
const media = resolveMediaType({ mime, src: filename });
|
|
1725
1750
|
if (media !== "unknown") return { kind: "media", mediaKind: media };
|
|
1726
|
-
|
|
1751
|
+
const type = (mime ?? "").split(";")[0].trim().toLowerCase();
|
|
1752
|
+
const ext = extensionOf(filename);
|
|
1753
|
+
const office = OFFICE_MIME[type] ?? OFFICE_EXT[ext];
|
|
1754
|
+
if (office) return { kind: "office", officeKind: office };
|
|
1755
|
+
const language = languageFromFilename(filename);
|
|
1756
|
+
const format = DOCUMENT_EXT[ext] ?? (language === "plaintext" ? DOCUMENT_MIME[type] : void 0);
|
|
1757
|
+
if (format) {
|
|
1758
|
+
const lang = language !== "plaintext" ? language : format === "markdown" ? "markdown" : format === "html" ? "html" : "plaintext";
|
|
1759
|
+
return { kind: "document", format, language: lang };
|
|
1760
|
+
}
|
|
1761
|
+
return { kind: "text", language };
|
|
1727
1762
|
}
|
|
1728
1763
|
function FileViewer({
|
|
1729
1764
|
filename,
|
|
@@ -1742,12 +1777,48 @@ function FileViewer({
|
|
|
1742
1777
|
minHeight,
|
|
1743
1778
|
maxHeight,
|
|
1744
1779
|
mediaProps,
|
|
1780
|
+
view = "rendered",
|
|
1781
|
+
frameProps,
|
|
1745
1782
|
className,
|
|
1746
1783
|
style
|
|
1747
1784
|
}) {
|
|
1748
1785
|
const resolved = fileKind ?? resolveFileKind({ filename, mime });
|
|
1786
|
+
if (resolved.kind === "document" && view === "rendered" && readOnly && line == null) {
|
|
1787
|
+
return /* @__PURE__ */ jsx(
|
|
1788
|
+
"div",
|
|
1789
|
+
{
|
|
1790
|
+
"data-fancy-file-viewer": "",
|
|
1791
|
+
"data-kind": "document",
|
|
1792
|
+
"data-format": resolved.format,
|
|
1793
|
+
className,
|
|
1794
|
+
style,
|
|
1795
|
+
children: /* @__PURE__ */ jsx(FauxClient, { variant: "device", controls: true, fill: true, bodyClassName: "overflow-auto", ...frameProps, children: /* @__PURE__ */ jsx("div", { className: "px-6 py-5", children: /* @__PURE__ */ jsx(
|
|
1796
|
+
ContentRenderer,
|
|
1797
|
+
{
|
|
1798
|
+
value: resolved.format === "text" ? textToHtml(value) : value,
|
|
1799
|
+
format: resolved.format === "markdown" ? "markdown" : "html"
|
|
1800
|
+
}
|
|
1801
|
+
) }) })
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
if (resolved.kind === "office") {
|
|
1806
|
+
return /* @__PURE__ */ jsx(
|
|
1807
|
+
"div",
|
|
1808
|
+
{
|
|
1809
|
+
"data-fancy-file-viewer": "",
|
|
1810
|
+
"data-kind": "office",
|
|
1811
|
+
"data-office-kind": resolved.officeKind,
|
|
1812
|
+
className: cn("overflow-hidden", className),
|
|
1813
|
+
style,
|
|
1814
|
+
children: /* @__PURE__ */ jsx(MediaViewer, { src: src ?? "", mime, alt: filename, className: "h-full w-full" })
|
|
1815
|
+
}
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1749
1818
|
if (resolved.kind === "media") {
|
|
1750
1819
|
const { className: mediaClassName, ...restMediaProps } = mediaProps ?? {};
|
|
1820
|
+
restMediaProps.imageProps = { frame: true, frameProps, ...restMediaProps.imageProps };
|
|
1821
|
+
restMediaProps.pdfProps = { frame: true, frameProps, ...restMediaProps.pdfProps };
|
|
1751
1822
|
const fill = resolved.mediaKind !== "audio";
|
|
1752
1823
|
return /* @__PURE__ */ jsx(
|
|
1753
1824
|
"div",
|
|
@@ -1800,6 +1871,10 @@ function FileViewer({
|
|
|
1800
1871
|
);
|
|
1801
1872
|
}
|
|
1802
1873
|
FileViewer.displayName = "FileViewer";
|
|
1874
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
1875
|
+
function textToHtml(text) {
|
|
1876
|
+
return text.replace(/\r\n?/g, "\n").split(/\n{2,}/).filter((block) => block.trim() !== "").map((block) => `<p>${block.replace(/[&<>"']/g, (c) => ESCAPES[c]).replace(/\n/g, "<br>")}</p>`).join("");
|
|
1877
|
+
}
|
|
1803
1878
|
|
|
1804
1879
|
// src/engine/markdown-render.ts
|
|
1805
1880
|
function escapeHtml(s) {
|