@ssml-builder-js/ssml-editor-elements 2.8.0 → 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 +19 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +531 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +532 -9
- 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/tsconfig.json +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) {
|
|
@@ -69,6 +69,7 @@ var INTRINSICALLY_EMPTY_ELEMENTS = /* @__PURE__ */ new Set([
|
|
|
69
69
|
"lexicon",
|
|
70
70
|
"mark",
|
|
71
71
|
"mstts:silence",
|
|
72
|
+
"mstts:audioduration",
|
|
72
73
|
"mstts:viseme",
|
|
73
74
|
"silence",
|
|
74
75
|
"viseme"
|
|
@@ -727,6 +728,8 @@ function updateEditableText(document2, value) {
|
|
|
727
728
|
// ../ssml-editor-react/src/constants/ssmlPresets.ts
|
|
728
729
|
var ssmlPresets_exports = {};
|
|
729
730
|
__export(ssmlPresets_exports, {
|
|
731
|
+
AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
|
|
732
|
+
AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
|
|
730
733
|
BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
|
|
731
734
|
BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
|
|
732
735
|
BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
|
|
@@ -759,6 +762,103 @@ __export(ssmlPresets_exports, {
|
|
|
759
762
|
getExpressAsStyleCategory: () => getExpressAsStyleCategory,
|
|
760
763
|
resolveExpressAsStyles: () => resolveExpressAsStyles
|
|
761
764
|
});
|
|
765
|
+
|
|
766
|
+
// ../ssml-editor-react/src/constants/azureVoiceStyleMap.generated.ts
|
|
767
|
+
var AZURE_VOICE_STYLE_MAP = {
|
|
768
|
+
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
769
|
+
"de-DE-KatjaNeural": ["cheerful", "sad"],
|
|
770
|
+
"en-US-AndrewNeural": ["empathetic", "relieved"],
|
|
771
|
+
"en-US-GuyNeural": [
|
|
772
|
+
"angry",
|
|
773
|
+
"cheerful",
|
|
774
|
+
"excited",
|
|
775
|
+
"friendly",
|
|
776
|
+
"hopeful",
|
|
777
|
+
"newscast",
|
|
778
|
+
"sad",
|
|
779
|
+
"shouting",
|
|
780
|
+
"terrified",
|
|
781
|
+
"unfriendly",
|
|
782
|
+
"whispering"
|
|
783
|
+
],
|
|
784
|
+
"en-US-JennyMultilingualNeural": [
|
|
785
|
+
"cheerful",
|
|
786
|
+
"empathetic",
|
|
787
|
+
"excited",
|
|
788
|
+
"friendly",
|
|
789
|
+
"hopeful",
|
|
790
|
+
"sad",
|
|
791
|
+
"shouting",
|
|
792
|
+
"terrified",
|
|
793
|
+
"unfriendly",
|
|
794
|
+
"whispering"
|
|
795
|
+
],
|
|
796
|
+
"en-US-JennyNeural": [
|
|
797
|
+
"assistant",
|
|
798
|
+
"chat",
|
|
799
|
+
"customerservice",
|
|
800
|
+
"newscast",
|
|
801
|
+
"cheerful",
|
|
802
|
+
"empathetic",
|
|
803
|
+
"excited",
|
|
804
|
+
"friendly",
|
|
805
|
+
"hopeful",
|
|
806
|
+
"sad",
|
|
807
|
+
"shouting",
|
|
808
|
+
"terrified",
|
|
809
|
+
"unfriendly",
|
|
810
|
+
"whispering"
|
|
811
|
+
],
|
|
812
|
+
"es-ES-ElviraNeural": [],
|
|
813
|
+
"fil-PH-AngeloNeural": [],
|
|
814
|
+
"fr-FR-DeniseNeural": ["cheerful", "sad"],
|
|
815
|
+
"fr-FR-HenriNeural": ["cheerful", "sad"],
|
|
816
|
+
"id-ID-GadisNeural": [],
|
|
817
|
+
"it-IT-ElsaNeural": ["cheerful", "sad"],
|
|
818
|
+
"ja-JP-KeitaNeural": ["chat"],
|
|
819
|
+
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
820
|
+
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
821
|
+
"ko-KR-SunHiNeural": ["cheerful", "sad"],
|
|
822
|
+
"ms-MY-YasminNeural": [],
|
|
823
|
+
"pt-BR-FranciscaNeural": ["calm"],
|
|
824
|
+
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
|
|
825
|
+
"th-TH-PremwadeeNeural": [],
|
|
826
|
+
"vi-VN-HoaiMyNeural": [],
|
|
827
|
+
"zh-CN-XiaoxiaoNeural": [
|
|
828
|
+
"assistant",
|
|
829
|
+
"chat",
|
|
830
|
+
"customerservice",
|
|
831
|
+
"newscast",
|
|
832
|
+
"cheerful",
|
|
833
|
+
"empathetic",
|
|
834
|
+
"excited",
|
|
835
|
+
"friendly",
|
|
836
|
+
"hopeful",
|
|
837
|
+
"sad",
|
|
838
|
+
"terrified",
|
|
839
|
+
"whispering",
|
|
840
|
+
"poetry-reading",
|
|
841
|
+
"sports_commentary",
|
|
842
|
+
"sports_commentary_excited",
|
|
843
|
+
"story"
|
|
844
|
+
],
|
|
845
|
+
"zh-CN-YunxiNeural": [
|
|
846
|
+
"narration-relaxed",
|
|
847
|
+
"embarrassed",
|
|
848
|
+
"fearful",
|
|
849
|
+
"sad",
|
|
850
|
+
"disgruntled",
|
|
851
|
+
"serious",
|
|
852
|
+
"angry",
|
|
853
|
+
"depressed",
|
|
854
|
+
"chat",
|
|
855
|
+
"cheerful",
|
|
856
|
+
"assistant"
|
|
857
|
+
],
|
|
858
|
+
"zh-TW-HsiaoChenNeural": []
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// ../ssml-editor-react/src/constants/ssmlPresets.ts
|
|
762
862
|
var SSML_PRESETS = [
|
|
763
863
|
{
|
|
764
864
|
id: "basic",
|
|
@@ -858,7 +958,7 @@ function getExpressAsStyleCategory(style) {
|
|
|
858
958
|
}
|
|
859
959
|
return "other";
|
|
860
960
|
}
|
|
861
|
-
var
|
|
961
|
+
var LEGACY_VOICE_STYLE_MAP = {
|
|
862
962
|
"ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
|
|
863
963
|
"ja-JP-KeitaNeural": ["chat"],
|
|
864
964
|
"ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
|
|
@@ -944,6 +1044,7 @@ var VOICE_STYLE_MAP = {
|
|
|
944
1044
|
"de-DE-ConradNeural": ["cheerful", "sad"],
|
|
945
1045
|
"ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
|
|
946
1046
|
};
|
|
1047
|
+
var VOICE_STYLE_MAP = Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
|
|
947
1048
|
var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
|
|
948
1049
|
Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
|
|
949
1050
|
);
|
|
@@ -986,6 +1087,7 @@ var SAY_AS_PRESETS = [
|
|
|
986
1087
|
];
|
|
987
1088
|
var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
|
|
988
1089
|
var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
|
|
1090
|
+
var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
|
|
989
1091
|
var SILENCE_TYPE_PRESETS = [
|
|
990
1092
|
"Leading",
|
|
991
1093
|
"Tailing",
|
|
@@ -994,6 +1096,11 @@ var SILENCE_TYPE_PRESETS = [
|
|
|
994
1096
|
"Semicolon",
|
|
995
1097
|
"Enumerationcomma"
|
|
996
1098
|
];
|
|
1099
|
+
var AUDIO_DURATION_DESCRIPTIONS = {
|
|
1100
|
+
"5s": { ja: "5\u79D2", en: "5 seconds" },
|
|
1101
|
+
"10s": { ja: "10\u79D2", en: "10 seconds" },
|
|
1102
|
+
"30s": { ja: "30\u79D2", en: "30 seconds" }
|
|
1103
|
+
};
|
|
997
1104
|
var PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"];
|
|
998
1105
|
var VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"];
|
|
999
1106
|
var SSML_ATTRIBUTE_PRESETS = {
|
|
@@ -1037,6 +1144,14 @@ var SSML_ATTRIBUTE_PRESETS = {
|
|
|
1037
1144
|
type: SILENCE_TYPE_PRESETS,
|
|
1038
1145
|
value: SILENCE_VALUE_PRESETS
|
|
1039
1146
|
},
|
|
1147
|
+
"mstts:audioduration": {
|
|
1148
|
+
value: AUDIO_DURATION_PRESETS
|
|
1149
|
+
},
|
|
1150
|
+
"mstts:backgroundaudio": {
|
|
1151
|
+
volume: PROSODY_VOLUME_PRESETS,
|
|
1152
|
+
fadein: AUDIO_DURATION_PRESETS,
|
|
1153
|
+
fadeout: AUDIO_DURATION_PRESETS
|
|
1154
|
+
},
|
|
1040
1155
|
silence: {
|
|
1041
1156
|
type: SILENCE_TYPE_PRESETS,
|
|
1042
1157
|
value: SILENCE_VALUE_PRESETS
|
|
@@ -1524,6 +1639,28 @@ var SSML_COMPLETION_SNIPPETS = [
|
|
|
1524
1639
|
label: "mstts:express-as",
|
|
1525
1640
|
insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`
|
|
1526
1641
|
},
|
|
1642
|
+
{
|
|
1643
|
+
label: "mstts:audioduration",
|
|
1644
|
+
insertText: '<mstts:audioduration value="10s" />'
|
|
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
|
+
},
|
|
1527
1664
|
{
|
|
1528
1665
|
label: "sub",
|
|
1529
1666
|
insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
|
|
@@ -1791,6 +1928,13 @@ var SSML_HOVER_COPY = {
|
|
|
1791
1928
|
parameters: {
|
|
1792
1929
|
type: { title: "type", description: "\u30D3\u30BC\u30FC\u30E0\u30A4\u30D9\u30F3\u30C8\u306E\u5F62\u5F0F\u3002" }
|
|
1793
1930
|
}
|
|
1931
|
+
},
|
|
1932
|
+
"mstts:audioduration": {
|
|
1933
|
+
title: "\u97F3\u58F0\u9577",
|
|
1934
|
+
description: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
1935
|
+
parameters: {
|
|
1936
|
+
value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
|
|
1937
|
+
}
|
|
1794
1938
|
}
|
|
1795
1939
|
}
|
|
1796
1940
|
},
|
|
@@ -1948,6 +2092,13 @@ var SSML_HOVER_COPY = {
|
|
|
1948
2092
|
parameters: {
|
|
1949
2093
|
type: { title: "type", description: "The viseme event format." }
|
|
1950
2094
|
}
|
|
2095
|
+
},
|
|
2096
|
+
"mstts:audioduration": {
|
|
2097
|
+
title: "Audio duration",
|
|
2098
|
+
description: "Sets the target duration of synthesized audio.",
|
|
2099
|
+
parameters: {
|
|
2100
|
+
value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
|
|
2101
|
+
}
|
|
1951
2102
|
}
|
|
1952
2103
|
}
|
|
1953
2104
|
}
|
|
@@ -2244,6 +2395,74 @@ var SSML_TAG_DEFINITIONS = [
|
|
|
2244
2395
|
values: VISEME_TYPE_PRESETS2
|
|
2245
2396
|
}
|
|
2246
2397
|
]
|
|
2398
|
+
},
|
|
2399
|
+
{
|
|
2400
|
+
name: "mstts:audioduration",
|
|
2401
|
+
description: "Sets the target duration of synthesized audio.",
|
|
2402
|
+
parameters: [
|
|
2403
|
+
{
|
|
2404
|
+
name: "value",
|
|
2405
|
+
description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
|
|
2406
|
+
example: "10s"
|
|
2407
|
+
}
|
|
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: []
|
|
2247
2466
|
}
|
|
2248
2467
|
];
|
|
2249
2468
|
var definitionsByName = /* @__PURE__ */ new Map();
|
|
@@ -2785,13 +3004,34 @@ var SSML_INSERTIONS = [
|
|
|
2785
3004
|
suffix: "",
|
|
2786
3005
|
mode: "insert"
|
|
2787
3006
|
})
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
id: "mstts:audioduration",
|
|
3010
|
+
icon: "\u25F7",
|
|
3011
|
+
tagName: "mstts:audioduration",
|
|
3012
|
+
selfClosing: true,
|
|
3013
|
+
labels: { ja: "\u97F3\u58F0\u9577", en: "Audio duration" },
|
|
3014
|
+
descriptions: {
|
|
3015
|
+
ja: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
|
|
3016
|
+
en: "Sets the target duration of synthesized audio."
|
|
3017
|
+
},
|
|
3018
|
+
parameterDescription: {
|
|
3019
|
+
ja: "\u76EE\u6A19\u6642\u9593\u3092\u9078\u629E\u3057\u307E\u3059\u3002",
|
|
3020
|
+
en: "Selects the target duration."
|
|
3021
|
+
},
|
|
3022
|
+
options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
|
|
3023
|
+
createTemplate: (value) => ({
|
|
3024
|
+
prefix: `<mstts:audioduration value="${value}"/>`,
|
|
3025
|
+
suffix: "",
|
|
3026
|
+
mode: "insert"
|
|
3027
|
+
})
|
|
2788
3028
|
}
|
|
2789
3029
|
];
|
|
2790
3030
|
var DEFAULT_INSERTION_GROUPS = [
|
|
2791
3031
|
{
|
|
2792
3032
|
id: "pauses",
|
|
2793
3033
|
labels: { ja: "\u9593\u30FB\u7121\u97F3", en: "Pauses" },
|
|
2794
|
-
insertionIds: ["break", "mstts:silence"]
|
|
3034
|
+
insertionIds: ["break", "mstts:silence", "mstts:audioduration"]
|
|
2795
3035
|
},
|
|
2796
3036
|
{
|
|
2797
3037
|
id: "prosody",
|
|
@@ -3028,6 +3268,60 @@ section[data-ssml-editor] {
|
|
|
3028
3268
|
border-radius: 0.25rem;
|
|
3029
3269
|
overflow: visible;
|
|
3030
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
|
+
}
|
|
3031
3325
|
`.trim();
|
|
3032
3326
|
function injectStyles() {
|
|
3033
3327
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) {
|
|
@@ -3041,6 +3335,55 @@ function injectStyles() {
|
|
|
3041
3335
|
function isDarkTheme(theme) {
|
|
3042
3336
|
return theme === "vs-dark" || theme.toLowerCase().includes("dark");
|
|
3043
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
|
+
}
|
|
3044
3387
|
function getMenuPosition(trigger, menu) {
|
|
3045
3388
|
const bounds = trigger.getBoundingClientRect();
|
|
3046
3389
|
const margin = 8;
|
|
@@ -3062,6 +3405,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3062
3405
|
this.toolbarActions = null;
|
|
3063
3406
|
this.display = null;
|
|
3064
3407
|
this.editorContainer = null;
|
|
3408
|
+
this.visualContainer = null;
|
|
3065
3409
|
this.helpPanel = null;
|
|
3066
3410
|
this.openMenu = null;
|
|
3067
3411
|
this.openMenuTrigger = null;
|
|
@@ -3075,6 +3419,8 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3075
3419
|
this.documentState = null;
|
|
3076
3420
|
this.decorationsVisible = false;
|
|
3077
3421
|
this.helpOpen = false;
|
|
3422
|
+
this.visualSelectedPath = null;
|
|
3423
|
+
this.visualSelection = { start: 0, end: 0 };
|
|
3078
3424
|
this.handleDocumentPointerDown = (event) => {
|
|
3079
3425
|
const target = event.target;
|
|
3080
3426
|
if (this.openMenu && target instanceof Node && !this.openMenu.contains(target) && !this.openMenuTrigger?.contains(target)) {
|
|
@@ -3116,6 +3462,12 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3116
3462
|
set locale(locale) {
|
|
3117
3463
|
this.setAttribute("locale", locale);
|
|
3118
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
|
+
}
|
|
3119
3471
|
prepareDocument(value) {
|
|
3120
3472
|
try {
|
|
3121
3473
|
this.documentState = parseSsml2(value);
|
|
@@ -3158,6 +3510,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3158
3510
|
}
|
|
3159
3511
|
}
|
|
3160
3512
|
}
|
|
3513
|
+
this.renderVisualEditor();
|
|
3161
3514
|
return;
|
|
3162
3515
|
}
|
|
3163
3516
|
if (name === "theme" && this.monaco) {
|
|
@@ -3175,6 +3528,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3175
3528
|
this.renderHelp();
|
|
3176
3529
|
return;
|
|
3177
3530
|
}
|
|
3531
|
+
if (name === "edit-mode") {
|
|
3532
|
+
this.updateEditMode();
|
|
3533
|
+
}
|
|
3178
3534
|
if (name === "show-decorations") {
|
|
3179
3535
|
this.decorationsVisible = newValue !== null;
|
|
3180
3536
|
this.updateDecorations();
|
|
@@ -3199,7 +3555,9 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3199
3555
|
display.dataset.ssmlEditorDisplay = "";
|
|
3200
3556
|
const editorContainer = document.createElement("div");
|
|
3201
3557
|
editorContainer.className = "ssml-editor-editor";
|
|
3202
|
-
|
|
3558
|
+
const visualContainer = document.createElement("div");
|
|
3559
|
+
visualContainer.className = "ssml-editor-visual";
|
|
3560
|
+
display.append(editorContainer, visualContainer);
|
|
3203
3561
|
root.append(toolbar, display);
|
|
3204
3562
|
this.replaceChildren(root);
|
|
3205
3563
|
this.root = root;
|
|
@@ -3207,8 +3565,11 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3207
3565
|
this.toolbarActions = toolbarActions;
|
|
3208
3566
|
this.display = display;
|
|
3209
3567
|
this.editorContainer = editorContainer;
|
|
3568
|
+
this.visualContainer = visualContainer;
|
|
3210
3569
|
this.renderToolbar();
|
|
3211
3570
|
this.renderHelp();
|
|
3571
|
+
this.updateEditMode();
|
|
3572
|
+
this.renderVisualEditor();
|
|
3212
3573
|
}
|
|
3213
3574
|
renderToolbar() {
|
|
3214
3575
|
const toolbar = this.toolbar;
|
|
@@ -3252,10 +3613,10 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3252
3613
|
for (const id of toolbarIds) {
|
|
3253
3614
|
const group = groupByButtonId.get(id);
|
|
3254
3615
|
if (previousGroup !== void 0 && group !== previousGroup) {
|
|
3255
|
-
const
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
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);
|
|
3259
3620
|
}
|
|
3260
3621
|
previousGroup = group;
|
|
3261
3622
|
const insertion = insertionById.get(id);
|
|
@@ -3267,8 +3628,24 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3267
3628
|
toolbarActions.append(this.createActionButton(id));
|
|
3268
3629
|
}
|
|
3269
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"));
|
|
3270
3635
|
this.updateActiveButtons();
|
|
3271
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
|
+
}
|
|
3272
3649
|
createActionButton(id) {
|
|
3273
3650
|
const copy = EDITOR_COPY[this.locale];
|
|
3274
3651
|
const labels = {
|
|
@@ -3648,6 +4025,150 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3648
4025
|
this.root.dataset.theme = isDarkTheme(this.theme) ? "dark" : "light";
|
|
3649
4026
|
}
|
|
3650
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
|
+
}
|
|
3651
4172
|
updateActiveButtons() {
|
|
3652
4173
|
const editor = this.editor;
|
|
3653
4174
|
const model = this.model;
|
|
@@ -3675,6 +4196,7 @@ var SsmlEditorElement = class extends HTMLElementBase {
|
|
|
3675
4196
|
return;
|
|
3676
4197
|
}
|
|
3677
4198
|
const model = monacoModule.editor.createModel(this.prepareDocument(this.value), "xml");
|
|
4199
|
+
this.renderVisualEditor();
|
|
3678
4200
|
const editor = monacoModule.editor.create(container, {
|
|
3679
4201
|
model,
|
|
3680
4202
|
theme: this.theme,
|
|
@@ -3773,7 +4295,8 @@ SsmlEditorElement.observedAttributes = [
|
|
|
3773
4295
|
"locale",
|
|
3774
4296
|
"show-toolbar",
|
|
3775
4297
|
"show-toolbar-labels",
|
|
3776
|
-
"show-decorations"
|
|
4298
|
+
"show-decorations",
|
|
4299
|
+
"edit-mode"
|
|
3777
4300
|
];
|
|
3778
4301
|
|
|
3779
4302
|
// src/index.ts
|