@@ -2,18 +2,22 @@ import _ from 'lodash';
22import React , { Component } from 'react' ;
33import PropTypes from 'prop-types' ;
44import Random from 'random-id' ;
5- import { CustomStep , OptionsStep , TextStep } from './steps/steps ' ;
5+ import { CustomStep , OptionsStep , TextStep } from './steps' ;
66import schema from './schemas/schema' ;
77import ChatBotContainer from './ChatBotContainer' ;
88import Content from './Content' ;
99import Header from './Header' ;
1010import HeaderTitle from './HeaderTitle' ;
1111import HeaderIcon from './HeaderIcon' ;
1212import FloatButton from './FloatButton' ;
13- import ChatIcon from './ChatIcon' ;
14- import CloseIcon from './CloseIcon' ;
1513import Footer from './Footer' ;
1614import Input from './Input' ;
15+ import SubmitButton from './SubmitButton' ;
16+ import {
17+ ChatIcon ,
18+ CloseIcon ,
19+ SubmitIcon ,
20+ } from './icons' ;
1721
1822class ChatBot extends Component {
1923 /* istanbul ignore next */
@@ -30,31 +34,29 @@ class ChatBot extends Component {
3034 opened : props . opened || ! props . floating ,
3135 inputValue : '' ,
3236 inputInvalid : false ,
33- defaultBotSettings : { } ,
3437 defaultUserSettings : { } ,
3538 } ;
3639
3740 this . renderStep = this . renderStep . bind ( this ) ;
3841 this . triggerNextStep = this . triggerNextStep . bind ( this ) ;
3942 this . onValueChange = this . onValueChange . bind ( this ) ;
4043 this . handleKeyPress = this . handleKeyPress . bind ( this ) ;
44+ this . handleSubmitButton = this . handleSubmitButton . bind ( this ) ;
4145 }
4246
4347 componentWillMount ( ) {
44- const { botDelay, botAvatar, customDelay, userDelay, userAvatar } = this . props ;
48+ const {
49+ botDelay,
50+ botAvatar,
51+ customDelay,
52+ userAvatar,
53+ userDelay,
54+ } = this . props ;
4555 const steps = { } ;
4656
47- const defaultBotSettings = {
48- delay : botDelay ,
49- avatar : botAvatar ,
50- } ;
51- const defaultUserSettings = {
52- delay : userDelay ,
53- avatar : userAvatar ,
54- } ;
55- const defaultCustomSettings = {
56- delay : customDelay ,
57- } ;
57+ const defaultBotSettings = { delay : botDelay , avatar : botAvatar } ;
58+ const defaultUserSettings = { delay : userDelay , avatar : userAvatar } ;
59+ const defaultCustomSettings = { delay : customDelay } ;
5860
5961 for ( let i = 0 , len = this . props . steps . length ; i < len ; i += 1 ) {
6062 const step = this . props . steps [ i ] ;
@@ -75,27 +77,23 @@ class ChatBot extends Component {
7577 ) ;
7678 }
7779
80+ schema . checkInvalidIds ( steps ) ;
81+
7882 const currentStep = this . props . steps [ 0 ] ;
7983 const renderedSteps = [ steps [ currentStep . id ] ] ;
8084 const previousSteps = [ steps [ currentStep . id ] ] ;
8185
8286 this . setState ( {
83- defaultBotSettings,
84- defaultUserSettings,
85- steps,
8687 currentStep,
87- renderedSteps ,
88+ defaultUserSettings ,
8889 previousSteps,
90+ renderedSteps,
91+ steps,
8992 } ) ;
9093 }
9194
9295 componentDidMount ( ) {
93- const chatbotContent = document . querySelector ( '.rsc-content' ) ;
94-
95- /* istanbul ignore next */
96- if ( chatbotContent ) {
97- chatbotContent . addEventListener ( 'DOMNodeInserted' , this . onNodeInserted ) ;
98- }
96+ this . content . addEventListener ( 'DOMNodeInserted' , this . onNodeInserted ) ;
9997 }
10098
10199 componentWillUpdate ( nextProps , nextState ) {
@@ -107,15 +105,9 @@ class ChatBot extends Component {
107105 }
108106
109107 componentWillUnmount ( ) {
110- const chatbotContent = document . querySelector ( '.rsc-content' ) ;
111-
112- /* istanbul ignore next */
113- if ( chatbotContent ) {
114- chatbotContent . removeEventListener ( 'DOMNodeInserted' , this . onNodeInserted ) ;
115- }
108+ this . content . removeEventListener ( 'DOMNodeInserted' , this . onNodeInserted ) ;
116109 }
117110
118- /* istanbul ignore next */
119111 onNodeInserted ( event ) {
120112 event . currentTarget . scrollTop = event . currentTarget . scrollHeight ;
121113 }
@@ -126,10 +118,10 @@ class ChatBot extends Component {
126118
127119 triggerNextStep ( data ) {
128120 const {
129- renderedSteps ,
121+ defaultUserSettings ,
130122 previousSteps,
123+ renderedSteps,
131124 steps,
132- defaultUserSettings,
133125 } = this . state ;
134126 let { currentStep, previousStep } = this . state ;
135127 const isEnd = currentStep . end ;
@@ -199,11 +191,7 @@ class ChatBot extends Component {
199191 this . setState ( { renderedSteps, currentStep, previousStep } , ( ) => {
200192 if ( nextStep . user ) {
201193 this . setState ( { disabled : false } , ( ) => {
202- const chatInput = document . querySelector ( '.rsc-input' ) ;
203- /* istanbul ignore next */
204- if ( chatInput ) {
205- chatInput . focus ( ) ;
206- }
194+ this . input . focus ( ) ;
207195 } ) ;
208196 } else {
209197 renderedSteps . push ( nextStep ) ;
@@ -278,40 +266,48 @@ class ChatBot extends Component {
278266
279267 handleKeyPress ( event ) {
280268 if ( event . key === 'Enter' ) {
281- const {
269+ this . submitUserMessage ( ) ;
270+ }
271+ }
272+
273+ handleSubmitButton ( ) {
274+ this . submitUserMessage ( ) ;
275+ }
276+
277+ submitUserMessage ( ) {
278+ const {
279+ defaultUserSettings,
280+ inputValue,
281+ previousSteps,
282+ renderedSteps,
283+ } = this . state ;
284+ let { currentStep } = this . state ;
285+
286+ const isInvalid = currentStep . validator && this . checkInvalidInput ( ) ;
287+
288+ if ( ! isInvalid ) {
289+ const step = {
290+ message : inputValue ,
291+ value : inputValue ,
292+ } ;
293+
294+ currentStep = Object . assign (
295+ { } ,
296+ defaultUserSettings ,
297+ currentStep ,
298+ step ,
299+ ) ;
300+
301+ renderedSteps . push ( currentStep ) ;
302+ previousSteps . push ( currentStep ) ;
303+
304+ this . setState ( {
305+ currentStep,
282306 renderedSteps,
283307 previousSteps,
284- inputValue,
285- defaultUserSettings,
286- } = this . state ;
287- let { currentStep } = this . state ;
288-
289- const isInvalid = currentStep . validator && this . checkInvalidInput ( ) ;
290-
291- if ( ! isInvalid ) {
292- const step = {
293- message : inputValue ,
294- value : inputValue ,
295- } ;
296-
297- currentStep = Object . assign (
298- { } ,
299- defaultUserSettings ,
300- currentStep ,
301- step ,
302- ) ;
303-
304- renderedSteps . push ( currentStep ) ;
305- previousSteps . push ( currentStep ) ;
306-
307- this . setState ( {
308- currentStep,
309- renderedSteps,
310- previousSteps,
311- disabled : true ,
312- inputValue : '' ,
313- } ) ;
314- }
308+ disabled : true ,
309+ inputValue : '' ,
310+ } ) ;
315311 }
316312 }
317313
@@ -332,11 +328,7 @@ class ChatBot extends Component {
332328 inputInvalid : false ,
333329 disabled : false ,
334330 } , ( ) => {
335- const chatInput = document . querySelector ( '.rsc-input' ) ;
336- /* istanbul ignore next */
337- if ( chatInput ) {
338- chatInput . focus ( ) ;
339- }
331+ this . input . focus ( ) ;
340332 } ) ;
341333 } , 2000 ) ;
342334 } ) ;
@@ -430,23 +422,25 @@ class ChatBot extends Component {
430422
431423 render ( ) {
432424 const {
433- opened,
434425 disabled,
435- inputValue,
436426 inputInvalid,
427+ inputValue,
428+ opened,
437429 renderedSteps,
438430 } = this . state ;
439431 const {
432+ className,
433+ contentStyle,
434+ floating,
435+ footerStyle,
440436 headerComponent,
441437 headerTitle,
442- floating,
443438 hideHeader,
444- style,
445- contentStyle,
446- footerStyle,
439+ hideSubmitButton,
447440 inputStyle,
448- className,
449441 placeholder,
442+ style,
443+ submitButtonStyle,
450444 } = this . props ;
451445
452446 const header = headerComponent || (
@@ -489,6 +483,7 @@ class ChatBot extends Component {
489483 { ! hideHeader && header }
490484 < Content
491485 className = "rsc-content"
486+ innerRef = { contentRef => this . content = contentRef }
492487 floating = { floating }
493488 style = { contentStyle }
494489 >
@@ -501,14 +496,29 @@ class ChatBot extends Component {
501496 < Input
502497 type = "textarea"
503498 style = { inputStyle }
499+ innerRef = { inputRef => this . input = inputRef }
504500 className = "rsc-input"
505- placeholder = { placeholder }
501+ placeholder = { inputInvalid ? '' : placeholder }
506502 onKeyPress = { this . handleKeyPress }
507503 onChange = { this . onValueChange }
508504 value = { inputValue }
505+ floating = { floating }
509506 invalid = { inputInvalid }
510507 disabled = { disabled }
508+ hasButton = { ! hideSubmitButton }
511509 />
510+ {
511+ ! hideSubmitButton &&
512+ < SubmitButton
513+ className = "rsc-submit-button"
514+ style = { submitButtonStyle }
515+ onClick = { this . handleSubmitButton }
516+ invalid = { inputInvalid }
517+ disabled = { disabled }
518+ >
519+ < SubmitIcon />
520+ </ SubmitButton >
521+ }
512522 </ Footer >
513523 </ ChatBotContainer >
514524 </ div >
@@ -518,52 +528,56 @@ class ChatBot extends Component {
518528
519529ChatBot . propTypes = {
520530 steps : PropTypes . array . isRequired ,
531+ avatarStyle : PropTypes . object ,
532+ botAvatar : PropTypes . string ,
533+ botDelay : PropTypes . number ,
534+ bubbleStyle : PropTypes . object ,
535+ className : PropTypes . string ,
536+ contentStyle : PropTypes . object ,
537+ customDelay : PropTypes . number ,
538+ customStyle : PropTypes . object ,
539+ floating : PropTypes . bool ,
540+ footerStyle : PropTypes . object ,
541+ handleEnd : PropTypes . func ,
521542 headerComponent : PropTypes . element ,
522543 headerTitle : PropTypes . string ,
523- hideHeader : PropTypes . bool ,
524544 hideBotAvatar : PropTypes . bool ,
545+ hideHeader : PropTypes . bool ,
546+ hideSubmitButton : PropTypes . bool ,
525547 hideUserAvatar : PropTypes . bool ,
526- floating : PropTypes . bool ,
548+ inputStyle : PropTypes . object ,
527549 opened : PropTypes . bool ,
528550 toggleFloating : PropTypes . func ,
551+ placeholder : PropTypes . string ,
529552 style : PropTypes . object ,
530- contentStyle : PropTypes . object ,
531- footerStyle : PropTypes . object ,
532- inputStyle : PropTypes . object ,
533- avatarStyle : PropTypes . object ,
534- bubbleStyle : PropTypes . object ,
535- customStyle : PropTypes . object ,
536- customDelay : PropTypes . number ,
537- className : PropTypes . string ,
538- botAvatar : PropTypes . string ,
539- botDelay : PropTypes . number ,
553+ submitButtonStyle : PropTypes . object ,
540554 userAvatar : PropTypes . string ,
541555 userDelay : PropTypes . number ,
542- handleEnd : PropTypes . func ,
543- placeholder : PropTypes . string ,
544556} ;
545557
546558ChatBot . defaultProps = {
547- placeholder : 'Type the message ...' ,
559+ avatarStyle : { } ,
560+ botDelay : 1000 ,
561+ bubbleStyle : { } ,
562+ className : '' ,
563+ contentStyle : { } ,
564+ customStyle : { } ,
565+ customDelay : 1000 ,
566+ floating : false ,
567+ footerStyle : { } ,
548568 handleEnd : undefined ,
549569 headerComponent : undefined ,
550570 headerTitle : 'Chat' ,
551- hideHeader : false ,
552571 hideBotAvatar : false ,
572+ hideHeader : false ,
573+ hideSubmitButton : false ,
553574 hideUserAvatar : false ,
554- floating : false ,
575+ inputStyle : { } ,
555576 opened : undefined ,
556- toggleFloating : undefined ,
577+ placeholder : 'Type the message ...' ,
557578 style : { } ,
558- contentStyle : { } ,
559- footerStyle : { } ,
560- inputStyle : { } ,
561- avatarStyle : { } ,
562- bubbleStyle : { } ,
563- customStyle : { } ,
564- customDelay : 1000 ,
565- className : '' ,
566- botDelay : 1000 ,
579+ submitButtonStyle : { } ,
580+ toggleFloating : undefined ,
567581 userDelay : 1000 ,
568582 botAvatar : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxwYXRoIHN0eWxlPSJmaWxsOiM5M0M3RUY7IiBkPSJNMzAyLjU0NSw2OS44MThjMC0yNS43MDctMjAuODQtNDYuNTQ1LTQ2LjU0NS00Ni41NDVzLTQ2LjU0NSwyMC44MzgtNDYuNTQ1LDQ2LjU0NQ0KCWMwLDE3LjIyNSw5LjM2NSwzMi4yNTQsMjMuMjczLDQwLjMwNHY4My44MThoNDYuNTQ1di04My44MThDMjkzLjE4MSwxMDIuMDczLDMwMi41NDUsODcuMDQzLDMwMi41NDUsNjkuODE4eiIvPg0KPHBhdGggc3R5bGU9ImZpbGw6IzVBOEJCMDsiIGQ9Ik0yNTYsMjMuMjczdjE3MC42NjdoMjMuMjczdi04My44MThjMTMuOTA4LTguMDQ5LDIzLjI3My0yMy4wNzcsMjMuMjczLTQwLjMwNA0KCUMzMDIuNTQ1LDQ0LjExMSwyODEuNzA1LDIzLjI3MywyNTYsMjMuMjczeiIvPg0KPHJlY3QgeT0iMjQwLjQ4NSIgc3R5bGU9ImZpbGw6IzkzQzdFRjsiIHdpZHRoPSIyNDguMjQyIiBoZWlnaHQ9IjEyNC4xMjEiLz4NCjxyZWN0IHg9IjI2My43NTgiIHk9IjI0MC40ODUiIHN0eWxlPSJmaWxsOiM1QThCQjA7IiB3aWR0aD0iMjQ4LjI0MiIgaGVpZ2h0PSIxMjQuMTIxIi8+DQo8cmVjdCB4PSIxODYuMTgyIiB5PSIzNjQuNjA2IiBzdHlsZT0iZmlsbDojOTNDN0VGOyIgd2lkdGg9IjEzOS42MzYiIGhlaWdodD0iMTI0LjEyMSIvPg0KPHJlY3QgeD0iMjU2IiB5PSIzNjQuNjA2IiBzdHlsZT0iZmlsbDojNUE4QkIwOyIgd2lkdGg9IjY5LjgxOCIgaGVpZ2h0PSIxMjQuMTIxIi8+DQo8cmVjdCB4PSI0Ni41NDUiIHk9IjE2Mi45MDkiIHN0eWxlPSJmaWxsOiNDQ0U5Rjk7IiB3aWR0aD0iNDE4LjkwOSIgaGVpZ2h0PSIyNzkuMjczIi8+DQo8cmVjdCB4PSIyNTYiIHk9IjE2Mi45MDkiIHN0eWxlPSJmaWxsOiM5M0M3RUY7IiB3aWR0aD0iMjA5LjQ1NSIgaGVpZ2h0PSIyNzkuMjczIi8+DQo8cGF0aCBzdHlsZT0iZmlsbDojM0M1RDc2OyIgZD0iTTE5My45MzksMjcxLjUxNWMwLDE3LjEzOC0xMy44OTQsMzEuMDMtMzEuMDMsMzEuMDNsMCwwYy0xNy4xMzYsMC0zMS4wMy0xMy44OTItMzEuMDMtMzEuMDNsMCwwDQoJYzAtMTcuMTM4LDEzLjg5NC0zMS4wMywzMS4wMy0zMS4wM2wwLDBDMTgwLjA0NiwyNDAuNDg1LDE5My45MzksMjU0LjM3NywxOTMuOTM5LDI3MS41MTVMMTkzLjkzOSwyNzEuNTE1eiIvPg0KPHBhdGggc3R5bGU9ImZpbGw6IzFFMkUzQjsiIGQ9Ik0zODAuMTIxLDI3MS41MTVjMCwxNy4xMzgtMTMuODk0LDMxLjAzLTMxLjAzLDMxLjAzbDAsMGMtMTcuMTM3LDAtMzEuMDMtMTMuODkyLTMxLjAzLTMxLjAzbDAsMA0KCWMwLTE3LjEzOCwxMy44OTQtMzEuMDMsMzEuMDMtMzEuMDNsMCwwQzM2Ni4yMjcsMjQwLjQ4NSwzODAuMTIxLDI1NC4zNzcsMzgwLjEyMSwyNzEuNTE1TDM4MC4xMjEsMjcxLjUxNXoiLz4NCjxwYXRoIHN0eWxlPSJmaWxsOiMzQzVENzY7IiBkPSJNMTg2LjE4MiwzNDkuMDkxYzAsMzguNTU4LDMxLjI1OCw2OS44MTgsNjkuODE4LDY5LjgxOGwwLDBjMzguNTU4LDAsNjkuODE4LTMxLjI2LDY5LjgxOC02OS44MTgNCglIMTg2LjE4MnoiLz4NCjxwYXRoIHN0eWxlPSJmaWxsOiMxRTJFM0I7IiBkPSJNMjU2LDM0OS4wOTFjMCwzOC41NTgsMCw0Ni41NDUsMCw2OS44MThsMCwwYzM4LjU1OCwwLDY5LjgxOC0zMS4yNiw2OS44MTgtNjkuODE4SDI1NnoiLz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjxnPg0KPC9nPg0KPGc+DQo8L2c+DQo8Zz4NCjwvZz4NCjwvc3ZnPg0K' ,
569583 userAvatar : 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgLTIwOC41IDIxIDEwMCAxMDAiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9Ii0yMDguNSAyMSAxMDAgMTAwIiB4bWw6c3BhY2U9InByZXNlcnZlIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGc+PGNpcmNsZSBjeD0iLTE1OC41IiBjeT0iNzEiIGZpbGw9IiNGNUVFRTUiIGlkPSJNYXNrIiByPSI1MCIvPjxnPjxkZWZzPjxjaXJjbGUgY3g9Ii0xNTguNSIgY3k9IjcxIiBpZD0iTWFza18yXyIgcj0iNTAiLz48L2RlZnM+PGNsaXBQYXRoIGlkPSJNYXNrXzRfIj48dXNlIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjTWFza18yXyIvPjwvY2xpcFBhdGg+PHBhdGggY2xpcC1wYXRoPSJ1cmwoI01hc2tfNF8pIiBkPSJNLTEwOC41LDEyMXYtMTRjMCwwLTIxLjItNC45LTI4LTYuN2MtMi41LTAuNy03LTMuMy03LTEyICAgICBjMC0xLjcsMC02LjMsMC02LjNoLTE1aC0xNWMwLDAsMCw0LjYsMCw2LjNjMCw4LjctNC41LDExLjMtNywxMmMtNi44LDEuOS0yOC4xLDcuMy0yOC4xLDYuN3YxNGg1MC4xSC0xMDguNXoiIGZpbGw9IiNFNkMxOUMiIGlkPSJNYXNrXzNfIi8+PGcgY2xpcC1wYXRoPSJ1cmwoI01hc2tfNF8pIj48ZGVmcz48cGF0aCBkPSJNLTEwOC41LDEyMXYtMTRjMCwwLTIxLjItNC45LTI4LTYuN2MtMi41LTAuNy03LTMuMy03LTEyYzAtMS43LDAtNi4zLDAtNi4zaC0xNWgtMTVjMCwwLDAsNC42LDAsNi4zICAgICAgIGMwLDguNy00LjUsMTEuMy03LDEyYy02LjgsMS45LTI4LjEsNy4zLTI4LjEsNi43djE0aDUwLjFILTEwOC41eiIgaWQ9Ik1hc2tfMV8iLz48L2RlZnM+PGNsaXBQYXRoIGlkPSJNYXNrXzVfIj48dXNlIG92ZXJmbG93PSJ2aXNpYmxlIiB4bGluazpocmVmPSIjTWFza18xXyIvPjwvY2xpcFBhdGg+PHBhdGggY2xpcC1wYXRoPSJ1cmwoI01hc2tfNV8pIiBkPSJNLTE1OC41LDEwMC4xYzEyLjcsMCwyMy0xOC42LDIzLTM0LjQgICAgICBjMC0xNi4yLTEwLjMtMjQuNy0yMy0yNC43cy0yMyw4LjUtMjMsMjQuN0MtMTgxLjUsODEuNS0xNzEuMiwxMDAuMS0xNTguNSwxMDAuMXoiIGZpbGw9IiNENEIwOEMiIGlkPSJoZWFkLXNoYWRvdyIvPjwvZz48L2c+PHBhdGggZD0iTS0xNTguNSw5NmMxMi43LDAsMjMtMTYuMywyMy0zMWMwLTE1LjEtMTAuMy0yMy0yMy0yM3MtMjMsNy45LTIzLDIzICAgIEMtMTgxLjUsNzkuNy0xNzEuMiw5Ni0xNTguNSw5NnoiIGZpbGw9IiNGMkNFQTUiIGlkPSJoZWFkIi8+PC9nPjwvc3ZnPg==' ,
0 commit comments