Improve caption editor usability, new 'add attachment' affordance
This commit is contained in:
parent
ac1a6d197a
commit
0de54e125c
14 changed files with 224 additions and 61 deletions
|
@ -952,6 +952,11 @@
|
||||||
"descripton":
|
"descripton":
|
||||||
"Used as the placeholder text in the caption editor text field"
|
"Used as the placeholder text in the caption editor text field"
|
||||||
},
|
},
|
||||||
|
"save": {
|
||||||
|
"message": "Save",
|
||||||
|
"descripton":
|
||||||
|
"Used as a 'commit changes' button in the Caption Editor for outgoing image attachments"
|
||||||
|
},
|
||||||
"fileIconAlt": {
|
"fileIconAlt": {
|
||||||
"message": "File icon",
|
"message": "File icon",
|
||||||
"description":
|
"description":
|
||||||
|
|
1
images/plus-36.svg
Normal file
1
images/plus-36.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Export" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><title>plus-36</title><polygon points="32 17.25 18.75 17.25 18.75 4 17.25 4 17.25 17.25 4 17.25 4 18.75 17.25 18.75 17.25 32 18.75 32 18.75 18.75 32 18.75 32 17.25"/></svg>
|
After Width: | Height: | Size: 244 B |
|
@ -156,6 +156,11 @@
|
||||||
'attachments-changed',
|
'attachments-changed',
|
||||||
this.toggleMicrophone
|
this.toggleMicrophone
|
||||||
);
|
);
|
||||||
|
this.listenTo(
|
||||||
|
this.fileInput,
|
||||||
|
'choose-attachment',
|
||||||
|
this.onChooseAttachment
|
||||||
|
);
|
||||||
|
|
||||||
const getHeaderProps = () => {
|
const getHeaderProps = () => {
|
||||||
const expireTimer = this.model.get('expireTimer');
|
const expireTimer = this.model.get('expireTimer');
|
||||||
|
@ -275,8 +280,10 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
onChooseAttachment(e) {
|
onChooseAttachment(e) {
|
||||||
e.stopPropagation();
|
if (e) {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
this.$('input.file-input').click();
|
this.$('input.file-input').click();
|
||||||
},
|
},
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
|
|
||||||
return {
|
return {
|
||||||
attachments,
|
attachments,
|
||||||
|
onAddAttachment: this.onAddAttachment.bind(this),
|
||||||
onClickAttachment: this.onClickAttachment.bind(this),
|
onClickAttachment: this.onClickAttachment.bind(this),
|
||||||
onCloseAttachment: this.onCloseAttachment.bind(this),
|
onCloseAttachment: this.onCloseAttachment.bind(this),
|
||||||
onClose: this.onClose.bind(this),
|
onClose: this.onClose.bind(this),
|
||||||
|
@ -97,18 +98,15 @@
|
||||||
url: attachment.videoUrl || attachment.url,
|
url: attachment.videoUrl || attachment.url,
|
||||||
caption: attachment.caption,
|
caption: attachment.caption,
|
||||||
attachment,
|
attachment,
|
||||||
onChangeCaption,
|
onSave,
|
||||||
});
|
});
|
||||||
|
|
||||||
const update = () => {
|
const onSave = caption => {
|
||||||
this.captionEditorView.update(getProps());
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChangeCaption = caption => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
attachment.caption = caption;
|
attachment.caption = caption;
|
||||||
|
this.captionEditorView.remove();
|
||||||
|
Signal.Backbone.Views.Lightbox.hide();
|
||||||
this.render();
|
this.render();
|
||||||
update();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.captionEditorView = new Whisper.ReactWrapperView({
|
this.captionEditorView = new Whisper.ReactWrapperView({
|
||||||
|
@ -126,6 +124,10 @@
|
||||||
this.render();
|
this.render();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onAddAttachment() {
|
||||||
|
this.trigger('choose-attachment');
|
||||||
|
},
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
this.trigger('attachments-changed');
|
this.trigger('attachments-changed');
|
||||||
|
|
|
@ -412,15 +412,6 @@ $loading-height: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type='text'],
|
|
||||||
input[type='search'],
|
|
||||||
textarea {
|
|
||||||
&:active,
|
|
||||||
&:focus {
|
|
||||||
outline: 1px solid $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.expiredAlert {
|
.expiredAlert {
|
||||||
background: #f3f3a7;
|
background: #f3f3a7;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
|
@ -2298,7 +2298,7 @@
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@include color-svg('../images/x.svg', $color-black);
|
@include color-svg('../images/x-16.svg', $color-black);
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-attachments__rail {
|
.module-attachments__rail {
|
||||||
|
@ -2416,7 +2416,7 @@
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@include color-svg('../images/x.svg', $color-white);
|
@include color-svg('../images/x-16.svg', $color-white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-caption-editor__media-container {
|
.module-caption-editor__media-container {
|
||||||
|
@ -2457,8 +2457,8 @@
|
||||||
.module-caption-editor__bottom-bar {
|
.module-caption-editor__bottom-bar {
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 3em;
|
height: 52px;
|
||||||
padding: 0.5em;
|
padding: 8px;
|
||||||
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -2468,23 +2468,23 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-caption-editor__add-caption-button {
|
.module-caption-editor__input-container {
|
||||||
display: inline-block;
|
position: relative;
|
||||||
margin-left: 6px;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
margin-right: 6px;
|
|
||||||
@include color-svg('../images/add-caption-24.svg', $color-white);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-caption-editor__caption-input {
|
.module-caption-editor__caption-input {
|
||||||
height: 2em;
|
height: 36px;
|
||||||
width: 40em;
|
width: 40em;
|
||||||
border: 1px solid $color-white;
|
|
||||||
border-radius: 1em;
|
font-size: 14px;
|
||||||
color: $color-white;
|
color: $color-white;
|
||||||
|
|
||||||
|
border: 1px solid $color-white;
|
||||||
|
border-radius: 18px;
|
||||||
background-color: $color-black;
|
background-color: $color-black;
|
||||||
padding: 0.5em;
|
padding: 9px;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 65px;
|
||||||
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $color-white-07;
|
color: $color-white-07;
|
||||||
|
@ -2495,6 +2495,54 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__save-button {
|
||||||
|
position: absolute;
|
||||||
|
background-color: $color-signal-blue;
|
||||||
|
color: $color-white;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 15px;
|
||||||
|
|
||||||
|
padding: 5px;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
|
||||||
|
right: 4px;
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module: Staged Placeholder Attachment
|
||||||
|
|
||||||
|
.module-staged-placeholder-attachment {
|
||||||
|
margin: 1px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid $color-gray-25;
|
||||||
|
height: 120px;
|
||||||
|
width: 120px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $color-gray-05;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-placeholder-attachment__plus-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
height: 36px;
|
||||||
|
width: 36px;
|
||||||
|
|
||||||
|
@include color-svg('../images/plus-36.svg', $color-gray-45);
|
||||||
|
}
|
||||||
|
|
||||||
// Third-party module: react-contextmenu
|
// Third-party module: react-contextmenu
|
||||||
|
|
||||||
.react-contextmenu {
|
.react-contextmenu {
|
||||||
|
|
|
@ -1350,6 +1350,20 @@ body.dark-theme {
|
||||||
color: $color-gray-90;
|
color: $color-gray-90;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Module: Staged Placeholder Attachment
|
||||||
|
|
||||||
|
.module-staged-placeholder-attachment {
|
||||||
|
border: 1px solid $color-gray-60;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $color-gray-75;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-placeholder-attachment__plus-icon {
|
||||||
|
@include color-svg('../images/plus-36.svg', $color-gray-60);
|
||||||
|
}
|
||||||
|
|
||||||
// Third-party module: react-contextmenu
|
// Third-party module: react-contextmenu
|
||||||
|
|
||||||
.react-contextmenu {
|
.react-contextmenu {
|
||||||
|
|
|
@ -9,7 +9,8 @@ let caption = null;
|
||||||
attachment={{
|
attachment={{
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
}}
|
}}
|
||||||
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
onSave={caption => console.log('onSave', caption)}
|
||||||
|
close={() => console.log('close')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -29,7 +30,8 @@ let caption =
|
||||||
}}
|
}}
|
||||||
caption={caption}
|
caption={caption}
|
||||||
contentType="image/jpeg"
|
contentType="image/jpeg"
|
||||||
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
onSave={caption => console.log('onSave', caption)}
|
||||||
|
close={() => console.log('close')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -46,7 +48,8 @@ let caption = null;
|
||||||
attachment={{
|
attachment={{
|
||||||
contentType: 'video/mp4',
|
contentType: 'video/mp4',
|
||||||
}}
|
}}
|
||||||
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
onSave={caption => console.log('onSave', caption)}
|
||||||
|
close={() => console.log('close')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
|
@ -65,7 +68,8 @@ let caption =
|
||||||
contentType: 'video/mp4',
|
contentType: 'video/mp4',
|
||||||
}}
|
}}
|
||||||
caption={caption}
|
caption={caption}
|
||||||
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
onSave={caption => console.log('onSave', caption)}
|
||||||
|
close={() => console.log('close')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
|
@ -12,31 +12,52 @@ interface Props {
|
||||||
i18n: Localizer;
|
i18n: Localizer;
|
||||||
url: string;
|
url: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
onChangeCaption?: (caption: string) => void;
|
onSave?: (caption: string) => void;
|
||||||
close?: () => void;
|
close?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CaptionEditor extends React.Component<Props> {
|
interface State {
|
||||||
private handleKeyUpBound: () => void;
|
caption: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CaptionEditor extends React.Component<Props, State> {
|
||||||
|
private handleKeyUpBound: (
|
||||||
|
event: React.KeyboardEvent<HTMLInputElement>
|
||||||
|
) => void;
|
||||||
private setFocusBound: () => void;
|
private setFocusBound: () => void;
|
||||||
|
// TypeScript doesn't like our React.Ref typing here, so we omit it
|
||||||
private captureRefBound: () => void;
|
private captureRefBound: () => void;
|
||||||
|
private onChangeBound: () => void;
|
||||||
|
private onSaveBound: () => void;
|
||||||
private inputRef: React.Ref<HTMLInputElement> | null;
|
private inputRef: React.Ref<HTMLInputElement> | null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
const { caption } = props;
|
||||||
|
this.state = {
|
||||||
|
caption: caption || '',
|
||||||
|
};
|
||||||
|
|
||||||
this.handleKeyUpBound = this.handleKeyUp.bind(this);
|
this.handleKeyUpBound = this.handleKeyUp.bind(this);
|
||||||
this.setFocusBound = this.setFocus.bind(this);
|
this.setFocusBound = this.setFocus.bind(this);
|
||||||
this.captureRefBound = this.captureRef.bind(this);
|
this.captureRefBound = this.captureRef.bind(this);
|
||||||
|
this.onChangeBound = this.onChange.bind(this);
|
||||||
|
this.onSaveBound = this.onSave.bind(this);
|
||||||
this.inputRef = null;
|
this.inputRef = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleKeyUp(event: React.KeyboardEvent<HTMLInputElement>) {
|
public handleKeyUp(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
const { close } = this.props;
|
const { close, onSave } = this.props;
|
||||||
|
|
||||||
if (close && (event.key === 'Escape' || event.key === 'Enter')) {
|
if (close && event.key === 'Escape') {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onSave && event.key === 'Enter') {
|
||||||
|
const { caption } = this.state;
|
||||||
|
onSave(caption);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setFocus() {
|
public setFocus() {
|
||||||
|
@ -55,6 +76,24 @@ export class CaptionEditor extends React.Component<Props> {
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onSave() {
|
||||||
|
const { onSave } = this.props;
|
||||||
|
const { caption } = this.state;
|
||||||
|
|
||||||
|
if (onSave) {
|
||||||
|
onSave(caption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChange(event: React.FormEvent<HTMLInputElement>) {
|
||||||
|
// @ts-ignore
|
||||||
|
const { value } = event.target;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
caption: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public renderObject() {
|
public renderObject() {
|
||||||
const { url, i18n, attachment } = this.props;
|
const { url, i18n, attachment } = this.props;
|
||||||
const { contentType } = attachment || { contentType: null };
|
const { contentType } = attachment || { contentType: null };
|
||||||
|
@ -83,7 +122,8 @@ export class CaptionEditor extends React.Component<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const { caption, i18n, close, onChangeCaption } = this.props;
|
const { i18n, close } = this.props;
|
||||||
|
const { caption } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -100,21 +140,27 @@ export class CaptionEditor extends React.Component<Props> {
|
||||||
{this.renderObject()}
|
{this.renderObject()}
|
||||||
</div>
|
</div>
|
||||||
<div className="module-caption-editor__bottom-bar">
|
<div className="module-caption-editor__bottom-bar">
|
||||||
<div className="module-caption-editor__add-caption-button" />
|
<div className="module-caption-editor__input-container">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
ref={this.captureRefBound}
|
ref={this.captureRefBound}
|
||||||
onKeyUp={close ? this.handleKeyUpBound : undefined}
|
value={caption}
|
||||||
value={caption || ''}
|
maxLength={200}
|
||||||
maxLength={200}
|
placeholder={i18n('addACaption')}
|
||||||
placeholder={i18n('addACaption')}
|
className="module-caption-editor__caption-input"
|
||||||
className="module-caption-editor__caption-input"
|
onKeyUp={close ? this.handleKeyUpBound : undefined}
|
||||||
onChange={event => {
|
onChange={this.onChangeBound}
|
||||||
if (onChangeCaption) {
|
/>
|
||||||
onChangeCaption(event.target.value);
|
{caption ? (
|
||||||
}
|
<div
|
||||||
}}
|
role="button"
|
||||||
/>
|
onClick={this.onSaveBound}
|
||||||
|
className="module-caption-editor__save-button"
|
||||||
|
>
|
||||||
|
{i18n('save')}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,8 +19,9 @@ const attachments = [
|
||||||
onCloseAttachment={attachment => {
|
onCloseAttachment={attachment => {
|
||||||
console.log('onCloseAttachment', attachment);
|
console.log('onCloseAttachment', attachment);
|
||||||
}}
|
}}
|
||||||
|
onAddAttachment={() => console.log('onAddAttachment')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>;
|
/>
|
||||||
</util.ConversationContext>;
|
</util.ConversationContext>;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -64,6 +65,7 @@ const attachments = [
|
||||||
onCloseAttachment={attachment => {
|
onCloseAttachment={attachment => {
|
||||||
console.log('onCloseAttachment', attachment);
|
console.log('onCloseAttachment', attachment);
|
||||||
}}
|
}}
|
||||||
|
onAddAttachment={() => console.log('onAddAttachment')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</util.ConversationContext>;
|
</util.ConversationContext>;
|
||||||
|
@ -101,6 +103,7 @@ const attachments = [
|
||||||
onCloseAttachment={attachment => {
|
onCloseAttachment={attachment => {
|
||||||
console.log('onCloseAttachment', attachment);
|
console.log('onCloseAttachment', attachment);
|
||||||
}}
|
}}
|
||||||
|
onAddAttachment={() => console.log('onAddAttachment')}
|
||||||
i18n={util.i18n}
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</util.ConversationContext>;
|
</util.ConversationContext>;
|
||||||
|
|
|
@ -6,7 +6,9 @@ import {
|
||||||
} from '../../util/GoogleChrome';
|
} from '../../util/GoogleChrome';
|
||||||
import { AttachmentType } from './types';
|
import { AttachmentType } from './types';
|
||||||
import { Image } from './Image';
|
import { Image } from './Image';
|
||||||
|
import { areAllAttachmentsVisual } from './ImageGrid';
|
||||||
import { StagedGenericAttachment } from './StagedGenericAttachment';
|
import { StagedGenericAttachment } from './StagedGenericAttachment';
|
||||||
|
import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment';
|
||||||
import { Localizer } from '../../types/Util';
|
import { Localizer } from '../../types/Util';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -15,6 +17,7 @@ interface Props {
|
||||||
// onError: () => void;
|
// onError: () => void;
|
||||||
onClickAttachment: (attachment: AttachmentType) => void;
|
onClickAttachment: (attachment: AttachmentType) => void;
|
||||||
onCloseAttachment: (attachment: AttachmentType) => void;
|
onCloseAttachment: (attachment: AttachmentType) => void;
|
||||||
|
onAddAttachment: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +30,7 @@ export class AttachmentList extends React.Component<Props> {
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
i18n,
|
i18n,
|
||||||
|
onAddAttachment,
|
||||||
onClickAttachment,
|
onClickAttachment,
|
||||||
onCloseAttachment,
|
onCloseAttachment,
|
||||||
onClose,
|
onClose,
|
||||||
|
@ -36,6 +40,8 @@ export class AttachmentList extends React.Component<Props> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allVisualAttachments = areAllAttachmentsVisual(attachments);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-attachments">
|
<div className="module-attachments">
|
||||||
{attachments.length > 1 ? (
|
{attachments.length > 1 ? (
|
||||||
|
@ -85,6 +91,9 @@ export class AttachmentList extends React.Component<Props> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
{allVisualAttachments ? (
|
||||||
|
<StagedPlaceholderAttachment onClick={onAddAttachment} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -389,7 +389,9 @@ function getImageDimensions(attachment: AttachmentType): DimensionsType {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function areAllAttachmentsVisual(attachments?: Array<AttachmentType>): boolean {
|
export function areAllAttachmentsVisual(
|
||||||
|
attachments?: Array<AttachmentType>
|
||||||
|
): boolean {
|
||||||
if (!attachments) {
|
if (!attachments) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -397,7 +399,7 @@ function areAllAttachmentsVisual(attachments?: Array<AttachmentType>): boolean {
|
||||||
const max = attachments.length;
|
const max = attachments.length;
|
||||||
for (let i = 0; i < max; i += 1) {
|
for (let i = 0; i < max; i += 1) {
|
||||||
const attachment = attachments[i];
|
const attachment = attachments[i];
|
||||||
if (!isImageAttachment(attachment) || !isVideoAttachment(attachment)) {
|
if (!isImageAttachment(attachment) && !isVideoAttachment(attachment)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
ts/components/conversation/StagedPlaceholderAttachment.md
Normal file
10
ts/components/conversation/StagedPlaceholderAttachment.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
```js
|
||||||
|
const attachment = {
|
||||||
|
contentType: 'text/plain',
|
||||||
|
fileName: 'manifesto.txt',
|
||||||
|
};
|
||||||
|
|
||||||
|
<util.ConversationContext theme={util.theme}>
|
||||||
|
<StagedPlaceholderAttachment onClick={attachment => console.log('onClick')} />
|
||||||
|
</util.ConversationContext>;
|
||||||
|
```
|
21
ts/components/conversation/StagedPlaceholderAttachment.tsx
Normal file
21
ts/components/conversation/StagedPlaceholderAttachment.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StagedPlaceholderAttachment extends React.Component<Props> {
|
||||||
|
public render() {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="module-staged-placeholder-attachment"
|
||||||
|
role="button"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div className="module-staged-placeholder-attachment__plus-icon" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue