cabloy 5.1.245 → 5.1.247

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 (42) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/package.json +1 -1
  4. package/repo-docs/frontend/column-configuration-guide.md +2 -0
  5. package/repo-docs/frontend/table-guide.md +24 -0
  6. package/repo-e2e/specs/cabloy-basic.spec.ts +350 -28
  7. package/repo-e2e/specs/markdown.spec.ts +38 -2
  8. package/vona/env/.env +1 -0
  9. package/vona/packages-cli/cli/package.json +1 -1
  10. package/vona/packages-cli/cli-set-api/package.json +1 -1
  11. package/vona/packages-vona/vona/package.json +1 -1
  12. package/vona/packages-vona/vona-core/package.json +1 -1
  13. package/vona/packages-vona/vona-core/src/types/utils/env.ts +1 -0
  14. package/vona/packages-vona/vona-mock/package.json +1 -1
  15. package/vona/pnpm-lock.yaml +1 -1
  16. package/vona/src/backend/config/config/config.ts +7 -2
  17. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  18. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  19. package/zova/packages-zova/zova/package.json +2 -2
  20. package/zova/src/front/config/config/config.ts +1 -1
  21. package/zova/src/suite/a-home/modules/home-layoutempty/src/component/layoutEmpty/controller.tsx +53 -3
  22. package/zova/src/suite/cabloy-basic/modules/basic-date/src/component/dateRange/controller.tsx +4 -3
  23. package/zova/src/suite/cabloy-basic/modules/basic-date/src/component/formFieldDateRange/controller.tsx +13 -1
  24. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +1 -1
  25. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +54 -10
  26. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +151 -118
  27. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +2 -0
  28. package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +2 -0
  29. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +1 -1
  30. package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/component/rowOperations.ts +31 -0
  31. package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/index.ts +13 -0
  32. package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionDelete.tsx +6 -2
  33. package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionOperationsRow.tsx +32 -4
  34. package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionUpdate.tsx +6 -2
  35. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/rowOperations/controller.tsx +233 -0
  36. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +75 -2
  37. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +40 -22
  38. package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/en-us.ts +4 -0
  39. package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/zh-cn.ts +4 -0
  40. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  41. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +19 -11
  42. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -7,6 +7,7 @@ export interface VonaConfigEnv {
7
7
  VONA_TEST_RUN_ID: string | undefined;
8
8
  APP_TITLE: string | undefined;
9
9
  APP_VERSION: string | undefined;
10
+ APP_LOCALE_DEFAULT: string | undefined;
10
11
  SERVER_KEYS: string | undefined;
11
12
  SERVER_GLOBALPREFIX: string | undefined;
12
13
  SERVER_PUBLICDIR: string | undefined;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-mock",
3
- "version": "6.1.53",
3
+ "version": "6.1.54",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona mock",
6
6
  "keywords": [
@@ -1990,7 +1990,7 @@ importers:
1990
1990
  specifier: ^5.0.3
1991
1991
  version: link:modules/a-markdown
1992
1992
  vona-module-a-rbac:
1993
- specifier: ^5.0.18
1993
+ specifier: ^5.0.19
1994
1994
  version: link:modules/a-rbac
1995
1995
  vona-module-a-socket:
1996
1996
  specifier: workspace:^
@@ -1,4 +1,9 @@
1
- import type { ILoggerOptionsClientInfo, VonaApplication, VonaConfigOptional } from 'vona';
1
+ import type {
2
+ ILocaleRecord,
3
+ ILoggerOptionsClientInfo,
4
+ VonaApplication,
5
+ VonaConfigOptional,
6
+ } from 'vona';
2
7
  import type { IMailClientRecord, TypeMailTransportService } from 'vona-module-a-mail';
3
8
  import type { IDatabaseClientRecord } from 'vona-module-a-orm';
4
9
 
@@ -45,7 +50,7 @@ export default async function (app: VonaApplication) {
45
50
  'en-us': 'LanguageEnglish',
46
51
  'zh-cn': 'LanguageChinese',
47
52
  },
48
- default: 'en-us',
53
+ default: (env.APP_LOCALE_DEFAULT || 'en-us') as keyof ILocaleRecord,
49
54
  };
50
55
 
51
56
  // instance
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-core",
3
- "version": "5.1.69",
3
+ "version": "5.1.70",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.114",
3
+ "version": "5.1.115",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova",
3
- "version": "5.1.177",
3
+ "version": "5.1.178",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A vue3 framework with ioc",
6
6
  "keywords": [
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "zova-core": "^5.1.95",
49
- "zova-suite-a-zova": "^5.1.176"
49
+ "zova-suite-a-zova": "^5.1.177"
50
50
  },
51
51
  "devDependencies": {
52
52
  "clean-package": "^2.2.0",
@@ -81,7 +81,7 @@ export default function (sys: ZovaSys) {
81
81
 
82
82
  // locale
83
83
  config.locale = {
84
- default: env.APP_LOCALE_DEFAULT as keyof ILocaleRecord,
84
+ default: (env.APP_LOCALE_DEFAULT || 'en-us') as keyof ILocaleRecord,
85
85
  storeKey: 'locale',
86
86
  items: {
87
87
  'en-us': 'LanguageEnglish',
@@ -1,7 +1,12 @@
1
- import { BeanControllerBase, Use, usePrepareArg } from 'zova';
1
+ import { BeanControllerBase, ClientOnly, Use, usePrepareArg } from 'zova';
2
2
  import { Controller } from 'zova-module-a-bean';
3
+ import { $icon } from 'zova-module-a-icon';
3
4
  import { ZRouterViewEmpty } from 'zova-module-a-router';
4
- import { IServiceSsrLayoutOptions, ServiceSsrLayout } from 'zova-module-home-base';
5
+ import {
6
+ closeNearestDetails,
7
+ IServiceSsrLayoutOptions,
8
+ ServiceSsrLayout,
9
+ } from 'zova-module-home-base';
5
10
 
6
11
  export interface ControllerLayoutEmptyProps {}
7
12
 
@@ -18,7 +23,52 @@ export class ControllerLayoutEmpty extends BeanControllerBase {
18
23
  } satisfies IServiceSsrLayoutOptions);
19
24
  }
20
25
 
26
+ protected renderLocale() {
27
+ const currentRoute = this.$currentRoute;
28
+ const metaLocale = currentRoute?.meta?.locale;
29
+ const locales = this.app.meta.locale.getItems(currentRoute?.meta?.locales);
30
+ return (
31
+ <li>
32
+ <details>
33
+ <summary aria-label="Language">{$icon('::language', 24)}</summary>
34
+ <ClientOnly>
35
+ <ul class="bg-base-100 rounded-t-none p-2 w-48 right-0">
36
+ {Object.keys(locales).map(key => {
37
+ const title = this.$scopeBase.locale[locales[key]]();
38
+ return (
39
+ <li key={key} class={this.app.meta.locale.current === key ? 'disabled' : ''}>
40
+ <a
41
+ onClick={event => {
42
+ if (metaLocale) {
43
+ this.app.$gotoPage(currentRoute.matched[0].path, {
44
+ params: { ...currentRoute.params, locale: key },
45
+ query: currentRoute.query,
46
+ });
47
+ } else {
48
+ this.app.meta.locale.current = key as any;
49
+ }
50
+ closeNearestDetails(event);
51
+ }}
52
+ >
53
+ {$icon(this.app.meta.locale.current === key ? '::done' : '::none', 24)}
54
+ {title}
55
+ </a>
56
+ </li>
57
+ );
58
+ })}
59
+ </ul>
60
+ </ClientOnly>
61
+ </details>
62
+ </li>
63
+ );
64
+ }
65
+
21
66
  protected render() {
22
- return <ZRouterViewEmpty></ZRouterViewEmpty>;
67
+ return (
68
+ <>
69
+ <ZRouterViewEmpty></ZRouterViewEmpty>
70
+ <ul class="menu menu-horizontal fixed top-4 right-4 z-50">{this.renderLocale()}</ul>
71
+ </>
72
+ );
23
73
  }
24
74
  }
@@ -21,6 +21,7 @@ export class ControllerDateRange extends BeanControllerBase {
21
21
  this.modelValue = this.$useModel('modelValue');
22
22
  this.cSeparator = this.$style({
23
23
  width: '20px',
24
+ flexShrink: 0,
24
25
  display: 'inline-block',
25
26
  textAlign: 'center',
26
27
  });
@@ -29,9 +30,9 @@ export class ControllerDateRange extends BeanControllerBase {
29
30
  protected render() {
30
31
  const [dateStartStr, dateEndStr] = this._parseValue(this.modelValue);
31
32
  return (
32
- <div>
33
+ <div class="inline-flex min-w-0 max-w-full items-center">
33
34
  <input
34
- style={{ width: '130px' }}
35
+ style={{ width: '130px', minWidth: 0 }}
35
36
  type="date"
36
37
  value={dateStartStr}
37
38
  onInput={e => {
@@ -41,7 +42,7 @@ export class ControllerDateRange extends BeanControllerBase {
41
42
  />
42
43
  <div class={this.cSeparator}>{this.$props.separator}</div>
43
44
  <input
44
- style={{ width: '130px' }}
45
+ style={{ width: '130px', minWidth: 0 }}
45
46
  type="date"
46
47
  value={dateEndStr}
47
48
  onInput={e => {
@@ -30,7 +30,19 @@ export class ControllerFormFieldDateRange extends BeanControllerBase {
30
30
  cContainer: string;
31
31
 
32
32
  protected async __init__() {
33
- this.cContainer = this.$style({ width: 'auto' });
33
+ this.cContainer = this.$style({
34
+ width: 'auto',
35
+ maxWidth: '100%',
36
+ $nest: {
37
+ '&.input': {
38
+ height: 'auto',
39
+ minHeight: 'var(--size)',
40
+ flexWrap: 'wrap',
41
+ paddingTop: '0.5rem',
42
+ paddingBottom: '0.5rem',
43
+ },
44
+ },
45
+ });
34
46
  }
35
47
 
36
48
  protected render() {
@@ -17,7 +17,7 @@ export interface ControllerBlockTableProps extends IResourceBlockOptionsBase {}
17
17
  @Controller()
18
18
  export class ControllerBlockTable<TData extends {} = {}> extends BeanControllerBase {
19
19
  static $propsDefault = {
20
- class: 'overflow-x-auto rounded-box border border-base-content/15 bg-base-100',
20
+ class: 'min-w-0 rounded-box border border-base-content/15 bg-base-100',
21
21
  };
22
22
 
23
23
  static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
@@ -102,6 +102,8 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
102
102
  editor?: Editor;
103
103
  value = '';
104
104
  readonly = false;
105
+ sourceMode = false;
106
+ private _sourceModeValue?: string;
105
107
  toolbarState: IMarkdownToolbarState = this._emptyToolbarState();
106
108
  tableToolbarPosition?: IMarkdownContextualToolbarPosition;
107
109
  codeBlockToolbarPosition?: IMarkdownContextualToolbarPosition;
@@ -232,8 +234,48 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
232
234
  this._scheduleContextualToolbarPosition();
233
235
  }
234
236
 
235
- private _runCommand(command: (editor: Editor) => boolean) {
237
+ public toggleSourceMode() {
236
238
  if (this.readonly || !this.editor) return;
239
+ if (this.sourceMode) {
240
+ this._leaveSourceMode();
241
+ } else {
242
+ this._sourceModeValue = this.value;
243
+ this.sourceMode = true;
244
+ this.closeTablePicker();
245
+ this._imageUploadSelection = undefined;
246
+ this._setTableToolbarPosition();
247
+ this._setCodeBlockToolbarPosition();
248
+ }
249
+ }
250
+
251
+ private _leaveSourceMode() {
252
+ if (this.value !== this._sourceModeValue) this._syncEditorValue(this.value);
253
+ this.sourceMode = false;
254
+ this._sourceModeValue = undefined;
255
+ this._refreshToolbarState();
256
+ }
257
+
258
+ public handleSourceInput(value: string) {
259
+ if (this.readonly || !this.sourceMode) return;
260
+ this._setValue?.(value);
261
+ }
262
+
263
+ public handleSourceBlur() {
264
+ this._handleBlur?.();
265
+ }
266
+
267
+ private _syncEditorValue(value: string) {
268
+ if (!this.editor) return;
269
+ this._syncing = true;
270
+ try {
271
+ this.editor.commands.setContent(value, { contentType: 'markdown', emitUpdate: false });
272
+ } finally {
273
+ this._syncing = false;
274
+ }
275
+ }
276
+
277
+ private _runCommand(command: (editor: Editor) => boolean) {
278
+ if (this.readonly || this.sourceMode || !this.editor) return;
237
279
  command(this.editor);
238
280
  }
239
281
 
@@ -242,6 +284,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
242
284
  return !!(
243
285
  editor &&
244
286
  !this.readonly &&
287
+ !this.sourceMode &&
245
288
  editor.isEditable &&
246
289
  editor.isActive('table') &&
247
290
  (editor.view.hasFocus() || this._tableToolbarFocused)
@@ -276,6 +319,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
276
319
  return !!(
277
320
  editor &&
278
321
  !this.readonly &&
322
+ !this.sourceMode &&
279
323
  editor.isEditable &&
280
324
  (editor.isActive('codeBlock') || (this.toolbarState.codeBlock && toolbarInteraction)) &&
281
325
  (editor.view.hasFocus() || toolbarInteraction)
@@ -535,7 +579,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
535
579
 
536
580
  public async editLink() {
537
581
  const editor = this.editor;
538
- if (this.readonly || !editor) return;
582
+ if (this.readonly || this.sourceMode || !editor) return;
539
583
  const selection = editor.state.selection;
540
584
  const linkActive = editor.isActive('link');
541
585
  const canEditLink = linkActive
@@ -550,7 +594,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
550
594
  text: this.scope.locale.LinkUrl(),
551
595
  defaultValue: href,
552
596
  });
553
- if (this.readonly || this.editor !== editor || result === undefined) return;
597
+ if (this.readonly || this.sourceMode || this.editor !== editor || result === undefined) return;
554
598
 
555
599
  const value = result.trim();
556
600
  const chain = editor.chain().focus().setTextSelection({
@@ -644,7 +688,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
644
688
 
645
689
  public beginImageUpload(chooseFiles: () => void) {
646
690
  const editor = this.editor;
647
- if (this.readonly || !editor || !editor.isEditable) return;
691
+ if (this.readonly || this.sourceMode || !editor || !editor.isEditable) return;
648
692
  this._imageUploadSelection = {
649
693
  from: editor.state.selection.from,
650
694
  to: editor.state.selection.to,
@@ -654,6 +698,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
654
698
  }
655
699
 
656
700
  public handleImageUploaded(result: IImageUploaderResult) {
701
+ if (this.sourceMode || !this._imageUploadSelection) return;
657
702
  const editor = this.editor;
658
703
  const selection = this._imageUploadSelection;
659
704
  const src = this._resolveImageUrl(result.url);
@@ -743,7 +788,7 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
743
788
  },
744
789
  },
745
790
  onUpdate: ({ editor }) => {
746
- if (!this._syncing) {
791
+ if (!this._syncing && !this.sourceMode) {
747
792
  this._setValue?.(editor.getMarkdown());
748
793
  }
749
794
  },
@@ -765,16 +810,15 @@ export class ControllerFormFieldMarkdown extends BeanControllerBase {
765
810
  this.$watch(
766
811
  () => this.value,
767
812
  value => {
768
- if (!this.editor || this.editor.getMarkdown() === value) return;
769
- this._syncing = true;
770
- this.editor.commands.setContent(value, { contentType: 'markdown' });
771
- this._syncing = false;
813
+ if (this.sourceMode || !this.editor || this.editor.getMarkdown() === value) return;
814
+ this._syncEditorValue(value);
772
815
  },
773
816
  );
774
817
  this.$watch(
775
818
  () => this.readonly,
776
819
  readonly => {
777
- this.editor?.setEditable(!readonly);
820
+ if (readonly && this.sourceMode) this._leaveSourceMode();
821
+ this.editor?.setEditable(!readonly, false);
778
822
  if (readonly) this.closeTablePicker();
779
823
  this._refreshToolbarState();
780
824
  },
@@ -189,123 +189,133 @@ export class RenderFormFieldMarkdown extends BeanRenderBase {
189
189
  role="toolbar"
190
190
  aria-label={this.scope.locale.MarkdownToolbar()}
191
191
  >
192
- {this._toolbarButton(':editor:undo', this.scope.locale.Undo(), () => this.undo(), {
193
- disabled: !state.canUndo,
194
- })}
195
- {this._toolbarButton(':editor:redo', this.scope.locale.Redo(), () => this.redo(), {
196
- disabled: !state.canRedo,
197
- })}
198
- <select
199
- class="select select-sm select-bordered w-auto min-w-28"
200
- aria-label={this.scope.locale.BlockStyle()}
201
- value={state.headingLevel ? `heading-${state.headingLevel}` : 'paragraph'}
202
- onMousedown={event => {
203
- event.stopPropagation();
204
- }}
205
- onChange={event => {
206
- event.stopPropagation();
207
- const value = (event.target as HTMLSelectElement).value;
208
- if (value === 'paragraph') {
209
- this.setParagraph();
210
- } else if (value.startsWith('heading-')) {
211
- this.setHeading(Number(value.slice('heading-'.length)) as 1 | 2 | 3 | 4 | 5 | 6);
212
- }
213
- }}
214
- >
215
- <option value="paragraph">{this.scope.locale.Paragraph()}</option>
216
- <option value="heading-1">{this.scope.locale.Heading1()}</option>
217
- <option value="heading-2">{this.scope.locale.Heading2()}</option>
218
- <option value="heading-3">{this.scope.locale.Heading3()}</option>
219
- <option value="heading-4">{this.scope.locale.Heading4()}</option>
220
- <option value="heading-5">{this.scope.locale.Heading5()}</option>
221
- <option value="heading-6">{this.scope.locale.Heading6()}</option>
222
- </select>
223
- {this._toolbarButton(
224
- ':editor:format-bold',
225
- this.scope.locale.Bold(),
226
- () => this.toggleBold(),
227
- { active: state.bold, disabled: !state.canBold },
228
- )}
229
- {this._toolbarButton(
230
- ':editor:format-italic',
231
- this.scope.locale.Italic(),
232
- () => this.toggleItalic(),
233
- { active: state.italic, disabled: !state.canItalic },
234
- )}
235
- {this._toolbarButton(
236
- ':editor:format-strikethrough',
237
- this.scope.locale.Strikethrough(),
238
- () => this.toggleStrike(),
239
- { active: state.strike, disabled: !state.canStrike },
240
- )}
241
- {this._toolbarButton(
242
- ':editor:code',
243
- this.scope.locale.InlineCode(),
244
- () => this.toggleCode(),
245
- { active: state.code, disabled: !state.canCode },
246
- )}
247
192
  {this._toolbarButton(
248
- ':editor:insert-link-outline',
249
- this.scope.locale.Link(),
250
- () => {
251
- void this.editLink();
252
- },
253
- { active: state.link, disabled: !state.canLink },
193
+ ':editor:source-outline',
194
+ this.scope.locale.SourceMode(),
195
+ () => this.toggleSourceMode(),
196
+ { active: this.sourceMode, disabled: !this.editor || imageUploader?.isUploading },
254
197
  )}
255
- {this._toolbarButton(
256
- ':editor:bookmark',
257
- this.scope.locale.Highlight(),
258
- () => this.toggleHighlight(),
259
- { active: state.highlight, disabled: !state.canHighlight },
260
- )}
261
- {this._toolbarButton(
262
- ':editor:format-list-bulleted',
263
- this.scope.locale.BulletList(),
264
- () => this.toggleBulletList(),
265
- { active: state.bulletList, disabled: !state.canBulletList },
266
- )}
267
- {this._toolbarButton(
268
- ':editor:format-list-numbered',
269
- this.scope.locale.OrderedList(),
270
- () => this.toggleOrderedList(),
271
- { active: state.orderedList, disabled: !state.canOrderedList },
272
- )}
273
- {this._toolbarButton(
274
- ':editor:task-alt',
275
- this.scope.locale.TaskList(),
276
- () => this.toggleTaskList(),
277
- { active: state.taskList, disabled: !state.canTaskList },
278
- )}
279
- {this._toolbarButton(
280
- ':editor:format-quote',
281
- this.scope.locale.Blockquote(),
282
- () => this.toggleBlockquote(),
283
- { active: state.blockquote, disabled: !state.canBlockquote },
284
- )}
285
- {this._toolbarButton(
286
- ':editor:code-block',
287
- this.scope.locale.CodeBlock(),
288
- () => this.toggleCodeBlock(),
289
- { active: state.codeBlock, disabled: !state.canCodeBlock },
290
- )}
291
- {this._toolbarButton(
292
- ':editor:horizontal-rule',
293
- this.scope.locale.HorizontalRule(),
294
- () => this.setHorizontalRule(),
295
- { disabled: !state.canHorizontalRule },
198
+ {!this.sourceMode && (
199
+ <>
200
+ {this._toolbarButton(':editor:undo', this.scope.locale.Undo(), () => this.undo(), {
201
+ disabled: !state.canUndo,
202
+ })}
203
+ {this._toolbarButton(':editor:redo', this.scope.locale.Redo(), () => this.redo(), {
204
+ disabled: !state.canRedo,
205
+ })}
206
+ <select
207
+ class="select select-sm select-bordered w-auto min-w-28"
208
+ aria-label={this.scope.locale.BlockStyle()}
209
+ value={state.headingLevel ? `heading-${state.headingLevel}` : 'paragraph'}
210
+ onMousedown={event => {
211
+ event.stopPropagation();
212
+ }}
213
+ onChange={event => {
214
+ event.stopPropagation();
215
+ const value = (event.target as HTMLSelectElement).value;
216
+ if (value === 'paragraph') {
217
+ this.setParagraph();
218
+ } else if (value.startsWith('heading-')) {
219
+ this.setHeading(Number(value.slice('heading-'.length)) as 1 | 2 | 3 | 4 | 5 | 6);
220
+ }
221
+ }}
222
+ >
223
+ <option value="paragraph">{this.scope.locale.Paragraph()}</option>
224
+ <option value="heading-1">{this.scope.locale.Heading1()}</option>
225
+ <option value="heading-2">{this.scope.locale.Heading2()}</option>
226
+ <option value="heading-3">{this.scope.locale.Heading3()}</option>
227
+ <option value="heading-4">{this.scope.locale.Heading4()}</option>
228
+ <option value="heading-5">{this.scope.locale.Heading5()}</option>
229
+ <option value="heading-6">{this.scope.locale.Heading6()}</option>
230
+ </select>
231
+ {this._toolbarButton(
232
+ ':editor:format-bold',
233
+ this.scope.locale.Bold(),
234
+ () => this.toggleBold(),
235
+ { active: state.bold, disabled: !state.canBold },
236
+ )}
237
+ {this._toolbarButton(
238
+ ':editor:format-italic',
239
+ this.scope.locale.Italic(),
240
+ () => this.toggleItalic(),
241
+ { active: state.italic, disabled: !state.canItalic },
242
+ )}
243
+ {this._toolbarButton(
244
+ ':editor:format-strikethrough',
245
+ this.scope.locale.Strikethrough(),
246
+ () => this.toggleStrike(),
247
+ { active: state.strike, disabled: !state.canStrike },
248
+ )}
249
+ {this._toolbarButton(
250
+ ':editor:code',
251
+ this.scope.locale.InlineCode(),
252
+ () => this.toggleCode(),
253
+ { active: state.code, disabled: !state.canCode },
254
+ )}
255
+ {this._toolbarButton(
256
+ ':editor:insert-link-outline',
257
+ this.scope.locale.Link(),
258
+ () => {
259
+ void this.editLink();
260
+ },
261
+ { active: state.link, disabled: !state.canLink },
262
+ )}
263
+ {this._toolbarButton(
264
+ ':editor:bookmark',
265
+ this.scope.locale.Highlight(),
266
+ () => this.toggleHighlight(),
267
+ { active: state.highlight, disabled: !state.canHighlight },
268
+ )}
269
+ {this._toolbarButton(
270
+ ':editor:format-list-bulleted',
271
+ this.scope.locale.BulletList(),
272
+ () => this.toggleBulletList(),
273
+ { active: state.bulletList, disabled: !state.canBulletList },
274
+ )}
275
+ {this._toolbarButton(
276
+ ':editor:format-list-numbered',
277
+ this.scope.locale.OrderedList(),
278
+ () => this.toggleOrderedList(),
279
+ { active: state.orderedList, disabled: !state.canOrderedList },
280
+ )}
281
+ {this._toolbarButton(
282
+ ':editor:task-alt',
283
+ this.scope.locale.TaskList(),
284
+ () => this.toggleTaskList(),
285
+ { active: state.taskList, disabled: !state.canTaskList },
286
+ )}
287
+ {this._toolbarButton(
288
+ ':editor:format-quote',
289
+ this.scope.locale.Blockquote(),
290
+ () => this.toggleBlockquote(),
291
+ { active: state.blockquote, disabled: !state.canBlockquote },
292
+ )}
293
+ {this._toolbarButton(
294
+ ':editor:code-block',
295
+ this.scope.locale.CodeBlock(),
296
+ () => this.toggleCodeBlock(),
297
+ { active: state.codeBlock, disabled: !state.canCodeBlock },
298
+ )}
299
+ {this._toolbarButton(
300
+ ':editor:horizontal-rule',
301
+ this.scope.locale.HorizontalRule(),
302
+ () => this.setHorizontalRule(),
303
+ { disabled: !state.canHorizontalRule },
304
+ )}
305
+ {imageUploader &&
306
+ this._toolbarButton(
307
+ ':editor:image-outline',
308
+ this.scope.locale.InsertImage(),
309
+ () => {
310
+ this.beginImageUpload(imageUploader.chooseFiles);
311
+ },
312
+ {
313
+ disabled: imageUploader.isUploading || imageUploader.policy.pending,
314
+ },
315
+ )}
316
+ {this._renderTablePickerTrigger()}
317
+ </>
296
318
  )}
297
- {imageUploader &&
298
- this._toolbarButton(
299
- ':editor:image-outline',
300
- this.scope.locale.InsertImage(),
301
- () => {
302
- this.beginImageUpload(imageUploader.chooseFiles);
303
- },
304
- {
305
- disabled: imageUploader.isUploading || imageUploader.policy.pending,
306
- },
307
- )}
308
- {this._renderTablePickerTrigger()}
309
319
  </div>
310
320
  );
311
321
  }
@@ -454,7 +464,7 @@ export class RenderFormFieldMarkdown extends BeanRenderBase {
454
464
  !$$formField.field.state.meta.isValid && 'border-error',
455
465
  ]}
456
466
  onClick={() => {
457
- if (!this.readonly) {
467
+ if (!this.readonly && !this.sourceMode) {
458
468
  this.editor?.commands.focus();
459
469
  }
460
470
  }}
@@ -476,14 +486,37 @@ export class RenderFormFieldMarkdown extends BeanRenderBase {
476
486
  slotDefault={state => this._renderToolbar(state)}
477
487
  ></ZImageUploader>
478
488
  )}
479
- {!this.readonly && this.editor && this._renderTableToolbar()}
480
- {!this.readonly && this.editor && this._renderCodeBlockToolbar()}
489
+ {!this.readonly &&
490
+ !this.sourceMode &&
491
+ this.editor &&
492
+ this._renderTableToolbar()}
493
+ {!this.readonly &&
494
+ !this.sourceMode &&
495
+ this.editor &&
496
+ this._renderCodeBlockToolbar()}
481
497
  {this.imageUploadError && (
482
498
  <p class="px-4 pt-3 text-sm text-error" role="alert" aria-live="polite">
483
499
  {this.imageUploadError}
484
500
  </p>
485
501
  )}
486
- <EditorContent editor={this.editor} class={this.cMarkdown} />
502
+ <EditorContent
503
+ editor={this.editor}
504
+ class={this.cMarkdown}
505
+ style={{ display: this.sourceMode ? 'none' : undefined }}
506
+ />
507
+ {this.sourceMode && (
508
+ <textarea
509
+ class="block min-h-96 w-full resize-y rounded-b-box bg-base-100 p-4 font-mono text-sm text-base-content outline-none"
510
+ aria-label={this.scope.locale.MarkdownSource()}
511
+ spellcheck={false}
512
+ readonly={this.readonly}
513
+ value={this.value}
514
+ onInput={event => {
515
+ this.handleSourceInput((event.target as HTMLTextAreaElement).value);
516
+ }}
517
+ onBlur={() => this.handleSourceBlur()}
518
+ ></textarea>
519
+ )}
487
520
  </>
488
521
  ),
489
522
  placeholder: () => <div class="min-h-96 p-4" aria-hidden="true"></div>,