@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/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,49 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.14.0] - 2026-10-19
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Images and PDFs in `FileViewer` sit in the `FauxClient` frame**, like
|
|
22
|
+
documents: an image gets the frame's zoom bar (driving its own pan/zoom) and
|
|
23
|
+
full screen; a PDF gets full screen, with zoom left to the browser's PDF
|
|
24
|
+
toolbar, and its file name in the address bar. Revealed on hover.
|
|
25
|
+
**What to do:** nothing to keep the new look. To turn it off, pass
|
|
26
|
+
`mediaProps={{ imageProps: { frame: false } }}` (or `pdfProps`).
|
|
27
|
+
- **Requires react-fancy 5.49.0 or later** (peer `>=5.49.0 <6.0.0`).
|
|
28
|
+
|
|
29
|
+
## [0.13.0] - 2026-10-19
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **Documents render as a reader sees them.** `FileViewer` shows Markdown, HTML
|
|
34
|
+
and plain text through react-fancy's `ContentRenderer`, sanitised (a file is
|
|
35
|
+
untrusted), in the `FauxClient` frame every file viewer now carries: zoom and
|
|
36
|
+
full screen, revealed on hover. Plain text keeps its paragraphs and line
|
|
37
|
+
breaks and is escaped, so `*x*` stays asterisks. `view="source"` shows the
|
|
38
|
+
source in the code editor instead, as do editing (`readOnly={false}`) and
|
|
39
|
+
landing on a `line`. `frameProps` reaches the frame.
|
|
40
|
+
- **Office files are recognised.** `.docx`, `.xlsx` and `.pptx` (by extension
|
|
41
|
+
or MIME type) resolve to `{ kind: "office", officeKind }`. They show
|
|
42
|
+
MediaViewer's no-preview card with a download until their viewers are wired
|
|
43
|
+
in. They used to be handed to the code editor as text.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **BREAKING:** a `.md`, `.html` or `.txt` file in `FileViewer` is rendered,
|
|
48
|
+
not shown as source. **What to do:** to keep the source, pass
|
|
49
|
+
`view="source"`. A viewer that lands on a `line` (a link into a file) already
|
|
50
|
+
shows the source, so file links need nothing.
|
|
51
|
+
- **BREAKING (types):** `FileKind` gains `{ kind: "document"; format; language }`
|
|
52
|
+
and `{ kind: "office"; officeKind }`. **What to do:** only if you `switch` on
|
|
53
|
+
`resolveFileKind(...).kind` exhaustively: add the two cases, or a `default`.
|
|
54
|
+
`notes.txt` now resolves to `document`, not `text`.
|
|
55
|
+
- The extension decides over a generic MIME type: a host that serves `app.ts`
|
|
56
|
+
as `text/plain` still gets the code editor.
|
|
57
|
+
- **Requires react-fancy 5.48.1 or later** (peer `>=5.48.1 <6.0.0`, was
|
|
58
|
+
`>=4.9.0`), for the frame and for the sanitiser that strips `<title>`.
|
|
59
|
+
|
|
17
60
|
## [0.12.1] - 2026-10-18
|
|
18
61
|
|
|
19
62
|
### Security
|
package/dist/index.cjs
CHANGED
|
@@ -1721,10 +1721,45 @@ function MarkdownEditor({
|
|
|
1721
1721
|
);
|
|
1722
1722
|
}
|
|
1723
1723
|
MarkdownEditor.displayName = "MarkdownEditor";
|
|
1724
|
+
var OFFICE_EXT = { docx: "docx", xlsx: "xlsx", xlsm: "xlsx", pptx: "pptx" };
|
|
1725
|
+
var OFFICE_MIME = {
|
|
1726
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
|
|
1727
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
|
|
1728
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx"
|
|
1729
|
+
};
|
|
1730
|
+
var DOCUMENT_EXT = {
|
|
1731
|
+
md: "markdown",
|
|
1732
|
+
markdown: "markdown",
|
|
1733
|
+
html: "html",
|
|
1734
|
+
htm: "html",
|
|
1735
|
+
txt: "text",
|
|
1736
|
+
text: "text"
|
|
1737
|
+
};
|
|
1738
|
+
var DOCUMENT_MIME = {
|
|
1739
|
+
"text/markdown": "markdown",
|
|
1740
|
+
"text/x-markdown": "markdown",
|
|
1741
|
+
"text/html": "html",
|
|
1742
|
+
"text/plain": "text"
|
|
1743
|
+
};
|
|
1744
|
+
function extensionOf(filename) {
|
|
1745
|
+
const name = (filename ?? "").split(/[?#]/)[0].split("/").pop() ?? "";
|
|
1746
|
+
const dot = name.lastIndexOf(".");
|
|
1747
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
1748
|
+
}
|
|
1724
1749
|
function resolveFileKind({ filename, mime }) {
|
|
1725
1750
|
const media = reactFancy.resolveMediaType({ mime, src: filename });
|
|
1726
1751
|
if (media !== "unknown") return { kind: "media", mediaKind: media };
|
|
1727
|
-
|
|
1752
|
+
const type = (mime ?? "").split(";")[0].trim().toLowerCase();
|
|
1753
|
+
const ext = extensionOf(filename);
|
|
1754
|
+
const office = OFFICE_MIME[type] ?? OFFICE_EXT[ext];
|
|
1755
|
+
if (office) return { kind: "office", officeKind: office };
|
|
1756
|
+
const language = fancyFileCommons.languageFromFilename(filename);
|
|
1757
|
+
const format = DOCUMENT_EXT[ext] ?? (language === "plaintext" ? DOCUMENT_MIME[type] : void 0);
|
|
1758
|
+
if (format) {
|
|
1759
|
+
const lang = language !== "plaintext" ? language : format === "markdown" ? "markdown" : format === "html" ? "html" : "plaintext";
|
|
1760
|
+
return { kind: "document", format, language: lang };
|
|
1761
|
+
}
|
|
1762
|
+
return { kind: "text", language };
|
|
1728
1763
|
}
|
|
1729
1764
|
function FileViewer({
|
|
1730
1765
|
filename,
|
|
@@ -1743,12 +1778,48 @@ function FileViewer({
|
|
|
1743
1778
|
minHeight,
|
|
1744
1779
|
maxHeight,
|
|
1745
1780
|
mediaProps,
|
|
1781
|
+
view = "rendered",
|
|
1782
|
+
frameProps,
|
|
1746
1783
|
className,
|
|
1747
1784
|
style
|
|
1748
1785
|
}) {
|
|
1749
1786
|
const resolved = fileKind ?? resolveFileKind({ filename, mime });
|
|
1787
|
+
if (resolved.kind === "document" && view === "rendered" && readOnly && line == null) {
|
|
1788
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1789
|
+
"div",
|
|
1790
|
+
{
|
|
1791
|
+
"data-fancy-file-viewer": "",
|
|
1792
|
+
"data-kind": "document",
|
|
1793
|
+
"data-format": resolved.format,
|
|
1794
|
+
className,
|
|
1795
|
+
style,
|
|
1796
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.FauxClient, { variant: "device", controls: true, fill: true, bodyClassName: "overflow-auto", ...frameProps, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-5", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1797
|
+
reactFancy.ContentRenderer,
|
|
1798
|
+
{
|
|
1799
|
+
value: resolved.format === "text" ? textToHtml(value) : value,
|
|
1800
|
+
format: resolved.format === "markdown" ? "markdown" : "html"
|
|
1801
|
+
}
|
|
1802
|
+
) }) })
|
|
1803
|
+
}
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
if (resolved.kind === "office") {
|
|
1807
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1808
|
+
"div",
|
|
1809
|
+
{
|
|
1810
|
+
"data-fancy-file-viewer": "",
|
|
1811
|
+
"data-kind": "office",
|
|
1812
|
+
"data-office-kind": resolved.officeKind,
|
|
1813
|
+
className: reactFancy.cn("overflow-hidden", className),
|
|
1814
|
+
style,
|
|
1815
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactFancy.MediaViewer, { src: src ?? "", mime, alt: filename, className: "h-full w-full" })
|
|
1816
|
+
}
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1750
1819
|
if (resolved.kind === "media") {
|
|
1751
1820
|
const { className: mediaClassName, ...restMediaProps } = mediaProps ?? {};
|
|
1821
|
+
restMediaProps.imageProps = { frame: true, frameProps, ...restMediaProps.imageProps };
|
|
1822
|
+
restMediaProps.pdfProps = { frame: true, frameProps, ...restMediaProps.pdfProps };
|
|
1752
1823
|
const fill = resolved.mediaKind !== "audio";
|
|
1753
1824
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1754
1825
|
"div",
|
|
@@ -1801,6 +1872,10 @@ function FileViewer({
|
|
|
1801
1872
|
);
|
|
1802
1873
|
}
|
|
1803
1874
|
FileViewer.displayName = "FileViewer";
|
|
1875
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
1876
|
+
function textToHtml(text) {
|
|
1877
|
+
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("");
|
|
1878
|
+
}
|
|
1804
1879
|
|
|
1805
1880
|
// src/engine/markdown-render.ts
|
|
1806
1881
|
function escapeHtml(s) {
|