@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/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/SsmlEditorElement.ts
|
|
8
|
-
import { buildSsml as buildSsml2, parseSsml as parseSsml2 } from "@ssml-builder-js/ssml-core";
|
|
8
|
+
import { buildSsml as buildSsml2, parseSsml as parseSsml2, validateAzureSsml } from "@ssml-builder-js/ssml-core";
|
|
9
9
|
|
|
10
10
|
// ../ssml-editor-react/src/clearSsmlDocument.ts
|
|
11
11
|
function getDocumentChildren(document2) {
|
|
@@ -1147,6 +1147,11 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
1147
1147
|
"mstts:audioduration": {
|
|
1148
1148
|
value: AUDIO_DURATION_PRESETS
|
|
1149
1149
|
},
|
|
1150
|
+
"mstts:backgroundaudio": {
|
|
1151
|
+
volume: PROSODY_VOLUME_PRESETS,
|
|
1152
|
+
fadein: AUDIO_DURATION_PRESETS,
|
|
1153
|
+
fadeout: AUDIO_DURATION_PRESETS
|
|
1154
|
+
},
|
|
1150
1155
|
silence: {
|
|
1151
1156
|
type: SILENCE_TYPE_PRESETS,
|
|
1152
1157
|
value: SILENCE_VALUE_PRESETS
|
|
@@ -1638,6 +1643,24 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
1638
1643
|
label: "mstts:audioduration",
|
|
1639
1644
|
insertText: '<mstts:audioduration value="10s" />'
|
|
1640
1645
|
},
|
|
1646
|
+
{
|
|
1647
|
+
label: "mstts:dialog",
|
|
1648
|
+
insertText: `<mstts:dialog>
|
|
1649
|
+
<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
|
|
1650
|
+
</mstts:dialog>`
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
label: "mstts:turn",
|
|
1654
|
+
insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
label: "mstts:backgroundaudio",
|
|
1658
|
+
insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
|
|
1659
|
+
},
|
|
1660
|
+
{
|
|
1661
|
+
label: "mstts:ttsembedding",
|
|
1662
|
+
insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
|
|
1663
|
+
},
|
|
1641
1664
|
{
|
|
1642
1665
|
label: "sub",
|
|
1643
1666
|
insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
|
|
@@ -2383,6 +2406,63 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
2383
2406
|
example: "10s"
|
|
2384
2407
|
}
|
|
2385
2408
|
]
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
name: "mstts:dialog",
|
|
2412
|
+
description: "Groups multiple Azure dialog turns that can use different voices.",
|
|
2413
|
+
parameters: []
|
|
2414
|
+
},
|
|
2415
|
+
{
|
|
2416
|
+
name: "mstts:turn",
|
|
2417
|
+
description: "Adds one dialog turn using the required Azure voice name.",
|
|
2418
|
+
parameters: [
|
|
2419
|
+
{
|
|
2420
|
+
name: "voice",
|
|
2421
|
+
description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
|
|
2422
|
+
example: "en-US-JennyNeural"
|
|
2423
|
+
}
|
|
2424
|
+
]
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
name: "mstts:backgroundaudio",
|
|
2428
|
+
description: "Plays background audio while speech is synthesized.",
|
|
2429
|
+
parameters: [
|
|
2430
|
+
{
|
|
2431
|
+
name: "src",
|
|
2432
|
+
description: "An absolute HTTP(S) URL for the background audio file.",
|
|
2433
|
+
example: "https://example.com/music.mp3"
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
name: "volume",
|
|
2437
|
+
description: "The background audio volume, for example `-3dB` or `medium`.",
|
|
2438
|
+
example: "-3dB"
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
name: "fadein",
|
|
2442
|
+
description: "The fade-in duration, for example `1s`.",
|
|
2443
|
+
example: "1s"
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
name: "fadeout",
|
|
2447
|
+
description: "The fade-out duration, for example `500ms`.",
|
|
2448
|
+
example: "500ms"
|
|
2449
|
+
}
|
|
2450
|
+
]
|
|
2451
|
+
},
|
|
2452
|
+
{
|
|
2453
|
+
name: "mstts:ttsembedding",
|
|
2454
|
+
description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
|
|
2455
|
+
parameters: []
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
name: "mstts:embedding",
|
|
2459
|
+
description: "Specifies embedding metadata for custom voice scenarios.",
|
|
2460
|
+
parameters: []
|
|
2461
|
+
},
|
|
2462
|
+
{
|
|
2463
|
+
name: "mstts:voiceconversion",
|
|
2464
|
+
description: "Specifies voice conversion metadata for custom voice scenarios.",
|
|
2465
|
+
parameters: []
|
|
2386
2466
|
}
|
|
2387
2467
|
];
|
|
2388
2468
|
var definitionsByName = /* @__PURE__ */ new Map();
|
|
@@ -3188,6 +3268,60 @@ section[data-ssml-editor] {
|
|
|
3188
3268
|
border-radius: 0.25rem;
|
|
3189
3269
|
overflow: visible;
|
|
3190
3270
|
}
|
|
3271
|
+
[data-ssml-editor] .ssml-editor-visual {
|
|
3272
|
+
display: grid;
|
|
3273
|
+
gap: 0.75rem;
|
|
3274
|
+
min-height: 8rem;
|
|
3275
|
+
padding: 0.75rem;
|
|
3276
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3277
|
+
border-radius: 0.25rem;
|
|
3278
|
+
}
|
|
3279
|
+
[data-ssml-editor] .ssml-editor-visual-layout {
|
|
3280
|
+
display: grid;
|
|
3281
|
+
grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
|
|
3282
|
+
gap: 1rem;
|
|
3283
|
+
}
|
|
3284
|
+
[data-ssml-editor] .ssml-editor-visual-tree ul {
|
|
3285
|
+
margin: 0;
|
|
3286
|
+
padding-left: 1.25rem;
|
|
3287
|
+
}
|
|
3288
|
+
[data-ssml-editor] .ssml-editor-visual button {
|
|
3289
|
+
padding: 0.35rem 0.5rem;
|
|
3290
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3291
|
+
border-radius: 0.25rem;
|
|
3292
|
+
color: var(--ssml-editor-color);
|
|
3293
|
+
background: var(--ssml-editor-control-bg);
|
|
3294
|
+
cursor: pointer;
|
|
3295
|
+
}
|
|
3296
|
+
[data-ssml-editor] .ssml-editor-visual button[data-selected="true"] {
|
|
3297
|
+
border-color: var(--ssml-editor-active-border);
|
|
3298
|
+
background: var(--ssml-editor-active-bg);
|
|
3299
|
+
}
|
|
3300
|
+
[data-ssml-editor] .ssml-editor-visual textarea {
|
|
3301
|
+
box-sizing: border-box;
|
|
3302
|
+
width: 100%;
|
|
3303
|
+
min-height: 6rem;
|
|
3304
|
+
padding: 0.5rem;
|
|
3305
|
+
color: var(--ssml-editor-color);
|
|
3306
|
+
background: var(--ssml-editor-control-bg);
|
|
3307
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
3308
|
+
border-radius: 0.25rem;
|
|
3309
|
+
font: inherit;
|
|
3310
|
+
}
|
|
3311
|
+
[data-ssml-editor] .ssml-editor-visual-actions,
|
|
3312
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb {
|
|
3313
|
+
display: flex;
|
|
3314
|
+
flex-wrap: wrap;
|
|
3315
|
+
gap: 0.4rem;
|
|
3316
|
+
align-items: center;
|
|
3317
|
+
}
|
|
3318
|
+
[data-ssml-editor] .ssml-editor-visual-errors {
|
|
3319
|
+
padding: 0.5rem;
|
|
3320
|
+
color: #b91c1c;
|
|
3321
|
+
background: #fef2f2;
|
|
3322
|
+
border: 1px solid #b91c1c;
|
|
3323
|
+
border-radius: 0.25rem;
|
|
3324
|
+
}
|
|
3191
3325
|
`.trim();
|
|
3192
3326
|
function injectStyles() {
|
|
3193
3327
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
|
|
@@ -3201,6 +3335,55 @@ function injectStyles() {
|
|
|
3201
3335
|
function isDarkTheme(theme) {
|
|
3202
3336
|
return theme === "vs-dark" || theme.toLowerCase().includes("dark");
|
|
3203
3337
|
}
|
|
3338
|
+
function isElement(node) {
|
|
3339
|
+
return typeof node !== "string" && node.type !== "text";
|
|
3340
|
+
}
|
|
3341
|
+
function visualElementName(element) {
|
|
3342
|
+
return element.type === "custom" || element.type === "element" ? element.name : element.type;
|
|
3343
|
+
}
|
|
3344
|
+
function collectVisualTextLeaves(nodes, path = [], ancestors = []) {
|
|
3345
|
+
return nodes.flatMap((node, index) => {
|
|
3346
|
+
const currentPath = [...path, index];
|
|
3347
|
+
if (typeof node === "string") return [{ path: currentPath, value: node, ancestors }];
|
|
3348
|
+
if (node.type === "text") return [{ path: currentPath, value: node.value, ancestors }];
|
|
3349
|
+
return collectVisualTextLeaves(node.children ?? [], currentPath, [...ancestors, visualElementName(node)]);
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
function updateVisualNodes(nodes, path, update) {
|
|
3353
|
+
if (path.length === 0) return nodes;
|
|
3354
|
+
const [index, ...rest] = path;
|
|
3355
|
+
return nodes.flatMap((node, nodeIndex) => {
|
|
3356
|
+
if (nodeIndex !== index) return [node];
|
|
3357
|
+
if (rest.length === 0) {
|
|
3358
|
+
const next = update(node);
|
|
3359
|
+
return Array.isArray(next) ? next : [next];
|
|
3360
|
+
}
|
|
3361
|
+
if (!isElement(node)) return [node];
|
|
3362
|
+
return [{ ...node, children: updateVisualNodes(node.children ?? [], rest, update) }];
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
function updateVisualText(document2, path, value) {
|
|
3366
|
+
return { ...document2, children: updateVisualNodes(document2.children ?? [], path, () => value) };
|
|
3367
|
+
}
|
|
3368
|
+
function wrapVisualText(document2, path, start, end, type, attributes) {
|
|
3369
|
+
return {
|
|
3370
|
+
...document2,
|
|
3371
|
+
children: updateVisualNodes(document2.children ?? [], path, (node) => {
|
|
3372
|
+
const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
|
|
3373
|
+
if (!value || start === end) return node;
|
|
3374
|
+
if (type === "break") {
|
|
3375
|
+
return [value.slice(0, start), { type, attributes, children: [] }, value.slice(start)].filter(
|
|
3376
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
const selected = value.slice(start, end);
|
|
3380
|
+
const wrapper = { type, attributes, children: [selected] };
|
|
3381
|
+
return [value.slice(0, start), wrapper, value.slice(end)].filter(
|
|
3382
|
+
(part) => typeof part === "string" ? part.length > 0 : true
|
|
3383
|
+
);
|
|
3384
|
+
})
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3204
3387
|
function getMenuPosition(trigger, menu) {
|
|
3205
3388
|
const bounds = trigger.getBoundingClientRect();
|
|
3206
3389
|
const margin = 8;
|
|
@@ -3222,6 +3405,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3222
3405
|
this.toolbarActions = null;
|
|
3223
3406
|
this.display = null;
|
|
3224
3407
|
this.editorContainer = null;
|
|
3408
|
+
this.visualContainer = null;
|
|
3225
3409
|
this.helpPanel = null;
|
|
3226
3410
|
this.openMenu = null;
|
|
3227
3411
|
this.openMenuTrigger = null;
|
|
@@ -3235,6 +3419,8 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3235
3419
|
this.documentState = null;
|
|
3236
3420
|
this.decorationsVisible = false;
|
|
3237
3421
|
this.helpOpen = false;
|
|
3422
|
+
this.visualSelectedPath = null;
|
|
3423
|
+
this.visualSelection = { start: 0, end: 0 };
|
|
3238
3424
|
this.handleDocumentPointerDown = (event) => {
|
|
3239
3425
|
const target = event.target;
|
|
3240
3426
|
if (this.openMenu && target instanceof Node && !this.openMenu.contains(target) && !this.openMenuTrigger?.contains(target)) {
|
|
@@ -3276,6 +3462,12 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3276
3462
|
set locale(locale) {
|
|
3277
3463
|
this.setAttribute("locale", locale);
|
|
3278
3464
|
}
|
|
3465
|
+
get editMode() {
|
|
3466
|
+
return this.getAttribute("edit-mode") === "visual" ? "visual" : "code";
|
|
3467
|
+
}
|
|
3468
|
+
set editMode(mode) {
|
|
3469
|
+
this.setAttribute("edit-mode", mode);
|
|
3470
|
+
}
|
|
3279
3471
|
prepareDocument(value) {
|
|
3280
3472
|
try {
|
|
3281
3473
|
this.documentState = parseSsml2(value);
|
|
@@ -3318,6 +3510,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3318
3510
|
}
|
|
3319
3511
|
}
|
|
3320
3512
|
}
|
|
3513
|
+
this.renderVisualEditor();
|
|
3321
3514
|
return;
|
|
3322
3515
|
}
|
|
3323
3516
|
if (name === "theme" && this.monaco) {
|
|
@@ -3335,6 +3528,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3335
3528
|
this.renderHelp();
|
|
3336
3529
|
return;
|
|
3337
3530
|
}
|
|
3531
|
+
if (name === "edit-mode") {
|
|
3532
|
+
this.updateEditMode();
|
|
3533
|
+
}
|
|
3338
3534
|
if (name === "show-decorations") {
|
|
3339
3535
|
this.decorationsVisible = newValue !== null;
|
|
3340
3536
|
this.updateDecorations();
|
|
@@ -3359,7 +3555,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3359
3555
|
display.dataset.ssmlEditorDisplay = "";
|
|
3360
3556
|
const editorContainer = document.createElement("div");
|
|
3361
3557
|
editorContainer.className = "ssml-editor-editor";
|
|
3362
|
-
|
|
3558
|
+
const visualContainer = document.createElement("div");
|
|
3559
|
+
visualContainer.className = "ssml-editor-visual";
|
|
3560
|
+
display.append(editorContainer, visualContainer);
|
|
3363
3561
|
root.append(toolbar, display);
|
|
3364
3562
|
this.replaceChildren(root);
|
|
3365
3563
|
this.root = root;
|
|
@@ -3367,8 +3565,11 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3367
3565
|
this.toolbarActions = toolbarActions;
|
|
3368
3566
|
this.display = display;
|
|
3369
3567
|
this.editorContainer = editorContainer;
|
|
3568
|
+
this.visualContainer = visualContainer;
|
|
3370
3569
|
this.renderToolbar();
|
|
3371
3570
|
this.renderHelp();
|
|
3571
|
+
this.updateEditMode();
|
|
3572
|
+
this.renderVisualEditor();
|
|
3372
3573
|
}
|
|
3373
3574
|
renderToolbar() {
|
|
3374
3575
|
const toolbar = this.toolbar;
|
|
@@ -3412,10 +3613,10 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3412
3613
|
for (const id of toolbarIds) {
|
|
3413
3614
|
const group = groupByButtonId.get(id);
|
|
3414
3615
|
if (previousGroup !== void 0 && group !== previousGroup) {
|
|
3415
|
-
const
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
toolbarActions.append(
|
|
3616
|
+
const separator2 = document.createElement("span");
|
|
3617
|
+
separator2.className = "ssml-editor-toolbar-separator";
|
|
3618
|
+
separator2.setAttribute("aria-hidden", "true");
|
|
3619
|
+
toolbarActions.append(separator2);
|
|
3419
3620
|
}
|
|
3420
3621
|
previousGroup = group;
|
|
3421
3622
|
const insertion = insertionById.get(id);
|
|
@@ -3427,8 +3628,24 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3427
3628
|
toolbarActions.append(this.createActionButton(id));
|
|
3428
3629
|
}
|
|
3429
3630
|
}
|
|
3631
|
+
const separator = document.createElement("span");
|
|
3632
|
+
separator.className = "ssml-editor-toolbar-separator";
|
|
3633
|
+
separator.setAttribute("aria-hidden", "true");
|
|
3634
|
+
toolbarActions.append(separator, this.createModeButton("visual", "Visual"), this.createModeButton("code", "Code"));
|
|
3430
3635
|
this.updateActiveButtons();
|
|
3431
3636
|
}
|
|
3637
|
+
createModeButton(mode, label) {
|
|
3638
|
+
const button = document.createElement("button");
|
|
3639
|
+
button.type = "button";
|
|
3640
|
+
button.className = "ssml-editor-toolbar-button";
|
|
3641
|
+
button.dataset.ssmlEditorButton = `edit-mode-${mode}`;
|
|
3642
|
+
button.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
3643
|
+
button.textContent = label;
|
|
3644
|
+
button.addEventListener("click", () => {
|
|
3645
|
+
this.editMode = mode;
|
|
3646
|
+
});
|
|
3647
|
+
return button;
|
|
3648
|
+
}
|
|
3432
3649
|
createActionButton(id) {
|
|
3433
3650
|
const copy = EDITOR_COPY[this.locale];
|
|
3434
3651
|
const labels = {
|
|
@@ -3808,6 +4025,150 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3808
4025
|
this.root.dataset.theme = isDarkTheme(this.theme) ? "dark" : "light";
|
|
3809
4026
|
}
|
|
3810
4027
|
}
|
|
4028
|
+
updateEditMode() {
|
|
4029
|
+
if (this.editorContainer && this.visualContainer) {
|
|
4030
|
+
const visual = this.editMode === "visual";
|
|
4031
|
+
this.editorContainer.hidden = visual;
|
|
4032
|
+
this.visualContainer.hidden = !visual;
|
|
4033
|
+
}
|
|
4034
|
+
for (const mode of ["visual", "code"]) {
|
|
4035
|
+
const button = this.toolbarActions?.querySelector(
|
|
4036
|
+
`[data-ssml-editor-button="edit-mode-${mode}"]`
|
|
4037
|
+
);
|
|
4038
|
+
button?.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
4039
|
+
button?.toggleAttribute("data-active", this.editMode === mode);
|
|
4040
|
+
}
|
|
4041
|
+
this.renderVisualEditor();
|
|
4042
|
+
}
|
|
4043
|
+
renderVisualEditor() {
|
|
4044
|
+
const container = this.visualContainer;
|
|
4045
|
+
if (!container) return;
|
|
4046
|
+
container.replaceChildren();
|
|
4047
|
+
if (!this.documentState) {
|
|
4048
|
+
const error = document.createElement("p");
|
|
4049
|
+
error.className = "ssml-editor-visual-errors";
|
|
4050
|
+
error.textContent = "SSML syntax must be valid before visual editing is available.";
|
|
4051
|
+
container.append(error);
|
|
4052
|
+
return;
|
|
4053
|
+
}
|
|
4054
|
+
const documentState = this.documentState;
|
|
4055
|
+
const leaves = collectVisualTextLeaves(documentState.children ?? []);
|
|
4056
|
+
const selectedLeaf = leaves.find((leaf) => leaf.path.join(".") === this.visualSelectedPath?.join(".")) ?? leaves[0];
|
|
4057
|
+
const breadcrumb = document.createElement("div");
|
|
4058
|
+
breadcrumb.className = "ssml-editor-visual-breadcrumb";
|
|
4059
|
+
breadcrumb.textContent = `<speak>${selectedLeaf ? ` / ${selectedLeaf.ancestors.map((name) => `<${name}>`).join(" / ")}` : ""}`;
|
|
4060
|
+
const clear = document.createElement("button");
|
|
4061
|
+
clear.type = "button";
|
|
4062
|
+
clear.textContent = "Clear parent";
|
|
4063
|
+
clear.disabled = !this.visualSelectedPath;
|
|
4064
|
+
clear.addEventListener("click", () => {
|
|
4065
|
+
this.visualSelectedPath = null;
|
|
4066
|
+
this.renderVisualEditor();
|
|
4067
|
+
});
|
|
4068
|
+
breadcrumb.append(clear);
|
|
4069
|
+
container.append(breadcrumb);
|
|
4070
|
+
const diagnostics = validateAzureSsml(buildSsml2(documentState));
|
|
4071
|
+
if (diagnostics.length > 0) {
|
|
4072
|
+
const errors = document.createElement("div");
|
|
4073
|
+
errors.className = "ssml-editor-visual-errors";
|
|
4074
|
+
errors.setAttribute("role", "alert");
|
|
4075
|
+
for (const diagnostic of diagnostics) {
|
|
4076
|
+
const message = document.createElement("div");
|
|
4077
|
+
message.textContent = diagnostic.message;
|
|
4078
|
+
errors.append(message);
|
|
4079
|
+
}
|
|
4080
|
+
container.append(errors);
|
|
4081
|
+
}
|
|
4082
|
+
const layout = document.createElement("div");
|
|
4083
|
+
layout.className = "ssml-editor-visual-layout";
|
|
4084
|
+
const tree = document.createElement("nav");
|
|
4085
|
+
tree.className = "ssml-editor-visual-tree";
|
|
4086
|
+
tree.setAttribute("aria-label", "SSML structure tree");
|
|
4087
|
+
tree.append(document.createTextNode("Structure"));
|
|
4088
|
+
const treeList = document.createElement("ul");
|
|
4089
|
+
const renderTree = (nodes, parent, parentPath = []) => {
|
|
4090
|
+
nodes.forEach((node, index) => {
|
|
4091
|
+
if (!isElement(node)) return;
|
|
4092
|
+
const path = [...parentPath, index];
|
|
4093
|
+
const item = document.createElement("li");
|
|
4094
|
+
const button = document.createElement("button");
|
|
4095
|
+
button.type = "button";
|
|
4096
|
+
button.textContent = `<${visualElementName(node)}>`;
|
|
4097
|
+
button.dataset.selected = String(path.join(".") === this.visualSelectedPath?.join("."));
|
|
4098
|
+
button.addEventListener("click", () => {
|
|
4099
|
+
this.visualSelectedPath = path;
|
|
4100
|
+
this.renderVisualEditor();
|
|
4101
|
+
});
|
|
4102
|
+
item.append(button);
|
|
4103
|
+
if ((node.children ?? []).some(isElement)) {
|
|
4104
|
+
const childList = document.createElement("ul");
|
|
4105
|
+
renderTree(node.children ?? [], childList, path);
|
|
4106
|
+
item.append(childList);
|
|
4107
|
+
}
|
|
4108
|
+
parent.append(item);
|
|
4109
|
+
});
|
|
4110
|
+
};
|
|
4111
|
+
renderTree(documentState.children ?? [], treeList);
|
|
4112
|
+
tree.append(treeList);
|
|
4113
|
+
layout.append(tree);
|
|
4114
|
+
const form = document.createElement("div");
|
|
4115
|
+
form.className = "ssml-editor-visual-form";
|
|
4116
|
+
if (selectedLeaf) {
|
|
4117
|
+
const label = document.createElement("label");
|
|
4118
|
+
label.append(document.createTextNode("Text"));
|
|
4119
|
+
const textarea = document.createElement("textarea");
|
|
4120
|
+
textarea.value = selectedLeaf.value;
|
|
4121
|
+
textarea.readOnly = this.readonly;
|
|
4122
|
+
textarea.addEventListener("select", () => {
|
|
4123
|
+
this.visualSelection = { start: textarea.selectionStart, end: textarea.selectionEnd };
|
|
4124
|
+
});
|
|
4125
|
+
textarea.addEventListener("input", () => {
|
|
4126
|
+
if (!this.readonly) this.replaceDocument(updateVisualText(documentState, selectedLeaf.path, textarea.value));
|
|
4127
|
+
});
|
|
4128
|
+
label.append(textarea);
|
|
4129
|
+
form.append(label);
|
|
4130
|
+
const actions = document.createElement("div");
|
|
4131
|
+
actions.className = "ssml-editor-visual-actions";
|
|
4132
|
+
const wrappers = [
|
|
4133
|
+
["Rate", "prosody", { rate: "slow" }],
|
|
4134
|
+
["Pitch", "prosody", { pitch: "high" }],
|
|
4135
|
+
["Emotion", "mstts:express-as", { style: "cheerful" }],
|
|
4136
|
+
["Pause", "break", { time: "500ms" }],
|
|
4137
|
+
["Pronunciation", "phoneme", { alphabet: "ipa", ph: selectedLeaf.value }]
|
|
4138
|
+
];
|
|
4139
|
+
for (const [labelText, type, attributes] of wrappers) {
|
|
4140
|
+
const button = document.createElement("button");
|
|
4141
|
+
button.type = "button";
|
|
4142
|
+
button.textContent = labelText;
|
|
4143
|
+
button.disabled = this.readonly;
|
|
4144
|
+
button.addEventListener("click", () => {
|
|
4145
|
+
const start = this.visualSelection.start === this.visualSelection.end ? 0 : this.visualSelection.start;
|
|
4146
|
+
const end = this.visualSelection.start === this.visualSelection.end ? selectedLeaf.value.length : this.visualSelection.end;
|
|
4147
|
+
this.replaceDocument(wrapVisualText(documentState, selectedLeaf.path, start, end, type, attributes));
|
|
4148
|
+
this.renderVisualEditor();
|
|
4149
|
+
});
|
|
4150
|
+
actions.append(button);
|
|
4151
|
+
}
|
|
4152
|
+
const preview = document.createElement("button");
|
|
4153
|
+
preview.type = "button";
|
|
4154
|
+
preview.textContent = "Preview selection";
|
|
4155
|
+
preview.addEventListener("click", () => {
|
|
4156
|
+
this.dispatchEvent(
|
|
4157
|
+
new CustomEvent("preview-selection", {
|
|
4158
|
+
detail: { ssml: buildSsml2({ ...documentState, children: [selectedLeaf.value] }) },
|
|
4159
|
+
bubbles: true,
|
|
4160
|
+
composed: true
|
|
4161
|
+
})
|
|
4162
|
+
);
|
|
4163
|
+
});
|
|
4164
|
+
actions.append(preview);
|
|
4165
|
+
form.append(actions);
|
|
4166
|
+
} else {
|
|
4167
|
+
form.textContent = "Select an element or text node to edit it.";
|
|
4168
|
+
}
|
|
4169
|
+
layout.append(form);
|
|
4170
|
+
container.append(layout);
|
|
4171
|
+
}
|
|
3811
4172
|
updateActiveButtons() {
|
|
3812
4173
|
const editor = this.editor;
|
|
3813
4174
|
const model = this.model;
|
|
@@ -3835,6 +4196,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3835
4196
|
return;
|
|
3836
4197
|
}
|
|
3837
4198
|
const model = monacoModule.editor.createModel(this.prepareDocument(this.value), "xml");
|
|
4199
|
+
this.renderVisualEditor();
|
|
3838
4200
|
const editor = monacoModule.editor.create(container, {
|
|
3839
4201
|
model,
|
|
3840
4202
|
theme: this.theme,
|
|
@@ -3933,7 +4295,8 @@ SsmlEditorElement.observedAttributes = [
|
|
|
3933
4295
|
"locale",
|
|
3934
4296
|
"show-toolbar",
|
|
3935
4297
|
"show-toolbar-labels",
|
|
3936
|
-
"show-decorations"
|
|
4298
|
+
"show-decorations",
|
|
4299
|
+
"edit-mode"
|
|
3937
4300
|
];
|
|
3938
4301
|
|
|
3939
4302
|
// src/index.ts
|