@open-mercato/ui 0.6.5-develop.5048.1.fd82f4ae17 → 0.6.5-develop.5116.1.f0af9e5080

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 (38) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/backend/CrudForm.js +7 -57
  3. package/dist/backend/CrudForm.js.map +3 -3
  4. package/dist/backend/detail/InlineEditors.js +2 -51
  5. package/dist/backend/detail/InlineEditors.js.map +3 -3
  6. package/dist/backend/inputs/MarkdownField.js +20 -0
  7. package/dist/backend/inputs/MarkdownField.js.map +7 -0
  8. package/dist/backend/inputs/MdxEditorImpl.js +98 -0
  9. package/dist/backend/inputs/MdxEditorImpl.js.map +7 -0
  10. package/dist/backend/inputs/SwitchableMarkdownInput.js +6 -38
  11. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +2 -2
  12. package/dist/backend/markdown/markdownToPlainText.js +29 -0
  13. package/dist/backend/markdown/markdownToPlainText.js.map +2 -2
  14. package/dist/types/css.d.js +1 -0
  15. package/dist/types/css.d.js.map +7 -0
  16. package/package.json +4 -3
  17. package/src/backend/CrudForm.tsx +3 -67
  18. package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +0 -1
  19. package/src/backend/__tests__/CrudForm.datetime.test.tsx +0 -1
  20. package/src/backend/__tests__/CrudForm.defaults.test.tsx +0 -1
  21. package/src/backend/__tests__/CrudForm.dotPath.test.tsx +0 -1
  22. package/src/backend/__tests__/CrudForm.doubleSubmit.test.tsx +0 -1
  23. package/src/backend/__tests__/CrudForm.navigation.test.tsx +0 -1
  24. package/src/backend/__tests__/CrudForm.optimisticLock.test.tsx +0 -1
  25. package/src/backend/__tests__/CrudForm.render.test.tsx +0 -1
  26. package/src/backend/__tests__/CrudForm.sortable.test.tsx +0 -1
  27. package/src/backend/__tests__/CrudForm.transformData.test.tsx +0 -1
  28. package/src/backend/__tests__/CrudForm.unsavedNavigation.test.tsx +0 -1
  29. package/src/backend/__tests__/CrudForm.validation.test.tsx +0 -1
  30. package/src/backend/__tests__/CustomDataSection.test.tsx +0 -1
  31. package/src/backend/__tests__/lazy-heavy-libraries.test.ts +16 -3
  32. package/src/backend/detail/InlineEditors.tsx +14 -76
  33. package/src/backend/inputs/MarkdownField.tsx +34 -0
  34. package/src/backend/inputs/MdxEditorImpl.tsx +113 -0
  35. package/src/backend/inputs/SwitchableMarkdownInput.tsx +5 -54
  36. package/src/backend/markdown/__tests__/markdownToPlainText.test.ts +13 -0
  37. package/src/backend/markdown/markdownToPlainText.ts +41 -0
  38. package/src/types/css.d.ts +5 -0
@@ -29,11 +29,8 @@
29
29
  "use client"
30
30
 
31
31
  import * as React from 'react'
32
- import dynamic from 'next/dynamic'
33
32
  import type { PluggableList } from 'unified'
34
- import { LoadingMessage } from '../detail/LoadingMessage'
35
- import { useMarkdownRemarkPlugins } from '../markdown/useMarkdownRemarkPlugins'
36
- import { useTheme } from '../../theme'
33
+ import MarkdownField from './MarkdownField'
37
34
 
38
35
  /** @deprecated See module-level JSDoc. */
39
36
  export type SwitchableMarkdownInputProps = {
@@ -53,75 +50,29 @@ export type SwitchableMarkdownInputProps = {
53
50
  remarkPlugins?: PluggableList
54
51
  }
55
52
 
56
- type UiMarkdownEditorProps = {
57
- value?: string
58
- height?: number
59
- onChange?: (value?: string) => void
60
- previewOptions?: { remarkPlugins?: unknown[] }
61
- }
62
-
63
- const isTestEnv =
64
- typeof process !== 'undefined' &&
65
- (process.env.NODE_ENV === 'test' || typeof process.env.JEST_WORKER_ID !== 'undefined')
66
-
67
- const MarkdownEditorTestStub: React.ComponentType<UiMarkdownEditorProps> = ({ value, onChange }) => (
68
- <textarea
69
- className="min-h-[160px] w-full rounded border px-3 py-2 text-sm"
70
- value={value ?? ''}
71
- onChange={(event) => onChange?.(event.target.value)}
72
- />
73
- )
74
-
75
- const UiMarkdownEditor = isTestEnv
76
- ? MarkdownEditorTestStub
77
- : (dynamic(() => import('@uiw/react-md-editor'), {
78
- ssr: false,
79
- loading: () => (
80
- <LoadingMessage
81
- label="Loading editor..."
82
- className="min-h-[220px] justify-center"
83
- />
84
- ),
85
- }) as unknown as React.ComponentType<UiMarkdownEditorProps>)
86
-
87
53
  /** @deprecated Use `RichEditor` from `@open-mercato/ui/primitives/rich-editor` for new code. See module JSDoc. */
88
54
  export function SwitchableMarkdownInput({
89
55
  value,
90
56
  onChange,
91
57
  isMarkdownEnabled,
92
58
  disableMarkdown,
93
- height = 220,
94
59
  placeholder,
95
60
  rows = 3,
96
61
  textareaRef,
97
62
  onTextareaInput,
98
63
  textareaClassName,
99
- editorWrapperClassName,
100
- editorClassName,
101
64
  disabled,
102
- remarkPlugins,
103
65
  }: SwitchableMarkdownInputProps) {
104
- const resolvedPlugins = useMarkdownRemarkPlugins(remarkPlugins)
105
- const { resolvedTheme } = useTheme()
106
- const editorWrapperClasses =
107
- editorWrapperClassName ?? 'w-full rounded-lg border border-muted-foreground/20 bg-background p-2'
108
- const editorClasses = editorClassName ?? 'w-full'
109
66
  const textareaClasses =
110
67
  textareaClassName
111
68
  ?? 'w-full resize-none overflow-hidden rounded-lg border border-muted-foreground/20 bg-background px-3 py-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring'
112
69
 
113
70
  if (isMarkdownEnabled && !disableMarkdown) {
114
71
  return (
115
- <div className={editorWrapperClasses}>
116
- <div data-color-mode={resolvedTheme} className={editorClasses}>
117
- <UiMarkdownEditor
118
- value={value}
119
- height={height}
120
- onChange={(nextValue) => onChange(typeof nextValue === 'string' ? nextValue : '')}
121
- previewOptions={resolvedPlugins.length ? { remarkPlugins: resolvedPlugins } : undefined}
122
- />
123
- </div>
124
- </div>
72
+ <MarkdownField
73
+ value={value}
74
+ onChange={(nextValue) => onChange(typeof nextValue === 'string' ? nextValue : '')}
75
+ />
125
76
  )
126
77
  }
127
78
 
@@ -26,4 +26,17 @@ describe('markdownToPlainText', () => {
26
26
  const input = '> quoted text\n\n~~gone~~\n\n| a | b |\n| - | - |\n| 1 | 2 |'
27
27
  expect(markdownToPlainText(input)).toBe('quoted text gone a b - - 1 2')
28
28
  })
29
+
30
+ it('strips legacy HTML tags and separates block content', () => {
31
+ expect(markdownToPlainText('<span style="font-weight:bold">Backend</span> and frontend')).toBe(
32
+ 'Backend and frontend',
33
+ )
34
+ expect(markdownToPlainText('<p>Hello</p><p>World</p>')).toBe('Hello World')
35
+ })
36
+
37
+ it('decodes named, decimal, and hex HTML entities', () => {
38
+ expect(markdownToPlainText('Tom &amp; Jerry')).toBe('Tom & Jerry')
39
+ expect(markdownToPlainText('it&#39;s &#x2014; done')).toBe("it's — done")
40
+ expect(markdownToPlainText('a&nbsp;b')).toBe('a b')
41
+ })
29
42
  })
@@ -1,7 +1,47 @@
1
+ const NAMED_HTML_ENTITIES: Record<string, string> = {
2
+ amp: '&',
3
+ lt: '<',
4
+ gt: '>',
5
+ quot: '"',
6
+ apos: "'",
7
+ nbsp: ' ',
8
+ hellip: '…',
9
+ mdash: '—',
10
+ ndash: '–',
11
+ rsquo: '’',
12
+ lsquo: '‘',
13
+ rdquo: '”',
14
+ ldquo: '“',
15
+ }
16
+
17
+ function decodeHtmlEntities(value: string): string {
18
+ return value
19
+ .replace(/&#(\d+);/g, (match, dec: string) => {
20
+ const code = Number(dec)
21
+ return Number.isFinite(code) ? String.fromCodePoint(code) : match
22
+ })
23
+ .replace(/&#x([0-9a-fA-F]+);/g, (match, hex: string) => {
24
+ const code = parseInt(hex, 16)
25
+ return Number.isFinite(code) ? String.fromCodePoint(code) : match
26
+ })
27
+ .replace(/&([a-zA-Z][a-zA-Z0-9]*);/g, (match, name: string) =>
28
+ Object.prototype.hasOwnProperty.call(NAMED_HTML_ENTITIES, name) ? NAMED_HTML_ENTITIES[name] : match,
29
+ )
30
+ }
31
+
32
+ /**
33
+ * Converts a Markdown (or legacy HTML) string into a plain-text preview suitable for compact,
34
+ * read-only contexts such as DataTable list cells. Strips Markdown syntax, strips HTML tags, and
35
+ * decodes HTML entities, then collapses whitespace. Handling legacy HTML keeps list previews clean
36
+ * for records authored before the editor moved back to Markdown — no data migration required.
37
+ *
38
+ * This is NOT a sanitizer — render rich content with the Markdown components, not this helper.
39
+ */
1
40
  export function markdownToPlainText(input: string | null | undefined): string {
2
41
  if (!input) return ''
3
42
  let text = String(input)
4
43
  text = text.replace(/```[\s\S]*?```/g, ' ')
44
+ text = text.replace(/<[^>]+>/g, ' ')
5
45
  text = text.replace(/`([^`]+)`/g, '$1')
6
46
  text = text.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ')
7
47
  text = text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
@@ -15,6 +55,7 @@ export function markdownToPlainText(input: string | null | undefined): string {
15
55
  text = text.replace(/\*([^*]+)\*/g, '$1')
16
56
  text = text.replace(/_([^_]+)_/g, '$1')
17
57
  text = text.replace(/\|/g, ' ')
58
+ text = decodeHtmlEntities(text)
18
59
  text = text.replace(/\s+/g, ' ').trim()
19
60
  return text
20
61
  }
@@ -0,0 +1,5 @@
1
+ // Ambient declaration so side-effect CSS imports from third-party packages
2
+ // (e.g. `import '@mdxeditor/editor/style.css'`) type-check. Packages that consume
3
+ // @open-mercato/ui as source type-check this file's `.tsx` too, so MdxEditorImpl.tsx
4
+ // pulls this declaration into their program via a `/// <reference path>` directive.
5
+ declare module '*.css'