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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pds-dev-kit-web",
3
- "version": "2.2.315",
3
+ "version": "2.2.316",
4
4
  "license": "MIT",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
package/release-note.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # PDS-DEV-KIT-WEB Release Notes
2
- ## [v2.2.315]
2
+ ## [v2.2.316]
3
3
  ## daily|https://design.storybook.publ.biz/
4
4
 
5
5
  ### 업데이트 사항
6
- * EmailRichTextEditor 컴포넌트 추가
6
+ * 다국어 입력 모달에 신규 언어 3종(베트남어/러시아어/이탈리아어) 노출 추가
@@ -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,5 +0,0 @@
1
- <component name="ProjectCodeStyleConfiguration">
2
- <state>
3
- <option name="USE_PER_PROJECT_SETTINGS" value="true" />
4
- </state>
5
- </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>
@@ -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>
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="PrettierConfiguration">
4
- <option name="myConfigurationMode" value="AUTOMATIC" />
5
- <option name="myRunOnSave" value="true" />
6
- </component>
7
- </project>
package/.idea/vcs.xml DELETED
@@ -1,6 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="VcsDirectoryMappings">
4
- <mapping directory="" vcs="Git" />
5
- </component>
6
- </project>
@@ -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,13 +0,0 @@
1
- /* TinyMCE toolbar button size reduction */
2
- .tox .tox-tbtn {
3
- width: 25px;
4
- min-width: 25px;
5
- }
6
-
7
- .tox .tox-tbtn--select {
8
- width: auto !important;
9
- }
10
-
11
- .tox.tox-tinymce {
12
- z-index: 0 !important;
13
- }
@@ -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