@capillarytech/creatives-library 3.8.7 → 3.8.8
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/assets/lineImageTemplate.png +0 -0
- package/package.json +1 -1
- package/routes.js +5 -0
- package/services/getSchema.js +4 -1
- package/translations/en.json +23 -1
- package/translations/zh.json +23 -1
- package/v2Components/FormBuilder/index.js +163 -31
- package/v2Components/FormBuilder/messages.js +16 -0
- package/v2Components/TemplatePreview/index.js +13 -4
- package/v2Containers/Assets/Gallery/index.js +40 -29
- package/v2Containers/CreativesContainer/SlideBoxContent.js +58 -3
- package/v2Containers/CreativesContainer/index.js +1 -0
- package/v2Containers/EmailWrapper/index.js +3 -5
- package/v2Containers/Line/Create/_lineCreate.scss +40 -19
- package/v2Containers/Line/Create/actions.js +11 -8
- package/v2Containers/Line/Create/index.js +405 -215
- package/v2Containers/Line/Create/initialSchema.js +378 -0
- package/v2Containers/Line/Create/messages.js +42 -2
- package/v2Containers/Line/Create/sagas.js +10 -4
- package/v2Containers/Line/Create/selectors.js +1 -2
- package/v2Containers/Line/CreateWrapper/constants.js +2 -0
- package/v2Containers/Line/CreateWrapper/index.js +114 -0
- package/v2Containers/Line/CreateWrapper/messages.js +43 -0
- package/v2Containers/Templates/_templates.scss +20 -1
- package/v2Containers/Templates/index.js +59 -5
- package/v2Containers/TemplatesV2/index.js +5 -4
- package/v2Containers/TemplatesV2/messages.js +4 -0
|
@@ -316,29 +316,34 @@ export class Gallery extends React.Component { // eslint-disable-line react/pref
|
|
|
316
316
|
{
|
|
317
317
|
key: `${currentChannel}-card-${template.name}`,
|
|
318
318
|
title: <span title={template.name} >{template.name}</span>,
|
|
319
|
-
extra: [
|
|
320
|
-
<CapIcon
|
|
321
|
-
className="view-gallery"
|
|
322
|
-
style={{marginRight: '16px'}}
|
|
323
|
-
type="eye"
|
|
324
|
-
onClick={() => { this.openPreviewMode(template); }}
|
|
325
|
-
/>,
|
|
326
|
-
<CapDropdown
|
|
327
|
-
overlay={
|
|
328
|
-
<CapMenu>
|
|
329
|
-
<CapMenu.Item className="delete-gallery" onClick={() => this.deleteAsset(template)}>
|
|
330
|
-
<FormattedMessage {...messages.delete} />
|
|
331
|
-
</CapMenu.Item>
|
|
332
|
-
</CapMenu>
|
|
333
|
-
}
|
|
334
|
-
>
|
|
335
|
-
<CapIcon type="more" />
|
|
336
|
-
</CapDropdown>,
|
|
337
|
-
],
|
|
338
319
|
};
|
|
320
|
+
if (!this.props.isLineAsset) {
|
|
321
|
+
templateData.extra = [
|
|
322
|
+
<CapIcon
|
|
323
|
+
className="view-gallery"
|
|
324
|
+
style={{marginRight: '16px'}}
|
|
325
|
+
type="eye"
|
|
326
|
+
onClick={() => { this.openPreviewMode(template); }}
|
|
327
|
+
/>,
|
|
328
|
+
<CapDropdown
|
|
329
|
+
overlay={
|
|
330
|
+
<CapMenu>
|
|
331
|
+
<CapMenu.Item className="delete-gallery" onClick={() => this.deleteAsset(template)}>
|
|
332
|
+
<FormattedMessage {...messages.delete} />
|
|
333
|
+
</CapMenu.Item>
|
|
334
|
+
</CapMenu>
|
|
335
|
+
}
|
|
336
|
+
>
|
|
337
|
+
<CapIcon type="more" />
|
|
338
|
+
</CapDropdown>,
|
|
339
|
+
];
|
|
340
|
+
}
|
|
339
341
|
if (!this.props.isFullMode) {
|
|
340
342
|
templateData.hoverOption = <CapButton onClick={(e) => handlers.selectAsset(e, template._id)}><FormattedMessage {...messages.select} /></CapButton>;
|
|
341
343
|
}
|
|
344
|
+
if (this.props.isLineAsset) {
|
|
345
|
+
templateData.hoverOption = <CapButton onClick={() => this.props.onGalleryImageSelect(template)}><FormattedMessage {...messages.select} /></CapButton>;
|
|
346
|
+
}
|
|
342
347
|
const secureFilePath = _.get(template, 'metaInfo.secure_file_path');
|
|
343
348
|
templateData.url = secureFilePath || _.get(template, 'metaInfo.public_url');
|
|
344
349
|
return templateData;
|
|
@@ -347,7 +352,7 @@ export class Gallery extends React.Component { // eslint-disable-line react/pref
|
|
|
347
352
|
{filterContent}
|
|
348
353
|
<CapSpin spinning={isLoading} tip={loadingTip} style={{width: "100%"}}>
|
|
349
354
|
{!isLoading && <div>
|
|
350
|
-
{!_.isEmpty(templates) || !_.isEmpty(searchText) ? <div className=
|
|
355
|
+
{!_.isEmpty(templates) || !_.isEmpty(searchText) ? <div className={`pagination-container ${this.props.isLineAsset ? 'gallery-upload' : ''}`}>
|
|
351
356
|
<CapCustomCardList key={`${currentChannel}-card-list`} cardList={cardDataList} type={'Email'} />
|
|
352
357
|
{!_.isEmpty(searchText) && _.isEmpty(templates) && (
|
|
353
358
|
<div style={{ height: '65vh', textAlign: 'center', lineHeight: '65vh'}}>
|
|
@@ -421,21 +426,25 @@ export class Gallery extends React.Component { // eslint-disable-line react/pref
|
|
|
421
426
|
<div style={{ marginTop: '8px', width: '50%'}}>
|
|
422
427
|
<CapInput.Search
|
|
423
428
|
className="search-text"
|
|
424
|
-
style={{width: '210px'}}
|
|
429
|
+
style={{width: `${this.props.isLineAsset ? '324px' : '210px'}`}}
|
|
425
430
|
placeholder={this.props.intl.formatMessage(messages.search)}
|
|
426
431
|
value={this.state.searchText}
|
|
427
432
|
onChange={(e) => this.searchAsset(e.target.value)}
|
|
428
433
|
onClear={() => this.searchAsset('')}
|
|
429
434
|
/>
|
|
430
435
|
</div>
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
<
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
{
|
|
437
|
+
!this.props.isLineAsset && (
|
|
438
|
+
<div style={{ textAlign: 'right', marginTop: '16px', width: '50%'}}>
|
|
439
|
+
<form encType="multipart/form-data" id="myForm">
|
|
440
|
+
<input style={{ display: 'none'}} id="fileName" type="file" onChange={(e) => this.uploadImages(e, {files: e.target.files})} accept="image/*" multiple/>
|
|
441
|
+
<CapButton className="upload-image-gallery" type={this.props.location.query.type !== 'embedded' ? 'primary' : 'secondary'} onClick={(e) => this.openFileDialog(e)} style={{float: 'right'}}>
|
|
442
|
+
<FormattedMessage {...messages.upload} />
|
|
443
|
+
</CapButton>
|
|
444
|
+
</form>
|
|
445
|
+
</div>
|
|
446
|
+
)
|
|
447
|
+
}
|
|
439
448
|
</div>
|
|
440
449
|
) : <input style={{ display: 'none'}} id="fileName" type="file" onChange={(e) => this.uploadImages(e, {files: e.target.files})} accept="image/*" multiple/>;
|
|
441
450
|
const isLoading = this.props.Gallery.assetUploading || this.props.Gallery.fetchingAllAssets;
|
|
@@ -496,6 +505,8 @@ Gallery.propTypes = {
|
|
|
496
505
|
location: PropTypes.object,
|
|
497
506
|
intl: intlShape.isRequired,
|
|
498
507
|
isFullMode: PropTypes.bool,
|
|
508
|
+
isLineAsset: PropTypes.bool,
|
|
509
|
+
onGalleryImageSelect: PropTypes.func,
|
|
499
510
|
};
|
|
500
511
|
|
|
501
512
|
const mapStateToProps = createStructuredSelector({
|
|
@@ -11,12 +11,14 @@ import EmailWrapper from '../EmailWrapper';
|
|
|
11
11
|
import MobilepushWrapper from '../MobilepushWrapper';
|
|
12
12
|
import EmailPreviewV2 from '../../v2Components/EmailPreviewV2';
|
|
13
13
|
import MobilePushPreview from '../../v2Components/MobilePushPreviewV2';
|
|
14
|
+
import LineEdit from '../Line/Create';
|
|
14
15
|
import WechatWrapper from '../WeChat/Wrapper';
|
|
15
16
|
import Facebook from '../Facebook';
|
|
16
17
|
import { RICH_MEDIA, MAP_TEMPLATE, CREATE } from '../WeChat/Wrapper/constants';
|
|
17
18
|
import CallTask from '../CallTask';
|
|
18
19
|
import MobliPushEdit from '../MobilePush/Edit';
|
|
19
20
|
import * as constants from './constants';
|
|
21
|
+
import CreateLineWrapper from '../Line/CreateWrapper';
|
|
20
22
|
const CreativesWrapper = styled.div`
|
|
21
23
|
.ant-popover,
|
|
22
24
|
.ant-notification,
|
|
@@ -41,6 +43,7 @@ function SlideBoxContent(props) {
|
|
|
41
43
|
location,
|
|
42
44
|
isGetFormData,
|
|
43
45
|
getFormData,
|
|
46
|
+
onCreateComplete,
|
|
44
47
|
messageDetails,
|
|
45
48
|
onCreateNew,
|
|
46
49
|
currentChannel,
|
|
@@ -80,9 +83,11 @@ function SlideBoxContent(props) {
|
|
|
80
83
|
};
|
|
81
84
|
let channel = currentChannel;//for create mode
|
|
82
85
|
const isCreateSms = slidBoxContent === 'createTemplate' && channel === constants.SMS;
|
|
86
|
+
const isCreateLine = slidBoxContent === 'createTemplate' && channel === constants.LINE;
|
|
83
87
|
const isCreateMPush = slidBoxContent === 'createTemplate' && channel === constants.MOBILE_PUSH;
|
|
84
88
|
const isCreateCallTask = slidBoxContent === 'createTemplate' && channel === constants.CALL_TASK;
|
|
85
89
|
let isEditSms = false;
|
|
90
|
+
let isEditLine = false;
|
|
86
91
|
let isEditEmailWithId = false;
|
|
87
92
|
let isEmailEditWithContent = false;
|
|
88
93
|
let isPreview = false;
|
|
@@ -95,16 +100,42 @@ function SlideBoxContent(props) {
|
|
|
95
100
|
if (templateData && channel) {
|
|
96
101
|
channel = templateData.type;// for edit mode with template data
|
|
97
102
|
isEditSms = slidBoxContent === 'editTemplate' && channel === constants.SMS;
|
|
103
|
+
isEditLine = slidBoxContent === 'editTemplate' && channel === constants.LINE;
|
|
98
104
|
isEditCallTask = slidBoxContent === 'editTemplate' && channel === constants.CALL_TASK;
|
|
99
105
|
isEditMPush = slidBoxContent === 'editTemplate' && channel === constants.MOBILE_PUSH;
|
|
100
106
|
isEditEmailWithId = slidBoxContent === 'editTemplate' && channel === constants.EMAIL && templateData._id;
|
|
101
107
|
isEmailEditWithContent = slidBoxContent === 'editTemplate' && channel === constants.EMAIL && !templateData._id;
|
|
102
108
|
isEditFacebook = slidBoxContent === 'editTemplate' && channel === constants.FACEBOOK;
|
|
103
|
-
isPreview = slidBoxContent === 'preview' && channel === constants.SMS;
|
|
109
|
+
isPreview = slidBoxContent === 'preview' && (channel === constants.SMS || channel === constants.LINE);
|
|
104
110
|
isEmailPreview = slidBoxContent === 'preview' && channel === constants.EMAIL;
|
|
105
111
|
isMpushPreview = slidBoxContent === 'preview' && channel === constants.MOBILE_PUSH;
|
|
106
112
|
}
|
|
107
113
|
|
|
114
|
+
const getChannelPreviewContent = (templateDataObject) => {
|
|
115
|
+
switch (templateDataObject.type.toUpperCase()) {
|
|
116
|
+
case constants.SMS:
|
|
117
|
+
return get(templateDataObject, `versions.base['sms-editor']`);
|
|
118
|
+
case constants.LINE: {
|
|
119
|
+
const content = get(templateDataObject, `versions.base.content.messages[0]`, {});
|
|
120
|
+
let previewContent = {};
|
|
121
|
+
if (content.text) {
|
|
122
|
+
previewContent = {
|
|
123
|
+
bodyText: content.text,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (content.originalContentUrl) {
|
|
127
|
+
previewContent = {
|
|
128
|
+
...previewContent,
|
|
129
|
+
bodyImage: content.previewImageUrl,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return previewContent;
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
return get(templateDataObject, `versions.base.content`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
108
139
|
return (
|
|
109
140
|
<CreativesWrapper>
|
|
110
141
|
{!isFullMode && slidBoxContent === 'templates' && (
|
|
@@ -127,10 +158,10 @@ function SlideBoxContent(props) {
|
|
|
127
158
|
)}
|
|
128
159
|
{isPreview && (
|
|
129
160
|
<TemplatePreview
|
|
130
|
-
content={
|
|
161
|
+
content={getChannelPreviewContent(templateData)}
|
|
131
162
|
unicodeEnabled={get(templateData, `versions.base['unicode-validity']`)}
|
|
132
163
|
channel={templateData.type.toLowerCase()}
|
|
133
|
-
charCounterEnabled
|
|
164
|
+
charCounterEnabled={channel === constants.SMS}
|
|
134
165
|
templateData={templateData}
|
|
135
166
|
showCount
|
|
136
167
|
forwardedTags={forwardedTags}
|
|
@@ -319,6 +350,29 @@ function SlideBoxContent(props) {
|
|
|
319
350
|
/>
|
|
320
351
|
)
|
|
321
352
|
}
|
|
353
|
+
{
|
|
354
|
+
isEditLine && (
|
|
355
|
+
<LineEdit
|
|
356
|
+
location={creativesLocationProps}
|
|
357
|
+
params={{
|
|
358
|
+
id: templateData._id,
|
|
359
|
+
mode: templateData.modeType || (templateData.definition && templateData.definition.mode),
|
|
360
|
+
}}
|
|
361
|
+
route={{ name: (templateData.modeType || (templateData.definition && templateData.definition.mode)) === 'image' ? 'edit_image' : 'edit_text' }}
|
|
362
|
+
onCreateComplete={onCreateComplete}
|
|
363
|
+
isFullMode={props.isFullMode}
|
|
364
|
+
/>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
{
|
|
368
|
+
isCreateLine && (
|
|
369
|
+
<CreateLineWrapper
|
|
370
|
+
location={creativesLocationProps}
|
|
371
|
+
onCreateComplete={onCreateComplete}
|
|
372
|
+
isFullMode={props.isFullMode}
|
|
373
|
+
/>
|
|
374
|
+
)
|
|
375
|
+
}
|
|
322
376
|
</CreativesWrapper>
|
|
323
377
|
);
|
|
324
378
|
}
|
|
@@ -359,5 +413,6 @@ SlideBoxContent.propTypes = {
|
|
|
359
413
|
onWeChatMaptemplateStepChange: PropTypes.func,
|
|
360
414
|
weChatMaptemplateStep: PropTypes.string,
|
|
361
415
|
onFacebookSubmit: PropTypes.func,
|
|
416
|
+
onCreateComplete: PropTypes.func,
|
|
362
417
|
};
|
|
363
418
|
export default SlideBoxContent;
|
|
@@ -503,6 +503,7 @@ class Creatives extends React.Component {
|
|
|
503
503
|
<SlideBoxContent
|
|
504
504
|
key="creatives-container-slidebox-content"
|
|
505
505
|
onSelectTemplate={this.onSelectTemplate}
|
|
506
|
+
onCreateComplete={getCreativesData}
|
|
506
507
|
onPreviewTemplate={this.onPreviewTemplate}
|
|
507
508
|
slidBoxContent={slidBoxContent}
|
|
508
509
|
templateData={templateData}
|
|
@@ -20,7 +20,7 @@ import * as templatesActionsCreators from '../Templates/actions';
|
|
|
20
20
|
import Email from '../Email';
|
|
21
21
|
import CmsTemplatesComponent from '../../v2Components/CmsTemplatesComponent';
|
|
22
22
|
import messages from './messages';
|
|
23
|
-
import { CHANNEL_CREATE_TRACK_MAPPING } from '../App/constants'
|
|
23
|
+
import { CHANNEL_CREATE_TRACK_MAPPING } from '../App/constants';
|
|
24
24
|
|
|
25
25
|
const CapRadioCardWithLabel = ComponentWithLabelHOC(CapRadioCard);
|
|
26
26
|
const { timeTracker } = GA;
|
|
@@ -110,16 +110,15 @@ export class EmailWrapper extends React.Component { // eslint-disable-line react
|
|
|
110
110
|
};
|
|
111
111
|
CapNotification.error(message);
|
|
112
112
|
}
|
|
113
|
-
stopTimerGA=()=>{
|
|
113
|
+
stopTimerGA=() => {
|
|
114
114
|
// stop timer
|
|
115
|
-
timeTracker.stopTimer(CHANNEL_CREATE_TRACK_MAPPING
|
|
115
|
+
timeTracker.stopTimer(CHANNEL_CREATE_TRACK_MAPPING.email, {
|
|
116
116
|
category: 'Creatives',
|
|
117
117
|
action: 'Create',
|
|
118
118
|
label: 'uploadZip',
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
handleFileUpload = (file) => {
|
|
122
|
-
|
|
123
122
|
const { templatesActions, intl, showNextStep, isUploading } = this.props;
|
|
124
123
|
if (!isUploading) {
|
|
125
124
|
const fileExtension = file.name.split('.').pop();
|
|
@@ -131,7 +130,6 @@ export class EmailWrapper extends React.Component { // eslint-disable-line react
|
|
|
131
130
|
this.setState({ modeContent: { file } }, showNextStep);
|
|
132
131
|
stopTimerGA();
|
|
133
132
|
}, this.handleZipUploadError);
|
|
134
|
-
|
|
135
133
|
} else if (fileExtension === 'html' || fileExtension === 'htm') {
|
|
136
134
|
const reader = new FileReader();
|
|
137
135
|
reader.onload = () => {
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
+
@import '~@capillarytech/cap-ui-library/styles/_variables.scss';
|
|
2
|
+
|
|
1
3
|
.line-row{
|
|
2
4
|
margin-bottom: 16px;
|
|
3
5
|
}
|
|
4
|
-
.
|
|
6
|
+
.slidebox-content-container {
|
|
7
|
+
overflow-x: hidden;
|
|
8
|
+
}
|
|
9
|
+
.line-image {
|
|
10
|
+
position: relative;
|
|
11
|
+
}
|
|
12
|
+
.image-container {
|
|
5
13
|
display: flex;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
14
|
+
& > div {
|
|
15
|
+
min-width: 516px;
|
|
16
|
+
}
|
|
9
17
|
border-radius: 5px;
|
|
10
18
|
.image-placeholder{
|
|
11
19
|
line-height: 150px;
|
|
@@ -18,21 +26,34 @@
|
|
|
18
26
|
width: auto !important;
|
|
19
27
|
}
|
|
20
28
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
|
|
30
|
+
.pagination-container.gallery-upload {
|
|
31
|
+
height: calc(100vh - 60px);
|
|
32
|
+
|
|
33
|
+
.cap-custom-card-list-col {
|
|
34
|
+
max-width: 156px;
|
|
35
|
+
max-height: 156px;
|
|
36
|
+
display: inline-block;
|
|
37
|
+
margin-left: $CAP_SPACE_04;
|
|
38
|
+
margin-right: $CAP_SPACE_04;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.ant-card-body {
|
|
42
|
+
padding-top: 0;
|
|
43
|
+
|
|
44
|
+
.ant-card-meta-description img {
|
|
45
|
+
height: 80px;
|
|
46
|
+
width: auto;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.gallery-image {
|
|
52
|
+
height: 156px;
|
|
53
|
+
width: 156px;
|
|
54
|
+
border-radius: 4px;
|
|
55
|
+
margin: 3px;
|
|
56
|
+
}
|
|
36
57
|
|
|
37
58
|
.sms-text {
|
|
38
59
|
img {
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
import * as types from './constants';
|
|
8
8
|
|
|
9
|
-
export function createTemplate(template) {
|
|
9
|
+
export function createTemplate(template, callback) {
|
|
10
10
|
console.log('action trigereed createtemplate');
|
|
11
11
|
return {
|
|
12
|
-
type: types.CREATE_TEMPLATE_REQUEST,
|
|
12
|
+
type: types.CREATE_TEMPLATE_REQUEST,
|
|
13
|
+
template,
|
|
14
|
+
callback,
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
|
|
@@ -48,9 +50,10 @@ export function clearAsset() {
|
|
|
48
50
|
export function editTemplate(template) {
|
|
49
51
|
console.log('action trigereed editTemplate line @@@');
|
|
50
52
|
return {
|
|
51
|
-
type: types.EDIT_TEMPLATE_REQUEST,
|
|
53
|
+
type: types.EDIT_TEMPLATE_REQUEST,
|
|
54
|
+
template,
|
|
52
55
|
};
|
|
53
|
-
}
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
export function getTemplateDetails(id) {
|
|
56
59
|
console.log('getting template line @@@', id);
|
|
@@ -59,14 +62,14 @@ export function getTemplateDetails(id) {
|
|
|
59
62
|
id,
|
|
60
63
|
channel: 'LINE',
|
|
61
64
|
};
|
|
62
|
-
}
|
|
65
|
+
}
|
|
63
66
|
|
|
64
67
|
export function clearEditResponse() {
|
|
65
68
|
console.log('clearEditResponse');
|
|
66
69
|
return {
|
|
67
70
|
type: types.CLEAR_EDIT_RESPONSE_REQUEST,
|
|
68
71
|
};
|
|
69
|
-
}
|
|
72
|
+
}
|
|
70
73
|
|
|
71
74
|
export function clearData() {
|
|
72
75
|
return {
|
|
@@ -80,11 +83,11 @@ export function getLineTemplatesList(channel, queryParams) {
|
|
|
80
83
|
channel,
|
|
81
84
|
queryParams,
|
|
82
85
|
};
|
|
83
|
-
}
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
export function setSelectedTemplate(templateData) {
|
|
86
89
|
return {
|
|
87
90
|
type: types.SET_SELECTED_TEMPLATE,
|
|
88
91
|
data: templateData,
|
|
89
92
|
};
|
|
90
|
-
}
|
|
93
|
+
}
|