@svadmin/lite 0.16.11 → 0.22.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.
Files changed (263) hide show
  1. package/README.md +71 -2
  2. package/dist/components/LiteActivityFeed.svelte +31 -9
  3. package/dist/components/LiteActivityFeed.svelte.d.ts +6 -0
  4. package/dist/components/LiteAffix.svelte +29 -0
  5. package/dist/components/LiteAffix.svelte.d.ts +13 -0
  6. package/dist/components/LiteAnchor.svelte +38 -0
  7. package/dist/components/LiteAnchor.svelte.d.ts +14 -0
  8. package/dist/components/LiteApprovalActionCard.svelte +8 -5
  9. package/dist/components/LiteApprovalActionCard.svelte.d.ts +2 -0
  10. package/dist/components/LiteAuditLog.svelte +9 -9
  11. package/dist/components/LiteAutoComplete.svelte +41 -0
  12. package/dist/components/LiteAutoComplete.svelte.d.ts +13 -0
  13. package/dist/components/LiteBackTop.svelte +16 -0
  14. package/dist/components/LiteBackTop.svelte.d.ts +8 -0
  15. package/dist/components/LiteBreadcrumbs.svelte +4 -1
  16. package/dist/components/LiteBreadcrumbs.svelte.d.ts +2 -0
  17. package/dist/components/LiteCalendar.svelte +124 -0
  18. package/dist/components/LiteCalendar.svelte.d.ts +20 -0
  19. package/dist/components/LiteCanvasAnnotation.svelte +13 -13
  20. package/dist/components/LiteCarousel.svelte +72 -0
  21. package/dist/components/LiteCarousel.svelte.d.ts +14 -0
  22. package/dist/components/LiteChatDialog.svelte +8 -8
  23. package/dist/components/LiteCollapse.svelte +49 -0
  24. package/dist/components/LiteCollapse.svelte.d.ts +15 -0
  25. package/dist/components/LiteColumnHeaderFilter.svelte +4 -1
  26. package/dist/components/LiteColumnHeaderFilter.svelte.d.ts +2 -0
  27. package/dist/components/LiteColumnSettings.svelte +7 -4
  28. package/dist/components/LiteColumnSettings.svelte.d.ts +2 -0
  29. package/dist/components/LiteCommentThread.svelte +113 -0
  30. package/dist/components/LiteCommentThread.svelte.d.ts +26 -0
  31. package/dist/components/LiteConfirmDialog.svelte +2 -2
  32. package/dist/components/LiteCountdown.svelte +49 -0
  33. package/dist/components/LiteCountdown.svelte.d.ts +10 -0
  34. package/dist/components/LiteDecisionTable.svelte +14 -14
  35. package/dist/components/LiteDetailDrawer.svelte +45 -0
  36. package/dist/components/LiteDetailDrawer.svelte.d.ts +14 -0
  37. package/dist/components/LiteDraggableRowTable.svelte +5 -2
  38. package/dist/components/LiteDraggableRowTable.svelte.d.ts +2 -0
  39. package/dist/components/LiteDrawerForm.svelte +11 -8
  40. package/dist/components/LiteDrawerForm.svelte.d.ts +2 -0
  41. package/dist/components/LiteDynamicFormList.svelte +5 -5
  42. package/dist/components/LiteEditableTable.svelte +5 -2
  43. package/dist/components/LiteEditableTable.svelte.d.ts +2 -0
  44. package/dist/components/LiteEmptyState.svelte +11 -4
  45. package/dist/components/LiteEmptyState.svelte.d.ts +3 -0
  46. package/dist/components/LiteFilterBuilder.svelte +8 -5
  47. package/dist/components/LiteFilterBuilder.svelte.d.ts +2 -0
  48. package/dist/components/LiteFloatButton.svelte +54 -0
  49. package/dist/components/LiteFloatButton.svelte.d.ts +15 -0
  50. package/dist/components/LiteGanttChart.svelte +20 -20
  51. package/dist/components/LiteGrid.svelte +34 -0
  52. package/dist/components/LiteGrid.svelte.d.ts +11 -0
  53. package/dist/components/LiteImage.svelte +54 -0
  54. package/dist/components/LiteImage.svelte.d.ts +14 -0
  55. package/dist/components/LiteImageCropper.svelte +7 -4
  56. package/dist/components/LiteImageCropper.svelte.d.ts +2 -0
  57. package/dist/components/LiteImagePreview.svelte +36 -0
  58. package/dist/components/LiteImagePreview.svelte.d.ts +13 -0
  59. package/dist/components/LiteImportWizard.svelte +7 -7
  60. package/dist/components/LiteItemList.svelte +50 -0
  61. package/dist/components/LiteItemList.svelte.d.ts +15 -0
  62. package/dist/components/LiteJsonSchemaForm.svelte +10 -7
  63. package/dist/components/LiteJsonSchemaForm.svelte.d.ts +2 -0
  64. package/dist/components/LiteKanbanBoard.svelte +19 -19
  65. package/dist/components/LiteLayout.svelte +18 -6
  66. package/dist/components/LiteLayout.svelte.d.ts +8 -0
  67. package/dist/components/LiteLink.svelte +32 -0
  68. package/dist/components/LiteLink.svelte.d.ts +11 -0
  69. package/dist/components/LiteLogin.svelte +9 -2
  70. package/dist/components/LiteLogin.svelte.d.ts +2 -0
  71. package/dist/components/LiteMasterDetailView.svelte +13 -10
  72. package/dist/components/LiteMasterDetailView.svelte.d.ts +2 -0
  73. package/dist/components/LiteMediaLibraryModal.svelte +8 -5
  74. package/dist/components/LiteMediaLibraryModal.svelte.d.ts +2 -0
  75. package/dist/components/LiteMediaThumbnail.svelte +3 -3
  76. package/dist/components/LiteMentionsInput.svelte +9 -9
  77. package/dist/components/LiteMenu.svelte +22 -0
  78. package/dist/components/LiteMenu.svelte.d.ts +12 -0
  79. package/dist/components/LiteMenuList.svelte +55 -0
  80. package/dist/components/LiteMenuList.svelte.d.ts +17 -0
  81. package/dist/components/LiteModalForm.svelte +9 -6
  82. package/dist/components/LiteModalForm.svelte.d.ts +2 -0
  83. package/dist/components/LiteMultiTabKeepAlive.svelte +6 -6
  84. package/dist/components/LiteNotificationCenter.svelte +85 -0
  85. package/dist/components/LiteNotificationCenter.svelte.d.ts +22 -0
  86. package/dist/components/LiteOfflineSyncBanner.svelte +6 -6
  87. package/dist/components/LiteOtpInput.svelte +50 -0
  88. package/dist/components/LiteOtpInput.svelte.d.ts +15 -0
  89. package/dist/components/LitePageHeader.svelte +34 -0
  90. package/dist/components/LitePageHeader.svelte.d.ts +13 -0
  91. package/dist/components/LitePagination.svelte +10 -2
  92. package/dist/components/LitePagination.svelte.d.ts +2 -0
  93. package/dist/components/LitePdfDocumentViewer.svelte +14 -14
  94. package/dist/components/LitePermissionMatrix.svelte +8 -8
  95. package/dist/components/LitePivotTable.svelte +11 -11
  96. package/dist/components/LitePopconfirm.svelte +49 -0
  97. package/dist/components/LitePopconfirm.svelte.d.ts +14 -0
  98. package/dist/components/LitePresenceAvatarGroup.svelte +8 -5
  99. package/dist/components/LitePresenceAvatarGroup.svelte.d.ts +2 -0
  100. package/dist/components/LitePrintableBill.svelte +12 -9
  101. package/dist/components/LitePrintableBill.svelte.d.ts +2 -0
  102. package/dist/components/LiteProgress.svelte +46 -0
  103. package/dist/components/LiteProgress.svelte.d.ts +12 -0
  104. package/dist/components/LiteQRCode.svelte +62 -0
  105. package/dist/components/LiteQRCode.svelte.d.ts +14 -0
  106. package/dist/components/LiteResizeBox.svelte +34 -0
  107. package/dist/components/LiteResizeBox.svelte.d.ts +12 -0
  108. package/dist/components/LiteResult.svelte +52 -0
  109. package/dist/components/LiteResult.svelte.d.ts +14 -0
  110. package/dist/components/LiteRowActions.svelte +36 -0
  111. package/dist/components/LiteRowActions.svelte.d.ts +17 -0
  112. package/dist/components/LiteSensitiveDataMask.svelte +4 -1
  113. package/dist/components/LiteSensitiveDataMask.svelte.d.ts +2 -0
  114. package/dist/components/LiteShow.svelte +1 -1
  115. package/dist/components/LiteShowField.svelte +2 -2
  116. package/dist/components/LiteSignaturePad.svelte +9 -9
  117. package/dist/components/LiteSkeleton.svelte +100 -0
  118. package/dist/components/LiteSkeleton.svelte.d.ts +19 -0
  119. package/dist/components/LiteSpace.svelte +31 -0
  120. package/dist/components/LiteSpace.svelte.d.ts +12 -0
  121. package/dist/components/LiteSpin.svelte +27 -0
  122. package/dist/components/LiteSpin.svelte.d.ts +10 -0
  123. package/dist/components/LiteSplitPaneLayout.svelte +18 -6
  124. package/dist/components/LiteSplitPaneLayout.svelte.d.ts +8 -0
  125. package/dist/components/LiteSpreadsheetView.svelte +8 -8
  126. package/dist/components/LiteStatistic.svelte +63 -0
  127. package/dist/components/LiteStatistic.svelte.d.ts +15 -0
  128. package/dist/components/LiteStatsCard.svelte +5 -2
  129. package/dist/components/LiteStatsCard.svelte.d.ts +2 -0
  130. package/dist/components/LiteStatusBadge.svelte +33 -0
  131. package/dist/components/LiteStatusBadge.svelte.d.ts +12 -0
  132. package/dist/components/LiteStatusTabs.svelte +35 -0
  133. package/dist/components/LiteStatusTabs.svelte.d.ts +14 -0
  134. package/dist/components/LiteStepForm.svelte +14 -11
  135. package/dist/components/LiteStepForm.svelte.d.ts +2 -0
  136. package/dist/components/LiteSteps.svelte +64 -0
  137. package/dist/components/LiteSteps.svelte.d.ts +18 -0
  138. package/dist/components/LiteTable.svelte +5 -2
  139. package/dist/components/LiteTable.svelte.d.ts +2 -0
  140. package/dist/components/LiteTableSummary.svelte +8 -5
  141. package/dist/components/LiteTableSummary.svelte.d.ts +2 -0
  142. package/dist/components/LiteTabs.svelte +30 -20
  143. package/dist/components/LiteTabs.svelte.d.ts +11 -2
  144. package/dist/components/LiteTag.svelte +31 -0
  145. package/dist/components/LiteTag.svelte.d.ts +13 -0
  146. package/dist/components/LiteTimeline.svelte +48 -0
  147. package/dist/components/LiteTimeline.svelte.d.ts +17 -0
  148. package/dist/components/LiteTour.svelte +54 -0
  149. package/dist/components/LiteTour.svelte.d.ts +15 -0
  150. package/dist/components/LiteTransfer.svelte +9 -6
  151. package/dist/components/LiteTransfer.svelte.d.ts +2 -0
  152. package/dist/components/LiteTree.svelte +48 -0
  153. package/dist/components/LiteTree.svelte.d.ts +17 -0
  154. package/dist/components/LiteTreeTable.svelte +1 -1
  155. package/dist/components/LiteTwoFactorStepper.svelte +53 -0
  156. package/dist/components/LiteTwoFactorStepper.svelte.d.ts +16 -0
  157. package/dist/components/LiteVersionDiffViewer.svelte +16 -13
  158. package/dist/components/LiteVersionDiffViewer.svelte.d.ts +2 -0
  159. package/dist/components/LiteVideoPlayer.svelte +67 -0
  160. package/dist/components/LiteVideoPlayer.svelte.d.ts +22 -0
  161. package/dist/components/LiteVirtualTable.svelte +1 -1
  162. package/dist/components/advanced/LiteAutoSaveIndicator.svelte +42 -13
  163. package/dist/components/advanced/LiteAutoSaveIndicator.svelte.d.ts +10 -4
  164. package/dist/components/advanced/LiteDraggableHeader.svelte +2 -2
  165. package/dist/components/advanced/LiteDrawerForm.svelte +4 -1
  166. package/dist/components/advanced/LiteDrawerForm.svelte.d.ts +2 -0
  167. package/dist/components/advanced/LiteInlineEdit.svelte +1 -1
  168. package/dist/components/advanced/LiteModalForm.svelte +4 -1
  169. package/dist/components/advanced/LiteModalForm.svelte.d.ts +2 -0
  170. package/dist/components/advanced/LiteToast.svelte +3 -3
  171. package/dist/components/advanced/LiteUndoableNotification.svelte +1 -1
  172. package/dist/components/compatibility/LiteRealtimeStatus.svelte +41 -2
  173. package/dist/components/compatibility/LiteRealtimeStatus.svelte.d.ts +15 -0
  174. package/dist/components/fields/LiteBooleanField.svelte +4 -2
  175. package/dist/components/fields/LiteBooleanField.svelte.d.ts +2 -0
  176. package/dist/components/fields/LiteCascader.svelte +5 -2
  177. package/dist/components/fields/LiteCascader.svelte.d.ts +2 -0
  178. package/dist/components/fields/LiteDateField.svelte +39 -9
  179. package/dist/components/fields/LiteDateField.svelte.d.ts +2 -0
  180. package/dist/components/fields/LiteDateRangeField.svelte +1 -1
  181. package/dist/components/fields/LiteImageField.svelte +6 -2
  182. package/dist/components/fields/LiteImageField.svelte.d.ts +2 -0
  183. package/dist/components/fields/LiteJsonField.svelte +1 -1
  184. package/dist/components/fields/LiteMarkdownField.svelte +49 -3
  185. package/dist/components/fields/LiteMarkdownField.svelte.d.ts +5 -0
  186. package/dist/components/fields/LiteNumberField.svelte +5 -3
  187. package/dist/components/fields/LiteNumberField.svelte.d.ts +2 -0
  188. package/dist/components/fields/LiteRichTextField.svelte +49 -12
  189. package/dist/components/fields/LiteRichTextField.svelte.d.ts +3 -0
  190. package/dist/components/fields/LiteSelectField.svelte +5 -3
  191. package/dist/components/fields/LiteSelectField.svelte.d.ts +2 -0
  192. package/dist/components/fields/LiteTagField.svelte +38 -15
  193. package/dist/components/fields/LiteTagField.svelte.d.ts +2 -0
  194. package/dist/components/fields/LiteTextField.svelte +26 -5
  195. package/dist/components/fields/LiteTextField.svelte.d.ts +2 -0
  196. package/dist/components/fields/LiteTreeSelect.svelte +5 -2
  197. package/dist/components/fields/LiteTreeSelect.svelte.d.ts +2 -0
  198. package/dist/components/fields/LiteUrlField.svelte +5 -3
  199. package/dist/components/fields/LiteUrlField.svelte.d.ts +2 -0
  200. package/dist/components/layout/LiteAuthenticated.svelte +2 -2
  201. package/dist/components/layout/LiteCanAccess.svelte +1 -1
  202. package/dist/components/layout/LiteErrorComponent.svelte +4 -4
  203. package/dist/components/layout/LiteHeader.svelte +2 -2
  204. package/dist/components/layout/LiteSidebar.svelte +6 -3
  205. package/dist/components/layout/LiteSidebar.svelte.d.ts +2 -0
  206. package/dist/components/pages/LiteCreatePage.svelte +4 -1
  207. package/dist/components/pages/LiteCreatePage.svelte.d.ts +2 -0
  208. package/dist/components/pages/LiteEditPage.svelte +4 -1
  209. package/dist/components/pages/LiteEditPage.svelte.d.ts +2 -0
  210. package/dist/components/pages/LiteForgotPasswordPage.svelte +7 -7
  211. package/dist/components/pages/LiteListPage.svelte +6 -3
  212. package/dist/components/pages/LiteListPage.svelte.d.ts +2 -0
  213. package/dist/components/pages/LiteProfilePage.svelte +2 -2
  214. package/dist/components/pages/LiteRegisterPage.svelte +6 -6
  215. package/dist/components/pages/LiteShowPage.svelte +6 -3
  216. package/dist/components/pages/LiteShowPage.svelte.d.ts +2 -0
  217. package/dist/components/pages/LiteUpdatePasswordPage.svelte +4 -4
  218. package/dist/components/widgets/LiteAnomalyBadge.svelte +6 -3
  219. package/dist/components/widgets/LiteAnomalyBadge.svelte.d.ts +2 -0
  220. package/dist/components/widgets/LiteAreaChart.svelte +20 -0
  221. package/dist/components/widgets/LiteAreaChart.svelte.d.ts +10 -0
  222. package/dist/components/widgets/LiteBarChart.svelte +6 -6
  223. package/dist/components/widgets/LiteChartSvg.svelte +102 -0
  224. package/dist/components/widgets/LiteChartSvg.svelte.d.ts +10 -0
  225. package/dist/components/widgets/LiteChartTable.svelte +44 -0
  226. package/dist/components/widgets/LiteChartTable.svelte.d.ts +10 -0
  227. package/dist/components/widgets/LiteDonutChart.svelte +20 -0
  228. package/dist/components/widgets/LiteDonutChart.svelte.d.ts +10 -0
  229. package/dist/components/widgets/LiteFunnelChart.svelte +20 -0
  230. package/dist/components/widgets/LiteFunnelChart.svelte.d.ts +10 -0
  231. package/dist/components/widgets/LiteGaugeChart.svelte +20 -0
  232. package/dist/components/widgets/LiteGaugeChart.svelte.d.ts +10 -0
  233. package/dist/components/widgets/LiteHeatmapChart.svelte +20 -0
  234. package/dist/components/widgets/LiteHeatmapChart.svelte.d.ts +10 -0
  235. package/dist/components/widgets/LiteInsightCard.svelte +35 -13
  236. package/dist/components/widgets/LiteInsightCard.svelte.d.ts +7 -4
  237. package/dist/components/widgets/LiteLineChart.svelte +13 -10
  238. package/dist/components/widgets/LiteLineChart.svelte.d.ts +0 -5
  239. package/dist/components/widgets/LitePieChart.svelte +11 -9
  240. package/dist/components/widgets/LitePieChart.svelte.d.ts +0 -5
  241. package/dist/components/widgets/LiteRadarChart.svelte +20 -0
  242. package/dist/components/widgets/LiteRadarChart.svelte.d.ts +10 -0
  243. package/dist/components/widgets/LiteRoseChart.svelte +20 -0
  244. package/dist/components/widgets/LiteRoseChart.svelte.d.ts +10 -0
  245. package/dist/components/widgets/LiteScatterChart.svelte +20 -0
  246. package/dist/components/widgets/LiteScatterChart.svelte.d.ts +10 -0
  247. package/dist/components/widgets/LiteWaterfallChart.svelte +20 -0
  248. package/dist/components/widgets/LiteWaterfallChart.svelte.d.ts +10 -0
  249. package/dist/components/widgets/chart-geometry.d.ts +65 -0
  250. package/dist/components/widgets/chart-geometry.js +229 -0
  251. package/dist/components/widgets/index.d.ts +10 -0
  252. package/dist/components/widgets/index.js +10 -0
  253. package/dist/index.d.ts +40 -0
  254. package/dist/index.js +47 -0
  255. package/dist/lite-code-theme.css +139 -0
  256. package/dist/lite.css +234 -127
  257. package/dist/rich-content.d.ts +47 -0
  258. package/dist/rich-content.js +498 -0
  259. package/dist/schema-generator.js +3 -1
  260. package/dist/server-adapter.js +6 -11
  261. package/dist/value-normalization.d.ts +4 -0
  262. package/dist/value-normalization.js +13 -0
  263. package/package.json +18 -4
@@ -0,0 +1,47 @@
1
+ export interface MarkdownRenderOptions {
2
+ /**
3
+ * Trusted server-side highlighter. It receives the raw code and the fenced
4
+ * language and must return ready-to-render HTML (for example from Shiki or
5
+ * Prism running on the server). Returning `undefined` keeps the escaped
6
+ * plain-text block. The result is inserted as-is, so never forward user input
7
+ * into a highlighter that echoes unescaped markup.
8
+ */
9
+ highlight?: (code: string, language: string) => string | undefined;
10
+ /** Extra class names applied to every fenced `<pre>`. */
11
+ codeBlockClass?: string;
12
+ /** Emit stable `id` attributes on headings (needed for anchors and the TOC). */
13
+ headingIds?: boolean;
14
+ /** Deepest heading level included in a generated `[toc]` (default 3). */
15
+ tocLevels?: number;
16
+ /**
17
+ * Open absolute `http(s)` links in a new tab and harden them with
18
+ * `rel="noopener noreferrer"`. Relative links are left untouched.
19
+ */
20
+ externalLinks?: boolean;
21
+ }
22
+ /**
23
+ * Register app-wide Markdown rendering options (for example a server-side
24
+ * highlighter). Returns the previous options so tests and hosts can restore them.
25
+ */
26
+ export declare function configureMarkdownRenderer(options: MarkdownRenderOptions): MarkdownRenderOptions;
27
+ export declare function escapeHtml(value: string): string;
28
+ export interface FootnoteContext {
29
+ /** Footnote label to 1-based number. */
30
+ numbers: Map<string, number>;
31
+ /** How many times each number has been referenced, for unique ids. */
32
+ refs: Map<number, number>;
33
+ }
34
+ export declare function renderInlineMarkdown(source: string, footnotes?: FootnoteContext, externalLinks?: boolean): string;
35
+ /**
36
+ * Render a Markdown subset (headings, lists, quotes, fenced code, rules,
37
+ * paragraphs, inline code/emphasis/links). Unsupported syntax stays literal
38
+ * text, which is safer than silently dropping user content.
39
+ */
40
+ export declare function renderMarkdown(value: unknown, options?: MarkdownRenderOptions): string;
41
+ /**
42
+ * Keep a small allowlist of formatting tags and escape everything else, so a
43
+ * stored rich-text value cannot inject script, event handlers or unsafe URLs.
44
+ */
45
+ export declare function sanitizeRichTextHtml(value: unknown): string;
46
+ /** Pick the renderer for a field: rich HTML values pass through the allowlist. */
47
+ export declare function renderRichContent(value: unknown, options?: MarkdownRenderOptions): string;
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Server-side rich content rendering for `@svadmin/lite`.
3
+ *
4
+ * Both helpers build HTML from untrusted strings, so every literal character is
5
+ * escaped first and only a fixed tag/attribute allowlist survives. The output is
6
+ * meant to be rendered with `{@html ...}` inside SSR markup — no client script,
7
+ * no DOM, no sanitizer dependency.
8
+ */
9
+ import { toSafeHref, toSafeText } from './security';
10
+ let configuredMarkdownOptions = {};
11
+ /**
12
+ * Register app-wide Markdown rendering options (for example a server-side
13
+ * highlighter). Returns the previous options so tests and hosts can restore them.
14
+ */
15
+ export function configureMarkdownRenderer(options) {
16
+ const previous = configuredMarkdownOptions;
17
+ configuredMarkdownOptions = { ...options };
18
+ return previous;
19
+ }
20
+ function resolveOptions(options) {
21
+ return options ? { ...configuredMarkdownOptions, ...options } : configuredMarkdownOptions;
22
+ }
23
+ function classAttribute(...values) {
24
+ const classes = values.filter((value) => Boolean(value && value.trim()));
25
+ return classes.length > 0 ? ` class="${escapeHtml(classes.join(' '))}"` : '';
26
+ }
27
+ const ESCAPES = {
28
+ '&': '&amp;',
29
+ '<': '&lt;',
30
+ '>': '&gt;',
31
+ '"': '&quot;',
32
+ "'": '&#39;',
33
+ };
34
+ export function escapeHtml(value) {
35
+ return value.replace(/[&<>"']/gu, (character) => ESCAPES[character] ?? character);
36
+ }
37
+ const INLINE_CODE = /`([^`\n]+)`/u;
38
+ const LINK = /\[([^\]\n]+)\]\(([^)\s]+)\)/u;
39
+ const FOOTNOTE_REF = /\[\^([^\]\s]+)\]/u;
40
+ /** Apply inline emphasis to an already-escaped fragment. */
41
+ function emphasize(value) {
42
+ return value
43
+ .replace(/\*\*([^*\n]+)\*\*/gu, '<strong>$1</strong>')
44
+ .replace(/__([^_\n]+)__/gu, '<strong>$1</strong>')
45
+ .replace(/\*([^*\n]+)\*/gu, '<em>$1</em>')
46
+ .replace(/(^|[^\w_])_([^_\n]+)_(?!\w)/gu, '$1<em>$2</em>')
47
+ .replace(/~~([^~\n]+)~~/gu, '<s>$1</s>');
48
+ }
49
+ /**
50
+ * Inline Markdown for one text run. Code spans and links are tokenized before
51
+ * escaping so their contents never re-enter the emphasis rules.
52
+ */
53
+ const TOC_PLACEHOLDER = '\u0000lite-toc\u0000';
54
+ const EXTERNAL_HREF = /^(?:https?:)?\/\//iu;
55
+ /** `rel`/`target` for a link, hardening cross-origin navigation on request. */
56
+ function anchorAttributes(href, externalLinks) {
57
+ return externalLinks && EXTERNAL_HREF.test(href)
58
+ ? ' target="_blank" rel="noopener noreferrer"'
59
+ : ' rel="noreferrer"';
60
+ }
61
+ const TOC_DIRECTIVE = /^\[toc\]$/iu;
62
+ function slugifyHeading(text) {
63
+ return text
64
+ .replace(/`([^`]*)`/gu, '$1')
65
+ .replace(/[*_~]/gu, '')
66
+ .replace(/\[([^\]]*)\]\([^)]*\)/gu, '$1')
67
+ .toLowerCase()
68
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
69
+ .replace(/^-+|-+$/gu, '') || 'section';
70
+ }
71
+ function buildToc(headings, levels) {
72
+ const items = headings
73
+ .filter((heading) => heading.level <= levels)
74
+ .map((heading) => `<li data-level="${heading.level}"><a href="#${escapeHtml(heading.id)}">${renderInlineMarkdown(heading.text)}</a></li>`)
75
+ .join('');
76
+ return items
77
+ ? `<nav class="lite-toc" aria-label="Table of contents"><ol class="lite-toc__list">${items}</ol></nav>`
78
+ : '';
79
+ }
80
+ function renderFootnoteRef(label, footnotes) {
81
+ const number = footnotes?.numbers.get(label);
82
+ if (!footnotes || number === undefined)
83
+ return undefined;
84
+ const seen = (footnotes.refs.get(number) ?? 0) + 1;
85
+ footnotes.refs.set(number, seen);
86
+ const id = seen === 1 ? `fnref-${number}` : `fnref-${number}-${seen}`;
87
+ return `<sup class="lite-footnote-ref"><a href="#fn-${number}" id="${id}">${number}</a></sup>`;
88
+ }
89
+ export function renderInlineMarkdown(source, footnotes, externalLinks) {
90
+ let rest = source;
91
+ let output = '';
92
+ while (rest.length > 0) {
93
+ const code = INLINE_CODE.exec(rest);
94
+ const link = LINK.exec(rest);
95
+ const footnote = FOOTNOTE_REF.exec(rest);
96
+ const next = [code, link, footnote]
97
+ .filter((match) => match !== null)
98
+ .sort((a, b) => a.index - b.index)[0];
99
+ if (!next) {
100
+ output += emphasize(escapeHtml(rest));
101
+ break;
102
+ }
103
+ output += emphasize(escapeHtml(rest.slice(0, next.index)));
104
+ if (next === code) {
105
+ output += `<code>${escapeHtml(next[1] ?? '')}</code>`;
106
+ }
107
+ else if (next === footnote) {
108
+ const reference = renderFootnoteRef(next[1] ?? '', footnotes);
109
+ output += reference ?? escapeHtml(next[0]);
110
+ }
111
+ else {
112
+ const label = emphasize(escapeHtml(next[1] ?? ''));
113
+ const href = toSafeHref(next[2] ?? '');
114
+ output += href
115
+ ? `<a href="${escapeHtml(href)}"${anchorAttributes(href, externalLinks)}>${label}</a>`
116
+ : label;
117
+ }
118
+ rest = rest.slice(next.index + next[0].length);
119
+ }
120
+ return output;
121
+ }
122
+ const TABLE_SEPARATOR = /^\s*\|?\s*:?-{1,}:?\s*(?:\|\s*:?-{1,}:?\s*)+\|?\s*$/u;
123
+ const FOOTNOTE_DEFINITION = /^\s*\[\^([^\]\s]+)\]:\s*(.+)$/u;
124
+ const DEFINITION_LINE = /^\s*:\s+(.+)$/u;
125
+ const TASK_ITEM = /^\s*\[( |x|X)\]\s+(.*)$/u;
126
+ function splitTableRow(line) {
127
+ let row = line.trim();
128
+ if (row.startsWith('|'))
129
+ row = row.slice(1);
130
+ if (row.endsWith('|'))
131
+ row = row.slice(0, -1);
132
+ const cells = [];
133
+ let current = '';
134
+ for (const character of row) {
135
+ if (character === '|') {
136
+ cells.push(current.trim());
137
+ current = '';
138
+ continue;
139
+ }
140
+ current += character;
141
+ }
142
+ cells.push(current.trim());
143
+ return cells;
144
+ }
145
+ function tableAlignment(cell) {
146
+ const trimmed = cell.trim();
147
+ const left = trimmed.startsWith(':');
148
+ const right = trimmed.endsWith(':');
149
+ if (left && right)
150
+ return 'center';
151
+ if (right)
152
+ return 'right';
153
+ if (left)
154
+ return 'left';
155
+ return undefined;
156
+ }
157
+ function renderTable(header, alignments, rows, externalLinks) {
158
+ const attribute = (index) => {
159
+ const align = alignments[index];
160
+ return align ? ` align="${align}"` : '';
161
+ };
162
+ const head = header
163
+ .map((cell, index) => `<th${attribute(index)}>${renderInlineMarkdown(cell, undefined, externalLinks)}</th>`)
164
+ .join('');
165
+ const body = rows
166
+ .map((row) => {
167
+ const cells = header.map((_, index) => `<td${attribute(index)}>${renderInlineMarkdown(row[index] ?? '', undefined, externalLinks)}</td>`).join('');
168
+ return `<tr>${cells}</tr>`;
169
+ })
170
+ .join('');
171
+ return `<table><thead><tr>${head}</tr></thead><tbody>${body}</tbody></table>`;
172
+ }
173
+ const HEADING = /^(#{1,6})\s+(.*)$/u;
174
+ const UNORDERED = /^\s*[-*+]\s+(.*)$/u;
175
+ const ORDERED = /^\s*\d+[.)]\s+(.*)$/u;
176
+ const QUOTE = /^\s*>\s?(.*)$/u;
177
+ const RULE = /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/u;
178
+ const FENCE = /^\s*```([\w-]*)\s*$/u;
179
+ /**
180
+ * Render a Markdown subset (headings, lists, quotes, fenced code, rules,
181
+ * paragraphs, inline code/emphasis/links). Unsupported syntax stays literal
182
+ * text, which is safer than silently dropping user content.
183
+ */
184
+ export function renderMarkdown(value, options) {
185
+ const source = toSafeText(value).replace(/\r\n?/gu, '\n');
186
+ if (source.trim() === '')
187
+ return '';
188
+ const renderOptions = resolveOptions(options);
189
+ const lines = source.split('\n');
190
+ const declaresToc = lines.some((line) => TOC_DIRECTIVE.test(line.trim()));
191
+ // Footnote definitions are lifted out of the body and rendered as a list.
192
+ const definitions = new Map();
193
+ const numbers = new Map();
194
+ const footnotes = { numbers, refs: new Map() };
195
+ const bodyLines = [];
196
+ for (const line of lines) {
197
+ const definition = FOOTNOTE_DEFINITION.exec(line);
198
+ if (!definition?.[1] || definition[2] === undefined) {
199
+ bodyLines.push(line);
200
+ continue;
201
+ }
202
+ if (!definitions.has(definition[1])) {
203
+ definitions.set(definition[1], definition[2].trim());
204
+ numbers.set(definition[1], definitions.size);
205
+ }
206
+ }
207
+ const blocks = [];
208
+ let paragraph = [];
209
+ const headings = [];
210
+ const usedSlugs = new Map();
211
+ let wantsToc = declaresToc;
212
+ const headingId = (text) => {
213
+ const base = slugifyHeading(text);
214
+ const seen = (usedSlugs.get(base) ?? 0) + 1;
215
+ usedSlugs.set(base, seen);
216
+ return seen === 1 ? base : `${base}-${seen}`;
217
+ };
218
+ const inline = (text) => renderInlineMarkdown(text, footnotes, renderOptions.externalLinks);
219
+ const flush = () => {
220
+ if (paragraph.length === 0)
221
+ return;
222
+ blocks.push(`<p>${paragraph.map((line) => inline(line)).join('<br />')}</p>`);
223
+ paragraph = [];
224
+ };
225
+ const content = bodyLines;
226
+ for (let index = 0; index < content.length; index += 1) {
227
+ const line = content[index] ?? '';
228
+ const fence = FENCE.exec(line);
229
+ if (fence) {
230
+ flush();
231
+ const code = [];
232
+ let cursor = index + 1;
233
+ while (cursor < content.length && !FENCE.test(content[cursor] ?? '')) {
234
+ code.push(content[cursor] ?? '');
235
+ cursor += 1;
236
+ }
237
+ index = cursor;
238
+ const language = fence[1] ?? '';
239
+ const highlighted = language && renderOptions.highlight
240
+ ? renderOptions.highlight(code.join('\n'), language)
241
+ : undefined;
242
+ const languageClass = language ? `language-${language}` : undefined;
243
+ const preClass = classAttribute(renderOptions.codeBlockClass);
244
+ const codeClass = classAttribute(languageClass);
245
+ blocks.push(highlighted === undefined
246
+ ? `<pre${preClass}><code${codeClass}>${escapeHtml(code.join('\n'))}</code></pre>`
247
+ : `<pre${preClass}><code${codeClass}>${highlighted}</code></pre>`);
248
+ continue;
249
+ }
250
+ if (line.trim() === '') {
251
+ flush();
252
+ continue;
253
+ }
254
+ if (TOC_DIRECTIVE.test(line.trim())) {
255
+ flush();
256
+ wantsToc = true;
257
+ blocks.push(TOC_PLACEHOLDER);
258
+ continue;
259
+ }
260
+ if (RULE.test(line)) {
261
+ flush();
262
+ blocks.push('<hr />');
263
+ continue;
264
+ }
265
+ const heading = HEADING.exec(line);
266
+ if (heading) {
267
+ flush();
268
+ const level = heading[1]?.length ?? 1;
269
+ const text = heading[2] ?? '';
270
+ const id = headingId(text);
271
+ headings.push({ level, id, text });
272
+ const anchor = renderOptions.headingIds || declaresToc ? ` id="${escapeHtml(id)}"` : '';
273
+ blocks.push(`<h${level}${anchor}>${inline(text)}</h${level}>`);
274
+ continue;
275
+ }
276
+ if (line.includes('|') && TABLE_SEPARATOR.test(content[index + 1] ?? '')) {
277
+ flush();
278
+ const header = splitTableRow(line);
279
+ const alignments = splitTableRow(content[index + 1] ?? '').map(tableAlignment);
280
+ const rows = [];
281
+ let cursor = index + 2;
282
+ while (cursor < content.length) {
283
+ const candidate = content[cursor] ?? '';
284
+ if (candidate.trim() === '' || !candidate.includes('|'))
285
+ break;
286
+ rows.push(splitTableRow(candidate));
287
+ cursor += 1;
288
+ }
289
+ index = cursor - 1;
290
+ blocks.push(renderTable(header, alignments, rows, renderOptions.externalLinks));
291
+ continue;
292
+ }
293
+ if (UNORDERED.test(line) || ORDERED.test(line)) {
294
+ flush();
295
+ const ordered = ORDERED.test(line);
296
+ const pattern = ordered ? ORDERED : UNORDERED;
297
+ const items = [];
298
+ let hasTask = false;
299
+ let cursor = index;
300
+ while (cursor < content.length) {
301
+ const item = pattern.exec(content[cursor] ?? '');
302
+ if (!item)
303
+ break;
304
+ const itemContent = item[1] ?? '';
305
+ const task = ordered ? null : TASK_ITEM.exec(itemContent);
306
+ if (task) {
307
+ hasTask = true;
308
+ const checked = task[1] !== ' ';
309
+ items.push(`<li class="lite-task-item"><input type="checkbox" disabled${checked ? ' checked' : ''} /> ${inline(task[2] ?? '')}</li>`);
310
+ }
311
+ else {
312
+ items.push(`<li>${inline(itemContent)}</li>`);
313
+ }
314
+ cursor += 1;
315
+ }
316
+ index = cursor - 1;
317
+ const listClass = hasTask ? ' class="lite-task-list"' : '';
318
+ blocks.push(ordered ? `<ol>${items.join('')}</ol>` : `<ul${listClass}>${items.join('')}</ul>`);
319
+ continue;
320
+ }
321
+ const quote = QUOTE.exec(line);
322
+ if (quote) {
323
+ flush();
324
+ const quoted = [];
325
+ let cursor = index;
326
+ while (cursor < content.length) {
327
+ const inner = QUOTE.exec(content[cursor] ?? '');
328
+ if (!inner)
329
+ break;
330
+ quoted.push(inner[1] ?? '');
331
+ cursor += 1;
332
+ }
333
+ index = cursor - 1;
334
+ blocks.push(`<blockquote><p>${quoted.map((line) => inline(line)).join('<br />')}</p></blockquote>`);
335
+ continue;
336
+ }
337
+ const definition = DEFINITION_LINE.exec(content[index + 1] ?? '');
338
+ if (definition) {
339
+ flush();
340
+ const terms = [];
341
+ let cursor = index;
342
+ while (cursor < content.length) {
343
+ const term = (content[cursor] ?? '').trim();
344
+ const description = DEFINITION_LINE.exec(content[cursor + 1] ?? '');
345
+ if (!term || !description?.[1])
346
+ break;
347
+ terms.push(`<dt>${inline(term)}</dt><dd>${inline(description[1])}</dd>`);
348
+ cursor += 2;
349
+ }
350
+ index = cursor - 1;
351
+ blocks.push(`<dl>${terms.join('')}</dl>`);
352
+ continue;
353
+ }
354
+ paragraph.push(line.trim());
355
+ }
356
+ flush();
357
+ if (wantsToc) {
358
+ const toc = buildToc(headings, renderOptions.tocLevels ?? 3);
359
+ const index = blocks.indexOf(TOC_PLACEHOLDER);
360
+ if (index >= 0) {
361
+ if (toc)
362
+ blocks[index] = toc;
363
+ else
364
+ blocks.splice(index, 1);
365
+ }
366
+ }
367
+ if (definitions.size > 0) {
368
+ const items = [...definitions.entries()]
369
+ .map(([label, text], index) => {
370
+ const number = numbers.get(label) ?? index + 1;
371
+ const backlinks = Math.max(1, footnotes.refs.get(number) ?? 0);
372
+ const links = Array.from({ length: backlinks }, (_, refIndex) => {
373
+ const id = refIndex === 0 ? `fnref-${number}` : `fnref-${number}-${refIndex + 1}`;
374
+ return `<a href="#${id}" class="lite-footnote-backlink" aria-label="Back to reference">↩</a>`;
375
+ }).join(' ');
376
+ return `<li id="fn-${number}">${inline(text)} ${links}</li>`;
377
+ })
378
+ .join('');
379
+ blocks.push(`<section class="lite-footnotes"><hr /><ol>${items}</ol></section>`);
380
+ }
381
+ return blocks.join('\n');
382
+ }
383
+ const ALLOWED_TAGS = {
384
+ a: ['href', 'title'],
385
+ b: [],
386
+ strong: [],
387
+ i: [],
388
+ em: [],
389
+ u: [],
390
+ s: [],
391
+ p: [],
392
+ br: [],
393
+ hr: [],
394
+ ul: [],
395
+ ol: [],
396
+ li: [],
397
+ blockquote: [],
398
+ code: [],
399
+ pre: [],
400
+ h1: [],
401
+ h2: [],
402
+ h3: [],
403
+ h4: [],
404
+ h5: [],
405
+ h6: [],
406
+ img: ['src', 'alt', 'title'],
407
+ table: [],
408
+ thead: [],
409
+ tbody: [],
410
+ tfoot: [],
411
+ tr: [],
412
+ th: ['align'],
413
+ td: ['align'],
414
+ input: ['type', 'checked', 'disabled'],
415
+ sup: [],
416
+ dl: [],
417
+ dt: [],
418
+ dd: [],
419
+ };
420
+ const VOID_TAGS = new Set(['br', 'hr', 'img', 'input']);
421
+ const ATTRIBUTE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/gu;
422
+ function safeAttributeValue(tag, name, raw) {
423
+ const value = raw.trim();
424
+ if (tag === 'a' && name === 'href') {
425
+ const href = toSafeHref(value);
426
+ return href ? escapeHtml(href) : undefined;
427
+ }
428
+ if (tag === 'img' && name === 'src') {
429
+ const src = toSafeHref(value);
430
+ return src ? escapeHtml(src) : undefined;
431
+ }
432
+ return escapeHtml(value);
433
+ }
434
+ /**
435
+ * Keep a small allowlist of formatting tags and escape everything else, so a
436
+ * stored rich-text value cannot inject script, event handlers or unsafe URLs.
437
+ */
438
+ export function sanitizeRichTextHtml(value) {
439
+ const source = toSafeText(value)
440
+ .replace(/<!--[\s\S]*?-->/gu, '')
441
+ .replace(/<(script|style|iframe|object|embed)\b[\s\S]*?<\/\1\s*>/giu, '');
442
+ let output = '';
443
+ let cursor = 0;
444
+ const tagPattern = /<\/?([a-zA-Z][a-zA-Z0-9-]*)((?:[^<>"']|"[^"]*"|'[^']*')*)\/?>/gu;
445
+ for (const match of source.matchAll(tagPattern)) {
446
+ const index = match.index ?? 0;
447
+ output += escapeHtml(source.slice(cursor, index));
448
+ cursor = index + match[0].length;
449
+ const tag = (match[1] ?? '').toLowerCase();
450
+ const closing = match[0].startsWith('</');
451
+ const allowedAttributes = ALLOWED_TAGS[tag];
452
+ if (!allowedAttributes)
453
+ continue;
454
+ if (closing) {
455
+ if (!VOID_TAGS.has(tag))
456
+ output += `</${tag}>`;
457
+ continue;
458
+ }
459
+ const attributes = [];
460
+ const isCheckbox = tag === 'input' && /type\s*=\s*["']?checkbox/iu.test(match[2] ?? '');
461
+ if ((tag === 'input' && !isCheckbox) || (tag === 'input' && closing))
462
+ continue;
463
+ for (const attribute of (match[2] ?? '').matchAll(ATTRIBUTE)) {
464
+ const name = (attribute[1] ?? '').toLowerCase();
465
+ if (!allowedAttributes.includes(name))
466
+ continue;
467
+ const raw = attribute[2] ?? attribute[3] ?? attribute[4] ?? '';
468
+ const safeValue = safeAttributeValue(tag, name, raw);
469
+ if (safeValue === undefined)
470
+ continue;
471
+ attributes.push(`${name}="${safeValue}"`);
472
+ }
473
+ if (tag === 'a' && attributes.some((attribute) => attribute.startsWith('href='))) {
474
+ attributes.push('rel="noopener noreferrer"');
475
+ }
476
+ if (tag === 'input') {
477
+ // Boolean attributes carry no value, so they cannot come from ATTRIBUTE.
478
+ const raw = match[2] ?? '';
479
+ if (/\bchecked\b/iu.test(raw))
480
+ attributes.push('checked');
481
+ if (/\bdisabled\b/iu.test(raw))
482
+ attributes.push('disabled');
483
+ }
484
+ const rendered = attributes.length > 0 ? `<${tag} ${attributes.join(' ')}` : `<${tag}`;
485
+ output += VOID_TAGS.has(tag) ? `${rendered} />` : `${rendered}>`;
486
+ }
487
+ output += escapeHtml(source.slice(cursor));
488
+ return output.trim();
489
+ }
490
+ /** Pick the renderer for a field: rich HTML values pass through the allowlist. */
491
+ export function renderRichContent(value, options) {
492
+ const text = toSafeText(value);
493
+ if (text.trim() === '')
494
+ return '';
495
+ return /<\/?[a-zA-Z][a-zA-Z0-9-]*[\s/>]/u.test(text)
496
+ ? sanitizeRichTextHtml(text)
497
+ : renderMarkdown(text, options);
498
+ }
@@ -421,7 +421,9 @@ export function fieldToInputType(field) {
421
421
  case "url": return "text";
422
422
  case "phone": return "tel";
423
423
  case "boolean": return "checkbox";
424
- case "date": return "text";
424
+ case "date": return "date";
425
+ case "time": return "time";
426
+ case "datetime": return "datetime-local";
425
427
  case "textarea":
426
428
  case "richtext":
427
429
  case "markdown":
@@ -2,7 +2,7 @@ import { definedOptions } from '@svadmin/core/options';
2
2
  import { FILTER_COLLECTION_LIMIT, isFilterCollectionValue, isCollectionFilterOperator, filterOperatorsForField } from '@svadmin/core/filter-values';
3
3
  import { error, redirect, isRedirect } from '@sveltejs/kit';
4
4
  import { resourceToTypeBoxSchema } from './schema-generator';
5
- import { parseExplicitBoolean } from './value-normalization';
5
+ import { isNativeFile, isNonEmptyNativeFile, parseExplicitBoolean, parseTagValues } from './value-normalization';
6
6
  function listRequestState(url, resource) {
7
7
  const requestedPage = Number(url.searchParams.get('page'));
8
8
  const page = Number.isSafeInteger(requestedPage) && requestedPage > 0 ? requestedPage : 1;
@@ -658,9 +658,6 @@ function validateFormVariables(resource, mode, values) {
658
658
  },
659
659
  };
660
660
  }
661
- function isNativeFile(value) {
662
- return typeof File !== 'undefined' && value instanceof File;
663
- }
664
661
  function formValuesForResponse(fields, values) {
665
662
  const responseValues = {};
666
663
  for (const field of fields) {
@@ -683,9 +680,6 @@ function formValuesForResponse(fields, values) {
683
680
  }
684
681
  return responseValues;
685
682
  }
686
- function isNonEmptyNativeFile(value) {
687
- return isNativeFile(value) && value.size > 0 && value.name !== '';
688
- }
689
683
  function formDataToObject(formData, fields) {
690
684
  const obj = {};
691
685
  for (const field of fields) {
@@ -702,6 +696,10 @@ function formDataToObject(formData, fields) {
702
696
  }
703
697
  continue;
704
698
  }
699
+ if (field.type === 'tags') {
700
+ obj[field.key] = parseTagValues(formData.getAll(field.key));
701
+ continue;
702
+ }
705
703
  if (field.type === 'images') {
706
704
  const values = formData
707
705
  .getAll(field.key)
@@ -734,9 +732,6 @@ function formDataToObject(formData, fields) {
734
732
  case 'boolean':
735
733
  obj[field.key] = parseExplicitBoolean(strRaw) ?? strRaw;
736
734
  break;
737
- case 'tags':
738
- obj[field.key] = strRaw ? strRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
739
- break;
740
735
  case 'json':
741
736
  try {
742
737
  obj[field.key] = strRaw ? JSON.parse(strRaw) : null;
@@ -853,7 +848,7 @@ function coerceFieldValues(field, rawValues) {
853
848
  case 'boolean':
854
849
  return parseExplicitBoolean(raw) ?? raw;
855
850
  case 'tags':
856
- return raw ? raw.split(',').map((value) => value.trim()).filter(Boolean) : [];
851
+ return parseTagValues(rawValues);
857
852
  case 'json':
858
853
  try {
859
854
  return raw ? JSON.parse(raw) : null;
@@ -1,3 +1,7 @@
1
1
  export declare function parseExplicitBoolean(rawBoolean: unknown): boolean | undefined;
2
2
  export declare function isExplicitBooleanTrue(rawBoolean: unknown): boolean;
3
+ export declare function isNativeFile(value: unknown): value is File;
4
+ export declare function isNonEmptyNativeFile(value: unknown): value is File;
5
+ /** 同时兼容逐个提交的标签字段和旧客户端的逗号分隔值。 */
6
+ export declare function parseTagValues(values: FormDataEntryValue[]): string[];
3
7
  export declare function getStatusBadgeClass(value: unknown): string;
@@ -19,6 +19,19 @@ export function parseExplicitBoolean(rawBoolean) {
19
19
  export function isExplicitBooleanTrue(rawBoolean) {
20
20
  return parseExplicitBoolean(rawBoolean) === true;
21
21
  }
22
+ export function isNativeFile(value) {
23
+ return typeof File !== 'undefined' && value instanceof File;
24
+ }
25
+ export function isNonEmptyNativeFile(value) {
26
+ return isNativeFile(value) && value.size > 0 && value.name !== '';
27
+ }
28
+ /** 同时兼容逐个提交的标签字段和旧客户端的逗号分隔值。 */
29
+ export function parseTagValues(values) {
30
+ return values
31
+ .flatMap((value) => (typeof value === 'string' ? value.split(',') : []))
32
+ .map((value) => value.trim())
33
+ .filter(Boolean);
34
+ }
22
35
  export function getStatusBadgeClass(value) {
23
36
  if (typeof value !== "string")
24
37
  return "lite-badge";