autumnnote 1.0.7 → 1.0.9
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 +332 -638
- package/dist/autumnnote.css +40 -4
- package/dist/autumnnote.es.js +3375 -262
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +3374 -261
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +4 -0
- package/src/js/editing/Style.js +195 -44
- package/src/js/editing/Typing.js +2 -2
- package/src/js/i18n/de.js +320 -0
- package/src/js/i18n/en.js +328 -0
- package/src/js/i18n/es.js +320 -0
- package/src/js/i18n/fr.js +321 -0
- package/src/js/i18n/index.js +59 -0
- package/src/js/i18n/ja.js +321 -0
- package/src/js/i18n/ko.js +320 -0
- package/src/js/i18n/vi.js +321 -0
- package/src/js/i18n/zh.js +321 -0
- package/src/js/index.js +2 -1
- package/src/js/module/CodeTooltip.js +12 -9
- package/src/js/module/ContextMenu.js +13 -11
- package/src/js/module/Editor.js +14 -3
- package/src/js/module/EmojiDialog.js +19 -7
- package/src/js/module/FindReplace.js +14 -13
- package/src/js/module/IconDialog.js +25 -13
- package/src/js/module/ImageDialog.js +25 -20
- package/src/js/module/ImageTooltip.js +13 -12
- package/src/js/module/LinkDialog.js +10 -9
- package/src/js/module/LinkTooltip.js +6 -5
- package/src/js/module/Placeholder.js +6 -1
- package/src/js/module/ShortcutsDialog.js +5 -4
- package/src/js/module/Statusbar.js +6 -5
- package/src/js/module/TableTooltip.js +412 -102
- package/src/js/module/Toolbar.js +21 -15
- package/src/js/module/VideoDialog.js +10 -9
- package/src/js/module/VideoTooltip.js +12 -11
- package/src/js/settings.js +4 -0
- package/src/styles/autumnnote.scss +50 -5
- package/types/index.d.ts +58 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zh.js - 简体中文 (Simplified Chinese) locale
|
|
3
|
+
* 仅包含与 en.js 不同的键值。
|
|
4
|
+
* resolveLocale 会将本文件与 en.js 进行深度合并。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** @type {Partial<import('../../../types/index.js').AsnLocale>} */
|
|
8
|
+
export const zh = {
|
|
9
|
+
toolbar: {
|
|
10
|
+
bold: '粗体 (Ctrl+B)',
|
|
11
|
+
italic: '斜体 (Ctrl+I)',
|
|
12
|
+
underline: '下划线 (Ctrl+U)',
|
|
13
|
+
strikethrough: '删除线',
|
|
14
|
+
superscript: '上标',
|
|
15
|
+
subscript: '下标',
|
|
16
|
+
alignLeft: '左对齐',
|
|
17
|
+
alignCenter: '居中',
|
|
18
|
+
alignRight: '右对齐',
|
|
19
|
+
alignJustify: '两端对齐',
|
|
20
|
+
ul: '无序列表',
|
|
21
|
+
ol: '有序列表',
|
|
22
|
+
checklist: '待办列表',
|
|
23
|
+
indent: '增加缩进',
|
|
24
|
+
outdent: '减少缩进',
|
|
25
|
+
undo: '撤销 (Ctrl+Z)',
|
|
26
|
+
redo: '重做 (Ctrl+Y)',
|
|
27
|
+
hr: '水平分割线',
|
|
28
|
+
link: '插入链接',
|
|
29
|
+
image: '插入图片',
|
|
30
|
+
video: '插入视频',
|
|
31
|
+
emoji: '插入表情',
|
|
32
|
+
icon: '插入 FA 图标',
|
|
33
|
+
table: '插入表格',
|
|
34
|
+
fontSize: '字号',
|
|
35
|
+
fontSizePlaceholder: '大小',
|
|
36
|
+
removeFormat: '清除格式',
|
|
37
|
+
direction: '切换文字方向 (LTR / RTL)',
|
|
38
|
+
fontFamily: '字体',
|
|
39
|
+
paragraphStyle: '段落样式',
|
|
40
|
+
paragraphStylePlaceholder: '样式',
|
|
41
|
+
lineHeight: '行高',
|
|
42
|
+
lineHeightPlaceholder: '\u2195 行距',
|
|
43
|
+
codeview: '查看 HTML 源码',
|
|
44
|
+
fullscreen: '全屏',
|
|
45
|
+
shortcuts: '键盘快捷键 (Ctrl+Shift+/)',
|
|
46
|
+
find: '搜索 (Ctrl+F)',
|
|
47
|
+
findReplace: '查找和替换 (Ctrl+H)',
|
|
48
|
+
inlineCode: '行内代码 (Ctrl+`)',
|
|
49
|
+
print: '打印',
|
|
50
|
+
foreColor: '文字颜色',
|
|
51
|
+
backColor: '高亮颜色',
|
|
52
|
+
chooseTextColor: '选择文字颜色',
|
|
53
|
+
chooseHighlightColor: '选择高亮颜色',
|
|
54
|
+
customColor: '自定义颜色',
|
|
55
|
+
insertTableLabel: '插入表格',
|
|
56
|
+
paragraphItems: {
|
|
57
|
+
p: '正文',
|
|
58
|
+
blockquote: '引用',
|
|
59
|
+
pre: '代码',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
linkDialog: {
|
|
64
|
+
ariaLabel: '插入链接',
|
|
65
|
+
title: '插入链接',
|
|
66
|
+
url: '链接地址',
|
|
67
|
+
urlPlaceholder: 'https://',
|
|
68
|
+
displayText: '显示文字',
|
|
69
|
+
textPlaceholder: '链接文字',
|
|
70
|
+
openInNewTab: '在新标签页中打开',
|
|
71
|
+
insertBtn: '插入',
|
|
72
|
+
cancelBtn: '取消',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
imageDialog: {
|
|
76
|
+
ariaLabel: '插入图片',
|
|
77
|
+
title: '插入图片',
|
|
78
|
+
imageUrl: '图片地址',
|
|
79
|
+
urlPlaceholder: 'https://example.com/image.png',
|
|
80
|
+
altText: '替代文字',
|
|
81
|
+
altPlaceholder: '图片描述',
|
|
82
|
+
alignment: '对齐方式',
|
|
83
|
+
alignNone: '无',
|
|
84
|
+
alignLeft: '左',
|
|
85
|
+
alignCenter: '居中',
|
|
86
|
+
alignRight: '右',
|
|
87
|
+
uploadLabel: '或上传文件',
|
|
88
|
+
insertBtn: '插入',
|
|
89
|
+
cancelBtn: '取消',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
videoDialog: {
|
|
93
|
+
ariaLabel: '插入视频',
|
|
94
|
+
title: '插入视频',
|
|
95
|
+
videoUrl: '视频地址',
|
|
96
|
+
urlPlaceholder: 'YouTube、Vimeo 或直接 .mp4 链接',
|
|
97
|
+
widthLabel: '宽度 (px)',
|
|
98
|
+
widthPlaceholder: '560',
|
|
99
|
+
insertBtn: '插入',
|
|
100
|
+
cancelBtn: '取消',
|
|
101
|
+
detected: (type) => `已识别: ${type}`,
|
|
102
|
+
unknownFormat: '未知格式 \u2014 将尝试直接嵌入视频',
|
|
103
|
+
invalidUrl: 'URL 无效 \u2014 请输入有效的视频链接。',
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
emojiDialog: {
|
|
107
|
+
ariaLabel: '插入表情',
|
|
108
|
+
title: '插入表情',
|
|
109
|
+
searchPlaceholder: '搜索表情\u2026',
|
|
110
|
+
all: '全部',
|
|
111
|
+
cancelBtn: '取消',
|
|
112
|
+
close: '关闭',
|
|
113
|
+
categories: {
|
|
114
|
+
smileys: '笑脸',
|
|
115
|
+
people: '人物',
|
|
116
|
+
animals: '动物',
|
|
117
|
+
food: '食物',
|
|
118
|
+
travel: '旅行',
|
|
119
|
+
objects: '物品',
|
|
120
|
+
symbols: '符号',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
iconDialog: {
|
|
125
|
+
ariaLabel: '插入 FA 图标',
|
|
126
|
+
title: '插入 FA 图标',
|
|
127
|
+
searchPlaceholder: '搜索图标\u2026',
|
|
128
|
+
all: '全部',
|
|
129
|
+
style: '样式',
|
|
130
|
+
size: '大小',
|
|
131
|
+
color: '颜色',
|
|
132
|
+
useColor: ' 使用颜色',
|
|
133
|
+
selectHint: '请选择一个图标',
|
|
134
|
+
insertBtn: '插入 FA 图标',
|
|
135
|
+
cancelBtn: '取消',
|
|
136
|
+
close: '关闭',
|
|
137
|
+
categories: {
|
|
138
|
+
popular: '热门',
|
|
139
|
+
interface: '界面',
|
|
140
|
+
navigation: '导航',
|
|
141
|
+
media: '媒体',
|
|
142
|
+
communication: '通讯',
|
|
143
|
+
files: '文件',
|
|
144
|
+
people: '人物',
|
|
145
|
+
objects: '物品',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
findReplace: {
|
|
150
|
+
findTitle: '搜索',
|
|
151
|
+
findReplaceTitle: '查找和替换',
|
|
152
|
+
findPlaceholder: '查找\u2026',
|
|
153
|
+
searchAriaLabel: '搜索文字',
|
|
154
|
+
caseSensitive: '\u00a0区分大小写',
|
|
155
|
+
prevBtn: '\u2190 上一个',
|
|
156
|
+
nextBtn: '下一个 \u2192',
|
|
157
|
+
replacePlaceholder: '替换为\u2026',
|
|
158
|
+
replaceAriaLabel: '替换为',
|
|
159
|
+
replaceBtn: '替换',
|
|
160
|
+
replaceAllBtn: '全部替换',
|
|
161
|
+
close: '\u00d7',
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
shortcutsDialog: {
|
|
165
|
+
title: '键盘快捷键',
|
|
166
|
+
ariaLabel: '键盘快捷键',
|
|
167
|
+
close: '关闭',
|
|
168
|
+
shortcuts: [
|
|
169
|
+
{
|
|
170
|
+
category: '文字格式',
|
|
171
|
+
items: [
|
|
172
|
+
{ keys: 'Ctrl + B', action: '粗体' },
|
|
173
|
+
{ keys: 'Ctrl + I', action: '斜体' },
|
|
174
|
+
{ keys: 'Ctrl + U', action: '下划线' },
|
|
175
|
+
{ keys: 'Ctrl + K', action: '插入 / 编辑链接' },
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
category: '历史记录',
|
|
180
|
+
items: [
|
|
181
|
+
{ keys: 'Ctrl + Z', action: '撤销' },
|
|
182
|
+
{ keys: 'Ctrl + Y / Ctrl + Shift + Z', action: '重做' },
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
category: '选择与导航',
|
|
187
|
+
items: [
|
|
188
|
+
{ keys: 'Ctrl + A', action: '全选' },
|
|
189
|
+
{ keys: 'Tab', action: '增加缩进 / 插入空格' },
|
|
190
|
+
{ keys: 'Shift + Tab', action: '减少缩进' },
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
category: '剪贴板',
|
|
195
|
+
items: [
|
|
196
|
+
{ keys: 'Ctrl + Shift + V', action: '粘贴为纯文本' },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
category: '查找和替换',
|
|
201
|
+
items: [
|
|
202
|
+
{ keys: 'Ctrl + F', action: '在文档中搜索' },
|
|
203
|
+
{ keys: 'Ctrl + H', action: '查找和替换' },
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
category: '编辑器',
|
|
208
|
+
items: [
|
|
209
|
+
{ keys: 'Ctrl + Shift + /', action: '显示快捷键对话框' },
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
contextMenu: {
|
|
216
|
+
cut: '剪切',
|
|
217
|
+
copy: '复制',
|
|
218
|
+
paste: '粘贴',
|
|
219
|
+
bold: '粗体',
|
|
220
|
+
italic: '斜体',
|
|
221
|
+
underline: '下划线',
|
|
222
|
+
textColor: '文字颜色',
|
|
223
|
+
highlightColor: '高亮颜色',
|
|
224
|
+
copyFormat: '复制格式',
|
|
225
|
+
pasteFormat: '粘贴格式',
|
|
226
|
+
removeFormat: '清除格式',
|
|
227
|
+
link: '插入链接',
|
|
228
|
+
image: '插入图片',
|
|
229
|
+
video: '插入视频',
|
|
230
|
+
table: '插入表格',
|
|
231
|
+
back: '返回',
|
|
232
|
+
noHighlight: '无高亮',
|
|
233
|
+
customColor: '自定义颜色',
|
|
234
|
+
customColorLabel: '自定义\u2026',
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
statusbar: {
|
|
238
|
+
resizeHandle: '拖动以调整大小',
|
|
239
|
+
words: (n) => `字数: ${n}`,
|
|
240
|
+
wordsLimit: (n, max) => `字数: ${n}/${max}`,
|
|
241
|
+
chars: (n) => `字符数: ${n}`,
|
|
242
|
+
charsLimit: (n, max) => `字符数: ${n}/${max}`,
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
tooltips: {
|
|
246
|
+
link: {
|
|
247
|
+
ariaLabel: '链接操作',
|
|
248
|
+
openLink: '打开链接',
|
|
249
|
+
copyUrl: '复制 URL',
|
|
250
|
+
editLink: '编辑链接',
|
|
251
|
+
removeLink: '删除链接',
|
|
252
|
+
},
|
|
253
|
+
image: {
|
|
254
|
+
ariaLabel: '图片操作',
|
|
255
|
+
label: '图片',
|
|
256
|
+
floatLeft: '左浮动',
|
|
257
|
+
noFloat: '不浮动',
|
|
258
|
+
alignCenter: '居中对齐',
|
|
259
|
+
floatRight: '右浮动',
|
|
260
|
+
originalSize: '原始尺寸',
|
|
261
|
+
rotateLeft: '向左旋转',
|
|
262
|
+
rotateRight: '向右旋转',
|
|
263
|
+
cropImage: '裁剪图片',
|
|
264
|
+
addCaption: '添加 / 编辑说明',
|
|
265
|
+
deleteImage: '删除图片',
|
|
266
|
+
},
|
|
267
|
+
code: {
|
|
268
|
+
ariaLabel: '代码块操作',
|
|
269
|
+
label: '代码',
|
|
270
|
+
syntaxLanguage: '语法语言',
|
|
271
|
+
syntaxAriaLabel: '语法语言',
|
|
272
|
+
copyCode: '复制代码',
|
|
273
|
+
toggleWordWrap: '切换自动换行',
|
|
274
|
+
enableWordWrap: '启用自动换行',
|
|
275
|
+
disableWordWrap: '禁用自动换行',
|
|
276
|
+
convertToParagraph: '转换为段落',
|
|
277
|
+
deleteCodeBlock: '删除代码块',
|
|
278
|
+
},
|
|
279
|
+
table: {
|
|
280
|
+
ariaLabel: '表格操作',
|
|
281
|
+
label: '表格',
|
|
282
|
+
selectCells: '选择单元格',
|
|
283
|
+
addRowAbove: '在上方插入行',
|
|
284
|
+
addRowBelow: '在下方插入行',
|
|
285
|
+
deleteRow: '删除行',
|
|
286
|
+
addColumnLeft: '在左侧插入列',
|
|
287
|
+
addColumnRight: '在右侧插入列',
|
|
288
|
+
deleteColumn: '删除列',
|
|
289
|
+
mergeCells: '合并单元格',
|
|
290
|
+
unmergeCells: '拆分单元格',
|
|
291
|
+
columnWidth: '列宽',
|
|
292
|
+
rowHeight: '行高',
|
|
293
|
+
tableBorderWidth: '表格边框宽度',
|
|
294
|
+
deleteTable: '删除表格',
|
|
295
|
+
columnWidthPx: '列宽 (px)',
|
|
296
|
+
rowHeightPx: '行高 (px)',
|
|
297
|
+
tableBorderWidthPx: '表格边框宽度 (px)',
|
|
298
|
+
cancelBtn: '取消',
|
|
299
|
+
applyBtn: '应用',
|
|
300
|
+
},
|
|
301
|
+
video: {
|
|
302
|
+
ariaLabel: '视频操作',
|
|
303
|
+
label: '视频',
|
|
304
|
+
floatLeft: '左浮动',
|
|
305
|
+
noFloat: '不浮动',
|
|
306
|
+
alignCenter: '居中对齐',
|
|
307
|
+
floatRight: '右浮动',
|
|
308
|
+
originalSize: '原始尺寸',
|
|
309
|
+
previewVideo: '预览视频',
|
|
310
|
+
exitPreview: '退出预览',
|
|
311
|
+
deleteVideo: '删除视频',
|
|
312
|
+
},
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
errors: {
|
|
316
|
+
imageFormat: (type) =>
|
|
317
|
+
`格式 "${type}" 不支持在浏览器中显示。请转换为 JPEG、PNG 或 WebP。`,
|
|
318
|
+
imageSize: (maxSize) =>
|
|
319
|
+
`图片文件过大。最大允许大小为 ${maxSize} MB。`,
|
|
320
|
+
},
|
|
321
|
+
};
|
package/src/js/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export * from './core/lists.js';
|
|
|
27
27
|
export * from './core/env.js';
|
|
28
28
|
export * from './core/sanitise.js';
|
|
29
29
|
export * from './module/Buttons.js';
|
|
30
|
+
export { locales, resolveLocale } from './i18n/index.js';
|
|
30
31
|
|
|
31
32
|
// ---------------------------------------------------------------------------
|
|
32
33
|
// Main factory
|
|
@@ -99,7 +100,7 @@ const AutumnNote = {
|
|
|
99
100
|
registerModule(name, ModuleClass) { _customModules.set(name, ModuleClass); },
|
|
100
101
|
|
|
101
102
|
/** Library version */
|
|
102
|
-
version: '1.0.
|
|
103
|
+
version: '1.0.9',
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
// ---------------------------------------------------------------------------
|
|
@@ -76,16 +76,17 @@ export class CodeTooltip {
|
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
77
|
|
|
78
78
|
_buildTooltip() {
|
|
79
|
+
const L = this.context.locale.tooltips.code;
|
|
79
80
|
const el = createElement('div', {
|
|
80
81
|
class: 'an-link-tooltip an-code-tooltip',
|
|
81
82
|
role: 'toolbar',
|
|
82
|
-
'aria-label':
|
|
83
|
+
'aria-label': L.ariaLabel,
|
|
83
84
|
});
|
|
84
85
|
el.style.display = 'none';
|
|
85
86
|
|
|
86
87
|
// Label
|
|
87
88
|
this._label = createElement('span', { class: 'an-link-tooltip-url' });
|
|
88
|
-
this._label.textContent =
|
|
89
|
+
this._label.textContent = L.label;
|
|
89
90
|
el.appendChild(this._label);
|
|
90
91
|
|
|
91
92
|
el.appendChild(this._sep());
|
|
@@ -93,8 +94,8 @@ export class CodeTooltip {
|
|
|
93
94
|
// Language selector
|
|
94
95
|
this._langSelect = createElement('select', {
|
|
95
96
|
class: 'an-code-lang-select',
|
|
96
|
-
title:
|
|
97
|
-
'aria-label':
|
|
97
|
+
title: L.syntaxLanguage,
|
|
98
|
+
'aria-label': L.syntaxAriaLabel,
|
|
98
99
|
});
|
|
99
100
|
const LANGUAGES = [
|
|
100
101
|
['', 'Plain text'], ['javascript', 'JavaScript'], ['typescript', 'TypeScript'],
|
|
@@ -115,25 +116,25 @@ export class CodeTooltip {
|
|
|
115
116
|
el.appendChild(this._sep());
|
|
116
117
|
|
|
117
118
|
// Copy code
|
|
118
|
-
this._copyBtn = this._makeBtn(ICONS.copy,
|
|
119
|
+
this._copyBtn = this._makeBtn(ICONS.copy, L.copyCode, () => this._copyCode());
|
|
119
120
|
|
|
120
121
|
el.appendChild(this._copyBtn);
|
|
121
122
|
|
|
122
123
|
el.appendChild(this._sep());
|
|
123
124
|
|
|
124
125
|
// Toggle word-wrap
|
|
125
|
-
this._wrapBtn = this._makeBtn(ICONS.wrapOn,
|
|
126
|
+
this._wrapBtn = this._makeBtn(ICONS.wrapOn, L.toggleWordWrap, () => this._toggleWrap());
|
|
126
127
|
el.appendChild(this._wrapBtn);
|
|
127
128
|
|
|
128
129
|
el.appendChild(this._sep());
|
|
129
130
|
|
|
130
131
|
// Convert to normal paragraph
|
|
131
|
-
el.appendChild(this._makeBtn(ICONS.toParagraph,
|
|
132
|
+
el.appendChild(this._makeBtn(ICONS.toParagraph, L.convertToParagraph, () => this._toParagraph()));
|
|
132
133
|
|
|
133
134
|
el.appendChild(this._sep());
|
|
134
135
|
|
|
135
136
|
// Delete block
|
|
136
|
-
el.appendChild(this._makeBtn(ICONS.deleteCode,
|
|
137
|
+
el.appendChild(this._makeBtn(ICONS.deleteCode, L.deleteCodeBlock, () => this._delete(), true));
|
|
137
138
|
|
|
138
139
|
// Keep tooltip alive while hovering it
|
|
139
140
|
this._disposers.push(
|
|
@@ -241,7 +242,9 @@ export class CodeTooltip {
|
|
|
241
242
|
const wrapped = (this._activePre.style.whiteSpace || '').includes('pre-wrap')
|
|
242
243
|
|| window.getComputedStyle(this._activePre).whiteSpace === 'pre-wrap';
|
|
243
244
|
this._wrapBtn.classList.toggle('active', wrapped);
|
|
244
|
-
this._wrapBtn.title = wrapped
|
|
245
|
+
this._wrapBtn.title = wrapped
|
|
246
|
+
? this.context.locale.tooltips.code.disableWordWrap
|
|
247
|
+
: this.context.locale.tooltips.code.enableWordWrap;
|
|
245
248
|
}
|
|
246
249
|
|
|
247
250
|
_syncLangSelect() {
|
|
@@ -48,8 +48,9 @@ const COLOR_PRESETS = [
|
|
|
48
48
|
|
|
49
49
|
function makeColorSubItems(colorType) {
|
|
50
50
|
const label = colorType === 'foreColor' ? 'Text Color' : 'Highlight Color';
|
|
51
|
+
const localeKey = colorType === 'foreColor' ? 'textColor' : 'highlightColor';
|
|
51
52
|
return () => [
|
|
52
|
-
{ back: true, label, navigate: () => defaultItems },
|
|
53
|
+
{ back: true, label, localeKey, navigate: () => defaultItems },
|
|
53
54
|
{ colorPalette: true, colorType },
|
|
54
55
|
];
|
|
55
56
|
}
|
|
@@ -171,7 +172,8 @@ export class ContextMenu {
|
|
|
171
172
|
const iconSpan = createElement('span', { class: 'an-context-icon', 'aria-hidden': 'true' });
|
|
172
173
|
iconSpan.innerHTML = ICONS.back;
|
|
173
174
|
backBtn.appendChild(iconSpan);
|
|
174
|
-
|
|
175
|
+
const backLabel = (it.localeKey && this.context.locale.contextMenu[it.localeKey]) || it.label || this.context.locale.contextMenu.back || 'Back';
|
|
176
|
+
backBtn.appendChild(createElement('span', { class: 'an-context-label' }, [backLabel]));
|
|
175
177
|
const off = on(backBtn, 'click', (e) => {
|
|
176
178
|
e.stopPropagation();
|
|
177
179
|
const curLeft = parseFloat(this.el.style.left);
|
|
@@ -204,7 +206,7 @@ export class ContextMenu {
|
|
|
204
206
|
btn.appendChild(iconSpan);
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
|
-
btn.appendChild(createElement('span', { class: 'an-context-label' }, [it.label || it.name]));
|
|
209
|
+
btn.appendChild(createElement('span', { class: 'an-context-label' }, [(this.context.locale.contextMenu[it.name] || it.label || it.name)]));
|
|
208
210
|
const chevron = createElement('span', { class: 'an-context-chevron', 'aria-hidden': 'true' });
|
|
209
211
|
chevron.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`;
|
|
210
212
|
btn.appendChild(chevron);
|
|
@@ -238,9 +240,9 @@ export class ContextMenu {
|
|
|
238
240
|
if (it.colorType === 'hiliteColor') {
|
|
239
241
|
const noColor = createElement('div', {
|
|
240
242
|
class: 'an-context-color-swatch an-context-color-none',
|
|
241
|
-
title: 'No highlight',
|
|
243
|
+
title: this.context.locale.contextMenu.noHighlight || 'No highlight',
|
|
242
244
|
role: 'button',
|
|
243
|
-
'aria-label': 'No highlight',
|
|
245
|
+
'aria-label': this.context.locale.contextMenu.noHighlight || 'No highlight',
|
|
244
246
|
});
|
|
245
247
|
noColor.innerHTML = ICONS.noColor;
|
|
246
248
|
const offNo = on(noColor, 'click', (e) => { e.stopPropagation(); this._applyColor('hiliteColor', 'transparent'); });
|
|
@@ -254,10 +256,10 @@ export class ContextMenu {
|
|
|
254
256
|
const colorInput = createElement('input', {
|
|
255
257
|
type: 'color',
|
|
256
258
|
value: it.colorType === 'foreColor' ? '#000000' : '#ffff00',
|
|
257
|
-
title: 'Custom color',
|
|
258
|
-
'aria-label': 'Custom color',
|
|
259
|
+
title: this.context.locale.contextMenu.customColor || 'Custom color',
|
|
260
|
+
'aria-label': this.context.locale.contextMenu.customColor || 'Custom color',
|
|
259
261
|
});
|
|
260
|
-
const customLabel = createElement('span', {}, ['Custom…']);
|
|
262
|
+
const customLabel = createElement('span', {}, [this.context.locale.contextMenu.customColorLabel || 'Custom…']);
|
|
261
263
|
const offCustom = on(colorInput, 'change', () => this._applyColor(it.colorType, colorInput.value));
|
|
262
264
|
this._menuDisposers.push(offCustom);
|
|
263
265
|
customRow.appendChild(colorInput);
|
|
@@ -277,7 +279,7 @@ export class ContextMenu {
|
|
|
277
279
|
iconSpan.innerHTML = it.icon;
|
|
278
280
|
headerBtn.appendChild(iconSpan);
|
|
279
281
|
}
|
|
280
|
-
headerBtn.appendChild(createElement('span', { class: 'an-context-label' }, [it.label || 'Insert Table']));
|
|
282
|
+
headerBtn.appendChild(createElement('span', { class: 'an-context-label' }, [this.context.locale.contextMenu.table || it.label || 'Insert Table']));
|
|
281
283
|
const chevron = createElement('span', { class: 'an-context-chevron', 'aria-hidden': 'true' });
|
|
282
284
|
chevron.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>`;
|
|
283
285
|
headerBtn.appendChild(chevron);
|
|
@@ -305,7 +307,7 @@ export class ContextMenu {
|
|
|
305
307
|
cells.forEach((cell) => {
|
|
306
308
|
cell.classList.toggle('active', +cell.dataset.row <= rows && +cell.dataset.col <= cols);
|
|
307
309
|
});
|
|
308
|
-
labelEl.textContent = (rows && cols) ? `${cols} × ${rows}` : 'Insert Table';
|
|
310
|
+
labelEl.textContent = (rows && cols) ? `${cols} × ${rows}` : (this.context.locale.contextMenu.table || 'Insert Table');
|
|
309
311
|
};
|
|
310
312
|
|
|
311
313
|
panel.appendChild(gridEl);
|
|
@@ -358,7 +360,7 @@ export class ContextMenu {
|
|
|
358
360
|
iconSpan.innerHTML = it.icon;
|
|
359
361
|
btn.appendChild(iconSpan);
|
|
360
362
|
}
|
|
361
|
-
btn.appendChild(createElement('span', { class: 'an-context-label' }, [it.label || it.name]));
|
|
363
|
+
btn.appendChild(createElement('span', { class: 'an-context-label' }, [(this.context.locale.contextMenu[it.name] || it.label || it.name)]));
|
|
362
364
|
const off = on(btn, 'click', (e) => {
|
|
363
365
|
e.stopPropagation();
|
|
364
366
|
this.hide();
|
package/src/js/module/Editor.js
CHANGED
|
@@ -153,8 +153,19 @@ export class Editor {
|
|
|
153
153
|
on(editable, 'click', onCheckboxClick),
|
|
154
154
|
on(editable, 'mouseup', fixChecklistCursor),
|
|
155
155
|
on(editable, 'keyup', fixChecklistCursor),
|
|
156
|
-
// Block drag-out and external drops in read-only mode
|
|
157
|
-
|
|
156
|
+
// Block drag-out and external drops in read-only mode.
|
|
157
|
+
// D-1: Also block dragging of iframes and .an-video-wrapper elements in
|
|
158
|
+
// edit mode — a user can inadvertently drag the iframe out of its wrapper
|
|
159
|
+
// (making it playable/removable from contenteditable protection) by holding
|
|
160
|
+
// the mouse and moving outside the wrapper before releasing.
|
|
161
|
+
on(editable, 'dragstart', (e) => {
|
|
162
|
+
if (isReadOnly()) { e.preventDefault(); return; }
|
|
163
|
+
const target = e.target;
|
|
164
|
+
if (target && (target.nodeName === 'IFRAME' ||
|
|
165
|
+
(target.closest && target.closest('.an-video-wrapper')))) {
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
}
|
|
168
|
+
}),
|
|
158
169
|
on(editable, 'drop', (e) => { if (isReadOnly()) e.preventDefault(); }),
|
|
159
170
|
);
|
|
160
171
|
|
|
@@ -370,7 +381,7 @@ export class Editor {
|
|
|
370
381
|
* @param {string} html - HTML string (will be sanitised)
|
|
371
382
|
*/
|
|
372
383
|
setHTML(html) {
|
|
373
|
-
this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html);
|
|
384
|
+
this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html, { allowIframes: true });
|
|
374
385
|
if (this._history) this._history.reset();
|
|
375
386
|
this.afterCommand();
|
|
376
387
|
}
|
|
@@ -555,19 +555,20 @@ export class EmojiDialog {
|
|
|
555
555
|
// ---------------------------------------------------------------------------
|
|
556
556
|
|
|
557
557
|
_buildDialog() {
|
|
558
|
+
const L = this.context.locale.emojiDialog;
|
|
558
559
|
const overlay = createElement('div', {
|
|
559
560
|
class: 'an-dialog-overlay',
|
|
560
561
|
role: 'dialog',
|
|
561
562
|
'aria-modal': 'true',
|
|
562
|
-
'aria-label':
|
|
563
|
+
'aria-label': L.ariaLabel,
|
|
563
564
|
});
|
|
564
565
|
const box = createElement('div', { class: 'an-dialog-box an-emoji-box' });
|
|
565
566
|
|
|
566
567
|
// Title row
|
|
567
568
|
const titleRow = createElement('div', { class: 'an-icon-title-row' });
|
|
568
569
|
const title = createElement('h3', { class: 'an-dialog-title' });
|
|
569
|
-
title.textContent =
|
|
570
|
-
const closeBtn = createElement('button', { type: 'button', class: 'an-icon-close', 'aria-label':
|
|
570
|
+
title.textContent = L.title;
|
|
571
|
+
const closeBtn = createElement('button', { type: 'button', class: 'an-icon-close', 'aria-label': L.close });
|
|
571
572
|
closeBtn.innerHTML = '×';
|
|
572
573
|
titleRow.append(title, closeBtn);
|
|
573
574
|
|
|
@@ -575,7 +576,7 @@ export class EmojiDialog {
|
|
|
575
576
|
const searchInput = createElement('input', {
|
|
576
577
|
type: 'search',
|
|
577
578
|
class: 'an-input an-icon-search',
|
|
578
|
-
placeholder:
|
|
579
|
+
placeholder: L.searchPlaceholder,
|
|
579
580
|
autocomplete: 'off',
|
|
580
581
|
});
|
|
581
582
|
this._searchInput = searchInput;
|
|
@@ -583,11 +584,11 @@ export class EmojiDialog {
|
|
|
583
584
|
// Category tabs
|
|
584
585
|
const catBar = createElement('div', { class: 'an-icon-cats' });
|
|
585
586
|
const allTab = createElement('button', { type: 'button', class: 'an-icon-cat active', 'data-cat': 'all' });
|
|
586
|
-
allTab.textContent =
|
|
587
|
+
allTab.textContent = L.all;
|
|
587
588
|
catBar.appendChild(allTab);
|
|
588
589
|
EMOJI_CATS.forEach(({ id, label }) => {
|
|
589
590
|
const tab = createElement('button', { type: 'button', class: 'an-icon-cat', 'data-cat': id });
|
|
590
|
-
tab.textContent = label;
|
|
591
|
+
tab.textContent = (L.categories && L.categories[id]) || label;
|
|
591
592
|
catBar.appendChild(tab);
|
|
592
593
|
});
|
|
593
594
|
this._catBar = catBar;
|
|
@@ -611,7 +612,7 @@ export class EmojiDialog {
|
|
|
611
612
|
// Cancel only — clicking an emoji inserts immediately
|
|
612
613
|
const btnRow = createElement('div', { class: 'an-dialog-actions' });
|
|
613
614
|
const cancelBtn = createElement('button', { type: 'button', class: 'an-btn' });
|
|
614
|
-
cancelBtn.textContent =
|
|
615
|
+
cancelBtn.textContent = L.cancelBtn;
|
|
615
616
|
btnRow.appendChild(cancelBtn);
|
|
616
617
|
|
|
617
618
|
box.append(titleRow, searchInput, catBar, grid, btnRow);
|
|
@@ -688,7 +689,18 @@ export class EmojiDialog {
|
|
|
688
689
|
|
|
689
690
|
// 2. Insert emoji as a plain text node — no ZWS or execCommand needed.
|
|
690
691
|
// Text nodes are natively navigable so the caret lands cleanly after.
|
|
692
|
+
//
|
|
693
|
+
// E-1: When the range covers the entire content of a <td>/<th>,
|
|
694
|
+
// deleteContents() can drift the range endpoint outside the cell in some
|
|
695
|
+
// browsers. Save the cell reference first and re-anchor after deletion.
|
|
696
|
+
const _sc = range.startContainer;
|
|
697
|
+
const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)
|
|
698
|
+
?.closest?.('td, th');
|
|
691
699
|
range.deleteContents();
|
|
700
|
+
if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
701
|
+
range.setStart(_tdAnchor, 0);
|
|
702
|
+
range.collapse(true);
|
|
703
|
+
}
|
|
692
704
|
const textNode = document.createTextNode(char);
|
|
693
705
|
range.insertNode(textNode);
|
|
694
706
|
|