@nan0web/ui 1.9.0 → 1.11.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 (108) hide show
  1. package/README.md +97 -12
  2. package/package.json +54 -25
  3. package/src/App/Command/DepsCommand.js +3 -4
  4. package/src/Frame/Props.js +12 -18
  5. package/src/InterfaceTemplate/InterfaceTemplate.js +9 -7
  6. package/src/Model/index.js +86 -2
  7. package/src/StdIn.js +2 -6
  8. package/src/cli.js +1 -0
  9. package/src/core/Form/Form.js +8 -7
  10. package/src/core/Form/Message.js +1 -1
  11. package/src/core/GeneratorRunner.js +77 -7
  12. package/src/core/InputAdapter.js +3 -1
  13. package/src/core/Intent.js +214 -16
  14. package/src/core/IntentErrorModel.js +6 -1
  15. package/src/core/Message/Message.js +4 -7
  16. package/src/core/Message/OutputMessage.js +4 -9
  17. package/src/core/Stream.js +16 -5
  18. package/src/core/StreamEntry.js +20 -28
  19. package/src/core/index.js +2 -1
  20. package/src/domain/Content.js +196 -0
  21. package/src/domain/Document.js +17 -0
  22. package/src/domain/FooterModel.js +37 -19
  23. package/src/domain/HeaderModel.js +47 -21
  24. package/src/domain/HeroModel.js +24 -22
  25. package/src/domain/LayoutModel.js +43 -0
  26. package/src/domain/ModelAsApp.js +46 -0
  27. package/src/domain/SandboxModel.js +19 -16
  28. package/src/domain/app/GalleryCommand.js +53 -0
  29. package/src/domain/app/GalleryRenderIntent.js +77 -0
  30. package/src/domain/app/SnapshotAuditor.js +401 -0
  31. package/src/domain/app/SnapshotRunner.js +264 -0
  32. package/src/domain/app/UIApp.js +78 -0
  33. package/src/domain/components/BreadcrumbModel.js +10 -6
  34. package/src/domain/components/FeatureGridModel.js +62 -0
  35. package/src/domain/components/MarkdownModel.js +24 -0
  36. package/src/domain/components/ShellModel.js +243 -0
  37. package/src/domain/components/TableModel.js +10 -6
  38. package/src/domain/components/ToastModel.js +10 -6
  39. package/src/domain/components/index.js +3 -1
  40. package/src/domain/index.js +14 -4
  41. package/src/index.js +21 -2
  42. package/src/inspect.js +2 -0
  43. package/src/test/ScenarioAdapter.js +59 -0
  44. package/src/test/ScenarioTest.js +51 -0
  45. package/src/test/ScenarioTest.story.js +56 -0
  46. package/src/testing/CrashReporter.js +56 -0
  47. package/src/testing/GalleryGenerator.js +29 -0
  48. package/src/testing/LogicInspector.js +55 -0
  49. package/src/testing/SnapshotRunner.js +22 -0
  50. package/src/testing/SpecAdapter.js +115 -0
  51. package/src/testing/SpecRunner.js +121 -0
  52. package/src/testing/VisualAdapter.js +46 -0
  53. package/src/testing/index.js +7 -0
  54. package/src/testing/verifySnapshot.js +17 -0
  55. package/types/App/Command/DepsCommand.d.ts +0 -2
  56. package/types/Model/index.d.ts +56 -4
  57. package/types/StdIn.d.ts +3 -3
  58. package/types/cli.d.ts +1 -0
  59. package/types/core/Form/Form.d.ts +2 -2
  60. package/types/core/GeneratorRunner.d.ts +18 -1
  61. package/types/core/InputAdapter.d.ts +2 -1
  62. package/types/core/Intent.d.ts +232 -26
  63. package/types/core/IntentErrorModel.d.ts +4 -0
  64. package/types/core/Message/Message.d.ts +2 -2
  65. package/types/core/Message/OutputMessage.d.ts +0 -2
  66. package/types/core/index.d.ts +2 -1
  67. package/types/domain/Content.d.ts +340 -0
  68. package/types/domain/Document.d.ts +21 -0
  69. package/types/domain/FooterModel.d.ts +22 -12
  70. package/types/domain/HeaderModel.d.ts +36 -13
  71. package/types/domain/HeroModel.d.ts +19 -17
  72. package/types/domain/LayoutModel.d.ts +34 -0
  73. package/types/domain/ModelAsApp.d.ts +23 -0
  74. package/types/domain/SandboxModel.d.ts +10 -0
  75. package/types/domain/app/GalleryCommand.d.ts +55 -0
  76. package/types/domain/app/GalleryRenderIntent.d.ts +31 -0
  77. package/types/domain/app/SnapshotAuditor.d.ts +99 -0
  78. package/types/domain/app/SnapshotRunner.d.ts +45 -0
  79. package/types/domain/app/UIApp.d.ts +60 -0
  80. package/types/domain/components/BreadcrumbModel.d.ts +6 -8
  81. package/types/domain/components/FeatureGridModel.d.ts +50 -0
  82. package/types/domain/components/MarkdownModel.d.ts +19 -0
  83. package/types/domain/components/ShellModel.d.ts +56 -0
  84. package/types/domain/components/TableModel.d.ts +4 -0
  85. package/types/domain/components/ToastModel.d.ts +4 -0
  86. package/types/domain/components/index.d.ts +3 -0
  87. package/types/domain/index.d.ts +10 -4
  88. package/types/index.d.ts +19 -1
  89. package/types/inspect.d.ts +2 -0
  90. package/types/test/ScenarioAdapter.d.ts +43 -0
  91. package/types/test/ScenarioTest.d.ts +24 -0
  92. package/types/test/ScenarioTest.story.d.ts +1 -0
  93. package/types/testing/CrashReporter.d.ts +13 -0
  94. package/types/testing/GalleryGenerator.d.ts +1 -0
  95. package/types/testing/LogicInspector.d.ts +22 -0
  96. package/types/testing/SnapshotRunner.d.ts +7 -0
  97. package/types/testing/SpecAdapter.d.ts +57 -0
  98. package/types/testing/SpecRunner.d.ts +41 -0
  99. package/types/testing/VisualAdapter.d.ts +9 -0
  100. package/types/testing/index.d.ts +7 -0
  101. package/types/testing/verifySnapshot.d.ts +14 -0
  102. package/src/README.md.js +0 -436
  103. package/types/App/Command/Options.d.ts +0 -43
  104. package/types/App/Command/index.d.ts +0 -8
  105. package/types/App/User/Command/Options.d.ts +0 -34
  106. package/types/core/Message/InputMessage.d.ts +0 -71
  107. package/types/domain/components/HeroModel.d.ts +0 -24
  108. package/types/domain/components/ShowcaseAppModel.d.ts +0 -32
@@ -0,0 +1,264 @@
1
+ import { Model } from '@nan0web/types'
2
+ import { LogicInspector } from '../../testing/LogicInspector.js'
3
+ import { VisualAdapter } from '../../testing/VisualAdapter.js'
4
+ import { result, show, render } from '../../core/Intent.js'
5
+ import SnapshotAuditor from './SnapshotAuditor.js'
6
+
7
+ /**
8
+ * SnapshotRunner — Zero-Hallucination Snapshot Generation & Audit (Model-as-Schema v2).
9
+ * Operates entirely through DB-FS abstraction without raw FS/Path hardcodes.
10
+ */
11
+ export class SnapshotRunner extends Model {
12
+ static UI = {
13
+ generating: '📸 Generating snapshots for {lang}/{comp}',
14
+ saved: '📸 Saved {file}',
15
+ auditFailed: '🚨 Audit failed for {file}: {errors}',
16
+ rootGallery:
17
+ '# 📸 Core Snapshots Gallery\n\n**Total Snapshots:** {count} | **Total Errors:** {errors}\n\n## Locales\n\n',
18
+ localeTitle: '🌍 Locale: {title}',
19
+ categoryTitle: '📂 Category: {title}',
20
+ backText: 'Back',
21
+ backLink: '[⬅ {text}](../index.md)',
22
+ galleryDescription: 'This gallery contains automatically generated interaction snapshots (Zero-Hallucination UI Core).',
23
+ }
24
+
25
+ static data = {
26
+ type: 'string',
27
+ help: 'Root directory containing locale folders with data.',
28
+ default: 'docs',
29
+ }
30
+
31
+ static snapshotsDir = {
32
+ type: 'string',
33
+ help: 'Directory where output text snapshots will be stored.',
34
+ default: 'snapshots/core',
35
+ }
36
+
37
+ /**
38
+ * @param {Partial<SnapshotRunner> | Record<string, any>} [data={}]
39
+ * @param {import('@nan0web/types').ModelOptions} [options={}]
40
+ */
41
+ constructor(data = {}, options = {}) {
42
+ super(data, options)
43
+ /** @type {string} Directory containing snapshots */ this.snapshotsDir
44
+ /** @type {string} Root data directory */ this.data
45
+ /** @type {(compName: string) => string} */ this.getCategory = (comp) => 'Components'
46
+ /** @type {(compName: string, varData: any) => AsyncGenerator<any>} */ this.createModelStream
47
+ }
48
+
49
+ get db() {
50
+ return /** @type {import('@nan0web/db').DB} */ (this._.db)
51
+ }
52
+
53
+ /**
54
+ * Recursive drop for directories via DB-FS.
55
+ * @param {string} uri
56
+ */
57
+ async dropRecursive(uri) {
58
+ let entries = []
59
+ try {
60
+ for await (const entry of this.db.readDir(uri)) entries.push(entry)
61
+ } catch (e) {
62
+ return // Dir doesn't exist
63
+ }
64
+ for (const entry of entries) {
65
+ if (entry.stat.isDirectory) {
66
+ await this.dropRecursive(entry.path)
67
+ } else {
68
+ await this.db.dropDocument(entry.path)
69
+ }
70
+ }
71
+ await this.db.dropDocument(uri)
72
+ }
73
+
74
+ async *run() {
75
+ const db = this.db
76
+ const t = this._.t || ((k) => k)
77
+
78
+ // Clean before generation
79
+ await this.dropRecursive(this.snapshotsDir)
80
+
81
+ const doc = (await this.db.fetch('index')) ?? {}
82
+
83
+ // Fetch languages
84
+ const langsData = (await this.db.fetch(`${this.data}/_/langs`)) || []
85
+ const langsIndex = {}
86
+ if (Array.isArray(langsData)) {
87
+ langsData.forEach((l) => {
88
+ if (l && l.locale) langsIndex[l.locale] = l.title || l.locale
89
+ })
90
+ }
91
+
92
+ const langs = []
93
+ for await (const entry of this.db.readDir(this.data)) {
94
+ if (entry.stat.isDirectory && entry.name !== '_' && entry.name !== 'site') langs.push(entry.name)
95
+ }
96
+
97
+ const galleryTree = {}
98
+ let globalErrors = 0
99
+ let globalCount = 0
100
+
101
+ for (const lang of langs) {
102
+ galleryTree[lang] = {}
103
+ const componentsBase = `${this.data}/${lang}/components`
104
+ const components = []
105
+ try {
106
+ for await (const entry of this.db.readDir(componentsBase)) {
107
+ if (!entry.isDirectory) components.push(entry.name)
108
+ }
109
+ } catch (e) {}
110
+
111
+ for (const file of components) {
112
+ const compName = file.replace(/\.[^/.]+$/, "")
113
+ const data = (await this.db.fetch(`${componentsBase}/${compName}`)) || {}
114
+
115
+ // Extract variations
116
+ const variations = data.content || []
117
+ const variationsData = []
118
+
119
+ for (let i = 0; i < variations.length; i++) {
120
+ const rawVar = variations[i]
121
+ let varData = rawVar[compName] !== undefined ? rawVar[compName] : rawVar
122
+
123
+ // Extract schema defaults
124
+ const schema = data['$' + compName] || {}
125
+ const defaultProps = {}
126
+ for (const [k, v] of Object.entries(schema)) {
127
+ if (v && v.default !== undefined) {
128
+ defaultProps[k] = v.default
129
+ }
130
+ }
131
+
132
+ // Merge defaults
133
+ if (typeof varData === 'object' && varData !== null) {
134
+ varData = { ...defaultProps, ...varData }
135
+ } else if (varData === true) {
136
+ varData = { ...defaultProps }
137
+ } else if (typeof varData === 'string' || typeof varData === 'number') {
138
+ varData = { ...defaultProps, content: String(varData) }
139
+ }
140
+
141
+ let varName = rawVar.content || rawVar.title || rawVar.message || `var${i + 1}`
142
+ if (typeof varName !== 'string') {
143
+ if (typeof varData.title === 'string') varName = varData.title
144
+ else if (typeof varData.content === 'string') varName = varData.content
145
+ else varName = `var${i + 1}`
146
+ }
147
+
148
+ const safeVarName = varName
149
+ .trim()
150
+ .toLowerCase()
151
+ .replace(/[./\\:]/g, '_')
152
+ .replace(/\s+/g, '_')
153
+ .replace(/_{2,}/g, '_')
154
+ .slice(0, 50)
155
+
156
+ // Build model stream
157
+ let intents
158
+ if (this.createModelStream) {
159
+ intents = await LogicInspector.capture(this.createModelStream(compName, varData))
160
+ } else {
161
+ const defaultModelStream = async function* () {
162
+ yield render(`ui-${compName.toLowerCase()}`, varData)
163
+ return result({})
164
+ }
165
+ intents = await LogicInspector.capture(defaultModelStream())
166
+ }
167
+
168
+ const snapshot = intents.map((it) => VisualAdapter.render(it)).join('\n')
169
+
170
+ const categoryPath = this.getCategory ? this.getCategory(compName) : 'Components'
171
+ const outPath = `${this.snapshotsDir}/${lang}/${categoryPath}/${compName}`
172
+
173
+ if (!galleryTree[lang][categoryPath]) galleryTree[lang][categoryPath] = {}
174
+ if (!galleryTree[lang][categoryPath][compName])
175
+ galleryTree[lang][categoryPath][compName] = { score: 100, errors: [] }
176
+
177
+ const filePath = `${outPath}/${safeVarName}.nan0`
178
+ await this.db.saveDocument(filePath, snapshot)
179
+
180
+ yield show(t(SnapshotRunner.UI.generating, { lang, comp: `${compName}/${safeVarName}` }))
181
+ variationsData.push({ safeVarName, snapshot })
182
+
183
+ // Instant Audit
184
+ const audit = SnapshotAuditor.inspectText(snapshot, lang, filePath, t)
185
+ if (audit.score < 100) {
186
+ galleryTree[lang][categoryPath][compName].score = Math.min(
187
+ galleryTree[lang][categoryPath][compName].score,
188
+ audit.score,
189
+ )
190
+ galleryTree[lang][categoryPath][compName].errors.push(...audit.errors)
191
+ globalErrors += audit.errors.length
192
+ yield show(
193
+ t(SnapshotRunner.UI.auditFailed, {
194
+ file: filePath,
195
+ errors: audit.errors.join('; '),
196
+ }),
197
+ 'error',
198
+ )
199
+ }
200
+ globalCount++
201
+ }
202
+
203
+ // Generate index.md for component
204
+ if (variationsData.length > 0) {
205
+ const categoryPath = this.getCategory ? this.getCategory(compName) : 'Components'
206
+ const outPath = `${this.snapshotsDir}/${lang}/${categoryPath}/${compName}`
207
+ const desc = t(SnapshotRunner.UI.galleryDescription, undefined)
208
+
209
+ const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
210
+ let markdown = `${backPrefix}\n\n# ${compName}\n\n> ${desc}\n\n`
211
+ for (const { safeVarName, snapshot } of variationsData) {
212
+ markdown += `## ${safeVarName}\n\n\`\`\`yaml\n${snapshot}\n\`\`\`\n\n`
213
+ }
214
+ await this.db.saveDocument(`${outPath}/index.md`, markdown)
215
+ }
216
+ }
217
+ }
218
+
219
+ // Generate top-level indexes
220
+ let rootMd = t(SnapshotRunner.UI.rootGallery, { count: globalCount, errors: globalErrors })
221
+
222
+ for (const lang of Object.keys(galleryTree)) {
223
+ const langTitle = langsIndex[lang] || lang
224
+ rootMd += `- [${langTitle}](./${lang}/index.md) — ${t(SnapshotRunner.UI.galleryDescription, undefined)}\n`
225
+
226
+ const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
227
+ let langMd = `${backPrefix}\n\n# ${t(SnapshotRunner.UI.localeTitle, {
228
+ title: langTitle,
229
+ })}\n\n`
230
+
231
+ for (const category of Object.keys(galleryTree[lang])) {
232
+ langMd += `## [${category}](./${category}/index.md)\n\n`
233
+
234
+ const backPrefix = t(SnapshotRunner.UI.backLink, { text: t(SnapshotRunner.UI.backText, undefined) })
235
+ let catMd = `${backPrefix}\n\n# ${t(SnapshotRunner.UI.categoryTitle, {
236
+ title: category,
237
+ })}\n\n`
238
+
239
+ for (const compName of Object.keys(galleryTree[lang][category])) {
240
+ const compData = galleryTree[lang][category][compName]
241
+ const status = compData.score === 100 ? '✅' : '❌'
242
+
243
+ langMd += `- [${compName}](./${category}/${compName}/index.md) ${status}\n`
244
+ catMd += `- [${compName}](./${compName}/index.md) ${status}\n`
245
+ if (compData.errors.length) {
246
+ catMd += ` - ${compData.errors.join('\\n - ')}\n`
247
+ }
248
+ }
249
+ langMd += '\n'
250
+
251
+ const catDir = `${this.snapshotsDir}/${lang}/${category}`
252
+ await this.db.saveDocument(`${catDir}/index.md`, catMd)
253
+ }
254
+
255
+ const langDir = `${this.snapshotsDir}/${lang}`
256
+ await this.db.saveDocument(`${langDir}/index.md`, langMd)
257
+ }
258
+
259
+ await this.db.saveDocument(`${this.snapshotsDir}/index.md`, rootMd)
260
+ return result({ success: globalErrors === 0, count: globalCount, errors: globalErrors })
261
+ }
262
+ }
263
+
264
+ export default SnapshotRunner
@@ -0,0 +1,78 @@
1
+ import { ModelAsApp } from '../ModelAsApp.js'
2
+ import { resolvePositionalArgs } from '@nan0web/ui-cli'
3
+ import SnapshotAuditor from './SnapshotAuditor.js'
4
+ import GalleryCommand from './GalleryCommand.js'
5
+ import { show, result } from '../../core/Intent.js'
6
+
7
+ export class UIApp extends ModelAsApp {
8
+ static command = {
9
+ type: 'string',
10
+ help: 'Command to run (e.g. gallery)',
11
+ options: [GalleryCommand, SnapshotAuditor],
12
+ default: GalleryCommand.alias || GalleryCommand.name,
13
+ positional: true,
14
+ }
15
+
16
+ static UI = {
17
+ helpText: [
18
+ '# ✨ NaN•Web UI CLI',
19
+ '',
20
+ 'Zero-Hallucination Tools for the NaN•Web ecosystem.',
21
+ '',
22
+ '## Usage',
23
+ '',
24
+ '```bash',
25
+ '',
26
+ 'nan0ui gallery audit',
27
+ '',
28
+ 'nan0ui gallery render',
29
+ '',
30
+ '```',
31
+ ].join('\n'),
32
+ unknownCommand: 'Unknown command: {command}',
33
+ }
34
+
35
+ static help = {
36
+ help: 'Show help message',
37
+ default: false,
38
+ }
39
+
40
+ /**
41
+ * @param {Partial<UIApp> | Record<string, any>} [data={}]
42
+ * @param {import('@nan0web/types').ModelOptions} [options={}]
43
+ */
44
+ constructor(data = {}, options = {}) {
45
+ super(data, options)
46
+ /** @type {string[]} */ this._positionals = []
47
+ /** @type {string} Type of command to run */ this.command
48
+ /** @type {boolean} Show help message */ this.help
49
+ }
50
+
51
+ async *run() {
52
+ const t = this._.t
53
+ if (this.help || this.command === 'help') {
54
+ yield show(t(UIApp.UI.helpText, undefined))
55
+ return result({})
56
+ }
57
+
58
+ const TargetCommand = UIApp.command.options.find((opt) =>
59
+ [opt.alias, opt.name].includes(this.command),
60
+ )
61
+
62
+ if (!TargetCommand) {
63
+ yield show(t(UIApp.UI.unknownCommand, { command: this.command }), 'error')
64
+ return result({ status: 'error' })
65
+ }
66
+
67
+ // Pass remaining positionals down to the target action
68
+ const nextData = resolvePositionalArgs(
69
+ /** @type {any} */ (TargetCommand),
70
+ this._positionals || [],
71
+ this
72
+ )
73
+ const intent = new TargetCommand(nextData, this._)
74
+ return yield* intent.run()
75
+ }
76
+ }
77
+
78
+ export default UIApp
@@ -1,4 +1,5 @@
1
1
  import { Model } from '@nan0web/types'
2
+ import { show, result } from '../../core/Intent.js'
2
3
 
3
4
  /**
4
5
  * @typedef {Object} BreadcrumbItem
@@ -28,6 +29,12 @@ import { Model } from '@nan0web/types'
28
29
  * Ctrl+C = always exit (adapter responsibility).
29
30
  */
30
31
  export class BreadcrumbModel extends Model {
32
+ static $id = '@nan0web/ui/BreadcrumbModel'
33
+
34
+ static UI = {
35
+ navigated: 'Navigated to Context',
36
+ }
37
+
31
38
  // ==========================================
32
39
  // 1. MODEL AS SCHEMA (Static Definition)
33
40
  // ==========================================
@@ -243,17 +250,14 @@ export class BreadcrumbModel extends Model {
243
250
  // ==========================================
244
251
 
245
252
  /**
246
- * Yields a log intent with the current breadcrumb path.
253
+ * Yields a show intent with the current breadcrumb path.
247
254
  * This is a "display-only" run — it shows the navigation state.
248
255
  */
249
256
  async *run() {
250
- yield {
251
- type: 'log',
252
- level: 'info',
253
- message: this.toString(),
257
+ yield show(this._.t(BreadcrumbModel.UI.navigated, {}), 'info', {
254
258
  component: 'Breadcrumbs',
255
259
  model: /** @type {BreadcrumbModel} */ (this),
256
- }
260
+ })
257
261
 
258
262
  return {
259
263
  type: 'result',
@@ -0,0 +1,62 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * FeatureItemModel — OLMUI Component Model
5
+ * Represents a single features block entry with an icon, title, and description.
6
+ */
7
+ export class FeatureItemModel extends Model {
8
+ static $id = '@nan0web/ui/FeatureItemModel'
9
+
10
+ static icon = {
11
+ help: 'Icon name from @nan0web/icons or SVG path',
12
+ placeholder: 'stars',
13
+ default: '',
14
+ }
15
+ static title = {
16
+ help: 'Feature heading',
17
+ placeholder: 'High Performance',
18
+ default: '',
19
+ required: true,
20
+ }
21
+ static description = {
22
+ help: 'Detailed feature description',
23
+ placeholder: 'Built with the latest technologies...',
24
+ default: '',
25
+ }
26
+
27
+ /**
28
+ * @param {Partial<FeatureItemModel>} data
29
+ */
30
+ constructor(data = {}) {
31
+ super(data)
32
+ /** @type {string} Icon name */ this.icon
33
+ /** @type {string} Feature heading */ this.title
34
+ /** @type {string} Feature description */ this.description
35
+ }
36
+ }
37
+
38
+ /**
39
+ * FeatureGridModel — OLMUI Component Model
40
+ * Grid of features with icons and descriptions.
41
+ */
42
+ export class FeatureGridModel extends Model {
43
+ static $id = '@nan0web/ui/FeatureGridModel'
44
+
45
+ static items = {
46
+ help: 'Array of features to display in a grid',
47
+ type: 'FeatureItemModel[]',
48
+ hint: FeatureItemModel,
49
+ default: [],
50
+ }
51
+
52
+ /**
53
+ * @param {Partial<FeatureGridModel>} data
54
+ */
55
+ constructor(data = {}) {
56
+ super(data)
57
+ /** @type {FeatureItemModel[]} List of features */
58
+ this.items = Array.isArray(this.items)
59
+ ? this.items.map((i) => (i instanceof FeatureItemModel ? i : new FeatureItemModel(i)))
60
+ : []
61
+ }
62
+ }
@@ -0,0 +1,24 @@
1
+ import { Model } from '@nan0web/types'
2
+
3
+ /**
4
+ * MarkdownModel — OLMUI Model-as-Schema
5
+ * Represents a rich text block powered by Markdown.
6
+ */
7
+ export class MarkdownModel extends Model {
8
+ static $id = '@nan0web/ui/MarkdownModel'
9
+
10
+ static content = {
11
+ help: 'Markdown content string',
12
+ type: 'textarea',
13
+ default: '',
14
+ }
15
+
16
+ /**
17
+ * @param {Partial<MarkdownModel> | Record<string, any>} data Model input data.
18
+ * @param {object} [options] Extended options
19
+ */
20
+ constructor(data = {}, options = {}) {
21
+ super(data, options)
22
+ /** @type {string} Markdown content string */ this.content
23
+ }
24
+ }