pds-dev-kit-web 2.2.315 → 2.2.316
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/dist/src/common/services/i18n/resources/en.json +6 -0
- package/dist/src/common/services/i18n/resources/es.json +6 -0
- package/dist/src/common/services/i18n/resources/fil.json +3 -0
- package/dist/src/common/services/i18n/resources/index.d.ts +39 -0
- package/dist/src/common/services/i18n/resources/ja.json +6 -0
- package/dist/src/common/services/i18n/resources/ko.json +6 -0
- package/dist/src/common/services/i18n/resources/zh-cn.json +6 -0
- package/dist/src/common/services/i18n/resources/zh-tw.json +6 -0
- package/dist/src/desktop/panels/LocalizedContentModal/LocalizedContentModal.js +8 -2
- package/package.json +1 -1
- package/release-note.md +2 -2
- package/.claude/settings.local.json +0 -18
- package/.idea/codeStyles/Project.xml +0 -59
- package/.idea/codeStyles/codeStyleConfig.xml +0 -5
- package/.idea/inspectionProfiles/Project_Default.xml +0 -7
- package/.idea/modules.xml +0 -8
- package/.idea/pds-dev-kit.iml +0 -12
- package/.idea/prettier.xml +0 -7
- package/.idea/vcs.xml +0 -6
- package/.reference/OldEmailRichTextEditor/CustomTinyMCEEditor.tsx +0 -209
- package/.reference/OldEmailRichTextEditor/personalization_tag_plugin.ts +0 -43
- package/.reference/OldEmailRichTextEditor/tinymce-toolbar.css +0 -13
- package/.reference/email_editor_refactoring_prd.md +0 -57
- package/pds-dev-kit-web-2.2.314.tgz +0 -0
|
@@ -80,6 +80,12 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "Chinese - Traditional (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "French (fr)",
|
|
82
82
|
"str_multilingual_es": "Spanish (es)",
|
|
83
|
+
"str_multilingual_vi": "Vietnamese (vi)",
|
|
84
|
+
"str_multilingual_ru": "Russian (ru)",
|
|
85
|
+
"str_multilingual_it": "Italian (it)",
|
|
86
|
+
"str_multilingual_desc_vi": "If Vietnamese is not entered, members will see the content in English.",
|
|
87
|
+
"str_multilingual_desc_ru": "If Russian is not entered, members will see the content in English.",
|
|
88
|
+
"str_multilingual_desc_it": "If Italian is not entered, members will see the content in English.",
|
|
83
89
|
"str_9071": "Please enter the text in the appropriate language for your country.",
|
|
84
90
|
"str_original_text": "Original Text",
|
|
85
91
|
"str_apply": "Apply",
|
|
@@ -80,6 +80,12 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "Chino - Tradicional (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "Francés (fr)",
|
|
82
82
|
"str_multilingual_es": "Español (es)",
|
|
83
|
+
"str_multilingual_vi": "Vietnamita (vi)",
|
|
84
|
+
"str_multilingual_ru": "Ruso (ru)",
|
|
85
|
+
"str_multilingual_it": "Italiano (it)",
|
|
86
|
+
"str_multilingual_desc_vi": "Si no ingresa en vietnamita, se mostrará a los miembros en inglés.",
|
|
87
|
+
"str_multilingual_desc_ru": "Si no ingresa en ruso, se mostrará a los miembros en inglés.",
|
|
88
|
+
"str_multilingual_desc_it": "Si no ingresa en italiano, se mostrará a los miembros en inglés.",
|
|
83
89
|
"str_9071": "Por favor, ingrese el texto en el idioma apropiado para su país.",
|
|
84
90
|
"str_original_text": "Texto Original",
|
|
85
91
|
"str_apply": "Aplicar",
|
|
@@ -80,6 +80,9 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "Chinese - Traditional (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "French (fr)",
|
|
82
82
|
"str_multilingual_es": "Spanish (es)",
|
|
83
|
+
"str_multilingual_vi": "Vietnamese (vi)",
|
|
84
|
+
"str_multilingual_ru": "Russian (ru)",
|
|
85
|
+
"str_multilingual_it": "Italian (it)",
|
|
83
86
|
"str_9071": "Mangyaring ilagay ang teksto sa naaangkop na wika para sa inyong bansa.",
|
|
84
87
|
"str_original_text": "Orihinal na Teksto",
|
|
85
88
|
"str_apply": "Apply",
|
|
@@ -82,6 +82,12 @@ declare const locale: {
|
|
|
82
82
|
str_multilingual_zh_tw: string;
|
|
83
83
|
str_multilingual_fr: string;
|
|
84
84
|
str_multilingual_es: string;
|
|
85
|
+
str_multilingual_vi: string;
|
|
86
|
+
str_multilingual_ru: string;
|
|
87
|
+
str_multilingual_it: string;
|
|
88
|
+
str_multilingual_desc_vi: string;
|
|
89
|
+
str_multilingual_desc_ru: string;
|
|
90
|
+
str_multilingual_desc_it: string;
|
|
85
91
|
str_9071: string;
|
|
86
92
|
str_original_text: string;
|
|
87
93
|
str_apply: string;
|
|
@@ -199,6 +205,12 @@ declare const locale: {
|
|
|
199
205
|
str_multilingual_zh_tw: string;
|
|
200
206
|
str_multilingual_fr: string;
|
|
201
207
|
str_multilingual_es: string;
|
|
208
|
+
str_multilingual_vi: string;
|
|
209
|
+
str_multilingual_ru: string;
|
|
210
|
+
str_multilingual_it: string;
|
|
211
|
+
str_multilingual_desc_vi: string;
|
|
212
|
+
str_multilingual_desc_ru: string;
|
|
213
|
+
str_multilingual_desc_it: string;
|
|
202
214
|
str_9071: string;
|
|
203
215
|
str_original_text: string;
|
|
204
216
|
str_apply: string;
|
|
@@ -316,6 +328,12 @@ declare const locale: {
|
|
|
316
328
|
str_multilingual_zh_tw: string;
|
|
317
329
|
str_multilingual_fr: string;
|
|
318
330
|
str_multilingual_es: string;
|
|
331
|
+
str_multilingual_vi: string;
|
|
332
|
+
str_multilingual_ru: string;
|
|
333
|
+
str_multilingual_it: string;
|
|
334
|
+
str_multilingual_desc_vi: string;
|
|
335
|
+
str_multilingual_desc_ru: string;
|
|
336
|
+
str_multilingual_desc_it: string;
|
|
319
337
|
str_9071: string;
|
|
320
338
|
str_original_text: string;
|
|
321
339
|
str_apply: string;
|
|
@@ -433,6 +451,12 @@ declare const locale: {
|
|
|
433
451
|
str_multilingual_zh_tw: string;
|
|
434
452
|
str_multilingual_fr: string;
|
|
435
453
|
str_multilingual_es: string;
|
|
454
|
+
str_multilingual_vi: string;
|
|
455
|
+
str_multilingual_ru: string;
|
|
456
|
+
str_multilingual_it: string;
|
|
457
|
+
str_multilingual_desc_vi: string;
|
|
458
|
+
str_multilingual_desc_ru: string;
|
|
459
|
+
str_multilingual_desc_it: string;
|
|
436
460
|
str_9071: string;
|
|
437
461
|
str_original_text: string;
|
|
438
462
|
str_apply: string;
|
|
@@ -550,6 +574,12 @@ declare const locale: {
|
|
|
550
574
|
str_multilingual_zh_tw: string;
|
|
551
575
|
str_multilingual_fr: string;
|
|
552
576
|
str_multilingual_es: string;
|
|
577
|
+
str_multilingual_vi: string;
|
|
578
|
+
str_multilingual_ru: string;
|
|
579
|
+
str_multilingual_it: string;
|
|
580
|
+
str_multilingual_desc_vi: string;
|
|
581
|
+
str_multilingual_desc_ru: string;
|
|
582
|
+
str_multilingual_desc_it: string;
|
|
553
583
|
str_9071: string;
|
|
554
584
|
str_original_text: string;
|
|
555
585
|
str_apply: string;
|
|
@@ -667,6 +697,12 @@ declare const locale: {
|
|
|
667
697
|
str_multilingual_zh_tw: string;
|
|
668
698
|
str_multilingual_fr: string;
|
|
669
699
|
str_multilingual_es: string;
|
|
700
|
+
str_multilingual_vi: string;
|
|
701
|
+
str_multilingual_ru: string;
|
|
702
|
+
str_multilingual_it: string;
|
|
703
|
+
str_multilingual_desc_vi: string;
|
|
704
|
+
str_multilingual_desc_ru: string;
|
|
705
|
+
str_multilingual_desc_it: string;
|
|
670
706
|
str_9071: string;
|
|
671
707
|
str_original_text: string;
|
|
672
708
|
str_apply: string;
|
|
@@ -784,6 +820,9 @@ declare const locale: {
|
|
|
784
820
|
str_multilingual_zh_tw: string;
|
|
785
821
|
str_multilingual_fr: string;
|
|
786
822
|
str_multilingual_es: string;
|
|
823
|
+
str_multilingual_vi: string;
|
|
824
|
+
str_multilingual_ru: string;
|
|
825
|
+
str_multilingual_it: string;
|
|
787
826
|
str_9071: string;
|
|
788
827
|
str_original_text: string;
|
|
789
828
|
str_apply: string;
|
|
@@ -80,6 +80,12 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "中国語 - 繁体 (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "フランス語 (fr)",
|
|
82
82
|
"str_multilingual_es": "スペイン語 (es)",
|
|
83
|
+
"str_multilingual_vi": "ベトナム語 (vi)",
|
|
84
|
+
"str_multilingual_ru": "ロシア語 (ru)",
|
|
85
|
+
"str_multilingual_it": "イタリア語 (it)",
|
|
86
|
+
"str_multilingual_desc_vi": "ベトナム語を入力しないと、メンバーに英語で表示されます。",
|
|
87
|
+
"str_multilingual_desc_ru": "ロシア語を入力しないと、メンバーに英語で表示されます。",
|
|
88
|
+
"str_multilingual_desc_it": "イタリア語を入力しないと、メンバーに英語で表示されます。",
|
|
83
89
|
"str_9071": "国に適した言語を入力してください。",
|
|
84
90
|
"str_original_text": "原文",
|
|
85
91
|
"str_apply": "適用",
|
|
@@ -81,6 +81,12 @@
|
|
|
81
81
|
"str_multilingual_zh_tw": "중국어 - 번체 (zh-tw)",
|
|
82
82
|
"str_multilingual_fr": "프랑스어 (fr)",
|
|
83
83
|
"str_multilingual_es": "스페인어 (es)",
|
|
84
|
+
"str_multilingual_vi": "베트남어 (vi)",
|
|
85
|
+
"str_multilingual_ru": "러시아어 (ru)",
|
|
86
|
+
"str_multilingual_it": "이탈리아어 (it)",
|
|
87
|
+
"str_multilingual_desc_vi": "베트남어를 입력하지 않을 경우, 회원에게 영어로 보여집니다.",
|
|
88
|
+
"str_multilingual_desc_ru": "러시아어를 입력하지 않을 경우, 회원에게 영어로 보여집니다.",
|
|
89
|
+
"str_multilingual_desc_it": "이탈리아어를 입력하지 않을 경우, 회원에게 영어로 보여집니다.",
|
|
84
90
|
"str_9071": "국가에 맞는 언어를 입력해 주세요.",
|
|
85
91
|
"str_original_text": "원문",
|
|
86
92
|
"str_apply": "적용",
|
|
@@ -80,6 +80,12 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "中文 - 繁体 (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "法语 (fr)",
|
|
82
82
|
"str_multilingual_es": "西班牙语 (es)",
|
|
83
|
+
"str_multilingual_vi": "越南语 (vi)",
|
|
84
|
+
"str_multilingual_ru": "俄语 (ru)",
|
|
85
|
+
"str_multilingual_it": "意大利语 (it)",
|
|
86
|
+
"str_multilingual_desc_vi": "如果您不输入越南语,则以英文显示给会员。",
|
|
87
|
+
"str_multilingual_desc_ru": "如果您不输入俄语,则以英文显示给会员。",
|
|
88
|
+
"str_multilingual_desc_it": "如果您不输入意大利语,则以英文显示给会员。",
|
|
83
89
|
"str_9071": "请输入适合您国家的语言。",
|
|
84
90
|
"str_original_text": "原文",
|
|
85
91
|
"str_apply": "应用",
|
|
@@ -80,6 +80,12 @@
|
|
|
80
80
|
"str_multilingual_zh_tw": "中文 - 繁體 (zh-tw)",
|
|
81
81
|
"str_multilingual_fr": "法語 (fr)",
|
|
82
82
|
"str_multilingual_es": "西班牙語 (es)",
|
|
83
|
+
"str_multilingual_vi": "越南語 (vi)",
|
|
84
|
+
"str_multilingual_ru": "俄語 (ru)",
|
|
85
|
+
"str_multilingual_it": "義大利語 (it)",
|
|
86
|
+
"str_multilingual_desc_vi": "如果您不輸入越南語,則以英文顯示給會員。",
|
|
87
|
+
"str_multilingual_desc_ru": "如果您不輸入俄語,則以英文顯示給會員。",
|
|
88
|
+
"str_multilingual_desc_it": "如果您不輸入義大利語,則以英文顯示給會員。",
|
|
83
89
|
"str_9071": "請輸入適合您國家的語言。",
|
|
84
90
|
"str_original_text": "原文",
|
|
85
91
|
"str_apply": "應用",
|
|
@@ -74,7 +74,10 @@ var LANGUAGE_I18N_KEYS = {
|
|
|
74
74
|
zh: 'str_multilingual_zh_cn',
|
|
75
75
|
'zh-Hant': 'str_multilingual_zh_tw',
|
|
76
76
|
fr: 'str_multilingual_fr',
|
|
77
|
-
es: 'str_multilingual_es'
|
|
77
|
+
es: 'str_multilingual_es',
|
|
78
|
+
vi: 'str_multilingual_vi',
|
|
79
|
+
ru: 'str_multilingual_ru',
|
|
80
|
+
it: 'str_multilingual_it'
|
|
78
81
|
};
|
|
79
82
|
var LANGUAGE_DESC_TEXT_KEYS = {
|
|
80
83
|
ko: 'str_9064',
|
|
@@ -83,7 +86,10 @@ var LANGUAGE_DESC_TEXT_KEYS = {
|
|
|
83
86
|
zh: 'str_9067',
|
|
84
87
|
'zh-Hant': 'str_9068',
|
|
85
88
|
fr: 'str_9069',
|
|
86
|
-
es: 'str_9070'
|
|
89
|
+
es: 'str_9070',
|
|
90
|
+
vi: 'str_multilingual_desc_vi',
|
|
91
|
+
ru: 'str_multilingual_desc_ru',
|
|
92
|
+
it: 'str_multilingual_desc_it'
|
|
87
93
|
};
|
|
88
94
|
function LocalizedContentModal(_a) {
|
|
89
95
|
var _this = this;
|
package/package.json
CHANGED
package/release-note.md
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(git mv:*)",
|
|
5
|
-
"Bash(mkdir -p reference/OldEmailRichTextEditor)",
|
|
6
|
-
"Bash(mv src/desktop/components/RichTextEditor/OldEmailRichTextEditor/* reference/OldEmailRichTextEditor/)",
|
|
7
|
-
"Bash(mv src/desktop/components/RichTextEditor/email_editor_refactoring_prd.md reference/)",
|
|
8
|
-
"Bash(rmdir src/desktop/components/RichTextEditor/OldEmailRichTextEditor)",
|
|
9
|
-
"Bash(git restore:*)",
|
|
10
|
-
"Bash(git add:*)",
|
|
11
|
-
"Bash(npm run:*)",
|
|
12
|
-
"Bash(source ~/.zshrc)",
|
|
13
|
-
"Bash(npx tsc:*)",
|
|
14
|
-
"Bash(./node_modules/.bin/tsc --noEmit --pretty)",
|
|
15
|
-
"Bash(git commit:*)"
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
<component name="ProjectCodeStyleConfiguration">
|
|
2
|
-
<code_scheme name="Project" version="173">
|
|
3
|
-
<HTMLCodeStyleSettings>
|
|
4
|
-
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
|
5
|
-
</HTMLCodeStyleSettings>
|
|
6
|
-
<JSCodeStyleSettings version="0">
|
|
7
|
-
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
8
|
-
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
9
|
-
<option name="USE_DOUBLE_QUOTES" value="false" />
|
|
10
|
-
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
11
|
-
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
12
|
-
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
13
|
-
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
14
|
-
</JSCodeStyleSettings>
|
|
15
|
-
<TypeScriptCodeStyleSettings version="0">
|
|
16
|
-
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
|
17
|
-
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
|
18
|
-
<option name="USE_DOUBLE_QUOTES" value="false" />
|
|
19
|
-
<option name="FORCE_QUOTE_STYlE" value="true" />
|
|
20
|
-
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
|
21
|
-
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
|
22
|
-
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
|
23
|
-
</TypeScriptCodeStyleSettings>
|
|
24
|
-
<VueCodeStyleSettings>
|
|
25
|
-
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
|
26
|
-
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
|
27
|
-
</VueCodeStyleSettings>
|
|
28
|
-
<codeStyleSettings language="HTML">
|
|
29
|
-
<option name="SOFT_MARGINS" value="100" />
|
|
30
|
-
<indentOptions>
|
|
31
|
-
<option name="INDENT_SIZE" value="2" />
|
|
32
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
33
|
-
<option name="TAB_SIZE" value="2" />
|
|
34
|
-
</indentOptions>
|
|
35
|
-
</codeStyleSettings>
|
|
36
|
-
<codeStyleSettings language="JavaScript">
|
|
37
|
-
<option name="SOFT_MARGINS" value="100" />
|
|
38
|
-
<indentOptions>
|
|
39
|
-
<option name="INDENT_SIZE" value="2" />
|
|
40
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
41
|
-
<option name="TAB_SIZE" value="2" />
|
|
42
|
-
</indentOptions>
|
|
43
|
-
</codeStyleSettings>
|
|
44
|
-
<codeStyleSettings language="TypeScript">
|
|
45
|
-
<option name="SOFT_MARGINS" value="100" />
|
|
46
|
-
<indentOptions>
|
|
47
|
-
<option name="INDENT_SIZE" value="2" />
|
|
48
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
49
|
-
<option name="TAB_SIZE" value="2" />
|
|
50
|
-
</indentOptions>
|
|
51
|
-
</codeStyleSettings>
|
|
52
|
-
<codeStyleSettings language="Vue">
|
|
53
|
-
<option name="SOFT_MARGINS" value="100" />
|
|
54
|
-
<indentOptions>
|
|
55
|
-
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
|
56
|
-
</indentOptions>
|
|
57
|
-
</codeStyleSettings>
|
|
58
|
-
</code_scheme>
|
|
59
|
-
</component>
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
<component name="InspectionProjectProfileManager">
|
|
2
|
-
<profile version="1.0">
|
|
3
|
-
<option name="myName" value="Project Default" />
|
|
4
|
-
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
|
5
|
-
<inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
|
|
6
|
-
</profile>
|
|
7
|
-
</component>
|
package/.idea/modules.xml
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<project version="4">
|
|
3
|
-
<component name="ProjectModuleManager">
|
|
4
|
-
<modules>
|
|
5
|
-
<module fileurl="file://$PROJECT_DIR$/.idea/pds-dev-kit.iml" filepath="$PROJECT_DIR$/.idea/pds-dev-kit.iml" />
|
|
6
|
-
</modules>
|
|
7
|
-
</component>
|
|
8
|
-
</project>
|
package/.idea/pds-dev-kit.iml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="WEB_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager">
|
|
4
|
-
<content url="file://$MODULE_DIR$">
|
|
5
|
-
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
|
6
|
-
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
|
7
|
-
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
|
8
|
-
</content>
|
|
9
|
-
<orderEntry type="inheritedJdk" />
|
|
10
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
-
</component>
|
|
12
|
-
</module>
|
package/.idea/prettier.xml
DELETED
package/.idea/vcs.xml
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { Editor } from '@tinymce/tinymce-react';
|
|
2
|
-
import { useRef, useEffect, useMemo } from 'react';
|
|
3
|
-
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import tinymce from 'tinymce';
|
|
5
|
-
import 'tinymce/icons/default';
|
|
6
|
-
import 'tinymce/models/dom';
|
|
7
|
-
import 'tinymce/plugins/advlist';
|
|
8
|
-
import 'tinymce/plugins/anchor';
|
|
9
|
-
import 'tinymce/plugins/autolink';
|
|
10
|
-
import 'tinymce/plugins/charmap';
|
|
11
|
-
import 'tinymce/plugins/code';
|
|
12
|
-
import 'tinymce/plugins/fullscreen';
|
|
13
|
-
import 'tinymce/plugins/image';
|
|
14
|
-
import 'tinymce/plugins/insertdatetime';
|
|
15
|
-
import 'tinymce/plugins/link';
|
|
16
|
-
import 'tinymce/plugins/lists';
|
|
17
|
-
import 'tinymce/plugins/media';
|
|
18
|
-
import 'tinymce/plugins/preview';
|
|
19
|
-
import 'tinymce/plugins/searchreplace';
|
|
20
|
-
import 'tinymce/plugins/table';
|
|
21
|
-
import 'tinymce/plugins/visualblocks';
|
|
22
|
-
import 'tinymce/skins/content/default/content.min.css';
|
|
23
|
-
import 'tinymce/skins/ui/oxide/content.min.css';
|
|
24
|
-
import 'tinymce/skins/ui/oxide/skin.min.css';
|
|
25
|
-
import 'tinymce/themes/silver';
|
|
26
|
-
|
|
27
|
-
import { showDesktopAlertDialog } from '@core/overlay';
|
|
28
|
-
|
|
29
|
-
import {
|
|
30
|
-
PersonalizationTagCallback,
|
|
31
|
-
setPersonalizationModalCallback,
|
|
32
|
-
registerCustomTokensPlugin
|
|
33
|
-
} from './personalization_tag_plugin';
|
|
34
|
-
import './tinymce-toolbar.css';
|
|
35
|
-
|
|
36
|
-
import type { Editor as TinyMCEEditor } from 'tinymce';
|
|
37
|
-
|
|
38
|
-
type ImageUploadHandler = (file: File) => Promise<string>;
|
|
39
|
-
|
|
40
|
-
type EditorProps = {
|
|
41
|
-
initialContent?: string;
|
|
42
|
-
onChange?: (content: string) => void;
|
|
43
|
-
onOpenPersonalizationModal?: PersonalizationTagCallback;
|
|
44
|
-
onImageUpload?: ImageUploadHandler;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// Register the custom plugin before any Editor component mounts
|
|
48
|
-
registerCustomTokensPlugin(tinymce);
|
|
49
|
-
|
|
50
|
-
export default function CustomTinyMCEEditor({
|
|
51
|
-
initialContent,
|
|
52
|
-
onChange,
|
|
53
|
-
onOpenPersonalizationModal,
|
|
54
|
-
onImageUpload
|
|
55
|
-
}: EditorProps) {
|
|
56
|
-
const { t } = useTranslation('translation');
|
|
57
|
-
const editorRef = useRef<TinyMCEEditor | null>(null);
|
|
58
|
-
const uploadedBlobsRef = useRef<Map<string, string>>(new Map());
|
|
59
|
-
const isEditorReady = useRef(false);
|
|
60
|
-
|
|
61
|
-
// Store initial content in ref so it doesn't change on re-renders
|
|
62
|
-
const initialContentRef = useRef(initialContent);
|
|
63
|
-
|
|
64
|
-
// Use refs to keep callbacks stable
|
|
65
|
-
const onChangeRef = useRef(onChange);
|
|
66
|
-
const onImageUploadRef = useRef(onImageUpload);
|
|
67
|
-
const tRef = useRef(t);
|
|
68
|
-
|
|
69
|
-
// Update refs when props change
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
onChangeRef.current = onChange;
|
|
72
|
-
}, [onChange]);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
onImageUploadRef.current = onImageUpload;
|
|
76
|
-
}, [onImageUpload]);
|
|
77
|
-
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
tRef.current = t;
|
|
80
|
-
}, [t]);
|
|
81
|
-
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
setPersonalizationModalCallback(onOpenPersonalizationModal || null);
|
|
84
|
-
return () => {
|
|
85
|
-
setPersonalizationModalCallback(null);
|
|
86
|
-
};
|
|
87
|
-
}, [onOpenPersonalizationModal]);
|
|
88
|
-
|
|
89
|
-
const handleInit = (_evt: unknown, editor: TinyMCEEditor) => {
|
|
90
|
-
editorRef.current = editor;
|
|
91
|
-
isEditorReady.current = true;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Use the official onEditorChange callback from @tinymce/tinymce-react
|
|
95
|
-
const handleEditorChange = (content: string) => {
|
|
96
|
-
onChangeRef.current?.(content);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Memoize init object to prevent re-initialization
|
|
100
|
-
const editorInit = useMemo(
|
|
101
|
-
() => ({
|
|
102
|
-
height: 500,
|
|
103
|
-
menubar: false,
|
|
104
|
-
statusbar: true,
|
|
105
|
-
elementpath: false,
|
|
106
|
-
branding: false,
|
|
107
|
-
toolbar_mode: 'wrap' as const,
|
|
108
|
-
plugins:
|
|
109
|
-
'advlist anchor autolink charmap code fullscreen image insertdatetime link lists media preview searchreplace table visualblocks customtokens',
|
|
110
|
-
toolbar:
|
|
111
|
-
'blocks bold italic underline strikethrough | forecolor backcolor alignleft aligncenter alignright alignjustify outdent indent | numlist bullist blockquote insertTag link image media undo redo',
|
|
112
|
-
skin: false,
|
|
113
|
-
content_css: false,
|
|
114
|
-
content_style: `
|
|
115
|
-
body { font-family:Helvetica,Arial,sans-serif; font-size:16px; line-height: 1.5; overflow-wrap: break-word; word-break: break-word; word-wrap: break-word; }
|
|
116
|
-
p { margin: 0; }
|
|
117
|
-
a { color: #0066cc; text-decoration: underline; }
|
|
118
|
-
img { max-width: 100%; height: auto; display: block; margin: 16px 0; border: none; object-fit: contain; }
|
|
119
|
-
video, iframe { width: 100%; margin: 16px 0; padding: 0; border: none; display: block; max-width: 100%; }
|
|
120
|
-
span[data-mce-object="video"], span[data-mce-object="iframe"] { display: block; width: 100%; margin: 16px 0; border: none; }
|
|
121
|
-
span[data-mce-object]::before { content: ''; display: block; }
|
|
122
|
-
span[data-mce-object]::after { content: ''; display: block; }
|
|
123
|
-
.mce-object-video, .mce-object-iframe, .mce-preview-object { margin: 0; padding: 0; border: none; }
|
|
124
|
-
.mce-preview-object > iframe { border: none; }
|
|
125
|
-
table { width: 100%; margin: 16px 0; border: none; border-collapse: collapse; }
|
|
126
|
-
.alert-warning { border-left: 5px solid #ffdd00; padding: 10px; background: #fff8e1; margin: 10px 0; border-radius: 4px; }
|
|
127
|
-
`,
|
|
128
|
-
automatic_uploads: true,
|
|
129
|
-
images_reuse_filename: true,
|
|
130
|
-
paste_data_images: true,
|
|
131
|
-
media_live_embeds: true,
|
|
132
|
-
images_upload_handler: (blobInfo: {
|
|
133
|
-
blobUri: () => string;
|
|
134
|
-
blob: () => Blob;
|
|
135
|
-
filename: () => string;
|
|
136
|
-
}): Promise<string> => {
|
|
137
|
-
return new Promise((resolve, reject) => {
|
|
138
|
-
const blobUri = blobInfo.blobUri();
|
|
139
|
-
|
|
140
|
-
const existingUrl = uploadedBlobsRef.current.get(blobUri);
|
|
141
|
-
if (existingUrl) {
|
|
142
|
-
resolve(existingUrl);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (!onImageUploadRef.current) {
|
|
147
|
-
reject(new Error('No upload handler'));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const file = new File([blobInfo.blob()], blobInfo.filename(), {
|
|
152
|
-
type: blobInfo.blob().type
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
onImageUploadRef
|
|
156
|
-
.current(file)
|
|
157
|
-
.then((url) => {
|
|
158
|
-
uploadedBlobsRef.current.set(blobUri, url);
|
|
159
|
-
resolve(url);
|
|
160
|
-
})
|
|
161
|
-
.catch((error) => {
|
|
162
|
-
showDesktopAlertDialog({
|
|
163
|
-
contentText: tRef.current('str_papp_image_upload_failed'),
|
|
164
|
-
btnMode: 'tbtn_amount1',
|
|
165
|
-
tBtn1Text: tRef.current('str_confirm')
|
|
166
|
-
});
|
|
167
|
-
reject(error);
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
},
|
|
171
|
-
// Additional event handlers for formatting changes that onEditorChange might miss
|
|
172
|
-
setup: (editor: TinyMCEEditor) => {
|
|
173
|
-
// Handle formatting commands - use setTimeout to ensure DOM is updated
|
|
174
|
-
editor.on('ExecCommand', (e) => {
|
|
175
|
-
// Only trigger for formatting-related commands
|
|
176
|
-
const formattingCommands = [
|
|
177
|
-
'mceToggleFormat',
|
|
178
|
-
'Bold',
|
|
179
|
-
'Italic',
|
|
180
|
-
'Underline',
|
|
181
|
-
'Strikethrough',
|
|
182
|
-
'ForeColor',
|
|
183
|
-
'BackColor',
|
|
184
|
-
'FontSize',
|
|
185
|
-
'FormatBlock'
|
|
186
|
-
];
|
|
187
|
-
if (formattingCommands.some((cmd) => e.command.includes(cmd))) {
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
const content = editor.getContent();
|
|
190
|
-
onChangeRef.current?.(content);
|
|
191
|
-
}, 10);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
}),
|
|
196
|
-
[]
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<div>
|
|
201
|
-
<Editor
|
|
202
|
-
onInit={handleInit}
|
|
203
|
-
initialValue={initialContentRef.current}
|
|
204
|
-
onEditorChange={handleEditorChange}
|
|
205
|
-
init={editorInit}
|
|
206
|
-
/>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { TinyMCE, Editor } from 'tinymce';
|
|
2
|
-
|
|
3
|
-
export type PersonalizationTagCallback = (insertTag: (tag: string) => void) => void;
|
|
4
|
-
|
|
5
|
-
let onOpenPersonalizationModalCallback: PersonalizationTagCallback | null = null;
|
|
6
|
-
|
|
7
|
-
export function setPersonalizationModalCallback(callback: PersonalizationTagCallback | null) {
|
|
8
|
-
onOpenPersonalizationModalCallback = callback;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Registers the custom tokens plugin with the provided TinyMCE instance.
|
|
13
|
-
* Must be called before the Editor component initializes.
|
|
14
|
-
*/
|
|
15
|
-
export function registerCustomTokensPlugin(tinymceInstance: TinyMCE): void {
|
|
16
|
-
// Prevent duplicate registration
|
|
17
|
-
if (tinymceInstance.PluginManager.get('customtokens')) {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
tinymceInstance.PluginManager.add('customtokens', (editor: Editor) => {
|
|
22
|
-
editor.ui.registry.addIcon(
|
|
23
|
-
'merge-field',
|
|
24
|
-
'<svg width="24" height="24" viewBox="0 0 24 24"><text x="4" y="17" font-size="14" font-weight="900" stroke="currentColor" stroke-width="0.5">{ }</text></svg>'
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
editor.ui.registry.addButton('insertTag', {
|
|
28
|
-
tooltip: 'Insert Personalization Tag',
|
|
29
|
-
icon: 'merge-field',
|
|
30
|
-
onAction: () => {
|
|
31
|
-
const insertTag = (tag: string) => {
|
|
32
|
-
// Insert as plain text so it can be formatted like regular text
|
|
33
|
-
editor.insertContent(tag);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
if (onOpenPersonalizationModalCallback) {
|
|
37
|
-
onOpenPersonalizationModalCallback(insertTag);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
return { getMetadata: () => ({ name: 'Personalization Tag Plugin', url: '' }) };
|
|
42
|
-
});
|
|
43
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# [Refactoring] PDS RichTextEditor 기반 범용 이메일 에디터 구축 및 피처 종속 로직 분리
|
|
2
|
-
|
|
3
|
-
## 1. 진행 배경 및 목적 (Background & Objective)
|
|
4
|
-
* **배경**: 현재 제품(Product) 프로젝트에는 특정 이메일 발송 피처를 위해 자체적으로 구현한 `CustomTinyMCEEditor`(`.reference/OldEmailRichTextEditor/CustomTinyMCEEditor.tsx`)가 존재합니다. 이번 통합 리팩토링을 위해 해당 컴포넌트를 임시로 PDS(`pds-dev-kit`) 프로젝트로 가져온 상태입니다.
|
|
5
|
-
* **문제점**: `CustomTinyMCEEditor`는 범용적인 이메일 에디터가 아니라 **특정 피처의 비즈니스 로직(예: 특정 모달 호출, 고유 이미지 처리 등)에 강하게 결합**되어 만들어졌습니다. 이를 그대로 PDS 라이브러리의 공통 컴포넌트로 편입시킬 경우 도메인 의존성이 생기는 문제가 발생합니다.
|
|
6
|
-
* **목적**:
|
|
7
|
-
1. `CustomTinyMCEEditor`에 혼재된 **'범용 이메일 에디터로서의 내부 로직'**과 **'특정 피처를 위한 외부 로직'**을 완벽하게 분리(Decoupling)합니다.
|
|
8
|
-
2. PDS의 표준인 `RichTextEditor`를 유연하게 확장하여, 범용으로 사용할 수 있는 래퍼 컴포넌트 `EmailRichTextEditor`를 PDS 내부에 구축합니다.
|
|
9
|
-
3. 제품(Consumer) 측에서는 이 `EmailRichTextEditor`를 가져다 쓰되, 도메인 특화 파트(모달, 업로드 등)는 Props와 콜백을 통해 주입(Injection)하도록 제어의 역전(IoC) 구조를 만듭니다.
|
|
10
|
-
|
|
11
|
-
## 2. 핵심 구현 전략 (Core Strategy)
|
|
12
|
-
* **관심사의 분리 (Separation of Concerns)**
|
|
13
|
-
* **PDS (EmailRichTextEditor)의 책임**: 이메일용 컴팩트 툴바 UI, 이메일에 적합한 Content Style(인라인 CSS 호환), 이메일 작성에 필요한 공통 플러그인 렌더링.
|
|
14
|
-
* **제품 프로젝트(Consumer)의 책임**: 이미지 업로드 API 통신, 특정 피처용 개인화 태그(치환 태그) 삽입 모달의 렌더링 및 제어 로직 구현 후 주입.
|
|
15
|
-
* **확장성 확보 (initOptions 도입)**: `RichTextEditor`가 모든 에디터 옵션을 Prop으로 들고 있지 않게 하고, TinyMCE의 `init` 설정을 소비자가 통째로 병합할 수 있도록 `initOptions` Prop을 신설합니다.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## 3. 상세 요구사항 (Detailed Requirements)
|
|
20
|
-
|
|
21
|
-
### Task 1. `RichTextEditor` 확장 및 하위 호환성 보장 (`src/desktop/components/RichTextEditor/RichTextEditor.tsx`)
|
|
22
|
-
PDS 전체에서 사용되는 표준 에디터이므로 레거시 화면이 깨지지 않는 것이 가장 중요합니다.
|
|
23
|
-
* **Prop 확장**
|
|
24
|
-
* `initOptions?: Parameters<TinyMCE['init']>[0];`: TinyMCE 고유 설정을 주입받습니다. 컴포넌트 내부의 기본 `init` 설정과 병합하되, `initOptions`의 값이 우선순위를 가지도록 구현하세요 (`...initOptions`).
|
|
25
|
-
* `onSetup?: (editor: TinyMCEEditor) => void;`: 에디터 초기화(Setup) 시점에 외부에서 이벤트를 바인딩할 수 있도록 콜백을 엽니다.
|
|
26
|
-
* `isCompactToolbar?: boolean;`: `true` 시 이메일 에디터 특유의 좁은 간격/작은 버튼 스타일이 적용되도록 CSS를 조건부 처리하세요.
|
|
27
|
-
* **기본 설정 정비**
|
|
28
|
-
* `automatic_uploads`, `images_reuse_filename`, `paste_data_images`, `media_live_embeds` 등을 기본 `init` 속성으로 명시하세요.
|
|
29
|
-
|
|
30
|
-
### Task 2. `EmailRichTextEditor` 범용 래퍼 구축 (`src/desktop/components/RichTextEditor/EmailRichTextEditor.tsx`)
|
|
31
|
-
PDS 라이브러리로서 제공될 순수 **'이메일 전용 텍스트 에디터'**입니다. 특정 비즈니스 로직은 철저히 배제하세요.
|
|
32
|
-
* `RichTextEditor`를 반환하는 Wrapper 형태로 구현합니다.
|
|
33
|
-
* **기본 이메일 설정 주입 (`initOptions`)**
|
|
34
|
-
* 범용 이메일 에디터에 필요한 상수들 (`EMAIL_PLUGINS`, `EMAIL_TOOLBAR`, `EMAIL_CONTENT_STYLE`)을 선언하고 `initOptions`를 통해 `RichTextEditor`에 주입합니다.
|
|
35
|
-
* 이미지 업로드 캐싱 로직(드래그 앤 드롭 시 기존 업로드 Blob 재사용)은 에디터의 범용 UI/UX 편의 기능이므로, 래퍼 컴포넌트 내 `images_upload_handler`에 공통으로 구현합니다. (실제 업로드 통신 함수인 `onImageUpload`는 Props로 받아 실행)
|
|
36
|
-
* **커스텀 이벤트 브릿지 (`onSetup`)**
|
|
37
|
-
* 포맷팅 변경 시점(`Bold`, `Italic` 등 `ExecCommand` 발생)에 강제로 `onChange`를 지연 트리거 시키는 동기화 로직을 주입합니다.
|
|
38
|
-
* 에디터 기본 높이(`height`)는 기존 UI와 동일하게 `500`으로 고정합니다.
|
|
39
|
-
|
|
40
|
-
### Task 3. 외부 도메인 로직 분리 설계 (`personalization_tag_plugin.ts` 리팩토링)
|
|
41
|
-
제품의 특정 피처에서만 쓰이던 '개인화 태그(치환 태그)' 플러그인을 범용 에디터에 올바르게 결합하기 위한 작업입니다.
|
|
42
|
-
* **글로벌 등록 폐기**: `tinymce.PluginManager`에 의존하던 전역 등록 방식을 버리고, 특정 에디터 인스턴스에만 적용되도록 변경합니다.
|
|
43
|
-
* **Setup 콜백 생성 함수로 변경**
|
|
44
|
-
* `getPersonalizationSetup(onOpenPersonalizationModal?: Callback)` 형태의 팩토리 함수로 리팩토링하세요.
|
|
45
|
-
* 이 함수는 `EmailRichTextEditor`가 렌더링 될 때, 외부(제품 프로젝트)로부터 `onOpenPersonalizationModal` 콜백을 주입받아 모달 트리거 로직을 완성한 뒤, TinyMCE의 `setup` 시점에 실행할 함수를 반환해야 합니다.
|
|
46
|
-
|
|
47
|
-
---
|
|
48
|
-
|
|
49
|
-
## 4. 완료 조건 및 검증 (Acceptance Criteria)
|
|
50
|
-
|
|
51
|
-
1. **관심사 분리 확인**: PDS 내의 `EmailRichTextEditor` 코드를 리뷰했을 때, 특정 제품의 모달 이름, API 엔드포인트 등의 비즈니스 종속 코드가 단 한 줄도 없어야 합니다. 오직 PDS의 컴포넌트로서 Props Interface만 제공해야 합니다.
|
|
52
|
-
2. **하위 호환성 (Regression Free)**: 기존 `RichTextEditor`를 사용하는 일반 화면에서 런타임 에러, TypeScript 인터페이스 에러, 스타일 깨짐이 없어야 합니다.
|
|
53
|
-
3. **기능 검증**: 제품 프로젝트에서 `EmailRichTextEditor`를 마운트했을 때,
|
|
54
|
-
* 기존과 동일한 컴팩트 툴바 UI가 노출될 것
|
|
55
|
-
* `{ }` (개인화 태그) 아이콘 클릭 시, 앱에서 주입한 전용 모달이 정상적으로 호출될 것
|
|
56
|
-
* 이미지 업로드, 텍스트 포맷팅 등이 기존 `CustomTinyMCEEditor` 환경과 100% 동일하게 동작할 것
|
|
57
|
-
4. **마무리**: 통합 검증 통과 시 `.reference/OldEmailRichTextEditor` 참고용 폴더를 안전하게 삭제합니다.
|
|
Binary file
|