@ssml-builder-js/ssml-editor-react 2.4.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 +91 -0
- package/dist/index.d.mts +288 -0
- package/dist/index.d.ts +288 -0
- package/dist/index.js +5423 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5385 -0
- package/dist/index.mjs.map +1 -0
- package/e2e/monaco-editor.spec.ts +126 -0
- package/package.json +57 -0
- package/src/SsmlEditor.tsx +1302 -0
- package/src/buttonVisibility.ts +36 -0
- package/src/clearSsmlDocument.ts +57 -0
- package/src/components/popovers/InsertionPopover.tsx +136 -0
- package/src/components/popovers/InsertionPopovers.tsx +47 -0
- package/src/components/popovers/ProsodyPopovers.tsx +78 -0
- package/src/components/popovers/TextPopovers.tsx +8 -0
- package/src/components/popovers/TimingPopovers.tsx +8 -0
- package/src/constants/ssmlPresets.ts +660 -0
- package/src/constants/ui.ts +11 -0
- package/src/editableSsml.ts +251 -0
- package/src/formatXml.ts +600 -0
- package/src/hooks/useSsmlEditorState.ts +704 -0
- package/src/hooks/useSsmlMonaco.ts +393 -0
- package/src/index.tsx +50 -0
- package/src/locales.ts +435 -0
- package/src/ssmlCodeAction.ts +144 -0
- package/src/ssmlCodeLens.ts +226 -0
- package/src/ssmlCompletion.ts +129 -0
- package/src/ssmlContext.ts +196 -0
- package/src/ssmlDiagnostics.ts +191 -0
- package/src/ssmlHover.ts +703 -0
- package/src/ssmlInsertion.ts +75 -0
- package/src/ssmlInsertions.ts +471 -0
- package/src/styles/editorStyles.ts +282 -0
- package/test/format-xml-edge-cases.test.ts +107 -0
- package/test/index.test.ts +597 -0
- package/test/randomized-editor-invariants.test.ts +520 -0
- package/test/ui-components.test.tsx +854 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +17 -0
- package/vitest.config.mjs +10 -0
package/src/locales.ts
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
export type SsmlEditorLocale = "ja" | "en";
|
|
2
|
+
export type SsmlEditorLanguage = SsmlEditorLocale;
|
|
3
|
+
export type SsmlEditorLocalizedText = Readonly<Record<SsmlEditorLocale, string>>;
|
|
4
|
+
|
|
5
|
+
export interface EditorCopy {
|
|
6
|
+
editorAriaLabel: string;
|
|
7
|
+
toolbarAriaLabel: string;
|
|
8
|
+
undo: string;
|
|
9
|
+
undoTitle: string;
|
|
10
|
+
redo: string;
|
|
11
|
+
redoTitle: string;
|
|
12
|
+
clearAll: string;
|
|
13
|
+
clearAllTitle: string;
|
|
14
|
+
help: string;
|
|
15
|
+
helpTitle: string;
|
|
16
|
+
helpHeading: string;
|
|
17
|
+
helpDescription: string;
|
|
18
|
+
parameters: string;
|
|
19
|
+
format: string;
|
|
20
|
+
formatTitle: string;
|
|
21
|
+
decorations: string;
|
|
22
|
+
decorationsShowTitle: string;
|
|
23
|
+
decorationsHideTitle: string;
|
|
24
|
+
syntaxError: string;
|
|
25
|
+
selectionActions: string;
|
|
26
|
+
selectionCountSuffix: string;
|
|
27
|
+
previewSelection: string;
|
|
28
|
+
previewSelectionTitle: string;
|
|
29
|
+
noAvailableOptions: string;
|
|
30
|
+
styleNotSupported: string;
|
|
31
|
+
categoryEmotions: string;
|
|
32
|
+
categoryScenarios: string;
|
|
33
|
+
categoryMedia: string;
|
|
34
|
+
categoryOther: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface InlineBadgeCopy {
|
|
38
|
+
pause: string;
|
|
39
|
+
pitch: string;
|
|
40
|
+
prosody: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SsmlHoverParameterCopy {
|
|
44
|
+
title: string;
|
|
45
|
+
description: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SsmlHoverTagCopy {
|
|
49
|
+
title: string;
|
|
50
|
+
description: string;
|
|
51
|
+
parameters: Readonly<Record<string, SsmlHoverParameterCopy>>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface SsmlHoverLocale {
|
|
55
|
+
parameterHeading: string;
|
|
56
|
+
parametersHeading: string;
|
|
57
|
+
allowedValues: string;
|
|
58
|
+
example: string;
|
|
59
|
+
noParameters: string;
|
|
60
|
+
tags: Readonly<Record<string, SsmlHoverTagCopy>>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const EDITOR_COPY: Readonly<Record<SsmlEditorLocale, EditorCopy>> = {
|
|
64
|
+
ja: {
|
|
65
|
+
editorAriaLabel: "SSMLエディター",
|
|
66
|
+
toolbarAriaLabel: "SSMLツールバー",
|
|
67
|
+
clearAll: "全てクリア",
|
|
68
|
+
clearAllTitle: "音声設定を保持してXML要素を削除し本文を残す",
|
|
69
|
+
undo: "元に戻す",
|
|
70
|
+
undoTitle: "直前の変更を元に戻す",
|
|
71
|
+
redo: "やり直す",
|
|
72
|
+
redoTitle: "元に戻した変更をやり直す",
|
|
73
|
+
help: "説明",
|
|
74
|
+
helpTitle: "ボタンとパラメータの説明を表示",
|
|
75
|
+
helpHeading: "ボタンとパラメータの説明",
|
|
76
|
+
helpDescription: "各コントロールの機能とパラメータを確認できます。",
|
|
77
|
+
parameters: "パラメータ",
|
|
78
|
+
format: "フォーマット",
|
|
79
|
+
formatTitle: "本文のXMLを改行して見やすく表示",
|
|
80
|
+
decorations: "装飾",
|
|
81
|
+
decorationsShowTitle: "インライン装飾を表示",
|
|
82
|
+
decorationsHideTitle: "インライン装飾を非表示",
|
|
83
|
+
syntaxError: "構文エラー",
|
|
84
|
+
selectionActions: "選択範囲の操作",
|
|
85
|
+
selectionCountSuffix: "文字",
|
|
86
|
+
previewSelection: "選択部分を試聴",
|
|
87
|
+
previewSelectionTitle: "選択部分のSSMLを試聴",
|
|
88
|
+
noAvailableOptions: "利用可能な選択肢がありません。",
|
|
89
|
+
styleNotSupported: "この音声はスタイル指定に対応していません",
|
|
90
|
+
categoryEmotions: "感情・トーン",
|
|
91
|
+
categoryScenarios: "会話・シナリオ",
|
|
92
|
+
categoryMedia: "メディア・ナレーション",
|
|
93
|
+
categoryOther: "その他",
|
|
94
|
+
},
|
|
95
|
+
en: {
|
|
96
|
+
editorAriaLabel: "SSML editor",
|
|
97
|
+
toolbarAriaLabel: "SSML toolbar",
|
|
98
|
+
clearAll: "Clear all",
|
|
99
|
+
clearAllTitle: "Remove non-voice XML elements and keep the text and voice settings",
|
|
100
|
+
undo: "Undo",
|
|
101
|
+
undoTitle: "Undo the last change",
|
|
102
|
+
redo: "Redo",
|
|
103
|
+
redoTitle: "Redo the last undone change",
|
|
104
|
+
help: "Help",
|
|
105
|
+
helpTitle: "Show button and parameter descriptions",
|
|
106
|
+
helpHeading: "Button and parameter descriptions",
|
|
107
|
+
helpDescription: "Review what each control does and its parameters.",
|
|
108
|
+
parameters: "Parameters",
|
|
109
|
+
format: "Format",
|
|
110
|
+
formatTitle: "Format the XML in the editor",
|
|
111
|
+
decorations: "Decorations",
|
|
112
|
+
decorationsShowTitle: "Show inline decorations",
|
|
113
|
+
decorationsHideTitle: "Hide inline decorations",
|
|
114
|
+
syntaxError: "Syntax error",
|
|
115
|
+
selectionActions: "Selection actions",
|
|
116
|
+
selectionCountSuffix: " characters",
|
|
117
|
+
previewSelection: "Preview selection",
|
|
118
|
+
previewSelectionTitle: "Preview the selected SSML",
|
|
119
|
+
noAvailableOptions: "No options are available.",
|
|
120
|
+
styleNotSupported: "This voice does not support style selection.",
|
|
121
|
+
categoryEmotions: "Emotions / Tone",
|
|
122
|
+
categoryScenarios: "Conversations / Scenarios",
|
|
123
|
+
categoryMedia: "Media / Broadcast",
|
|
124
|
+
categoryOther: "Other",
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const INLINE_BADGE_COPY: Readonly<Record<SsmlEditorLocale, InlineBadgeCopy>> = {
|
|
129
|
+
ja: {
|
|
130
|
+
pause: "間",
|
|
131
|
+
pitch: "ピッチ変化",
|
|
132
|
+
prosody: "声の調整",
|
|
133
|
+
},
|
|
134
|
+
en: {
|
|
135
|
+
pause: "Pause",
|
|
136
|
+
pitch: "Pitch change",
|
|
137
|
+
prosody: "Prosody",
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const SSML_HOVER_COPY: Readonly<Record<SsmlEditorLocale, SsmlHoverLocale>> = {
|
|
142
|
+
ja: {
|
|
143
|
+
parameterHeading: "パラメータ",
|
|
144
|
+
parametersHeading: "パラメータ",
|
|
145
|
+
allowedValues: "使用できる値",
|
|
146
|
+
example: "例",
|
|
147
|
+
noParameters: "この要素にパラメータはありません。",
|
|
148
|
+
tags: {
|
|
149
|
+
voice: {
|
|
150
|
+
title: "音声",
|
|
151
|
+
description: "囲まれたテキストの合成に使用する音声と、任意の音声効果を選択します。",
|
|
152
|
+
parameters: {
|
|
153
|
+
name: { title: "name", description: "音声名。例: `en-US-JennyNeural`。" },
|
|
154
|
+
effect: { title: "effect", description: "任意の音声効果。例: `eq_car`。" },
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
prosody: {
|
|
158
|
+
title: "韻律",
|
|
159
|
+
description: "囲まれたテキストの速度、ピッチ、音量、またはピッチ曲線を変更します。",
|
|
160
|
+
parameters: {
|
|
161
|
+
rate: { title: "rate", description: "発話速度を指定します。" },
|
|
162
|
+
pitch: { title: "pitch", description: "名前付きの値、割合、周波数、または半音でピッチを調整します。" },
|
|
163
|
+
volume: { title: "volume", description: "名前付きの値、割合、またはデシベル値で音量を指定します。" },
|
|
164
|
+
contour: { title: "contour", description: "テキスト内の位置ごとの相対的なピッチ変化を定義します。" },
|
|
165
|
+
range: { title: "range", description: "音声のピッチ範囲を調整します。" },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
break: {
|
|
169
|
+
title: "間",
|
|
170
|
+
description: "単語やその他の音声コンテンツの間にポーズを挿入します。",
|
|
171
|
+
parameters: {
|
|
172
|
+
time: { title: "time", description: "ポーズの長さ。例: `500ms` または `1s`。" },
|
|
173
|
+
strength: { title: "strength", description: "相対的なポーズの強さ。" },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
"mstts:express-as": {
|
|
177
|
+
title: "表現",
|
|
178
|
+
description: "囲まれたテキストに音声スタイル、スタイルの強さ、または役割を適用します。",
|
|
179
|
+
parameters: {
|
|
180
|
+
style: { title: "style", description: "選択した音声が対応する音声スタイル。例: `cheerful`。" },
|
|
181
|
+
styledegree: { title: "styledegree", description: "選択した音声スタイルの強さを指定します。" },
|
|
182
|
+
role: { title: "role", description: "対応している場合に音声の役割を変更します。" },
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
"say-as": {
|
|
186
|
+
title: "読み上げ方",
|
|
187
|
+
description: "囲まれたテキストの解釈方法と読み上げ方を指定します。",
|
|
188
|
+
parameters: {
|
|
189
|
+
"interpret-as": { title: "interpret-as", description: "文字、数字、日付、時刻などの解釈を指定します。" },
|
|
190
|
+
format: { title: "format", description: "選択した解釈の形式を補足します。" },
|
|
191
|
+
detail: { title: "detail", description: "選択した解釈の詳細を補足します。" },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
phoneme: {
|
|
195
|
+
title: "音素",
|
|
196
|
+
description: "指定した音素表記で通常の発音を置き換えます。",
|
|
197
|
+
parameters: {
|
|
198
|
+
alphabet: { title: "alphabet", description: "`ph` 値に使用する音素アルファベット。" },
|
|
199
|
+
ph: { title: "ph", description: "囲まれたテキストの音素表記。" },
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
emphasis: {
|
|
203
|
+
title: "強調",
|
|
204
|
+
description: "囲まれたテキストを強調します。",
|
|
205
|
+
parameters: {
|
|
206
|
+
level: { title: "level", description: "強調の度合いを指定します。" },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
audio: {
|
|
210
|
+
title: "音声ファイル",
|
|
211
|
+
description: "合成結果の一部として音声ファイルを再生します。",
|
|
212
|
+
parameters: {
|
|
213
|
+
src: { title: "src", description: "音声ファイルの URI。" },
|
|
214
|
+
desc: { title: "desc", description: "音声を再生できない場合に使用する代替テキスト。" },
|
|
215
|
+
clipBegin: { title: "clipBegin", description: "音声ファイル内の開始位置。例: `0s`。" },
|
|
216
|
+
clipEnd: { title: "clipEnd", description: "音声ファイル内の終了位置。例: `5s`。" },
|
|
217
|
+
speed: { title: "speed", description: "音声ファイルの再生速度。例: `1.0`。" },
|
|
218
|
+
repeatCount: { title: "repeatCount", description: "音声を繰り返す回数。例: `2`。" },
|
|
219
|
+
repeatDuration: { title: "repeatDuration", description: "音声を繰り返す合計時間。例: `10s`。" },
|
|
220
|
+
soundLevel: { title: "soundLevel", description: "デシベル単位の音量調整。例: `-3dB`。" },
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
sub: {
|
|
224
|
+
title: "置換",
|
|
225
|
+
description: "囲まれたテキストの代わりに別名テキストを読み上げます。",
|
|
226
|
+
parameters: {
|
|
227
|
+
alias: { title: "alias", description: "元のテキストの代わりに読み上げるテキスト。例: `World Wide Web`。" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
lang: {
|
|
231
|
+
title: "言語",
|
|
232
|
+
description: "囲まれたテキストの読み上げ言語を変更します。",
|
|
233
|
+
parameters: {
|
|
234
|
+
"xml:lang": { title: "xml:lang", description: "BCP-47 言語タグ。例: `ja-JP`。" },
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
mark: {
|
|
238
|
+
title: "マーカー",
|
|
239
|
+
description: "合成音声ストリームにカスタムマーカーを挿入します。",
|
|
240
|
+
parameters: {
|
|
241
|
+
name: { title: "name", description: "アプリケーションで定義したマーカー名。例: `chapter-1`。" },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
bookmark: {
|
|
245
|
+
title: "ブックマーク",
|
|
246
|
+
description: "合成音声ストリームにブックマークマーカーを挿入します。",
|
|
247
|
+
parameters: {
|
|
248
|
+
mark: { title: "mark", description: "アプリケーションで定義したブックマーク名。例: `chapter-1`。" },
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
lexicon: {
|
|
252
|
+
title: "発音辞書",
|
|
253
|
+
description: "合成文書に発音辞書を関連付けます。",
|
|
254
|
+
parameters: {
|
|
255
|
+
uri: { title: "uri", description: "発音辞書の URI。" },
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
p: { title: "段落", description: "テキストを段落としてまとめます。", parameters: {} },
|
|
259
|
+
s: { title: "文", description: "テキストを文としてまとめます。", parameters: {} },
|
|
260
|
+
w: { title: "単語", description: "テキストを単語としてまとめます。", parameters: {} },
|
|
261
|
+
"mstts:silence": {
|
|
262
|
+
title: "無音",
|
|
263
|
+
description: "テキストの前後、または句読点の境界に指定した無音を追加します。",
|
|
264
|
+
parameters: {
|
|
265
|
+
type: { title: "type", description: "無音の位置または句読点の境界。" },
|
|
266
|
+
value: { title: "value", description: "無音の長さ。例: `300ms`。" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
"mstts:viseme": {
|
|
270
|
+
title: "ビゼーム",
|
|
271
|
+
description: "合成音声のビゼームイベントを要求します。",
|
|
272
|
+
parameters: {
|
|
273
|
+
type: { title: "type", description: "ビゼームイベントの形式。" },
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
en: {
|
|
279
|
+
parameterHeading: "Parameter",
|
|
280
|
+
parametersHeading: "Parameters",
|
|
281
|
+
allowedValues: "Allowed values",
|
|
282
|
+
example: "Example",
|
|
283
|
+
noParameters: "This element has no parameters.",
|
|
284
|
+
tags: {
|
|
285
|
+
voice: {
|
|
286
|
+
title: "Voice",
|
|
287
|
+
description: "Selects the voice and optional voice effect used to synthesize the enclosed text.",
|
|
288
|
+
parameters: {
|
|
289
|
+
name: { title: "name", description: "The voice name, such as `en-US-JennyNeural`." },
|
|
290
|
+
effect: { title: "effect", description: "An optional voice effect, such as `eq_car`." },
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
prosody: {
|
|
294
|
+
title: "Prosody",
|
|
295
|
+
description: "Changes the speaking rate, pitch, volume, or pitch contour of the enclosed text.",
|
|
296
|
+
parameters: {
|
|
297
|
+
rate: { title: "rate", description: "Controls speaking speed." },
|
|
298
|
+
pitch: {
|
|
299
|
+
title: "pitch",
|
|
300
|
+
description: "Adjusts pitch using a named value, percentage, frequency, or semitone value.",
|
|
301
|
+
},
|
|
302
|
+
volume: {
|
|
303
|
+
title: "volume",
|
|
304
|
+
description: "Controls loudness using a named value, percentage, or decibel value.",
|
|
305
|
+
},
|
|
306
|
+
contour: {
|
|
307
|
+
title: "contour",
|
|
308
|
+
description: "Defines a sequence of relative pitch changes at positions in the text.",
|
|
309
|
+
},
|
|
310
|
+
range: { title: "range", description: "Adjusts the pitch range of the voice." },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
break: {
|
|
314
|
+
title: "Break",
|
|
315
|
+
description: "Inserts a pause between words or other spoken content.",
|
|
316
|
+
parameters: {
|
|
317
|
+
time: { title: "time", description: "The pause duration, for example `500ms` or `1s`." },
|
|
318
|
+
strength: { title: "strength", description: "The relative pause strength." },
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
"mstts:express-as": {
|
|
322
|
+
title: "Express-as",
|
|
323
|
+
description: "Applies a speaking style, style degree, or role to the enclosed text.",
|
|
324
|
+
parameters: {
|
|
325
|
+
style: {
|
|
326
|
+
title: "style",
|
|
327
|
+
description: "The speaking style supported by the selected voice, such as `cheerful`.",
|
|
328
|
+
},
|
|
329
|
+
styledegree: { title: "styledegree", description: "Controls the intensity of the selected speaking style." },
|
|
330
|
+
role: { title: "role", description: "Changes the speaking role when supported by the selected voice." },
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
"say-as": {
|
|
334
|
+
title: "Say-as",
|
|
335
|
+
description: "Controls how the enclosed text is interpreted and spoken.",
|
|
336
|
+
parameters: {
|
|
337
|
+
"interpret-as": {
|
|
338
|
+
title: "interpret-as",
|
|
339
|
+
description: "Specifies the interpretation, such as characters, digits, date, or time.",
|
|
340
|
+
},
|
|
341
|
+
format: { title: "format", description: "Provides a format hint for the selected interpretation." },
|
|
342
|
+
detail: {
|
|
343
|
+
title: "detail",
|
|
344
|
+
description: "Provides an additional detail hint for the selected interpretation.",
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
phoneme: {
|
|
349
|
+
title: "Phoneme",
|
|
350
|
+
description: "Replaces normal pronunciation with the supplied phonetic pronunciation.",
|
|
351
|
+
parameters: {
|
|
352
|
+
alphabet: { title: "alphabet", description: "The phonetic alphabet used by the `ph` value." },
|
|
353
|
+
ph: { title: "ph", description: "The phonetic pronunciation for the enclosed text." },
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
emphasis: {
|
|
357
|
+
title: "Emphasis",
|
|
358
|
+
description: "Adds emphasis to the enclosed text.",
|
|
359
|
+
parameters: {
|
|
360
|
+
level: { title: "level", description: "Controls the amount of emphasis." },
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
audio: {
|
|
364
|
+
title: "Audio",
|
|
365
|
+
description: "Plays an audio file as part of the synthesized output.",
|
|
366
|
+
parameters: {
|
|
367
|
+
src: { title: "src", description: "The URI of the audio file." },
|
|
368
|
+
desc: { title: "desc", description: "Alternative text to use if the audio cannot be played." },
|
|
369
|
+
clipBegin: { title: "clipBegin", description: "The starting offset within the audio file." },
|
|
370
|
+
clipEnd: { title: "clipEnd", description: "The ending offset within the audio file." },
|
|
371
|
+
speed: { title: "speed", description: "The playback speed of the audio file." },
|
|
372
|
+
repeatCount: { title: "repeatCount", description: "The number of times to repeat the audio." },
|
|
373
|
+
repeatDuration: {
|
|
374
|
+
title: "repeatDuration",
|
|
375
|
+
description: "The total duration for which the audio may repeat.",
|
|
376
|
+
},
|
|
377
|
+
soundLevel: { title: "soundLevel", description: "The audio volume adjustment in decibels." },
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
sub: {
|
|
381
|
+
title: "Substitution",
|
|
382
|
+
description: "Substitutes the alias text when speaking the enclosed text.",
|
|
383
|
+
parameters: {
|
|
384
|
+
alias: { title: "alias", description: "The text to speak instead of the enclosed text." },
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
lang: {
|
|
388
|
+
title: "Language",
|
|
389
|
+
description: "Changes the language used for the enclosed text.",
|
|
390
|
+
parameters: {
|
|
391
|
+
"xml:lang": { title: "xml:lang", description: "The BCP-47 language tag." },
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
mark: {
|
|
395
|
+
title: "Mark",
|
|
396
|
+
description: "Inserts a custom marker into the synthesized audio stream.",
|
|
397
|
+
parameters: {
|
|
398
|
+
name: { title: "name", description: "The application-defined marker name." },
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
bookmark: {
|
|
402
|
+
title: "Bookmark",
|
|
403
|
+
description: "Inserts a bookmark marker into the synthesized audio stream.",
|
|
404
|
+
parameters: {
|
|
405
|
+
mark: { title: "mark", description: "The application-defined bookmark name." },
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
lexicon: {
|
|
409
|
+
title: "Lexicon",
|
|
410
|
+
description: "Associates a pronunciation lexicon with the synthesized document.",
|
|
411
|
+
parameters: {
|
|
412
|
+
uri: { title: "uri", description: "The URI of the pronunciation lexicon." },
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
p: { title: "Paragraph", description: "Groups text into a paragraph.", parameters: {} },
|
|
416
|
+
s: { title: "Sentence", description: "Groups text into a sentence.", parameters: {} },
|
|
417
|
+
w: { title: "Word", description: "Groups text into a word.", parameters: {} },
|
|
418
|
+
"mstts:silence": {
|
|
419
|
+
title: "Silence",
|
|
420
|
+
description: "Adds a specified silence before or after text or at a punctuation boundary.",
|
|
421
|
+
parameters: {
|
|
422
|
+
type: { title: "type", description: "The silence position or punctuation boundary." },
|
|
423
|
+
value: { title: "value", description: "The silence duration, for example `300ms`." },
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
"mstts:viseme": {
|
|
427
|
+
title: "Viseme",
|
|
428
|
+
description: "Requests viseme events for the synthesized audio.",
|
|
429
|
+
parameters: {
|
|
430
|
+
type: { title: "type", description: "The viseme event format." },
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { Monaco } from "@monaco-editor/react";
|
|
2
|
+
import { SSML_DIAGNOSTIC_CODES } from "./ssmlDiagnostics";
|
|
3
|
+
|
|
4
|
+
type MonacoLanguages = Monaco["languages"];
|
|
5
|
+
type MonacoCodeActionProvider = Parameters<MonacoLanguages["registerCodeActionProvider"]>[1];
|
|
6
|
+
type MonacoCodeActionMethod = NonNullable<MonacoCodeActionProvider["provideCodeActions"]>;
|
|
7
|
+
type MonacoCodeActionModel = Parameters<MonacoCodeActionMethod>[0];
|
|
8
|
+
type MonacoCodeActionRange = Parameters<MonacoCodeActionMethod>[1];
|
|
9
|
+
type MonacoCodeActionContext = Parameters<MonacoCodeActionMethod>[2];
|
|
10
|
+
type MonacoCodeActionMarker = MonacoCodeActionContext["markers"][number];
|
|
11
|
+
|
|
12
|
+
const MISSING_TIME_UNIT_ACTION_TITLE = '単位 "ms" を付与して修復';
|
|
13
|
+
|
|
14
|
+
interface StructuredMarkerCode {
|
|
15
|
+
suggestedValue?: unknown;
|
|
16
|
+
target?: unknown;
|
|
17
|
+
value?: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getStructuredMarkerCode(marker: MonacoCodeActionMarker): StructuredMarkerCode | undefined {
|
|
21
|
+
return typeof marker.code === "object" && marker.code !== null
|
|
22
|
+
? (marker.code as unknown as StructuredMarkerCode)
|
|
23
|
+
: undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createMissingTimeUnitAction(model: MonacoCodeActionModel, marker: MonacoCodeActionMarker) {
|
|
27
|
+
return {
|
|
28
|
+
title: MISSING_TIME_UNIT_ACTION_TITLE,
|
|
29
|
+
kind: "quickfix",
|
|
30
|
+
isPreferred: true,
|
|
31
|
+
diagnostics: [marker],
|
|
32
|
+
edit: {
|
|
33
|
+
edits: [
|
|
34
|
+
{
|
|
35
|
+
resource: model.uri,
|
|
36
|
+
versionId: model.getVersionId(),
|
|
37
|
+
textEdit: {
|
|
38
|
+
range: {
|
|
39
|
+
startLineNumber: marker.startLineNumber,
|
|
40
|
+
startColumn: marker.startColumn,
|
|
41
|
+
endLineNumber: marker.endLineNumber,
|
|
42
|
+
endColumn: marker.endColumn,
|
|
43
|
+
},
|
|
44
|
+
text: `${model.getValueInRange(marker)}ms`,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createUnclosedTagAction(model: MonacoCodeActionModel, marker: MonacoCodeActionMarker, target: string) {
|
|
53
|
+
return {
|
|
54
|
+
title: `閉じタグ "</${target}>" を自動挿入`,
|
|
55
|
+
kind: "quickfix",
|
|
56
|
+
isPreferred: true,
|
|
57
|
+
diagnostics: [marker],
|
|
58
|
+
edit: {
|
|
59
|
+
edits: [
|
|
60
|
+
{
|
|
61
|
+
resource: model.uri,
|
|
62
|
+
versionId: model.getVersionId(),
|
|
63
|
+
textEdit: {
|
|
64
|
+
range: {
|
|
65
|
+
startLineNumber: marker.endLineNumber,
|
|
66
|
+
startColumn: marker.endColumn,
|
|
67
|
+
endLineNumber: marker.endLineNumber,
|
|
68
|
+
endColumn: marker.endColumn,
|
|
69
|
+
},
|
|
70
|
+
text: `</${target}>`,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createInvalidAttributeValueAction(
|
|
79
|
+
model: MonacoCodeActionModel,
|
|
80
|
+
marker: MonacoCodeActionMarker,
|
|
81
|
+
suggestedValue: string,
|
|
82
|
+
) {
|
|
83
|
+
return {
|
|
84
|
+
title: `"${suggestedValue}" に変更`,
|
|
85
|
+
kind: "quickfix",
|
|
86
|
+
isPreferred: true,
|
|
87
|
+
diagnostics: [marker],
|
|
88
|
+
edit: {
|
|
89
|
+
edits: [
|
|
90
|
+
{
|
|
91
|
+
resource: model.uri,
|
|
92
|
+
versionId: model.getVersionId(),
|
|
93
|
+
textEdit: {
|
|
94
|
+
range: {
|
|
95
|
+
startLineNumber: marker.startLineNumber,
|
|
96
|
+
startColumn: marker.startColumn,
|
|
97
|
+
endLineNumber: marker.endLineNumber,
|
|
98
|
+
endColumn: marker.endColumn,
|
|
99
|
+
},
|
|
100
|
+
text: suggestedValue,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function registerSsmlCodeActions(monaco: Monaco): ReturnType<MonacoLanguages["registerCodeActionProvider"]> {
|
|
109
|
+
const provider: MonacoCodeActionProvider = {
|
|
110
|
+
provideCodeActions(
|
|
111
|
+
model: MonacoCodeActionModel,
|
|
112
|
+
_range: MonacoCodeActionRange,
|
|
113
|
+
context: MonacoCodeActionContext,
|
|
114
|
+
): ReturnType<MonacoCodeActionMethod> {
|
|
115
|
+
const actions = context.markers.flatMap((marker: MonacoCodeActionMarker) => {
|
|
116
|
+
if (marker.code === SSML_DIAGNOSTIC_CODES.MISSING_TIME_UNIT) {
|
|
117
|
+
return [createMissingTimeUnitAction(model, marker)];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const structuredCode = getStructuredMarkerCode(marker);
|
|
121
|
+
if (structuredCode?.value === SSML_DIAGNOSTIC_CODES.UNCLOSED_TAG && typeof structuredCode.target === "string") {
|
|
122
|
+
return [createUnclosedTagAction(model, marker, structuredCode.target)];
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
structuredCode?.value === SSML_DIAGNOSTIC_CODES.INVALID_ATTR_VALUE &&
|
|
126
|
+
typeof structuredCode.suggestedValue === "string"
|
|
127
|
+
) {
|
|
128
|
+
return [createInvalidAttributeValueAction(model, marker, structuredCode.suggestedValue)];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return [];
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
actions,
|
|
136
|
+
dispose() {},
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return monaco.languages.registerCodeActionProvider("xml", provider, {
|
|
142
|
+
providedCodeActionKinds: ["quickfix"],
|
|
143
|
+
});
|
|
144
|
+
}
|