cabloy 5.1.244 → 5.1.246
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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +17 -0
- package/package.json +1 -1
- package/repo-e2e/specs/home-user-account.spec.ts +2 -2
- package/repo-e2e/specs/markdown.spec.ts +38 -2
- package/vona/env/.env +1 -0
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/types/utils/env.ts +1 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +1 -1
- package/vona/src/backend/config/config/config.ts +7 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/src/front/config/config/config.ts +1 -1
- package/zova/src/suite/a-home/modules/home-layoutempty/src/component/layoutEmpty/controller.tsx +53 -3
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/controller.tsx +54 -10
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/component/formFieldMarkdown/render.tsx +151 -118
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/en-us.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-markdown/src/config/locale/zh-cn.ts +2 -0
package/.cabloy-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.1.
|
|
1
|
+
5.1.246
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.1.246
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- Add `APP_LOCALE_DEFAULT` to configure the default application locale.
|
|
8
|
+
|
|
9
|
+
### Improvements
|
|
10
|
+
|
|
11
|
+
- Enhance Markdown source mode.
|
|
12
|
+
- Update dependencies in the pnpm lockfile.
|
|
13
|
+
|
|
14
|
+
## 5.1.245
|
|
15
|
+
|
|
16
|
+
### Improvements
|
|
17
|
+
|
|
18
|
+
- Update the home user account tests.
|
|
19
|
+
|
|
3
20
|
## 5.1.244
|
|
4
21
|
|
|
5
22
|
### Features
|
package/package.json
CHANGED
|
@@ -492,8 +492,8 @@ test(
|
|
|
492
492
|
await expect(userMenu).toHaveCount(1);
|
|
493
493
|
await expect(userMenu.locator('summary')).toContainText(profileName);
|
|
494
494
|
await userMenu.locator('summary').click();
|
|
495
|
-
const
|
|
496
|
-
await expect(
|
|
495
|
+
const actions = userMenu.locator('ul > li > a, ul > li > button');
|
|
496
|
+
await expect(actions).toHaveText(['Account Settings', 'Logout']);
|
|
497
497
|
await userMenu.getByText('Account Settings', { exact: true }).click();
|
|
498
498
|
await expect(page).toHaveURL(accountPath);
|
|
499
499
|
await expect(page.getByRole('heading', { name: 'Account Settings' })).toBeVisible();
|
|
@@ -329,6 +329,22 @@ const product = '${suffix}';
|
|
|
329
329
|
await expect(editor).toBeVisible();
|
|
330
330
|
const toolbar = adminPage.getByRole('toolbar', { name: 'Markdown toolbar' });
|
|
331
331
|
await expect(toolbar).toBeVisible();
|
|
332
|
+
const sourceButton = toolbar.getByRole('button', { name: 'Source editing mode' });
|
|
333
|
+
const sourceInput = adminPage.getByRole('textbox', { name: 'Markdown source' });
|
|
334
|
+
await expect(sourceButton).toHaveAttribute('type', 'button');
|
|
335
|
+
await expect(sourceButton).toHaveAttribute('aria-pressed', 'false');
|
|
336
|
+
await sourceButton.click();
|
|
337
|
+
await expect(sourceButton).toHaveAttribute('aria-pressed', 'true');
|
|
338
|
+
await expect(sourceInput).toHaveValue(markdown);
|
|
339
|
+
await expect(editor).toBeHidden();
|
|
340
|
+
await expect(toolbar.getByRole('button', { name: 'Bold', exact: true })).toHaveCount(0);
|
|
341
|
+
await sourceInput.click();
|
|
342
|
+
await expect(sourceInput).toBeFocused();
|
|
343
|
+
await sourceButton.click();
|
|
344
|
+
await expect(editor).toBeVisible();
|
|
345
|
+
await sourceButton.click();
|
|
346
|
+
await expect(sourceInput).toHaveValue(markdown);
|
|
347
|
+
await sourceButton.click();
|
|
332
348
|
const undoButton = toolbar.getByRole('button', { name: 'Undo' });
|
|
333
349
|
await expect(toolbar.getByRole('combobox', { name: 'Code block language' })).toHaveCount(0);
|
|
334
350
|
const codeBlockToolbar = adminPage.getByRole('toolbar', { name: 'Code block language' });
|
|
@@ -456,20 +472,40 @@ const product = '${suffix}';
|
|
|
456
472
|
await expect(tableToolbar).toBeHidden();
|
|
457
473
|
expect(adminPageErrors).toEqual([]);
|
|
458
474
|
|
|
475
|
+
await sourceButton.click();
|
|
476
|
+
expect((await sourceInput.inputValue()).trimEnd()).toBe(
|
|
477
|
+
markdown.replace('```ts', '```python'),
|
|
478
|
+
);
|
|
479
|
+
await expect(tableToolbar).toBeHidden();
|
|
480
|
+
await expect(codeBlockToolbar).toBeHidden();
|
|
481
|
+
const sourceMarkdown = `${markdown.replace('```ts', '```python')}\n\n## Source heading\n\nSource **content**`;
|
|
482
|
+
await sourceInput.fill(sourceMarkdown);
|
|
483
|
+
await sourceButton.click();
|
|
484
|
+
await expect(editor.locator('h2')).toHaveText('Source heading');
|
|
485
|
+
await expect(editor.locator('strong')).toHaveText('content');
|
|
486
|
+
await sourceButton.click();
|
|
487
|
+
await expect(sourceInput).toHaveValue(sourceMarkdown);
|
|
488
|
+
const savedMarkdown = `${sourceMarkdown}\n\n- Saved from source\n`;
|
|
489
|
+
await sourceInput.fill(savedMarkdown);
|
|
490
|
+
|
|
459
491
|
const productUpdate = waitForApiResponse(
|
|
460
492
|
adminPage,
|
|
461
493
|
'PATCH',
|
|
462
494
|
`${productActionPath}/${productId}`,
|
|
463
495
|
);
|
|
464
496
|
await adminPage.getByRole('button', { name: 'Submit', exact: true }).click();
|
|
465
|
-
await productUpdate;
|
|
497
|
+
const updateResponse = await productUpdate;
|
|
498
|
+
expect(updateResponse.request().postDataJSON().productContentForm.descriptionMarkdown).toBe(
|
|
499
|
+
savedMarkdown,
|
|
500
|
+
);
|
|
466
501
|
const productResponseAfterUpdate = await adminPage.request.get(
|
|
467
502
|
`${productActionPath}/${productId}`,
|
|
468
503
|
{ headers },
|
|
469
504
|
);
|
|
470
505
|
expect(productResponseAfterUpdate.ok()).toBeTruthy();
|
|
506
|
+
// Product content persistence trims boundary whitespace on the server.
|
|
471
507
|
expect((await productResponseAfterUpdate.json()).productContentForm.descriptionMarkdown).toBe(
|
|
472
|
-
|
|
508
|
+
savedMarkdown.trim(),
|
|
473
509
|
);
|
|
474
510
|
expect(adminPageErrors).toEqual([]);
|
|
475
511
|
} finally {
|
package/vona/env/.env
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.124",
|
|
4
4
|
"gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
|
|
5
5
|
"description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
|
|
6
6
|
"keywords": [
|
|
@@ -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;
|
package/vona/pnpm-lock.yaml
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
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
|
|
@@ -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',
|
package/zova/src/suite/a-home/modules/home-layoutempty/src/component/layoutEmpty/controller.tsx
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
}
|
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
249
|
-
this.scope.locale.
|
|
250
|
-
() =>
|
|
251
|
-
|
|
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.
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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 &&
|
|
480
|
-
|
|
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
|
|
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>,
|
|
@@ -22,6 +22,8 @@ export default {
|
|
|
22
22
|
Link: 'Link',
|
|
23
23
|
LinkUrl: 'Link URL',
|
|
24
24
|
MarkdownToolbar: 'Markdown toolbar',
|
|
25
|
+
MarkdownSource: 'Markdown source',
|
|
26
|
+
SourceMode: 'Source editing mode',
|
|
25
27
|
OrderedList: 'Ordered list',
|
|
26
28
|
Paragraph: 'Paragraph',
|
|
27
29
|
PlainText: 'Plain text',
|