@siliconoid/xssml-editor 1.0.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/README.md ADDED
@@ -0,0 +1,244 @@
1
+ # @siliconoid/xssml-editor
2
+
3
+ 基于 ProseMirror 的 XSSML 富文本编辑器 SDK,支持语音合成标记语言 (XSSML) 的可视化编辑。
4
+
5
+ ## ✨ 特性
6
+
7
+ - 🎯 **XSSML 标准对齐** - 基本遵循 W3C XSSML 1.1 规范
8
+ - 🔌 **可扩展架构** - Registry 模式支持自定义扩展
9
+ - 📦 **模块化设计** - Command/Node/Mark/Plugin 分离
10
+ - 🛡️ **类型安全** - 完整的 TypeScript 类型定义
11
+ - 🔍 **内容提取** - 内置分句与段落提取引擎 (TextExtractor)
12
+
13
+ ## 📦 安装
14
+
15
+ ```bash
16
+ npm install @siliconoid/xssml-editor
17
+ ```
18
+
19
+ ## 🚀 快速开始
20
+
21
+ ```typescript
22
+ import { MarkEditor, SayAsCommands, SayAsInterpretType } from '@siliconoid/xssml-editor';
23
+ import '@siliconoid/xssml-editor/dist/index.css';
24
+
25
+ // 创建编辑器
26
+ const editor = new MarkEditor({
27
+ parent: document.getElementById('editor')!,
28
+ content: '',
29
+ });
30
+
31
+ // 设置数字读法
32
+ editor.execute(SayAsCommands.setSayAs, {
33
+ interpretAs: SayAsInterpretType.cardinal,
34
+ });
35
+
36
+ // 获取内容
37
+ const content = editor.getContent();
38
+ ```
39
+
40
+ ## 📖 核心 API (Core API)
41
+
42
+ ### MarkEditor
43
+
44
+ 编辑器核心实例,管理文档状态与命令执行。
45
+
46
+ #### 初始化
47
+
48
+ ```typescript
49
+ import { MarkEditor } from '@siliconoid/xssml-editor';
50
+ import '@siliconoid/xssml-editor/dist/index.css';
51
+
52
+ const editor = new MarkEditor({
53
+ parent: document.getElementById('editor-root')!, // 挂载容器
54
+ content: '', // 初始内容 (JSON 或空字符串)
55
+ maxWordCount: 1000, // 最大字数限制
56
+ editable: true, // 是否可编辑
57
+ // 自定义悬浮菜单渲染
58
+ floatingMenu: {
59
+ onRender: (mountPoint, state) => {
60
+ // 在 mountPoint 中渲染您的自定义菜单
61
+ // state.isActive: 是否显示
62
+ // state.selection: 当前选区信息
63
+ },
64
+ },
65
+ // 自定义右键菜单渲染
66
+ contextMenu: {
67
+ onRender: (mountPoint, state) => {
68
+ // 在 mountPoint 中渲染您的自定义菜单
69
+ },
70
+ },
71
+ });
72
+ ```
73
+
74
+ #### 常用属性
75
+
76
+ | 属性 | 类型 | 说明 |
77
+ | :--------- | :-------- | :---------------------------------- |
78
+ | `editable` | `boolean` | 获取或设置编辑器的读写状态 |
79
+ | `count` | `number` | **[只读]** 获取当前文档的有效字符数 |
80
+
81
+ #### 常用方法
82
+
83
+ | 方法 | 说明 | 示例 |
84
+ | :------------------------- | :---------------------------------- | :---------------------------------------------------------- |
85
+ | `setContent(json)` | 设置编辑器内容 (JSON格式) | `editor.setContent(docJSON)` |
86
+ | `setContentFromSSML(ssml)` | 解析并导入 XSSML 字符串 | `editor.setContentFromSSML('<speak>Hello</speak>')` |
87
+ | `setContentFromHTML(html)` | 解析并导入 HTML (自动兼容旧版标记) | `editor.setContentFromHTML('<p>Text</p>')` |
88
+ | `execute(cmd, args)` | 执行编辑器命令 | `editor.execute(BreakCommands.insertBreak, { value: 500 })` |
89
+ | `toJSON()` | 导出标准的 JSON 文档结构 | `const doc = editor.toJSON()` |
90
+ | `getPlainText(mode?)` | 获取纯文本 (支持全文/选区/光标后) | `editor.getPlainText({ contentMode: 'selection' })` |
91
+ | `getDetermination()` | 获取提取后的播放片段列表 | `editor.getPlaybackSegments()` |
92
+ | `getSelection()` | 获取当前选区信息 (start, end, text) | `const { text } = editor.getSelection()` |
93
+ | `destroy()` | 销毁编辑器实例 | `editor.destroy()` |
94
+
95
+ #### 事件监听
96
+
97
+ ```typescript
98
+ // 监听字数变化
99
+ editor.on('wordCount', ({ count, exceeded }) => {
100
+ console.log(`当前字数: ${count}`, exceeded ? '已超限' : '');
101
+ });
102
+
103
+ // 监听命令状态变化 (用于更新 UI 按钮状态)
104
+ editor.on('stateChange', ({ commands }) => {
105
+ const isUndoable = commands['undo'].available;
106
+ });
107
+
108
+ // 监听富媒体点击 (如图片被点击)
109
+ editor.on('plugin.textmedia_click', (data) => {
110
+ console.log('Clicked:', data);
111
+ });
112
+ ```
113
+
114
+ ## 🛠️ 命令系统 (Commands)
115
+
116
+ 所有命令均通过 `editor.execute(Command, payload)` 调用。
117
+
118
+ ### 1. 语音标注 (TTS)
119
+
120
+ 控制语音合成的核心参数。
121
+
122
+ ```typescript
123
+ import {
124
+ BreakCommands,
125
+ SayAsCommands,
126
+ SayAsInterpretType,
127
+ SpeechRateCommands,
128
+ } from '@siliconoid/xssml-editor';
129
+
130
+ // 插入 500ms 停顿
131
+ editor.execute(BreakCommands.insertBreak, { value: 500 }); // value: ms
132
+
133
+ // 插入换气
134
+ editor.execute(BreathCommands.insertBreath);
135
+
136
+ // 设置选中文本的读法 (如:数值)
137
+ editor.execute(SayAsCommands.setSayAs, {
138
+ interpretAs: SayAsInterpretType.cardinal, // 'digits' | 'cardinal' | ...
139
+ });
140
+
141
+ // 调整选中文本的语速
142
+ editor.execute(SpeechRateCommands.setSpeechRate, { value: 1.5 }); // value:倍率
143
+
144
+ // 开启/关闭多音字建议模式
145
+ editor.execute(PhonemeCommands.switchPhonemeMode, { active: true });
146
+ ```
147
+
148
+ ### 2. 交互与数字人 (Interaction & Avatar)
149
+
150
+ 用于控制数字人动作或互动逻辑。
151
+
152
+ ```typescript
153
+ import { ActionCommands, WaitCommands, CmdCommands } from '@siliconoid/xssml-editor';
154
+
155
+ // 插入动作 (动作ID, 动作名称)
156
+ editor.execute(ActionCommands.insertAction, { value: 'wave_hand', label: '招手' });
157
+
158
+ // 插入等待指令 (等待类型, 参数)
159
+ editor.execute(WaitCommands.insertWait, {
160
+ waitType: 'video_ended', // 'sleep' | 'video_ended' | 'audio_ended'
161
+ label: '等待视频结束',
162
+ });
163
+
164
+ // 插入自定义指令
165
+ editor.execute(CmdCommands.insertCmd, { value: 'custom_cmd', label: '自定义指令' });
166
+ ```
167
+
168
+ ### 3. 特效与资源 (Effect & Media)
169
+
170
+ ```typescript
171
+ import { SoundEventCommands, TextMediaCommands } from '@siliconoid/xssml-editor';
172
+
173
+ // 插入音效
174
+ editor.execute(SoundEventCommands.insertSoundEvent, {
175
+ value: 'applause.mp3',
176
+ label: '欢呼声',
177
+ volume: 80,
178
+ });
179
+
180
+ // 插入或更新图片/视频 (TextMedia)
181
+ editor.execute(TextMediaCommands.setTextMedia, {
182
+ src: 'https://example.com/image.png',
183
+ mediaType: 'image',
184
+ extra: 'some_metadata',
185
+ });
186
+ ```
187
+
188
+ ### 4. 基础编辑 (Basis)
189
+
190
+ ```typescript
191
+ import { HistoryCommands, TextInputCommands, HighlightCommands } from '@siliconoid/xssml-editor';
192
+
193
+ // 撤销 / 重做
194
+ editor.execute(HistoryCommands.undo);
195
+ editor.execute(HistoryCommands.redo);
196
+
197
+ // 替换指定范围文本 (常用于 AI 润色)
198
+ editor.execute(TextInputCommands.replaceRange, {
199
+ from: 0,
200
+ to: 5,
201
+ content: 'New Text',
202
+ });
203
+
204
+ // 高亮范围 / 清除高亮
205
+ editor.execute(HighlightCommands.highlight, { from: 0, to: 10 });
206
+ editor.execute(HighlightCommands.clearHighlight);
207
+ ```
208
+
209
+ ## 🧩 文本提取引擎 (TextExtractor)
210
+
211
+ `TextExtractor` 是一个独立的无头工具,用于将结构化的编辑器 JSON 数据转换为扁平化的播放列表或合成请求序列。
212
+
213
+ ```typescript
214
+ import { TextExtractor } from '@siliconoid/xssml-editor';
215
+
216
+ const extractor = new TextExtractor();
217
+ const docJSON = editor.toJSON();
218
+
219
+ // 1. 提取所有句子 (自动处理标点分句)
220
+ const sentences = extractor.extractSentences(docJSON);
221
+ sentences.forEach((s) => {
222
+ console.log(`[${s.start}-${s.end}] ${s.text}`);
223
+ // s.events 包含该句子内的动作、音效等指令
224
+ });
225
+
226
+ // 2. 按段落提取 (保留段落结构和段落级指令)
227
+ const paragraphResult = extractor.paragraphize(docJSON);
228
+ console.log(paragraphResult.paragraphs);
229
+ ```
230
+
231
+ ## 📁 目录结构
232
+
233
+ ```
234
+ dist/
235
+ ├── index.js # CJS (Legacy)
236
+ ├── index.esm.js # ESM (Recommended)
237
+ ├── index.umd.js # UMD (Browser Global: XSSMLEditor)
238
+ ├── index.css # Core Styles
239
+ └── index.d.ts # Typings
240
+ ```
241
+
242
+ ## 📄 许可证
243
+
244
+ MIT
package/dist/index.css ADDED
@@ -0,0 +1,465 @@
1
+ .ssml-editor-phoneme__list {
2
+ display: flex;
3
+ flex-flow: row nowrap;
4
+ align-items: center;
5
+ gap: 4px;
6
+ user-select: none;
7
+ }
8
+
9
+ .ssml-editor-phoneme__item {
10
+ padding: 0.3em 4px;
11
+ cursor: pointer;
12
+ color: var(--tts-mark-ssml-editor-phoneme__popover-item__stroke-color);
13
+ }
14
+ .ssml-editor-phoneme__item:hover {
15
+ background-color: rgba(0, 0, 0, 0.1);
16
+ }
17
+ .ssml-editor-phoneme__item {
18
+ border-radius: 4px;
19
+ }
20
+
21
+ .ssml-editor-phoneme__popover {
22
+ z-index: 10000;
23
+ background: var(--tts-mark-editor-popover__fill-color, #fff);
24
+ border-radius: 6px;
25
+ padding: 0.1em 0.3em;
26
+ box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.3);
27
+ }
28
+
29
+ .ssml-editor-number-tip__char {
30
+ background-color: rgba(39, 174, 96, 0.2);
31
+ border-bottom: 2px solid #27ae60;
32
+ cursor: pointer;
33
+ transition: background-color 0.2s;
34
+ }
35
+ .ssml-editor-number-tip__char:hover {
36
+ background-color: rgba(39, 174, 96, 0.3);
37
+ }
38
+
39
+ .ssml-editor-number-tip__popover .ssml-editor-number-tip__list {
40
+ display: flex;
41
+ flex-direction: column;
42
+ padding: 4px;
43
+ background: #fff;
44
+ border-radius: 4px;
45
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
46
+ min-width: 100px;
47
+ }
48
+ .ssml-editor-number-tip__popover .ssml-editor-number-tip__item {
49
+ padding: 6px 12px;
50
+ cursor: pointer;
51
+ font-size: 13px;
52
+ color: #333;
53
+ border-radius: 2px;
54
+ white-space: nowrap;
55
+ }
56
+ .ssml-editor-number-tip__popover .ssml-editor-number-tip__item:hover {
57
+ background-color: #f5f5f5;
58
+ color: #27ae60;
59
+ }
60
+ .ssml-editor-number-tip__popover .ssml-editor-number-tip__item[data-value=""] {
61
+ color: #ff4d4f;
62
+ border-top: 1px solid #f0f0f0;
63
+ margin-top: 4px;
64
+ padding-top: 8px;
65
+ }
66
+ .ssml-editor-number-tip__popover .ssml-editor-number-tip__item[data-value=""]:hover {
67
+ background-color: #fff1f0;
68
+ }
69
+
70
+ :root {
71
+ --tts-mark-ssml-editor-phoneme__popover-item__stroke-color: #333;
72
+ }
73
+
74
+ .ssml-editor {
75
+ -webkit-overflow-scrolling: touch;
76
+ min-height: 100%;
77
+ line-height: 1.5;
78
+ overflow: auto;
79
+ padding: 0.5em;
80
+ caret-color: auto;
81
+ }
82
+ .ssml-editor.readonly {
83
+ caret-color: transparent;
84
+ cursor: default;
85
+ }
86
+ .ssml-editor.readonly.ssml-editor-focused {
87
+ outline: none;
88
+ box-shadow: none;
89
+ }
90
+ .ssml-editor.readonly * {
91
+ caret-color: transparent;
92
+ }
93
+ .ssml-editor {
94
+ --tts-mark-editor-popover__fill-color: #fff;
95
+ --tts-mark-word__fill-color: rgb(13 226 162 / 100%);
96
+ --tts-mark-breath__fill-color: rgb(16 185 237 / 100%);
97
+ --tts-mark-break__fill-color: rgb(104 56 252 / 100%);
98
+ --tts-mark-speaker__fill-color: #dc62f4;
99
+ --tts-mark-speaker__stroke-color: #dc62f4;
100
+ --tts-mark-speed__speaker-color: #fff;
101
+ --tts-mark-action__fill-color: transparent;
102
+ --tts-mark-action__stroke-color: rgb(255 145 2 / 100%);
103
+ --tts-mark-emotion__fill-color: transparent;
104
+ --tts-mark-emotion__stroke-color: #027bff;
105
+ --tts-mark-speed__fill-color: transparent;
106
+ --tts-mark-speed__stroke-color: #ff9102;
107
+ --tts-mark-pitch__fill-color: transparent;
108
+ --tts-mark-pitch__stroke-color: #0ca4fb;
109
+ --tts-mark-volume__fill-color: transparent;
110
+ --tts-mark-volume__stroke-color: green;
111
+ --tts-mark-say-as__border-color: rgb(26 145 255 / 100%);
112
+ --tts-mark-say-as__fill-color: rgb(26 145 255 / 100%);
113
+ /** @deprecated Use tts-mark-say-as instead */
114
+ --tts-mark-number__border-color: var(--tts-mark-say-as__border-color);
115
+ --tts-mark-number__fill-color: var(--tts-mark-say-as__fill-color);
116
+ --tts-mark-phoneme__fill-color: #0ca4fb;
117
+ --tts-mark-phoneme__border-color: #0ca4fb;
118
+ --tts-deco-phoneme__fill-color: rgb(255 220 114);
119
+ --tts-deco-phoneme__color: rgb(255 0 0);
120
+ --tts-mark-sub__fill-color: transparent;
121
+ --tts-mark-sub__border-color: #0ca4fb;
122
+ /** @deprecated Use tts-mark-sub instead */
123
+ --tts-mark-alias__fill-color: var(--tts-mark-sub__fill-color);
124
+ --tts-mark-alias__border-color: var(--tts-mark-sub__border-color);
125
+ --tts-mark-avatar__fill-color: rgb(132 179 132);
126
+ --tts-mark-soundEvent__fill-color: rgb(27 145 255 / 10%);
127
+ --tts-mark-soundEvent__stroke-color: rgb(27 145 255);
128
+ --tts-mark-command__fill-color: transparent;
129
+ --tts-mark-command__stroke-color: rgb(170 128 0);
130
+ --tts-mark-data__fill-color: transparent;
131
+ --tts-mark-data__stroke-color: rgb(0 156 80);
132
+ --tts-mark-wait__fill-color: transparent;
133
+ --tts-mark-wait__stroke-color: rgb(254 186 0);
134
+ --tts-mark-ppt-control__fill-color: transparent;
135
+ --tts-mark-ppt-control__stroke-color: rgb(0 211 109);
136
+ --tts-text-media-mark__border-color: #027bff;
137
+ --tts-text-media-mark__start__fill-color: rgb(0 0 0 / 10%);
138
+ --tts-text-highlight__color: red;
139
+ }
140
+ .ssml-editor p {
141
+ margin: 0;
142
+ line-height: 1.5;
143
+ }
144
+ .ssml-editor b {
145
+ font-weight: initial;
146
+ }
147
+ .ssml-editor .ssml-range-lbracket .icon-bracket {
148
+ display: inline-block;
149
+ width: 0.4em;
150
+ height: 1em;
151
+ border: 3px solid transparent;
152
+ border-right: 0;
153
+ border-bottom: 0.5em;
154
+ border-bottom-color: transparent !important;
155
+ box-sizing: border-box;
156
+ margin-right: 0.2em;
157
+ margin-left: 0.2em;
158
+ }
159
+ .ssml-editor .ssml-range-rbracket .icon-bracket {
160
+ display: inline-block;
161
+ width: 0.4em;
162
+ height: 1em;
163
+ border: 3px solid transparent;
164
+ border-left: 0;
165
+ border-top: 0.5em;
166
+ border-top-color: transparent !important;
167
+ box-sizing: border-box;
168
+ margin-right: 0.2em;
169
+ }
170
+ .ssml-editor .ssml-tag {
171
+ color: inherit;
172
+ padding: 0.1em 0.5em;
173
+ margin: 0 0.1em;
174
+ border-radius: 0.2em;
175
+ user-select: auto;
176
+ font-size: 0.9em;
177
+ }
178
+ .ssml-editor .ssml-tag-word {
179
+ background: var(--tts-mark-word__fill-color);
180
+ }
181
+ .ssml-editor .ssml-tag-breath {
182
+ background: var(--tts-mark-breath__fill-color);
183
+ }
184
+ .ssml-editor .ssml-tag-break {
185
+ background: var(--tts-mark-break__fill-color);
186
+ }
187
+ .ssml-editor .ssml-tag-action {
188
+ background: var(--tts-mark-action__fill-color);
189
+ border: 1px solid var(--tts-mark-action__stroke-color);
190
+ color: var(--tts-mark-action__stroke-color);
191
+ }
192
+ .ssml-editor .ssml-tag-action svg path {
193
+ stroke: var(--tts-mark-action__stroke-color);
194
+ }
195
+ .ssml-editor .ssml-tag-soundEvent {
196
+ background: var(--tts-mark-soundEvent__fill-color);
197
+ border: 1px solid var(--tts-mark-soundEvent__stroke-color);
198
+ color: var(--tts-mark-soundEvent__stroke-color);
199
+ }
200
+ .ssml-editor .ssml-tag-soundEvent svg path {
201
+ stroke: var(--tts-mark-soundEvent__stroke-color);
202
+ }
203
+ .ssml-editor .ssml-tag-emotion {
204
+ background: var(--tts-mark-emotion__fill-color);
205
+ border: 1px solid var(--tts-mark-emotion__stroke-color);
206
+ color: var(--tts-mark-emotion__stroke-color);
207
+ }
208
+ .ssml-editor .ssml-tag-phoneme {
209
+ padding: 0;
210
+ margin: 0;
211
+ border-radius: 0;
212
+ background: transparent;
213
+ color: inherit;
214
+ display: inline-flex;
215
+ align-items: center;
216
+ }
217
+ .ssml-editor .ssml-tag-phoneme .tag-text {
218
+ box-shadow: 0 2px var(--tts-mark-phoneme__border-color);
219
+ }
220
+ .ssml-editor .ssml-tag-phoneme .ssml-tag-label {
221
+ display: inline-block;
222
+ background: var(--tts-mark-phoneme__fill-color);
223
+ margin-right: 0.2em;
224
+ border-radius: 0.3em;
225
+ }
226
+ .ssml-editor .ssml-tag-number, .ssml-editor .ssml-tag-say-as {
227
+ padding: 0;
228
+ margin: 0;
229
+ border-radius: 0;
230
+ background: transparent;
231
+ color: inherit;
232
+ display: inline-flex;
233
+ align-items: center;
234
+ }
235
+ .ssml-editor .ssml-tag-number .tag-text, .ssml-editor .ssml-tag-say-as .tag-text {
236
+ box-shadow: 0 2px var(--tts-mark-say-as__border-color);
237
+ }
238
+ .ssml-editor .ssml-tag-number .ssml-tag-label, .ssml-editor .ssml-tag-say-as .ssml-tag-label {
239
+ display: inline-block;
240
+ background: var(--tts-mark-say-as__fill-color);
241
+ border: 1px solid var(--tts-mark-say-as__border-color);
242
+ margin-right: 0.2em;
243
+ border-radius: 0.3em;
244
+ border-radius: 4px;
245
+ }
246
+ .ssml-editor .ssml-tag-alias, .ssml-editor .ssml-tag-sub {
247
+ padding: 0;
248
+ margin: 0;
249
+ border-radius: 0;
250
+ background: transparent;
251
+ color: inherit;
252
+ display: inline-flex;
253
+ align-items: center;
254
+ }
255
+ .ssml-editor .ssml-tag-alias .tag-text, .ssml-editor .ssml-tag-sub .tag-text {
256
+ box-shadow: 0 2px var(--tts-mark-sub__border-color);
257
+ }
258
+ .ssml-editor .ssml-tag-alias .tag-value, .ssml-editor .ssml-tag-sub .tag-value {
259
+ display: inline-block;
260
+ background: var(--tts-mark-sub__fill-color);
261
+ border: 1px solid var(--tts-mark-sub__border-color);
262
+ margin-right: 0.2em;
263
+ border-radius: 0.3em;
264
+ border-radius: 4px;
265
+ }
266
+ .ssml-editor .ssml-tag-number .tag-value, .ssml-editor .ssml-tag-say-as .tag-value {
267
+ cursor: pointer;
268
+ }
269
+ .ssml-editor .ssml-tag-speaker {
270
+ padding: 0.1em 0.2em;
271
+ }
272
+ .ssml-editor .ssml-tag-speaker .ssml-tag-label {
273
+ background: var(--tts-mark-speaker__fill-color);
274
+ border: 1px solid var(--tts-mark-speaker__stroke-color);
275
+ color: var(--tts-mark-speed__speaker-color);
276
+ }
277
+ .ssml-editor .ssml-tag-speaker .icon-bracket {
278
+ border-color: var(--tts-mark-speaker__stroke-color);
279
+ }
280
+ .ssml-editor .ssml-tag-anchor-start {
281
+ background: var(--current-tag-fill);
282
+ border: 1px solid var(--current-tag-stroke);
283
+ color: var(--current-tag-stroke);
284
+ }
285
+ .ssml-editor .ssml-tag-speed {
286
+ --current-tag-fill: var(--tts-mark-speed__fill-color);
287
+ --current-tag-stroke: var(--tts-mark-speed__stroke-color);
288
+ }
289
+ .ssml-editor .ssml-tag-pitch {
290
+ --current-tag-fill: var(--tts-mark-pitch__fill-color);
291
+ --current-tag-stroke: var(--tts-mark-pitch__stroke-color);
292
+ }
293
+ .ssml-editor .ssml-tag-volume {
294
+ --current-tag-fill: var(--tts-mark-volume__fill-color);
295
+ --current-tag-stroke: var(--tts-mark-volume__stroke-color);
296
+ }
297
+ .ssml-editor .ssml-tag-speed, .ssml-editor .ssml-tag-pitch, .ssml-editor .ssml-tag-volume {
298
+ margin: 0;
299
+ padding: 0;
300
+ }
301
+ .ssml-editor .ssml-tag-speed .icon-bracket, .ssml-editor .ssml-tag-pitch .icon-bracket, .ssml-editor .ssml-tag-volume .icon-bracket {
302
+ border-color: var(--current-tag-stroke);
303
+ }
304
+ .ssml-editor .ssml-tag-avatar_head {
305
+ padding-bottom: 8px;
306
+ }
307
+ .ssml-editor .ssml-tag-avatar_head-wrap {
308
+ display: inline-flex;
309
+ align-items: center;
310
+ justify-content: flex-start;
311
+ background-color: var(--tts-mark-avatar__fill-color);
312
+ border-radius: 8px;
313
+ padding: 0 6px;
314
+ }
315
+ .ssml-editor .ssml-tag-avatar_head-delete {
316
+ display: inline-flex;
317
+ width: 1em;
318
+ height: 1em;
319
+ border: 1px solid #0ca4fb;
320
+ cursor: pointer;
321
+ }
322
+ .ssml-editor .ssml-tag-command {
323
+ background: var(--tts-mark-command__fill-color);
324
+ border: 1px solid var(--tts-mark-command__stroke-color);
325
+ color: var(--tts-mark-command__stroke-color);
326
+ }
327
+ .ssml-editor .ssml-tag-data {
328
+ background: var(--tts-mark-data__fill-color);
329
+ border: 1px solid var(--tts-mark-data__stroke-color);
330
+ color: var(--tts-mark-data__stroke-color);
331
+ }
332
+ .ssml-editor .ssml-tag-wait {
333
+ background: var(--tts-mark-wait__fill-color);
334
+ border: 1px solid var(--tts-mark-wait__stroke-color);
335
+ color: var(--tts-mark-wait__stroke-color);
336
+ }
337
+ .ssml-editor .ssml-tag-ppt_control {
338
+ background: var(--tts-mark-ppt-control__fill-color);
339
+ border: 1px solid var(--tts-mark-ppt-control__stroke-color);
340
+ color: var(--tts-mark-ppt-control__stroke-color);
341
+ }
342
+
343
+ .ssml-editor-placeholder {
344
+ opacity: 0.7;
345
+ pointer-events: none;
346
+ position: absolute;
347
+ }
348
+
349
+ .ssml-editor-phoneme__char {
350
+ background-color: var(--tts-deco-phoneme__fill-color);
351
+ color: var(--tts-deco-phoneme__color);
352
+ font-weight: bold;
353
+ cursor: pointer;
354
+ }
355
+
356
+ .ssml-word-mark {
357
+ background-color: #fff000;
358
+ color: #333;
359
+ }
360
+
361
+ .ssml-text-highlight {
362
+ color: var(--tts-text-highlight__color);
363
+ }
364
+ .ssml-text-highlight .tag-text {
365
+ color: var(--tts-text-highlight__color);
366
+ }
367
+
368
+ .ssml-text-mark-textmedia {
369
+ box-shadow: var(--tts-text-media-mark__border-color) 0 2px 0 0;
370
+ }
371
+
372
+ .ssml-editor-text-media__mark {
373
+ display: inline-block;
374
+ height: 100%;
375
+ vertical-align: middle;
376
+ }
377
+ .ssml-editor-text-media__mark.text-media-mark__start {
378
+ background-color: var(--tts-text-media-mark__start__fill-color);
379
+ border-radius: 4px;
380
+ margin: 0 0.2em;
381
+ cursor: pointer;
382
+ }
383
+ .ssml-editor-text-media__mark.text-media-mark__start .thumbnail-icon {
384
+ width: 1.6em;
385
+ height: 1em;
386
+ display: inline-block;
387
+ overflow: hidden;
388
+ }
389
+ .ssml-editor-text-media__mark.text-media-mark__start .thumbnail-icon img {
390
+ width: 100%;
391
+ height: 1em;
392
+ max-height: 1em;
393
+ aspect-ratio: 16/9;
394
+ margin: 0;
395
+ object-fit: cover;
396
+ }
397
+
398
+ .ssml-tag-remove {
399
+ margin-left: 0.2em;
400
+ cursor: pointer;
401
+ }
402
+ .ssml-tag-remove .icon-remove {
403
+ display: inline-block;
404
+ width: 0.8em;
405
+ height: 0.8em;
406
+ }
407
+
408
+ .range-annotation {
409
+ position: relative;
410
+ display: inline-block;
411
+ }
412
+ .range-annotation-icon {
413
+ display: inline-block;
414
+ width: 16px;
415
+ height: 16px;
416
+ cursor: pointer;
417
+ position: relative;
418
+ z-index: 1;
419
+ }
420
+ .range-annotation-icon::before {
421
+ content: "";
422
+ position: absolute;
423
+ top: 50%;
424
+ left: 50%;
425
+ transform: translate(-50%, -50%);
426
+ width: 8px;
427
+ height: 8px;
428
+ border-radius: 50%;
429
+ background-color: currentColor;
430
+ }
431
+ .range-annotation-icon-start {
432
+ margin-right: 4px;
433
+ }
434
+ .range-annotation-icon-end {
435
+ margin-left: 4px;
436
+ }
437
+ .range-annotation-content {
438
+ position: relative;
439
+ display: inline-block;
440
+ padding: 0 4px;
441
+ border-radius: 2px;
442
+ }
443
+ .range-annotation-content.speech-rate-content {
444
+ border-bottom: 2px solid rgba(255, 152, 0, 0.8);
445
+ }
446
+ .range-annotation-content.speech-pitch-content {
447
+ border-bottom: 2px solid rgba(33, 150, 243, 0.8);
448
+ }
449
+ .range-annotation-content.speech-volume-content {
450
+ border-bottom: 2px solid rgba(76, 175, 80, 0.8);
451
+ }
452
+
453
+ .ProseMirror .range-annotation-icon::after {
454
+ content: "";
455
+ position: absolute;
456
+ top: 0;
457
+ bottom: 0;
458
+ width: 2px;
459
+ background-color: currentColor;
460
+ opacity: 0;
461
+ transition: opacity 0.2s;
462
+ }
463
+ .ProseMirror .range-annotation-icon.ProseMirror-selectednode::after {
464
+ opacity: 1;
465
+ }