@ssml-builder-js/ssml-editor-elements 2.8.1 → 2.9.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.
- package/CHANGELOG.md +11 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +369 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +370 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/SsmlEditorElement.ts +322 -3
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @ssml-builder-js/ssml-editor-elements
|
|
2
2
|
|
|
3
|
+
## 2.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Release v2.9.0 with Azure SSML validation and migration utilities, voice catalog support, and visual/editor improvements.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
- @ssml-builder-js/ssml-core@2.9.0
|
|
13
|
+
|
|
3
14
|
## 2.8.1
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/dist/index.d.mts
CHANGED
|
@@ -15,6 +15,7 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
15
15
|
private toolbarActions;
|
|
16
16
|
private display;
|
|
17
17
|
private editorContainer;
|
|
18
|
+
private visualContainer;
|
|
18
19
|
private helpPanel;
|
|
19
20
|
private openMenu;
|
|
20
21
|
private openMenuTrigger;
|
|
@@ -29,6 +30,8 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
29
30
|
private documentState;
|
|
30
31
|
private decorationsVisible;
|
|
31
32
|
private helpOpen;
|
|
33
|
+
private visualSelectedPath;
|
|
34
|
+
private visualSelection;
|
|
32
35
|
get value(): string;
|
|
33
36
|
set value(value: string);
|
|
34
37
|
get theme(): SsmlEditorTheme;
|
|
@@ -37,12 +40,15 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
37
40
|
set readonly(readonly: boolean);
|
|
38
41
|
get locale(): SsmlEditorLocale;
|
|
39
42
|
set locale(locale: SsmlEditorLocale);
|
|
43
|
+
get editMode(): "code" | "visual";
|
|
44
|
+
set editMode(mode: "code" | "visual");
|
|
40
45
|
private prepareDocument;
|
|
41
46
|
connectedCallback(): void;
|
|
42
47
|
disconnectedCallback(): void;
|
|
43
48
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
44
49
|
private render;
|
|
45
50
|
private renderToolbar;
|
|
51
|
+
private createModeButton;
|
|
46
52
|
private createActionButton;
|
|
47
53
|
private createDecorationsSwitch;
|
|
48
54
|
private createInsertionButton;
|
|
@@ -59,6 +65,8 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
59
65
|
private updateDecorations;
|
|
60
66
|
private renderHelp;
|
|
61
67
|
private updateTheme;
|
|
68
|
+
private updateEditMode;
|
|
69
|
+
private renderVisualEditor;
|
|
62
70
|
private updateActiveButtons;
|
|
63
71
|
private initialize;
|
|
64
72
|
private readonly handleDocumentPointerDown;
|
|
@@ -67,6 +75,7 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
67
75
|
private disposeEditor;
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
type SsmlEditorEditMode = "code" | "visual";
|
|
70
79
|
declare function defineSsmlEditorElement(): void;
|
|
71
80
|
|
|
72
|
-
export { type SsmlEditorChangeDetail, SsmlEditorElement, type SsmlEditorLocale, type SsmlEditorTheme, defineSsmlEditorElement };
|
|
81
|
+
export { type SsmlEditorChangeDetail, type SsmlEditorEditMode, SsmlEditorElement, type SsmlEditorLocale, type SsmlEditorTheme, defineSsmlEditorElement };
|
package/dist/index.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
15
15
|
private toolbarActions;
|
|
16
16
|
private display;
|
|
17
17
|
private editorContainer;
|
|
18
|
+
private visualContainer;
|
|
18
19
|
private helpPanel;
|
|
19
20
|
private openMenu;
|
|
20
21
|
private openMenuTrigger;
|
|
@@ -29,6 +30,8 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
29
30
|
private documentState;
|
|
30
31
|
private decorationsVisible;
|
|
31
32
|
private helpOpen;
|
|
33
|
+
private visualSelectedPath;
|
|
34
|
+
private visualSelection;
|
|
32
35
|
get value(): string;
|
|
33
36
|
set value(value: string);
|
|
34
37
|
get theme(): SsmlEditorTheme;
|
|
@@ -37,12 +40,15 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
37
40
|
set readonly(readonly: boolean);
|
|
38
41
|
get locale(): SsmlEditorLocale;
|
|
39
42
|
set locale(locale: SsmlEditorLocale);
|
|
43
|
+
get editMode(): "code" | "visual";
|
|
44
|
+
set editMode(mode: "code" | "visual");
|
|
40
45
|
private prepareDocument;
|
|
41
46
|
connectedCallback(): void;
|
|
42
47
|
disconnectedCallback(): void;
|
|
43
48
|
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null): void;
|
|
44
49
|
private render;
|
|
45
50
|
private renderToolbar;
|
|
51
|
+
private createModeButton;
|
|
46
52
|
private createActionButton;
|
|
47
53
|
private createDecorationsSwitch;
|
|
48
54
|
private createInsertionButton;
|
|
@@ -59,6 +65,8 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
59
65
|
private updateDecorations;
|
|
60
66
|
private renderHelp;
|
|
61
67
|
private updateTheme;
|
|
68
|
+
private updateEditMode;
|
|
69
|
+
private renderVisualEditor;
|
|
62
70
|
private updateActiveButtons;
|
|
63
71
|
private initialize;
|
|
64
72
|
private readonly handleDocumentPointerDown;
|
|
@@ -67,6 +75,7 @@ declare class SsmlEditorElement extends HTMLElementBase {
|
|
|
67
75
|
private disposeEditor;
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
type SsmlEditorEditMode = "code" | "visual";
|
|
70
79
|
declare function defineSsmlEditorElement(): void;
|
|
71
80
|
|
|
72
|
-
export { type SsmlEditorChangeDetail, SsmlEditorElement, type SsmlEditorLocale, type SsmlEditorTheme, defineSsmlEditorElement };
|
|
81
|
+
export { type SsmlEditorChangeDetail, type SsmlEditorEditMode, SsmlEditorElement, type SsmlEditorLocale, type SsmlEditorTheme, defineSsmlEditorElement };
|
package/dist/index.js
CHANGED
|
@@ -1178,6 +1178,11 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
1178
1178
|
"mstts:audioduration": {
|
|
1179
1179
|
value: AUDIO_DURATION_PRESETS
|
|
1180
1180
|
},
|
|
1181
|
+
"mstts:backgroundaudio": {
|
|
1182
|
+
volume: PROSODY_VOLUME_PRESETS,
|
|
1183
|
+
fadein: AUDIO_DURATION_PRESETS,
|
|
1184
|
+
fadeout: AUDIO_DURATION_PRESETS
|
|
1185
|
+
},
|
|
1181
1186
|
silence: {
|
|
1182
1187
|
type: SILENCE_TYPE_PRESETS,
|
|
1183
1188
|
value: SILENCE_VALUE_PRESETS
|
|
@@ -1669,6 +1674,24 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
1669
1674
|
label: "mstts:audioduration",
|
|
1670
1675
|
insertText: '<mstts:audioduration value="10s" />'
|
|
1671
1676
|
},
|
|
1677
|
+
{
|
|
1678
|
+
label: "mstts:dialog",
|
|
1679
|
+
insertText: `<mstts:dialog>
|
|
1680
|
+
<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
|
|
1681
|
+
</mstts:dialog>`
|
|
1682
|
+
},
|
|
1683
|
+
{
|
|
1684
|
+
label: "mstts:turn",
|
|
1685
|
+
insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
label: "mstts:backgroundaudio",
|
|
1689
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
label: "mstts:ttsembedding",
|
|
1693
|
+
insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
|
|
1694
|
+
},
|
|
1672
1695
|
{
|
|
1673
1696
|
label: "sub",
|
|
1674
1697
|
insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
|
|
@@ -2414,6 +2437,63 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
2414
2437
|
example: "10s"
|
|
2415
2438
|
}
|
|
2416
2439
|
]
|
|
2440
|
+
},
|
|
2441
|
+
{
|
|
2442
|
+
name: "mstts:dialog",
|
|
2443
|
+
description: "Groups multiple Azure dialog turns that can use different voices.",
|
|
2444
|
+
parameters: []
|
|
2445
|
+
},
|
|
2446
|
+
{
|
|
2447
|
+
name: "mstts:turn",
|
|
2448
|
+
description: "Adds one dialog turn using the required Azure voice name.",
|
|
2449
|
+
parameters: [
|
|
2450
|
+
{
|
|
2451
|
+
name: "voice",
|
|
2452
|
+
description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
|
|
2453
|
+
example: "en-US-JennyNeural"
|
|
2454
|
+
}
|
|
2455
|
+
]
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
name: "mstts:backgroundaudio",
|
|
2459
|
+
description: "Plays background audio while speech is synthesized.",
|
|
2460
|
+
parameters: [
|
|
2461
|
+
{
|
|
2462
|
+
name: "src",
|
|
2463
|
+
description: "An absolute HTTP(S) URL for the background audio file.",
|
|
2464
|
+
example: "https://example.com/music.mp3"
|
|
2465
|
+
},
|
|
2466
|
+
{
|
|
2467
|
+
name: "volume",
|
|
2468
|
+
description: "The background audio volume, for example `-3dB` or `medium`.",
|
|
2469
|
+
example: "-3dB"
|
|
2470
|
+
},
|
|
2471
|
+
{
|
|
2472
|
+
name: "fadein",
|
|
2473
|
+
description: "The fade-in duration, for example `1s`.",
|
|
2474
|
+
example: "1s"
|
|
2475
|
+
},
|
|
2476
|
+
{
|
|
2477
|
+
name: "fadeout",
|
|
2478
|
+
description: "The fade-out duration, for example `500ms`.",
|
|
2479
|
+
example: "500ms"
|
|
2480
|
+
}
|
|
2481
|
+
]
|
|
2482
|
+
},
|
|
2483
|
+
{
|
|
2484
|
+
name: "mstts:ttsembedding",
|
|
2485
|
+
description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
|
|
2486
|
+
parameters: []
|
|
2487
|
+
},
|
|
2488
|
+
{
|
|
2489
|
+
name: "mstts:embedding",
|
|
2490
|
+
description: "Specifies embedding metadata for custom voice scenarios.",
|
|
2491
|
+
parameters: []
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
name: "mstts:voiceconversion",
|
|
2495
|
+
description: "Specifies voice conversion metadata for custom voice scenarios.",
|
|
2496
|
+
parameters: []
|
|
2417
2497
|
}
|
|
2418
2498
|
];
|
|
2419
2499
|
var definitionsByName = /* @__PURE__ */ new Map();
|
|
@@ -3219,6 +3299,60 @@ section[data-ssml-editor] {
|
|
|
3219
3299
|
border-radius: 0.25rem;
|
|
3220
3300
|
overflow: visible;
|
|
3221
3301
|
}
|
|
3302
|
+
[data-ssml-editor] .ssml-editor-visual {
|
|
3303
|
+
display: grid;
|
|
3304
|
+
gap: 0.75rem;
|
|
3305
|
+
min-height: 8rem;
|
|
3306
|
+
padding: 0.75rem;
|
|
3307
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3308
|
+
border-radius: 0.25rem;
|
|
3309
|
+
}
|
|
3310
|
+
[data-ssml-editor] .ssml-editor-visual-layout {
|
|
3311
|
+
display: grid;
|
|
3312
|
+
grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
|
|
3313
|
+
gap: 1rem;
|
|
3314
|
+
}
|
|
3315
|
+
[data-ssml-editor] .ssml-editor-visual-tree ul {
|
|
3316
|
+
margin: 0;
|
|
3317
|
+
padding-left: 1.25rem;
|
|
3318
|
+
}
|
|
3319
|
+
[data-ssml-editor] .ssml-editor-visual button {
|
|
3320
|
+
padding: 0.35rem 0.5rem;
|
|
3321
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3322
|
+
border-radius: 0.25rem;
|
|
3323
|
+
color: var(--ssml-editor-color);
|
|
3324
|
+
background: var(--ssml-editor-control-bg);
|
|
3325
|
+
cursor: pointer;
|
|
3326
|
+
}
|
|
3327
|
+
[data-ssml-editor] .ssml-editor-visual button[data-selected="true"] {
|
|
3328
|
+
border-color: var(--ssml-editor-active-border);
|
|
3329
|
+
background: var(--ssml-editor-active-bg);
|
|
3330
|
+
}
|
|
3331
|
+
[data-ssml-editor] .ssml-editor-visual textarea {
|
|
3332
|
+
box-sizing: border-box;
|
|
3333
|
+
width: 100%;
|
|
3334
|
+
min-height: 6rem;
|
|
3335
|
+
padding: 0.5rem;
|
|
3336
|
+
color: var(--ssml-editor-color);
|
|
3337
|
+
background: var(--ssml-editor-control-bg);
|
|
3338
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3339
|
+
border-radius: 0.25rem;
|
|
3340
|
+
font: inherit;
|
|
3341
|
+
}
|
|
3342
|
+
[data-ssml-editor] .ssml-editor-visual-actions,
|
|
3343
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb {
|
|
3344
|
+
display: flex;
|
|
3345
|
+
flex-wrap: wrap;
|
|
3346
|
+
gap: 0.4rem;
|
|
3347
|
+
align-items: center;
|
|
3348
|
+
}
|
|
3349
|
+
[data-ssml-editor] .ssml-editor-visual-errors {
|
|
3350
|
+
padding: 0.5rem;
|
|
3351
|
+
color: #b91c1c;
|
|
3352
|
+
background: #fef2f2;
|
|
3353
|
+
border: 1px solid #b91c1c;
|
|
3354
|
+
border-radius: 0.25rem;
|
|
3355
|
+
}
|
|
3222
3356
|
`.trim();
|
|
3223
3357
|
function injectStyles() {
|
|
3224
3358
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
|
|
@@ -3232,6 +3366,55 @@ function injectStyles() {
|
|
|
3232
3366
|
function isDarkTheme(theme) {
|
|
3233
3367
|
return theme === "vs-dark" || theme.toLowerCase().includes("dark");
|
|
3234
3368
|
}
|
|
3369
|
+
function isElement(node) {
|
|
3370
|
+
return typeof node !== "string" && node.type !== "text";
|
|
3371
|
+
}
|
|
3372
|
+
function visualElementName(element) {
|
|
3373
|
+
return element.type === "custom" || element.type === "element" ? element.name : element.type;
|
|
3374
|
+
}
|
|
3375
|
+
function collectVisualTextLeaves(nodes, path = [], ancestors = []) {
|
|
3376
|
+
return nodes.flatMap((node, index) => {
|
|
3377
|
+
const currentPath = [...path, index];
|
|
3378
|
+
if (typeof node === "string") return [{ path: currentPath, value: node, ancestors }];
|
|
3379
|
+
if (node.type === "text") return [{ path: currentPath, value: node.value, ancestors }];
|
|
3380
|
+
return collectVisualTextLeaves(node.children ?? [], currentPath, [...ancestors, visualElementName(node)]);
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
function updateVisualNodes(nodes, path, update) {
|
|
3384
|
+
if (path.length === 0) return nodes;
|
|
3385
|
+
const [index, ...rest] = path;
|
|
3386
|
+
return nodes.flatMap((node, nodeIndex) => {
|
|
3387
|
+
if (nodeIndex !== index) return [node];
|
|
3388
|
+
if (rest.length === 0) {
|
|
3389
|
+
const next = update(node);
|
|
3390
|
+
return Array.isArray(next) ? next : [next];
|
|
3391
|
+
}
|
|
3392
|
+
if (!isElement(node)) return [node];
|
|
3393
|
+
return [{ ...node, children: updateVisualNodes(node.children ?? [], rest, update) }];
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
function updateVisualText(document2, path, value) {
|
|
3397
|
+
return { ...document2, children: updateVisualNodes(document2.children ?? [], path, () => value) };
|
|
3398
|
+
}
|
|
3399
|
+
function wrapVisualText(document2, path, start, end, type, attributes) {
|
|
3400
|
+
return {
|
|
3401
|
+
...document2,
|
|
3402
|
+
children: updateVisualNodes(document2.children ?? [], path, (node) => {
|
|
3403
|
+
const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
|
|
3404
|
+
if (!value || start === end) return node;
|
|
3405
|
+
if (type === "break") {
|
|
3406
|
+
return [value.slice(0, start), { type, attributes, children: [] }, value.slice(start)].filter(
|
|
3407
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
3408
|
+
);
|
|
3409
|
+
}
|
|
3410
|
+
const selected = value.slice(start, end);
|
|
3411
|
+
const wrapper = { type, attributes, children: [selected] };
|
|
3412
|
+
return [value.slice(0, start), wrapper, value.slice(end)].filter(
|
|
3413
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
3414
|
+
);
|
|
3415
|
+
})
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3235
3418
|
function getMenuPosition(trigger, menu) {
|
|
3236
3419
|
const bounds = trigger.getBoundingClientRect();
|
|
3237
3420
|
const margin = 8;
|
|
@@ -3253,6 +3436,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3253
3436
|
this.toolbarActions = null;
|
|
3254
3437
|
this.display = null;
|
|
3255
3438
|
this.editorContainer = null;
|
|
3439
|
+
this.visualContainer = null;
|
|
3256
3440
|
this.helpPanel = null;
|
|
3257
3441
|
this.openMenu = null;
|
|
3258
3442
|
this.openMenuTrigger = null;
|
|
@@ -3266,6 +3450,8 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3266
3450
|
this.documentState = null;
|
|
3267
3451
|
this.decorationsVisible = false;
|
|
3268
3452
|
this.helpOpen = false;
|
|
3453
|
+
this.visualSelectedPath = null;
|
|
3454
|
+
this.visualSelection = { start: 0, end: 0 };
|
|
3269
3455
|
this.handleDocumentPointerDown = (event) => {
|
|
3270
3456
|
const target = event.target;
|
|
3271
3457
|
if (this.openMenu && target instanceof Node && !this.openMenu.contains(target) && !this.openMenuTrigger?.contains(target)) {
|
|
@@ -3307,6 +3493,12 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3307
3493
|
set locale(locale) {
|
|
3308
3494
|
this.setAttribute("locale", locale);
|
|
3309
3495
|
}
|
|
3496
|
+
get editMode() {
|
|
3497
|
+
return this.getAttribute("edit-mode") === "visual" ? "visual" : "code";
|
|
3498
|
+
}
|
|
3499
|
+
set editMode(mode) {
|
|
3500
|
+
this.setAttribute("edit-mode", mode);
|
|
3501
|
+
}
|
|
3310
3502
|
prepareDocument(value) {
|
|
3311
3503
|
try {
|
|
3312
3504
|
this.documentState = (0, import_ssml_core2.parseSsml)(value);
|
|
@@ -3349,6 +3541,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3349
3541
|
}
|
|
3350
3542
|
}
|
|
3351
3543
|
}
|
|
3544
|
+
this.renderVisualEditor();
|
|
3352
3545
|
return;
|
|
3353
3546
|
}
|
|
3354
3547
|
if (name === "theme" && this.monaco) {
|
|
@@ -3366,6 +3559,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3366
3559
|
this.renderHelp();
|
|
3367
3560
|
return;
|
|
3368
3561
|
}
|
|
3562
|
+
if (name === "edit-mode") {
|
|
3563
|
+
this.updateEditMode();
|
|
3564
|
+
}
|
|
3369
3565
|
if (name === "show-decorations") {
|
|
3370
3566
|
this.decorationsVisible = newValue !== null;
|
|
3371
3567
|
this.updateDecorations();
|
|
@@ -3390,7 +3586,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3390
3586
|
display.dataset.ssmlEditorDisplay = "";
|
|
3391
3587
|
const editorContainer = document.createElement("div");
|
|
3392
3588
|
editorContainer.className = "ssml-editor-editor";
|
|
3393
|
-
|
|
3589
|
+
const visualContainer = document.createElement("div");
|
|
3590
|
+
visualContainer.className = "ssml-editor-visual";
|
|
3591
|
+
display.append(editorContainer, visualContainer);
|
|
3394
3592
|
root.append(toolbar, display);
|
|
3395
3593
|
this.replaceChildren(root);
|
|
3396
3594
|
this.root = root;
|
|
@@ -3398,8 +3596,11 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3398
3596
|
this.toolbarActions = toolbarActions;
|
|
3399
3597
|
this.display = display;
|
|
3400
3598
|
this.editorContainer = editorContainer;
|
|
3599
|
+
this.visualContainer = visualContainer;
|
|
3401
3600
|
this.renderToolbar();
|
|
3402
3601
|
this.renderHelp();
|
|
3602
|
+
this.updateEditMode();
|
|
3603
|
+
this.renderVisualEditor();
|
|
3403
3604
|
}
|
|
3404
3605
|
renderToolbar() {
|
|
3405
3606
|
const toolbar = this.toolbar;
|
|
@@ -3443,10 +3644,10 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3443
3644
|
for (const id of toolbarIds) {
|
|
3444
3645
|
const group = groupByButtonId.get(id);
|
|
3445
3646
|
if (previousGroup !== void 0 && group !== previousGroup) {
|
|
3446
|
-
const
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
toolbarActions.append(
|
|
3647
|
+
const separator2 = document.createElement("span");
|
|
3648
|
+
separator2.className = "ssml-editor-toolbar-separator";
|
|
3649
|
+
separator2.setAttribute("aria-hidden", "true");
|
|
3650
|
+
toolbarActions.append(separator2);
|
|
3450
3651
|
}
|
|
3451
3652
|
previousGroup = group;
|
|
3452
3653
|
const insertion = insertionById.get(id);
|
|
@@ -3458,8 +3659,24 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3458
3659
|
toolbarActions.append(this.createActionButton(id));
|
|
3459
3660
|
}
|
|
3460
3661
|
}
|
|
3662
|
+
const separator = document.createElement("span");
|
|
3663
|
+
separator.className = "ssml-editor-toolbar-separator";
|
|
3664
|
+
separator.setAttribute("aria-hidden", "true");
|
|
3665
|
+
toolbarActions.append(separator, this.createModeButton("visual", "Visual"), this.createModeButton("code", "Code"));
|
|
3461
3666
|
this.updateActiveButtons();
|
|
3462
3667
|
}
|
|
3668
|
+
createModeButton(mode, label) {
|
|
3669
|
+
const button = document.createElement("button");
|
|
3670
|
+
button.type = "button";
|
|
3671
|
+
button.className = "ssml-editor-toolbar-button";
|
|
3672
|
+
button.dataset.ssmlEditorButton = `edit-mode-${mode}`;
|
|
3673
|
+
button.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
3674
|
+
button.textContent = label;
|
|
3675
|
+
button.addEventListener("click", () => {
|
|
3676
|
+
this.editMode = mode;
|
|
3677
|
+
});
|
|
3678
|
+
return button;
|
|
3679
|
+
}
|
|
3463
3680
|
createActionButton(id) {
|
|
3464
3681
|
const copy = EDITOR_COPY[this.locale];
|
|
3465
3682
|
const labels = {
|
|
@@ -3839,6 +4056,150 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3839
4056
|
this.root.dataset.theme = isDarkTheme(this.theme) ? "dark" : "light";
|
|
3840
4057
|
}
|
|
3841
4058
|
}
|
|
4059
|
+
updateEditMode() {
|
|
4060
|
+
if (this.editorContainer && this.visualContainer) {
|
|
4061
|
+
const visual = this.editMode === "visual";
|
|
4062
|
+
this.editorContainer.hidden = visual;
|
|
4063
|
+
this.visualContainer.hidden = !visual;
|
|
4064
|
+
}
|
|
4065
|
+
for (const mode of ["visual", "code"]) {
|
|
4066
|
+
const button = this.toolbarActions?.querySelector(
|
|
4067
|
+
`[data-ssml-editor-button="edit-mode-${mode}"]`
|
|
4068
|
+
);
|
|
4069
|
+
button?.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
4070
|
+
button?.toggleAttribute("data-active", this.editMode === mode);
|
|
4071
|
+
}
|
|
4072
|
+
this.renderVisualEditor();
|
|
4073
|
+
}
|
|
4074
|
+
renderVisualEditor() {
|
|
4075
|
+
const container = this.visualContainer;
|
|
4076
|
+
if (!container) return;
|
|
4077
|
+
container.replaceChildren();
|
|
4078
|
+
if (!this.documentState) {
|
|
4079
|
+
const error = document.createElement("p");
|
|
4080
|
+
error.className = "ssml-editor-visual-errors";
|
|
4081
|
+
error.textContent = "SSML syntax must be valid before visual editing is available.";
|
|
4082
|
+
container.append(error);
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
const documentState = this.documentState;
|
|
4086
|
+
const leaves = collectVisualTextLeaves(documentState.children ?? []);
|
|
4087
|
+
const selectedLeaf = leaves.find((leaf) => leaf.path.join(".") === this.visualSelectedPath?.join(".")) ?? leaves[0];
|
|
4088
|
+
const breadcrumb = document.createElement("div");
|
|
4089
|
+
breadcrumb.className = "ssml-editor-visual-breadcrumb";
|
|
4090
|
+
breadcrumb.textContent = `<speak>${selectedLeaf ? ` / ${selectedLeaf.ancestors.map((name) => `<${name}>`).join(" / ")}` : ""}`;
|
|
4091
|
+
const clear = document.createElement("button");
|
|
4092
|
+
clear.type = "button";
|
|
4093
|
+
clear.textContent = "Clear parent";
|
|
4094
|
+
clear.disabled = !this.visualSelectedPath;
|
|
4095
|
+
clear.addEventListener("click", () => {
|
|
4096
|
+
this.visualSelectedPath = null;
|
|
4097
|
+
this.renderVisualEditor();
|
|
4098
|
+
});
|
|
4099
|
+
breadcrumb.append(clear);
|
|
4100
|
+
container.append(breadcrumb);
|
|
4101
|
+
const diagnostics = (0, import_ssml_core2.validateAzureSsml)((0, import_ssml_core2.buildSsml)(documentState));
|
|
4102
|
+
if (diagnostics.length > 0) {
|
|
4103
|
+
const errors = document.createElement("div");
|
|
4104
|
+
errors.className = "ssml-editor-visual-errors";
|
|
4105
|
+
errors.setAttribute("role", "alert");
|
|
4106
|
+
for (const diagnostic of diagnostics) {
|
|
4107
|
+
const message = document.createElement("div");
|
|
4108
|
+
message.textContent = diagnostic.message;
|
|
4109
|
+
errors.append(message);
|
|
4110
|
+
}
|
|
4111
|
+
container.append(errors);
|
|
4112
|
+
}
|
|
4113
|
+
const layout = document.createElement("div");
|
|
4114
|
+
layout.className = "ssml-editor-visual-layout";
|
|
4115
|
+
const tree = document.createElement("nav");
|
|
4116
|
+
tree.className = "ssml-editor-visual-tree";
|
|
4117
|
+
tree.setAttribute("aria-label", "SSML structure tree");
|
|
4118
|
+
tree.append(document.createTextNode("Structure"));
|
|
4119
|
+
const treeList = document.createElement("ul");
|
|
4120
|
+
const renderTree = (nodes, parent, parentPath = []) => {
|
|
4121
|
+
nodes.forEach((node, index) => {
|
|
4122
|
+
if (!isElement(node)) return;
|
|
4123
|
+
const path = [...parentPath, index];
|
|
4124
|
+
const item = document.createElement("li");
|
|
4125
|
+
const button = document.createElement("button");
|
|
4126
|
+
button.type = "button";
|
|
4127
|
+
button.textContent = `<${visualElementName(node)}>`;
|
|
4128
|
+
button.dataset.selected = String(path.join(".") === this.visualSelectedPath?.join("."));
|
|
4129
|
+
button.addEventListener("click", () => {
|
|
4130
|
+
this.visualSelectedPath = path;
|
|
4131
|
+
this.renderVisualEditor();
|
|
4132
|
+
});
|
|
4133
|
+
item.append(button);
|
|
4134
|
+
if ((node.children ?? []).some(isElement)) {
|
|
4135
|
+
const childList = document.createElement("ul");
|
|
4136
|
+
renderTree(node.children ?? [], childList, path);
|
|
4137
|
+
item.append(childList);
|
|
4138
|
+
}
|
|
4139
|
+
parent.append(item);
|
|
4140
|
+
});
|
|
4141
|
+
};
|
|
4142
|
+
renderTree(documentState.children ?? [], treeList);
|
|
4143
|
+
tree.append(treeList);
|
|
4144
|
+
layout.append(tree);
|
|
4145
|
+
const form = document.createElement("div");
|
|
4146
|
+
form.className = "ssml-editor-visual-form";
|
|
4147
|
+
if (selectedLeaf) {
|
|
4148
|
+
const label = document.createElement("label");
|
|
4149
|
+
label.append(document.createTextNode("Text"));
|
|
4150
|
+
const textarea = document.createElement("textarea");
|
|
4151
|
+
textarea.value = selectedLeaf.value;
|
|
4152
|
+
textarea.readOnly = this.readonly;
|
|
4153
|
+
textarea.addEventListener("select", () => {
|
|
4154
|
+
this.visualSelection = { start: textarea.selectionStart, end: textarea.selectionEnd };
|
|
4155
|
+
});
|
|
4156
|
+
textarea.addEventListener("input", () => {
|
|
4157
|
+
if (!this.readonly) this.replaceDocument(updateVisualText(documentState, selectedLeaf.path, textarea.value));
|
|
4158
|
+
});
|
|
4159
|
+
label.append(textarea);
|
|
4160
|
+
form.append(label);
|
|
4161
|
+
const actions = document.createElement("div");
|
|
4162
|
+
actions.className = "ssml-editor-visual-actions";
|
|
4163
|
+
const wrappers = [
|
|
4164
|
+
["Rate", "prosody", { rate: "slow" }],
|
|
4165
|
+
["Pitch", "prosody", { pitch: "high" }],
|
|
4166
|
+
["Emotion", "mstts:express-as", { style: "cheerful" }],
|
|
4167
|
+
["Pause", "break", { time: "500ms" }],
|
|
4168
|
+
["Pronunciation", "phoneme", { alphabet: "ipa", ph: selectedLeaf.value }]
|
|
4169
|
+
];
|
|
4170
|
+
for (const [labelText, type, attributes] of wrappers) {
|
|
4171
|
+
const button = document.createElement("button");
|
|
4172
|
+
button.type = "button";
|
|
4173
|
+
button.textContent = labelText;
|
|
4174
|
+
button.disabled = this.readonly;
|
|
4175
|
+
button.addEventListener("click", () => {
|
|
4176
|
+
const start = this.visualSelection.start === this.visualSelection.end ? 0 : this.visualSelection.start;
|
|
4177
|
+
const end = this.visualSelection.start === this.visualSelection.end ? selectedLeaf.value.length : this.visualSelection.end;
|
|
4178
|
+
this.replaceDocument(wrapVisualText(documentState, selectedLeaf.path, start, end, type, attributes));
|
|
4179
|
+
this.renderVisualEditor();
|
|
4180
|
+
});
|
|
4181
|
+
actions.append(button);
|
|
4182
|
+
}
|
|
4183
|
+
const preview = document.createElement("button");
|
|
4184
|
+
preview.type = "button";
|
|
4185
|
+
preview.textContent = "Preview selection";
|
|
4186
|
+
preview.addEventListener("click", () => {
|
|
4187
|
+
this.dispatchEvent(
|
|
4188
|
+
new CustomEvent("preview-selection", {
|
|
4189
|
+
detail: { ssml: (0, import_ssml_core2.buildSsml)({ ...documentState, children: [selectedLeaf.value] }) },
|
|
4190
|
+
bubbles: true,
|
|
4191
|
+
composed: true
|
|
4192
|
+
})
|
|
4193
|
+
);
|
|
4194
|
+
});
|
|
4195
|
+
actions.append(preview);
|
|
4196
|
+
form.append(actions);
|
|
4197
|
+
} else {
|
|
4198
|
+
form.textContent = "Select an element or text node to edit it.";
|
|
4199
|
+
}
|
|
4200
|
+
layout.append(form);
|
|
4201
|
+
container.append(layout);
|
|
4202
|
+
}
|
|
3842
4203
|
updateActiveButtons() {
|
|
3843
4204
|
const editor = this.editor;
|
|
3844
4205
|
const model = this.model;
|
|
@@ -3866,6 +4227,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3866
4227
|
return;
|
|
3867
4228
|
}
|
|
3868
4229
|
const model = monacoModule.editor.createModel(this.prepareDocument(this.value), "xml");
|
|
4230
|
+
this.renderVisualEditor();
|
|
3869
4231
|
const editor = monacoModule.editor.create(container, {
|
|
3870
4232
|
model,
|
|
3871
4233
|
theme: this.theme,
|
|
@@ -3964,7 +4326,8 @@ SsmlEditorElement.observedAttributes = [
|
|
|
3964
4326
|
"locale",
|
|
3965
4327
|
"show-toolbar",
|
|
3966
4328
|
"show-toolbar-labels",
|
|
3967
|
-
"show-decorations"
|
|
4329
|
+
"show-decorations",
|
|
4330
|
+
"edit-mode"
|
|
3968
4331
|
];
|
|
3969
4332
|
|
|
3970
4333
|
// src/index.ts
|