@coywolf/emdash 0.0.0-stage → 0.35.13

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.
Files changed (261) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -2
  3. package/package.json +70 -5
  4. package/src/admin/admin-css.ts +57 -0
  5. package/src/admin/ai.tsx +915 -0
  6. package/src/admin/backups.tsx +613 -0
  7. package/src/admin/breadcrumbs.tsx +542 -0
  8. package/src/admin/codeBlocks.tsx +204 -0
  9. package/src/admin/customBlocks.tsx +321 -0
  10. package/src/admin/dirty.ts +20 -0
  11. package/src/admin/discovery.tsx +463 -0
  12. package/src/admin/download.ts +28 -0
  13. package/src/admin/feature-switches.tsx +124 -0
  14. package/src/admin/features.tsx +142 -0
  15. package/src/admin/files.tsx +791 -0
  16. package/src/admin/formUploads.tsx +445 -0
  17. package/src/admin/guides.tsx +18 -0
  18. package/src/admin/headings.tsx +262 -0
  19. package/src/admin/images.tsx +825 -0
  20. package/src/admin/index.tsx +62 -0
  21. package/src/admin/links.tsx +980 -0
  22. package/src/admin/performance.tsx +330 -0
  23. package/src/admin/preview.tsx +63 -0
  24. package/src/admin/redirects-removed.tsx +266 -0
  25. package/src/admin/redirects.tsx +470 -0
  26. package/src/admin/reviews.tsx +232 -0
  27. package/src/admin/robots-bots.tsx +647 -0
  28. package/src/admin/robots-shared.tsx +109 -0
  29. package/src/admin/robots-wizard.tsx +748 -0
  30. package/src/admin/robots.tsx +911 -0
  31. package/src/admin/save-bar.tsx +134 -0
  32. package/src/admin/schema.tsx +1187 -0
  33. package/src/admin/search.tsx +355 -0
  34. package/src/admin/settings-ui.tsx +87 -0
  35. package/src/admin/trash-prompt-core.ts +135 -0
  36. package/src/admin/trash-prompt.tsx +156 -0
  37. package/src/admin/usage.tsx +301 -0
  38. package/src/admin/videos-display.tsx +248 -0
  39. package/src/admin/videos.tsx +1202 -0
  40. package/src/admin/wpImport.tsx +1516 -0
  41. package/src/ai/entities.ts +59 -0
  42. package/src/ai/export.ts +61 -0
  43. package/src/ai/logic.test.ts +187 -0
  44. package/src/ai/logic.ts +606 -0
  45. package/src/ai/pack.ts +121 -0
  46. package/src/ai/prompts.ts +87 -0
  47. package/src/ai/providers.ts +276 -0
  48. package/src/ai/queue.test.ts +97 -0
  49. package/src/ai/routes.ts +359 -0
  50. package/src/ai/store.ts +301 -0
  51. package/src/ai/wikidata.test.ts +184 -0
  52. package/src/ai/wikidata.ts +87 -0
  53. package/src/ai/worker.ts +530 -0
  54. package/src/astro/astro-modules.d.ts +6 -0
  55. package/src/astro/breadcrumbs/Breadcrumbs.astro +129 -0
  56. package/src/astro/breadcrumbs/BreadcrumbsBlock.astro +12 -0
  57. package/src/astro/codeBlocks/CodeBlock.astro +62 -0
  58. package/src/astro/codeBlocks/EmDashCode.astro +61 -0
  59. package/src/astro/customBlocks/CustomBlock.astro +64 -0
  60. package/src/astro/customBlocks/Details.astro +8 -0
  61. package/src/astro/customBlocks/Disclosure.astro +8 -0
  62. package/src/astro/customBlocks/Note.astro +8 -0
  63. package/src/astro/customBlocks/Podcast.astro +8 -0
  64. package/src/astro/customBlocks/Quote.astro +8 -0
  65. package/src/astro/customBlocks/Testimonial.astro +8 -0
  66. package/src/astro/files/FileCard.astro +103 -0
  67. package/src/astro/headings/HeadingBlock.astro +129 -0
  68. package/src/astro/headings/TableOfContents.astro +45 -0
  69. package/src/astro/headings/request.ts +19 -0
  70. package/src/astro/images/ResponsiveImage.astro +60 -0
  71. package/src/astro/index.ts +84 -0
  72. package/src/astro/reviews/CoywolfReview.astro +27 -0
  73. package/src/astro/reviews/Review.astro +49 -0
  74. package/src/astro/search/SearchBox.astro +61 -0
  75. package/src/astro/search/SearchBoxForm.astro +509 -0
  76. package/src/astro/tables/CaptionedTable.astro +72 -0
  77. package/src/astro/videos/CoywolfVideo.astro +448 -0
  78. package/src/backups/download.ts +112 -0
  79. package/src/backups/dump.test.mjs +287 -0
  80. package/src/backups/dump.ts +288 -0
  81. package/src/backups/module.ts +276 -0
  82. package/src/backups/pack.ts +33 -0
  83. package/src/backups/restore.test.mjs +103 -0
  84. package/src/backups/restore.ts +209 -0
  85. package/src/backups/store.ts +409 -0
  86. package/src/breadcrumbs/pack.ts +113 -0
  87. package/src/breadcrumbs/settings.ts +102 -0
  88. package/src/breadcrumbs/stamp.ts +40 -0
  89. package/src/breadcrumbs/trail.ts +123 -0
  90. package/src/codeBlocks/contrast.ts +179 -0
  91. package/src/codeBlocks/hljs.d.ts +5 -0
  92. package/src/codeBlocks/pack.ts +110 -0
  93. package/src/codeBlocks/render.test.ts +152 -0
  94. package/src/codeBlocks/render.ts +304 -0
  95. package/src/codeBlocks/settings.ts +18 -0
  96. package/src/codeBlocks/themes.generated.ts +304 -0
  97. package/src/codeBlocks/themes.ts +114 -0
  98. package/src/core/compose.ts +244 -0
  99. package/src/core/content-url.ts +888 -0
  100. package/src/core/csv.ts +119 -0
  101. package/src/core/d1-batch.ts +65 -0
  102. package/src/core/entry-docs.ts +175 -0
  103. package/src/core/features-module.ts +48 -0
  104. package/src/core/features.ts +392 -0
  105. package/src/core/generation.ts +88 -0
  106. package/src/core/guide-content.ts +166 -0
  107. package/src/core/module.ts +104 -0
  108. package/src/core/read-failures.ts +57 -0
  109. package/src/core/request-memo.ts +71 -0
  110. package/src/core/secrets.ts +83 -0
  111. package/src/core/settings-enhance.ts +74 -0
  112. package/src/core/site.ts +36 -0
  113. package/src/core/storage.ts +27 -0
  114. package/src/customBlocks/pack.ts +260 -0
  115. package/src/customBlocks/render.ts +424 -0
  116. package/src/customBlocks/rich.ts +313 -0
  117. package/src/customBlocks/settings.ts +46 -0
  118. package/src/discovery/build.ts +252 -0
  119. package/src/discovery/feed-build.ts +217 -0
  120. package/src/discovery/feed.ts +172 -0
  121. package/src/discovery/indexnow.ts +114 -0
  122. package/src/discovery/llms.ts +79 -0
  123. package/src/discovery/markdown.ts +294 -0
  124. package/src/discovery/memory-cache.ts +97 -0
  125. package/src/discovery/middleware.ts +186 -0
  126. package/src/discovery/module.ts +353 -0
  127. package/src/discovery/news.ts +99 -0
  128. package/src/discovery/pack.ts +58 -0
  129. package/src/discovery/settings.ts +91 -0
  130. package/src/discovery/urls.ts +35 -0
  131. package/src/env.d.ts +10 -0
  132. package/src/files/card.ts +231 -0
  133. package/src/files/counts.test.ts +114 -0
  134. package/src/files/counts.ts +60 -0
  135. package/src/files/export.ts +44 -0
  136. package/src/files/format.test.ts +93 -0
  137. package/src/files/format.ts +163 -0
  138. package/src/files/module.ts +596 -0
  139. package/src/files/pack.ts +92 -0
  140. package/src/files/r2-rules.test.ts +30 -0
  141. package/src/files/r2-rules.ts +41 -0
  142. package/src/files/range.ts +50 -0
  143. package/src/files/s3.ts +114 -0
  144. package/src/files/serve.ts +141 -0
  145. package/src/files/sigv4.test.ts +64 -0
  146. package/src/files/sigv4.ts +160 -0
  147. package/src/files/site.ts +137 -0
  148. package/src/files/walker.ts +58 -0
  149. package/src/formUploads/lib.ts +266 -0
  150. package/src/formUploads/pack.ts +55 -0
  151. package/src/formUploads/wrap.ts +592 -0
  152. package/src/forms.ts +16 -0
  153. package/src/headings/headings.test.ts +159 -0
  154. package/src/headings/pack.ts +154 -0
  155. package/src/headings/render.ts +139 -0
  156. package/src/headings/settings.ts +117 -0
  157. package/src/headings/slug.ts +68 -0
  158. package/src/headings/stamp.ts +142 -0
  159. package/src/headings/test-hooks.mjs +14 -0
  160. package/src/headings/toc.ts +48 -0
  161. package/src/images/cloudflare.ts +329 -0
  162. package/src/images/lib.ts +221 -0
  163. package/src/images/module.ts +372 -0
  164. package/src/images/pack.ts +804 -0
  165. package/src/images/pending.ts +37 -0
  166. package/src/images/settings.ts +48 -0
  167. package/src/images/setup.ts +192 -0
  168. package/src/images/variants-estimate.ts +162 -0
  169. package/src/images/variants-store.ts +475 -0
  170. package/src/images/variants.ts +311 -0
  171. package/src/index.ts +143 -0
  172. package/src/links/check.ts +101 -0
  173. package/src/links/classify.ts +270 -0
  174. package/src/links/export.ts +59 -0
  175. package/src/links/module.ts +652 -0
  176. package/src/links/pack.ts +43 -0
  177. package/src/links/pt.ts +325 -0
  178. package/src/links/store.ts +704 -0
  179. package/src/middleware.ts +287 -0
  180. package/src/modules.ts +53 -0
  181. package/src/options.ts +66 -0
  182. package/src/pageCache/lib.test.mjs +224 -0
  183. package/src/pageCache/lib.ts +274 -0
  184. package/src/pageCache/pack.ts +240 -0
  185. package/src/pageCache/usage-core.ts +152 -0
  186. package/src/pageCache/usage.ts +262 -0
  187. package/src/pageCache/warm.test.mjs +670 -0
  188. package/src/pageCache/warm.ts +722 -0
  189. package/src/redirects/middleware.ts +169 -0
  190. package/src/redirects/module.ts +124 -0
  191. package/src/redirects/pack.ts +89 -0
  192. package/src/redirects/removed-core.ts +151 -0
  193. package/src/redirects/removed.ts +293 -0
  194. package/src/redirects/rules.ts +355 -0
  195. package/src/redirects/slug-changes.ts +207 -0
  196. package/src/redirects/slug-core.test.ts +50 -0
  197. package/src/redirects/slug-core.ts +49 -0
  198. package/src/redirects/transfer.ts +92 -0
  199. package/src/reviews/lib.ts +624 -0
  200. package/src/reviews/pack.ts +135 -0
  201. package/src/reviews/schema.ts +128 -0
  202. package/src/reviews/settings.ts +26 -0
  203. package/src/robots/bots.ts +321 -0
  204. package/src/robots/data/bots.json +696 -0
  205. package/src/robots/data/verified.json +582 -0
  206. package/src/robots/directory.ts +26 -0
  207. package/src/robots/explain.ts +191 -0
  208. package/src/robots/guided.ts +300 -0
  209. package/src/robots/importer.ts +263 -0
  210. package/src/robots/middleware.ts +151 -0
  211. package/src/robots/module.ts +446 -0
  212. package/src/robots/pack.ts +69 -0
  213. package/src/robots/radar.ts +129 -0
  214. package/src/robots/rep.test.ts +335 -0
  215. package/src/robots/rep.ts +362 -0
  216. package/src/robots/rules.test.ts +251 -0
  217. package/src/robots/rules.ts +883 -0
  218. package/src/robots/transfer.ts +45 -0
  219. package/src/robots/validate.ts +524 -0
  220. package/src/schema/catalog.ts +146 -0
  221. package/src/schema/graph.test.mjs +522 -0
  222. package/src/schema/graph.ts +791 -0
  223. package/src/schema/module.ts +1138 -0
  224. package/src/schema/og-image.ts +100 -0
  225. package/src/schema/pack.ts +58 -0
  226. package/src/search/engine.ts +578 -0
  227. package/src/search/fallback.ts +80 -0
  228. package/src/search/live-cache.ts +116 -0
  229. package/src/search/live-client.ts +770 -0
  230. package/src/search/live-serve.ts +282 -0
  231. package/src/search/module.ts +261 -0
  232. package/src/search/pack.ts +57 -0
  233. package/src/search/query.ts +132 -0
  234. package/src/search/ratelimit-core.ts +118 -0
  235. package/src/search/ratelimit.ts +79 -0
  236. package/src/search/snippet.ts +135 -0
  237. package/src/search/title-index.ts +139 -0
  238. package/src/search/title-match.ts +192 -0
  239. package/src/shared.ts +71 -0
  240. package/src/version.ts +2 -0
  241. package/src/videos/date.ts +40 -0
  242. package/src/videos/engagement.ts +75 -0
  243. package/src/videos/lib.ts +679 -0
  244. package/src/videos/module.ts +1160 -0
  245. package/src/videos/pack.ts +187 -0
  246. package/src/videos/pause.ts +42 -0
  247. package/src/videos/poster.ts +324 -0
  248. package/src/videos/render.ts +336 -0
  249. package/src/videos/store.ts +551 -0
  250. package/src/videos/stream.ts +134 -0
  251. package/src/wpImport/convert.ts +702 -0
  252. package/src/wpImport/guests.ts +329 -0
  253. package/src/wpImport/gutenberg.ts +157 -0
  254. package/src/wpImport/markers.ts +66 -0
  255. package/src/wpImport/pack.ts +451 -0
  256. package/src/wpImport/parents.ts +202 -0
  257. package/src/wpImport/prepare.ts +592 -0
  258. package/src/wpImport/redirects.ts +358 -0
  259. package/src/wpImport/sources.ts +67 -0
  260. package/src/wpImport/stream.ts +160 -0
  261. package/src/wpImport/urls.ts +354 -0
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Pure helpers shared by the download card, the download middleware and the
3
+ * admin page: file-type badges, sizes, safe names, and Content-Disposition.
4
+ * No imports, so `node --test` can load this file directly.
5
+ */
6
+
7
+ /** Badge color per extension (the palette Coywolf Files uses in WordPress). */
8
+ const ICON_COLORS: Record<string, string> = {};
9
+ const FAMILIES: Array<[string, string[]]> = [
10
+ ["#B42318", ["pdf"]],
11
+ ["#155EEF", ["doc", "docx", "rtf", "odt", "pages"]],
12
+ ["#475467", ["txt", "md"]],
13
+ ["#067647", ["xls", "xlsx", "csv", "ods", "numbers"]],
14
+ ["#C4320A", ["ppt", "pptx", "key", "odp"]],
15
+ ["#C11574", ["png", "jpg", "jpeg", "gif", "webp", "avif", "svg", "heic", "bmp", "tif", "tiff"]],
16
+ ["#93264A", ["mp4", "mov", "webm", "mkv", "avi", "m4v"]],
17
+ ["#026AA2", ["mp3", "wav", "m4a", "aac", "ogg", "flac"]],
18
+ ["#6941E0", ["zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "xz", "dmg", "iso"]],
19
+ ["#B54708", ["json", "js", "jsx", "ts", "tsx", "html", "css", "xml", "yml", "yaml"]],
20
+ ];
21
+ for (const [color, exts] of FAMILIES) for (const ext of exts) ICON_COLORS[ext] = color;
22
+
23
+ export const DEFAULT_ICON_COLOR = "#667085";
24
+
25
+ /** Lowercase extension of a file name, without the dot ("" when there is none). */
26
+ export function extensionOf(name: string): string {
27
+ const match = /\.([a-z0-9]+)$/i.exec(String(name ?? "").trim());
28
+ return match ? match[1].toLowerCase() : "";
29
+ }
30
+
31
+ /** Badge color and label (at most four characters) for an extension. */
32
+ export function iconFor(ext: string): { color: string; label: string } {
33
+ const clean = String(ext ?? "")
34
+ .toLowerCase()
35
+ .replace(/[^a-z0-9]/g, "");
36
+ return { color: ICON_COLORS[clean] ?? DEFAULT_ICON_COLOR, label: clean ? clean.toUpperCase().slice(0, 4) : "FILE" };
37
+ }
38
+
39
+ /** 1024-based size with one decimal, e.g. "2.4 MB". */
40
+ export function formatSize(bytes: number): string {
41
+ const n = Math.max(0, Math.floor(Number(bytes) || 0));
42
+ if (n < 1024) return `${n} B`;
43
+ const units = ["KB", "MB", "GB", "TB", "PB"];
44
+ let value = n / 1024;
45
+ let i = 0;
46
+ while (value >= 1024 && i < units.length - 1) {
47
+ value /= 1024;
48
+ i++;
49
+ }
50
+ return `${value.toFixed(1)} ${units[i]}`;
51
+ }
52
+
53
+ /** "Mar 4, 2026" (UTC, so server and build output agree). */
54
+ export function formatDate(iso: string): string {
55
+ const date = new Date(iso);
56
+ if (Number.isNaN(date.getTime())) return "";
57
+ return new Intl.DateTimeFormat("en-US", { dateStyle: "medium", timeZone: "UTC" }).format(date);
58
+ }
59
+
60
+ /**
61
+ * A file name that is safe in an object key and a URL path segment: ASCII
62
+ * letters, digits, dot, dash and underscore. Keeps the extension.
63
+ */
64
+ export function safeFilename(name: string): string {
65
+ const base = String(name ?? "")
66
+ .split(/[\\/]/)
67
+ .pop()!
68
+ .normalize("NFKD")
69
+ .replace(/[\u0300-\u036f]/g, "")
70
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
71
+ .replace(/-{2,}/g, "-")
72
+ .replace(/-+\./g, ".")
73
+ .replace(/^[-.]+|[-.]+$/g, "");
74
+ return (base || "file").slice(0, 120);
75
+ }
76
+
77
+ /** RFC 3986 percent-encoding as RFC 5987 requires (attr-char only). */
78
+ function encodeRfc5987(value: string): string {
79
+ return encodeURIComponent(value).replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
80
+ }
81
+
82
+ /**
83
+ * Content-Disposition for a download: an ASCII `filename` fallback plus a
84
+ * UTF-8 `filename*` (RFC 6266 / RFC 5987) when the name isn't plain ASCII.
85
+ */
86
+ export function contentDisposition(filename: string, type: "attachment" | "inline" = "attachment"): string {
87
+ const name = String(filename ?? "")
88
+ .replace(/[\r\n\t\0]/g, " ")
89
+ .split(/[\\/]/)
90
+ .pop()!
91
+ .trim() || "download";
92
+ const fallback = name
93
+ .normalize("NFKD")
94
+ .replace(/[\u0300-\u036f]/g, "")
95
+ .replace(/[^\x20-\x7e]/g, "_")
96
+ .replace(/["\\]/g, "_")
97
+ .replace(/%/g, "_");
98
+ if (fallback === name) return `${type}; filename="${fallback}"`;
99
+ return `${type}; filename="${fallback}"; filename*=UTF-8''${encodeRfc5987(name)}`;
100
+ }
101
+
102
+ /** Download URL path: /<base>/<id>/<filename>. */
103
+ export function downloadPath(base: string, id: string, filename: string): string {
104
+ return `/${base}/${encodeURIComponent(id)}/${encodeURIComponent(safeFilename(filename))}`;
105
+ }
106
+
107
+ /** A valid base slug: one lowercase path segment. */
108
+ export function normalizeBase(value: unknown): string {
109
+ const slug = String(value ?? "")
110
+ .trim()
111
+ .replace(/^\/+|\/+$/g, "")
112
+ .toLowerCase();
113
+ return /^[a-z0-9][a-z0-9_-]{0,63}$/.test(slug) && !slug.startsWith("_") ? slug : "download";
114
+ }
115
+
116
+ /** A #rgb / #rrggbb accent color, or "" when the value isn't one. */
117
+ export function safeColor(value: unknown): string {
118
+ const color = String(value ?? "").trim();
119
+ return /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(color) ? color : "";
120
+ }
121
+
122
+ /** Large-upload ids: "f" + 15 lowercase base-32 characters. Media library ids are ULIDs (26 uppercase). */
123
+ export const UPLOAD_ID = /^f[a-z2-7]{15}$/;
124
+ export const MEDIA_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;
125
+ /**
126
+ * Files moved from Coywolf Files for WordPress keep their WordPress id (20
127
+ * lowercase hex characters), so old download links and imported blocks still
128
+ * resolve. They're stored like large uploads.
129
+ */
130
+ export const WORDPRESS_FILE_ID = /^[0-9a-f]{20}$/;
131
+
132
+ /** Ids stored as large uploads (plugin storage) rather than media library items. */
133
+ export function isUploadId(id: string): boolean {
134
+ return UPLOAD_ID.test(id) || WORDPRESS_FILE_ID.test(id);
135
+ }
136
+
137
+ export function isFileId(id: string): boolean {
138
+ return isUploadId(id) || MEDIA_ID.test(id);
139
+ }
140
+
141
+ export function newUploadId(): string {
142
+ const alphabet = "abcdefghijklmnopqrstuvwxyz234567";
143
+ const bytes = crypto.getRandomValues(new Uint8Array(15));
144
+ let id = "f";
145
+ for (const b of bytes) id += alphabet[b & 31];
146
+ return id;
147
+ }
148
+
149
+ /** Parse /<base>/<id>[/<filename>] (null when it isn't a download URL). */
150
+ export function parseDownloadPath(pathname: string, base: string): { id: string; filename: string | null } | null {
151
+ const prefix = `/${base}/`;
152
+ if (!pathname.startsWith(prefix)) return null;
153
+ const rest = pathname.slice(prefix.length).split("/");
154
+ if (rest.length > 2 || !rest[0]) return null;
155
+ let id: string;
156
+ try {
157
+ id = decodeURIComponent(rest[0]);
158
+ } catch {
159
+ return null;
160
+ }
161
+ if (!isFileId(id)) return null;
162
+ return { id, filename: rest[1] ? rest[1] : null };
163
+ }