@@ -4,15 +4,137 @@ import ReactQuill from "react-quill";
44import { CiEdit } from "react-icons/ci" ;
55import {
66 convertToPersian ,
7+ SanitizeHTML ,
78 getFieldTranslationByNames ,
89} from "../../services/Utility" ;
9- import {
10- EditorContainer as StyledEditorContainer ,
11- Label ,
12- Char ,
13- formats ,
14- modulesWithoutImage ,
15- } from "../editorContainerStyle" ;
10+ import styled from "styled-components" ;
11+
12+ const EditorContainer = styled . div `
13+ background-color: ${ ( props ) =>
14+ props . theme . colors . newColors . otherColors . inputBg } ;
15+ border-radius: 5px;
16+ overflow: hidden;
17+ color: white;
18+ margin: 10px auto;
19+ height: 212px;
20+ border: ${ ( { border } ) => ( border ? "1px solid gray" : "none" ) } ;
21+ .ql-toolbar {
22+ background-color: ${ ( props ) =>
23+ props . theme . colors . newColors . otherColors . inputBg } ;
24+ border: none;
25+ border-bottom: 1px solid gray;
26+ }
27+
28+ .ql-container {
29+ background-color: ${ ( props ) =>
30+ props . theme . colors . newColors . otherColors . inputBg } ;
31+ color: ${ ( props ) => props . theme . colors . newColors . shades . title } ;
32+ border: none;
33+ overflow: auto;
34+ max-height: 150px;
35+ }
36+
37+ && .ql-editor {
38+ min-height: 150px;
39+ text-align: unset;
40+ font-size: 18px !important;
41+ line-height: 1.6;
42+ -webkit-text-size-adjust: 100%;
43+ font-family: "AzarMehr" !important;
44+ }
45+
46+ /* placeholder */
47+ && .ql-editor::before {
48+ font-size: inherit !important;
49+ color: #888;
50+ opacity: 0.7;
51+ font-family: "AzarMehr" !important;
52+ }
53+
54+ .ql-toolbar .ql-picker {
55+ color: white;
56+ }
57+
58+ .ql-toolbar .ql-stroke {
59+ stroke: ${ ( props ) => props . theme . colors . newColors . shades . title } ;
60+ }
61+
62+ .ql-toolbar .ql-fill {
63+ fill: ${ ( props ) => props . theme . colors . newColors . shades . title } ;
64+ }
65+
66+ .ql-toolbar .ql-picker-options {
67+ border: 1px solid #555;
68+ }
69+
70+ @media (max-width: 700px) {
71+ && .ql-editor {
72+ font-size: 15px !important;
73+ -webkit-text-size-adjust: 100%;
74+ }
75+ }
76+ ` ;
77+
78+ const Label = styled . h2 `
79+ color: ${ ( props ) => props . theme . colors . newColors . shades . title } ;
80+ display: block;
81+ margin-bottom: 10px;
82+ font-weight: 500;
83+ font-size: 16px;
84+ margin-top: 20px;
85+ ` ;
86+
87+ const Char = styled . div `
88+ display: flex;
89+ justify-content: end;
90+ align-items: center;
91+ gap: 5px;
92+ svg {
93+ color: ${ ( { isOverLimit, theme } ) =>
94+ isOverLimit ? "red" : theme . colors . newColors . shades . title } ;
95+ }
96+
97+ span {
98+ color: ${ ( { isOverLimit } ) => ( isOverLimit ? "red" : "#a0a0ab" ) } ;
99+ font-size: 13px;
100+ font-weight: 400;
101+ }
102+ ` ;
103+ const formats = [
104+ "size" ,
105+ "bold" ,
106+ "italic" ,
107+ "underline" ,
108+ "strike" ,
109+ "blockquote" ,
110+ "list" ,
111+ "bullet" ,
112+ "indent" ,
113+ "link" ,
114+ "code-block" ,
115+ "align" ,
116+ ] ;
117+
118+ const getModules = ( img = false ) => {
119+ const toolbar = [
120+ [ "bold" , "italic" , "underline" , "strike" , "blockquote" ] ,
121+ [
122+ { list : "ordered" } ,
123+ { list : "bullet" } ,
124+ { indent : "-1" } ,
125+ { indent : "+1" } ,
126+ ] ,
127+ [ "link" , "code-block" ] , // دکمه تصویر به صورت داینامیک اضافه میشود
128+ [ { align : [ ] } ] ,
129+ ] ;
130+
131+ if ( img ) {
132+ // اضافه کردن دکمه image در صورت فعال بودن پراپ
133+ toolbar [ 2 ] . splice ( 1 , 0 , "image" ) ; // دکمه image بعد از link اضافه میشود
134+ }
135+
136+ return { toolbar } ;
137+ } ;
16138
17139/**
18140 * Reusable RichTextEditor with strict char limit
@@ -33,28 +155,28 @@ const CustomEditor = ({
33155 showIcon = true ,
34156 placeholder = "" ,
35157 border = false ,
158+ img = false ,
36159} ) => {
37160 const [ content , setContent ] = useState ( value ) ;
38161
39162 useEffect ( ( ) => {
40163 setContent ( value ) ;
41164 } , [ value ] ) ;
42165 const handleChange = ( val , delta , source , editor ) => {
43- // طول واقعی متن بدون html
44- const text = editor . getText ( ) ; // این متن plain text است
166+ const text = editor . getText ( ) ;
45167 let newValue = val ;
46168
47169 if ( text . length - 1 > charLimit ) {
48- // -1 چون editor یه \n اضافه میکنه
49170 const allowedText = text . slice ( 0 , charLimit ) ;
50- // جایگزینی متن editor با محدودیت
51171 const quill = editor ;
52- quill . deleteText ( charLimit , text . length ) ; // بقیه رو حذف میکنه
172+ quill . deleteText ( charLimit , text . length ) ;
53173 newValue = quill . root . innerHTML ;
54174 }
55175
56- setContent ( newValue ) ;
57- onChange ?. ( newValue ) ;
176+ const safeValue = SanitizeHTML ( newValue ) ;
177+
178+ setContent ( safeValue ) ;
179+ onChange ?. ( safeValue ) ;
58180 } ;
59181
60182 const handleKeyDown = ( event ) => {
@@ -70,16 +192,22 @@ const CustomEditor = ({
70192 event . preventDefault ( ) ;
71193 }
72194 } ;
73-
74195 const handlePaste = ( event ) => {
75196 event . preventDefault ( ) ;
197+
76198 const paste = event . clipboardData . getData ( "text" ) ;
77199 const remaining = charLimit - content . length ;
78- if ( remaining <= 0 ) return ; // هیچ چیزی اضافه نشود
79- const toPaste = paste . slice ( 0 , remaining ) ;
80- const newValue = content + toPaste ;
81- setContent ( newValue ) ;
82- onChange ?. ( newValue ) ;
200+
201+ if ( paste . length > remaining ) {
202+ return ;
203+ }
204+
205+ const newValue = content + paste ;
206+
207+ const safeValue = SanitizeHTML ( newValue ) ;
208+
209+ setContent ( safeValue ) ;
210+ onChange ?. ( safeValue ) ;
83211 } ;
84212
85213 const remainingChars = charLimit - content . length ;
@@ -88,17 +216,17 @@ const CustomEditor = ({
88216 return (
89217 < div >
90218 { label && < Label > { label } </ Label > }
91- < StyledEditorContainer border = { border } >
219+ < EditorContainer border = { border } >
92220 < ReactQuill
93221 value = { content }
94222 onChange = { handleChange }
95223 onKeyDown = { handleKeyDown }
96224 onPaste = { handlePaste }
97- modules = { modulesWithoutImage }
225+ modules = { getModules ( img ) }
98226 formats = { formats }
99227 placeholder = { placeholder }
100228 />
101- </ StyledEditorContainer >
229+ </ EditorContainer >
102230 < Char isOverLimit = { isOverLimit } >
103231 < span >
104232 { convertToPersian ( remainingChars ) } { getFieldTranslationByNames ( "530" ) }
0 commit comments