@particle-academy/fancy-code 0.12.1 → 0.13.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 +31 -0
- package/dist/index.cjs +74 -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 +75 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
14
14
|
|
|
15
15
|
## [Unreleased]
|
|
16
16
|
|
|
17
|
+
## [0.13.0] - 2026-10-19
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- **Documents render as a reader sees them.** `FileViewer` shows Markdown, HTML
|
|
22
|
+
and plain text through react-fancy's `ContentRenderer`, sanitised (a file is
|
|
23
|
+
untrusted), in the `FauxClient` frame every file viewer now carries: zoom and
|
|
24
|
+
full screen, revealed on hover. Plain text keeps its paragraphs and line
|
|
25
|
+
breaks and is escaped, so `*x*` stays asterisks. `view="source"` shows the
|
|
26
|
+
source in the code editor instead, as do editing (`readOnly={false}`) and
|
|
27
|
+
landing on a `line`. `frameProps` reaches the frame.
|
|
28
|
+
- **Office files are recognised.** `.docx`, `.xlsx` and `.pptx` (by extension
|
|
29
|
+
or MIME type) resolve to `{ kind: "office", officeKind }`. They show
|
|
30
|
+
MediaViewer's no-preview card with a download until their viewers are wired
|
|
31
|
+
in. They used to be handed to the code editor as text.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **BREAKING:** a `.md`, `.html` or `.txt` file in `FileViewer` is rendered,
|
|
36
|
+
not shown as source. **What to do:** to keep the source, pass
|
|
37
|
+
`view="source"`. A viewer that lands on a `line` (a link into a file) already
|
|
38
|
+
shows the source, so file links need nothing.
|
|
39
|
+
- **BREAKING (types):** `FileKind` gains `{ kind: "document"; format; language }`
|
|
40
|
+
and `{ kind: "office"; officeKind }`. **What to do:** only if you `switch` on
|
|
41
|
+
`resolveFileKind(...).kind` exhaustively: add the two cases, or a `default`.
|
|
42
|
+
`notes.txt` now resolves to `document`, not `text`.
|
|
43
|
+
- The extension decides over a generic MIME type: a host that serves `app.ts`
|
|
44
|
+
as `text/plain` still gets the code editor.
|
|
45
|
+
- **Requires react-fancy 5.48.1 or later** (peer `>=5.48.1 <6.0.0`, was
|
|
46
|
+
`>=4.9.0`), for the frame and for the sanitiser that strips `<title>`.
|
|
47
|
+
|
|
17
48
|
## [0.12.1] - 2026-10-18
|
|
18
49
|
|
|
19
50
|
### 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,10 +1778,44 @@ 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 ?? {};
|
|
1752
1821
|
const fill = resolved.mediaKind !== "audio";
|
|
@@ -1801,6 +1870,10 @@ function FileViewer({
|
|
|
1801
1870
|
);
|
|
1802
1871
|
}
|
|
1803
1872
|
FileViewer.displayName = "FileViewer";
|
|
1873
|
+
var ESCAPES = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" };
|
|
1874
|
+
function textToHtml(text) {
|
|
1875
|
+
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("");
|
|
1876
|
+
}
|
|
1804
1877
|
|
|
1805
1878
|
// src/engine/markdown-render.ts
|
|
1806
1879
|
function escapeHtml(s) {
|