New staged attachments UI, multiple image attachments per message
This commit is contained in:
parent
e4babdaef0
commit
985b1d6aa6
22 changed files with 1550 additions and 648 deletions
|
@ -172,6 +172,10 @@
|
||||||
"message": "Choose folder",
|
"message": "Choose folder",
|
||||||
"description": "Button to allow the user to find a folder on disk"
|
"description": "Button to allow the user to find a folder on disk"
|
||||||
},
|
},
|
||||||
|
"chooseFile": {
|
||||||
|
"message": "Choose file",
|
||||||
|
"description": "Button to allow the user to find a file on disk"
|
||||||
|
},
|
||||||
"loadDataHeader": {
|
"loadDataHeader": {
|
||||||
"message": "Load your data",
|
"message": "Load your data",
|
||||||
"description": "Header shown on the first screen in the data import process"
|
"description": "Header shown on the first screen in the data import process"
|
||||||
|
@ -542,15 +546,27 @@
|
||||||
"message": "Voice Message",
|
"message": "Voice Message",
|
||||||
"description": "Name for a voice message attachment"
|
"description": "Name for a voice message attachment"
|
||||||
},
|
},
|
||||||
"unsupportedFileType": {
|
|
||||||
"message": "Unsupported file type",
|
|
||||||
"description": "Displayed for outgoing unsupported attachment"
|
|
||||||
},
|
|
||||||
"dangerousFileType": {
|
"dangerousFileType": {
|
||||||
"message": "Attachment type not allowed for security reasons",
|
"message": "Attachment type not allowed for security reasons",
|
||||||
"description":
|
"description":
|
||||||
"Shown in toast when user attempts to send .exe file, for example"
|
"Shown in toast when user attempts to send .exe file, for example"
|
||||||
},
|
},
|
||||||
|
"oneNonImageAtATimeToast": {
|
||||||
|
"message":
|
||||||
|
"When including a non-image attachment, the limit is one attachment per message.",
|
||||||
|
"description":
|
||||||
|
"An error popup when the user has attempted to add an attachment"
|
||||||
|
},
|
||||||
|
"cannotMixImageAdnNonImageAttachments": {
|
||||||
|
"message": "You cannot mix non-image and image attachments in one message.",
|
||||||
|
"description":
|
||||||
|
"An error popup when the user has attempted to add an attachment"
|
||||||
|
},
|
||||||
|
"maximumAttachments": {
|
||||||
|
"message": "You cannot add any more attachments to this message.",
|
||||||
|
"description":
|
||||||
|
"An error popup when the user has attempted to add an attachment"
|
||||||
|
},
|
||||||
"fileSizeWarning": {
|
"fileSizeWarning": {
|
||||||
"message": "Sorry, the selected file exceeds message size restrictions."
|
"message": "Sorry, the selected file exceeds message size restrictions."
|
||||||
},
|
},
|
||||||
|
@ -732,6 +748,12 @@
|
||||||
"description":
|
"description":
|
||||||
"Shown in toast if user clicks on quote references messages not loaded in view, but in database"
|
"Shown in toast if user clicks on quote references messages not loaded in view, but in database"
|
||||||
},
|
},
|
||||||
|
"voiceNoteMustBeOnlyAttachment": {
|
||||||
|
"message":
|
||||||
|
"A voice note must be the only attachment included in a message.",
|
||||||
|
"description":
|
||||||
|
"Shown in toast if tries to record a voice note with any staged attachments"
|
||||||
|
},
|
||||||
"you": {
|
"you": {
|
||||||
"message": "You",
|
"message": "You",
|
||||||
"description":
|
"description":
|
||||||
|
@ -910,6 +932,11 @@
|
||||||
"description":
|
"description":
|
||||||
"Used for the icon layered on top of an image in message bubbles"
|
"Used for the icon layered on top of an image in message bubbles"
|
||||||
},
|
},
|
||||||
|
"addACaption": {
|
||||||
|
"message": "Add a caption...",
|
||||||
|
"descripton":
|
||||||
|
"Used as the placeholder text in the caption editor text field"
|
||||||
|
},
|
||||||
"fileIconAlt": {
|
"fileIconAlt": {
|
||||||
"message": "File icon",
|
"message": "File icon",
|
||||||
"description":
|
"description":
|
||||||
|
|
|
@ -118,9 +118,9 @@
|
||||||
|
|
||||||
<div class='bottom-bar' id='footer'>
|
<div class='bottom-bar' id='footer'>
|
||||||
<div class='emoji-panel-container'></div>
|
<div class='emoji-panel-container'></div>
|
||||||
|
<div class='attachment-list'></div>
|
||||||
<div class='compose'>
|
<div class='compose'>
|
||||||
<form class='send clearfix'>
|
<form class='send clearfix file-input'>
|
||||||
<div class='attachment-previews'></div>
|
|
||||||
<div class='flex'>
|
<div class='flex'>
|
||||||
<button class='emoji'></button>
|
<button class='emoji'></button>
|
||||||
<textarea class='send-message' placeholder='{{ send-message }}' rows='1' dir='auto'></textarea>
|
<textarea class='send-message' placeholder='{{ send-message }}' rows='1' dir='auto'></textarea>
|
||||||
|
@ -160,15 +160,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
<script type='text/x-tmpl-mustache' id='attachment-preview'>
|
|
||||||
<div class='image-container'>
|
|
||||||
<img src='{{ source }}' class='preview' />
|
|
||||||
<div class='outer'>
|
|
||||||
<div class='play icon'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a class='x close' alt='remove attachment' href='#'></a>
|
|
||||||
</script>
|
|
||||||
<script type='text/x-tmpl-mustache' id='file-view'>
|
<script type='text/x-tmpl-mustache' id='file-view'>
|
||||||
<div class='icon {{ mediaType }}'></div>
|
<div class='icon {{ mediaType }}'></div>
|
||||||
<div class='text'>
|
<div class='text'>
|
||||||
|
@ -619,7 +610,6 @@
|
||||||
<script type='text/javascript' src='js/views/last_seen_indicator_view.js'></script>
|
<script type='text/javascript' src='js/views/last_seen_indicator_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/scroll_down_button_view.js'></script>
|
<script type='text/javascript' src='js/views/scroll_down_button_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/toast_view.js'></script>
|
<script type='text/javascript' src='js/views/toast_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/attachment_preview_view.js'></script>
|
|
||||||
<script type='text/javascript' src='js/views/file_input_view.js'></script>
|
<script type='text/javascript' src='js/views/file_input_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/list_view.js'></script>
|
<script type='text/javascript' src='js/views/list_view.js'></script>
|
||||||
<script type='text/javascript' src='js/views/conversation_list_item_view.js'></script>
|
<script type='text/javascript' src='js/views/conversation_list_item_view.js'></script>
|
||||||
|
|
1
images/add-caption-24.svg
Normal file
1
images/add-caption-24.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Shape" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>add-caption-24</title><rect x="16" y="14" width="7" height="1"/><rect x="16" y="14" width="7" height="1" transform="translate(5 34) rotate(-90)"/><rect x="2" y="11" width="15" height="1"/><rect x="2" y="8" width="18" height="1"/><rect x="2" y="14" width="12" height="1"/></svg>
|
After Width: | Height: | Size: 355 B |
1
images/x-16.svg
Normal file
1
images/x-16.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Shape" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>x-16</title><polygon points="14.35 2.35 13.65 1.65 8 7.29 2.35 1.65 1.65 2.35 7.29 8 1.65 13.65 2.35 14.35 8 8.71 13.65 14.35 14.35 13.65 8.71 8 14.35 2.35"/></svg>
|
After Width: | Height: | Size: 242 B |
1
images/x-shadow-16.svg
Normal file
1
images/x-shadow-16.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg id="Shape" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 12 12"><defs><style>.cls-1{opacity:0.5;filter:url(#shadow_blur_2);}.cls-2{fill:#fff;}</style><filter id="shadow_blur_2" name="shadow_blur_2"><feGaussianBlur stdDeviation="0.5" in="SourceGraphic"/></filter></defs><title>x-shadow-12</title><g class="cls-1"><polygon points="10.6 2.6 9.9 1.9 6 5.79 2.1 1.9 1.4 2.6 5.29 6.5 1.4 10.4 2.1 11.1 6 7.21 9.9 11.1 10.6 10.4 6.71 6.5 10.6 2.6"/></g><polygon class="cls-2" points="10.6 2.1 9.9 1.4 6 5.29 2.1 1.4 1.4 2.1 5.29 6 1.4 9.9 2.1 10.6 6 6.71 9.9 10.6 10.6 9.9 6.71 6 10.6 2.1"/></svg>
|
After Width: | Height: | Size: 641 B |
|
@ -15,6 +15,10 @@ const Metadata = require('./metadata/SecretSessionCipher');
|
||||||
const RefreshSenderCertificate = require('./refresh_sender_certificate');
|
const RefreshSenderCertificate = require('./refresh_sender_certificate');
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
const {
|
||||||
|
AttachmentList,
|
||||||
|
} = require('../../ts/components/conversation/AttachmentList');
|
||||||
|
const { CaptionEditor } = require('../../ts/components/CaptionEditor');
|
||||||
const {
|
const {
|
||||||
ContactDetail,
|
ContactDetail,
|
||||||
} = require('../../ts/components/conversation/ContactDetail');
|
} = require('../../ts/components/conversation/ContactDetail');
|
||||||
|
@ -172,6 +176,8 @@ exports.setup = (options = {}) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const Components = {
|
const Components = {
|
||||||
|
AttachmentList,
|
||||||
|
CaptionEditor,
|
||||||
ContactDetail,
|
ContactDetail,
|
||||||
ContactListItem,
|
ContactListItem,
|
||||||
ContactName,
|
ContactName,
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
/* global Whisper */
|
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
|
||||||
(function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
window.Whisper = window.Whisper || {};
|
|
||||||
|
|
||||||
Whisper.AttachmentPreviewView = Whisper.View.extend({
|
|
||||||
className: 'attachment-preview',
|
|
||||||
templateName: 'attachment-preview',
|
|
||||||
render_attributes() {
|
|
||||||
return { source: this.src };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -57,6 +57,11 @@
|
||||||
return { toastMessage: i18n('messageFoundButNotLoaded') };
|
return { toastMessage: i18n('messageFoundButNotLoaded') };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Whisper.VoiceNoteMustBeOnlyAttachmentToast = Whisper.ToastView.extend({
|
||||||
|
render_attributes() {
|
||||||
|
return { toastMessage: i18n('voiceNoteMustBeOnlyAttachment') };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.ConversationLoadingScreen = Whisper.View.extend({
|
Whisper.ConversationLoadingScreen = Whisper.View.extend({
|
||||||
templateName: 'conversation-loading-screen',
|
templateName: 'conversation-loading-screen',
|
||||||
|
@ -144,8 +149,7 @@
|
||||||
|
|
||||||
this.window = options.window;
|
this.window = options.window;
|
||||||
this.fileInput = new Whisper.FileInputView({
|
this.fileInput = new Whisper.FileInputView({
|
||||||
el: this.$('form.send'),
|
el: this.$('.attachment-list'),
|
||||||
window: this.window,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getHeaderProps = () => {
|
const getHeaderProps = () => {
|
||||||
|
@ -255,15 +259,41 @@
|
||||||
'farFromBottom .message-list': 'addScrollDownButton',
|
'farFromBottom .message-list': 'addScrollDownButton',
|
||||||
'lazyScroll .message-list': 'onLazyScroll',
|
'lazyScroll .message-list': 'onLazyScroll',
|
||||||
'force-resize': 'forceUpdateMessageFieldSize',
|
'force-resize': 'forceUpdateMessageFieldSize',
|
||||||
dragover: 'sendToFileInput',
|
|
||||||
drop: 'sendToFileInput',
|
'click button.paperclip': 'onChooseAttachment',
|
||||||
dragleave: 'sendToFileInput',
|
'change input.file-input': 'onChoseAttachment',
|
||||||
|
|
||||||
|
dragover: 'onDragOver',
|
||||||
|
dragleave: 'onDragLeave',
|
||||||
|
drop: 'onDrop',
|
||||||
|
paste: 'onPaste',
|
||||||
},
|
},
|
||||||
sendToFileInput(e) {
|
|
||||||
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
onChooseAttachment(e) {
|
||||||
return;
|
e.stopPropagation();
|
||||||
}
|
e.preventDefault();
|
||||||
this.fileInput.$el.trigger(e);
|
|
||||||
|
this.$('input.file-input').click();
|
||||||
|
},
|
||||||
|
async onChoseAttachment() {
|
||||||
|
const fileField = this.$('input.file-input');
|
||||||
|
const file = fileField.prop('files')[0];
|
||||||
|
await this.fileInput.maybeAddAttachment(file);
|
||||||
|
this.toggleMicrophone();
|
||||||
|
fileField.val(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
onDragOver(e) {
|
||||||
|
this.fileInput.onDragOver(e);
|
||||||
|
},
|
||||||
|
onDragLeave(e) {
|
||||||
|
this.fileInput.onDragLeave(e);
|
||||||
|
},
|
||||||
|
onDrop(e) {
|
||||||
|
this.fileInput.onDrop(e);
|
||||||
|
},
|
||||||
|
onPaste(e) {
|
||||||
|
this.fileInput.onPaste(e);
|
||||||
},
|
},
|
||||||
|
|
||||||
onPrune() {
|
onPrune() {
|
||||||
|
@ -483,6 +513,13 @@
|
||||||
captureAudio(e) {
|
captureAudio(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (this.fileInput.hasFiles()) {
|
||||||
|
const toast = new Whisper.VoiceNoteMustBeOnlyAttachmentToast();
|
||||||
|
toast.$el.appendTo(this.$el);
|
||||||
|
toast.render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Note - clicking anywhere will close the audio capture panel, due to
|
// Note - clicking anywhere will close the audio capture panel, due to
|
||||||
// the onClick handler in InboxView, which calls its closeRecording method.
|
// the onClick handler in InboxView, which calls its closeRecording method.
|
||||||
|
|
||||||
|
@ -503,9 +540,11 @@
|
||||||
this.$('.microphone').hide();
|
this.$('.microphone').hide();
|
||||||
},
|
},
|
||||||
handleAudioCapture(blob) {
|
handleAudioCapture(blob) {
|
||||||
this.fileInput.file = blob;
|
this.fileInput.addAttachment({
|
||||||
this.fileInput.isVoiceNote = true;
|
contentType: blob.type,
|
||||||
this.fileInput.previewImages();
|
file: blob,
|
||||||
|
isVoiceNote: true,
|
||||||
|
});
|
||||||
this.$('.bottom-bar form').submit();
|
this.$('.bottom-bar form').submit();
|
||||||
},
|
},
|
||||||
endCaptureAudio() {
|
endCaptureAudio() {
|
||||||
|
@ -1576,7 +1615,7 @@
|
||||||
this.setQuoteMessage(null);
|
this.setQuoteMessage(null);
|
||||||
this.focusMessageFieldAndClearDisabled();
|
this.focusMessageFieldAndClearDisabled();
|
||||||
this.forceUpdateMessageFieldSize(e);
|
this.forceUpdateMessageFieldSize(e);
|
||||||
this.fileInput.deleteFiles();
|
this.fileInput.clearAttachments();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
window.log.error(
|
window.log.error(
|
||||||
'Error pulling attached files before send',
|
'Error pulling attached files before send',
|
||||||
|
|
|
@ -30,91 +30,405 @@
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Whisper.UnsupportedFileTypeToast = Whisper.ToastView.extend({
|
|
||||||
template: i18n('unsupportedFileType'),
|
|
||||||
});
|
|
||||||
|
|
||||||
Whisper.DangerousFileTypeToast = Whisper.ToastView.extend({
|
Whisper.DangerousFileTypeToast = Whisper.ToastView.extend({
|
||||||
template: i18n('dangerousFileType'),
|
template: i18n('dangerousFileType'),
|
||||||
});
|
});
|
||||||
|
Whisper.OneNonImageAtATimeToast = Whisper.ToastView.extend({
|
||||||
|
template: i18n('oneNonImageAtATimeToast'),
|
||||||
|
});
|
||||||
|
Whisper.CannotMixImageAndNonImageAttachmentsToast = Whisper.ToastView.extend({
|
||||||
|
template: i18n('cannotMixImageAdnNonImageAttachments'),
|
||||||
|
});
|
||||||
|
Whisper.MaxAttachmentsToast = Whisper.ToastView.extend({
|
||||||
|
template: i18n('maximumAttachments'),
|
||||||
|
});
|
||||||
|
|
||||||
Whisper.FileInputView = Backbone.View.extend({
|
Whisper.FileInputView = Backbone.View.extend({
|
||||||
tagName: 'span',
|
tagName: 'span',
|
||||||
className: 'file-input',
|
className: 'file-input',
|
||||||
initialize(options) {
|
initialize() {
|
||||||
this.$input = this.$('input[type=file]');
|
this.attachments = [];
|
||||||
this.$input.click(e => {
|
|
||||||
e.stopPropagation();
|
this.attachmentListView = new Whisper.ReactWrapperView({
|
||||||
|
el: this.el,
|
||||||
|
Component: window.Signal.Components.AttachmentList,
|
||||||
|
props: this.getPropsForAttachmentList(),
|
||||||
});
|
});
|
||||||
this.thumb = new Whisper.AttachmentPreviewView();
|
|
||||||
this.$el.addClass('file-input');
|
|
||||||
this.window = options.window;
|
|
||||||
this.previewObjectUrl = null;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
events: {
|
remove() {
|
||||||
'change .choose-file': 'previewImages',
|
if (this.attachmentListView) {
|
||||||
'click .close': 'deleteFiles',
|
this.attachmentListView.remove();
|
||||||
'click .choose-file': 'open',
|
}
|
||||||
drop: 'openDropped',
|
if (this.captionEditorView) {
|
||||||
dragover: 'showArea',
|
this.captionEditorView.remove();
|
||||||
dragleave: 'hideArea',
|
}
|
||||||
paste: 'onPaste',
|
|
||||||
|
Backbone.View.prototype.remove.call(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
open(e) {
|
render() {
|
||||||
|
this.attachmentListView.update(this.getPropsForAttachmentList());
|
||||||
|
},
|
||||||
|
|
||||||
|
getPropsForAttachmentList() {
|
||||||
|
const { attachments } = this;
|
||||||
|
|
||||||
|
// We never want to display voice notes in our attachment list
|
||||||
|
if (_.any(attachments, attachment => Boolean(attachment.isVoiceNote))) {
|
||||||
|
return {
|
||||||
|
attachments: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
attachments,
|
||||||
|
onClickAttachment: this.onClickAttachment.bind(this),
|
||||||
|
onCloseAttachment: this.onCloseAttachment.bind(this),
|
||||||
|
onClose: this.onClose.bind(this),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
onClickAttachment(attachment) {
|
||||||
|
const getProps = () => ({
|
||||||
|
url: attachment.videoUrl || attachment.url,
|
||||||
|
caption: attachment.caption,
|
||||||
|
attachment,
|
||||||
|
onChangeCaption,
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
this.captionEditorView.update(getProps());
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeCaption = caption => {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
attachment.caption = caption;
|
||||||
|
this.render();
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.captionEditorView = new Whisper.ReactWrapperView({
|
||||||
|
className: 'attachment-list-wrapper',
|
||||||
|
Component: window.Signal.Components.CaptionEditor,
|
||||||
|
props: getProps(),
|
||||||
|
onClose: () => Signal.Backbone.Views.Lightbox.hide(),
|
||||||
|
});
|
||||||
|
Signal.Backbone.Views.Lightbox.show(this.captionEditorView.el);
|
||||||
|
},
|
||||||
|
|
||||||
|
onCloseAttachment(attachment) {
|
||||||
|
this.attachments = _.without(this.attachments, attachment);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
this.attachments = [];
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
// These event handlers are called by ConversationView, which listens for these events
|
||||||
|
|
||||||
|
onDragOver(e) {
|
||||||
|
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// hack
|
this.$el.addClass('dropoff');
|
||||||
if (this.window && this.window.chrome && this.window.chrome.fileSystem) {
|
},
|
||||||
this.window.chrome.fileSystem.chooseEntry(
|
|
||||||
{ type: 'openFile' },
|
onDragLeave(e) {
|
||||||
entry => {
|
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
||||||
if (!entry) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
entry.file(file => {
|
e.stopPropagation();
|
||||||
this.file = file;
|
e.preventDefault();
|
||||||
this.previewImages();
|
this.$el.removeClass('dropoff');
|
||||||
});
|
},
|
||||||
}
|
|
||||||
);
|
onDrop(e) {
|
||||||
} else {
|
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
||||||
this.$input.click();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-destructuring
|
||||||
|
const file = e.originalEvent.dataTransfer.files[0];
|
||||||
|
this.maybeAddAttachment(file);
|
||||||
|
this.$el.removeClass('dropoff');
|
||||||
|
},
|
||||||
|
|
||||||
|
onPaste(e) {
|
||||||
|
const { items } = e.originalEvent.clipboardData;
|
||||||
|
let imgBlob = null;
|
||||||
|
for (let i = 0; i < items.length; i += 1) {
|
||||||
|
if (items[i].type.split('/')[0] === 'image') {
|
||||||
|
imgBlob = items[i].getAsFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (imgBlob !== null) {
|
||||||
|
const file = imgBlob;
|
||||||
|
this.maybeAddAttachment(file);
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addThumb(src, options = {}) {
|
// Public interface
|
||||||
_.defaults(options, { addPlayIcon: false });
|
|
||||||
this.$('.avatar').hide();
|
|
||||||
this.thumb.src = src;
|
|
||||||
this.$('.attachment-previews').append(this.thumb.render().el);
|
|
||||||
|
|
||||||
if (options.addPlayIcon) {
|
hasFiles() {
|
||||||
this.$el.addClass('video-attachment');
|
return this.attachments.length > 0;
|
||||||
} else {
|
|
||||||
this.$el.removeClass('video-attachment');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.thumb.$('img')[0].onload = () => {
|
|
||||||
this.$el.trigger('force-resize');
|
|
||||||
};
|
|
||||||
this.thumb.$('img')[0].onerror = () => {
|
|
||||||
this.unableToLoadAttachment();
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
unableToLoadAttachment() {
|
async getFiles() {
|
||||||
|
const files = await Promise.all(
|
||||||
|
this.attachments.map(attachment => this.getFile(attachment))
|
||||||
|
);
|
||||||
|
this.clearAttachments();
|
||||||
|
return files;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearAttachments() {
|
||||||
|
this.attachments.forEach(attachment => {
|
||||||
|
if (attachment.url) {
|
||||||
|
URL.revokeObjectURL(attachment.url);
|
||||||
|
}
|
||||||
|
if (attachment.videoUrl) {
|
||||||
|
URL.revokeObjectURL(attachment.videoUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.attachments = [];
|
||||||
|
this.render();
|
||||||
|
this.$el.trigger('force-resize');
|
||||||
|
},
|
||||||
|
|
||||||
|
// Show errors
|
||||||
|
|
||||||
|
showLoadFailure() {
|
||||||
const toast = new Whisper.UnableToLoadToast();
|
const toast = new Whisper.UnableToLoadToast();
|
||||||
toast.$el.insertAfter(this.$el);
|
toast.$el.insertAfter(this.$el);
|
||||||
toast.render();
|
toast.render();
|
||||||
|
|
||||||
this.deleteFiles();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
autoScale(file) {
|
showDangerousError() {
|
||||||
if (file.type.split('/')[0] !== 'image' || file.type === 'image/tiff') {
|
const toast = new Whisper.DangerousFileTypeToast();
|
||||||
|
toast.$el.insertAfter(this.$el);
|
||||||
|
toast.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
showFileSizeError({ limit, units, u }) {
|
||||||
|
const toast = new Whisper.FileSizeToast({
|
||||||
|
model: { limit, units: units[u] },
|
||||||
|
});
|
||||||
|
toast.$el.insertAfter(this.$el);
|
||||||
|
toast.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
showCannotMixError() {
|
||||||
|
const toast = new Whisper.CannotMixImageAndNonImageAttachmentsToast();
|
||||||
|
toast.$el.insertAfter(this.$el);
|
||||||
|
toast.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
showMultipleNonImageError() {
|
||||||
|
const toast = new Whisper.OneNonImageAtATimeToast();
|
||||||
|
toast.$el.insertAfter(this.$el);
|
||||||
|
toast.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
showMaximumAttachmentsError() {
|
||||||
|
const toast = new Whisper.MaxAttachmentsToast();
|
||||||
|
toast.$el.insertAfter(this.$el);
|
||||||
|
toast.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Housekeeping
|
||||||
|
|
||||||
|
addAttachment(attachment) {
|
||||||
|
if (attachment.isVoiceNote && this.attachments.length > 0) {
|
||||||
|
throw new Error('A voice note cannot be sent with other attachments');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attachments.push(attachment);
|
||||||
|
this.render();
|
||||||
|
},
|
||||||
|
|
||||||
|
async maybeAddAttachment(file) {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = file.name;
|
||||||
|
const contentType = file.type;
|
||||||
|
|
||||||
|
if (window.Signal.Util.isFileDangerous(fileName)) {
|
||||||
|
this.showDangerousError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attachments.length >= 32) {
|
||||||
|
this.showMaximumAttachmentsError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const haveNonImage = _.any(
|
||||||
|
this.attachments,
|
||||||
|
attachment => !MIME.isImage(attachment.contentType)
|
||||||
|
);
|
||||||
|
// You can't add another attachment if you already have a non-image staged
|
||||||
|
if (haveNonImage) {
|
||||||
|
this.showMultipleNonImageError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can't add a non-image attachment if you already have attachments staged
|
||||||
|
if (!MIME.isImage(contentType) && this.attachments.length > 0) {
|
||||||
|
this.showCannotMixError();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderVideoPreview = async () => {
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
try {
|
||||||
|
const type = 'image/png';
|
||||||
|
const thumbnail = await VisualAttachment.makeVideoScreenshot({
|
||||||
|
objectUrl,
|
||||||
|
contentType: type,
|
||||||
|
logger: window.log,
|
||||||
|
});
|
||||||
|
const data = await VisualAttachment.blobToArrayBuffer(thumbnail);
|
||||||
|
const url = Signal.Util.arrayBufferToObjectURL({
|
||||||
|
data,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
this.addAttachment({
|
||||||
|
file,
|
||||||
|
size: file.size,
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
videoUrl: objectUrl,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderImagePreview = async () => {
|
||||||
|
if (!MIME.isJPEG(contentType)) {
|
||||||
|
const url = URL.createObjectURL(file);
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('Failed to create object url for image!');
|
||||||
|
}
|
||||||
|
this.addAttachment({
|
||||||
|
file,
|
||||||
|
size: file.size,
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = await window.autoOrientImage(file);
|
||||||
|
this.addAttachment({
|
||||||
|
file,
|
||||||
|
size: file.size,
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
url,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = await this.autoScale({
|
||||||
|
contentType,
|
||||||
|
file,
|
||||||
|
});
|
||||||
|
let limitKb = 1000000;
|
||||||
|
const blobType =
|
||||||
|
file.type === 'image/gif' ? 'gif' : contentType.split('/')[0];
|
||||||
|
|
||||||
|
switch (blobType) {
|
||||||
|
case 'image':
|
||||||
|
limitKb = 6000;
|
||||||
|
break;
|
||||||
|
case 'gif':
|
||||||
|
limitKb = 25000;
|
||||||
|
break;
|
||||||
|
case 'audio':
|
||||||
|
limitKb = 100000;
|
||||||
|
break;
|
||||||
|
case 'video':
|
||||||
|
limitKb = 100000;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
limitKb = 100000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((blob.size / 1024).toFixed(4) >= limitKb) {
|
||||||
|
const units = ['kB', 'MB', 'GB'];
|
||||||
|
let u = -1;
|
||||||
|
let limit = limitKb * 1000;
|
||||||
|
do {
|
||||||
|
limit /= 1000;
|
||||||
|
u += 1;
|
||||||
|
} while (limit >= 1000 && u < units.length - 1);
|
||||||
|
this.showFileSizeError({ limit, units, u });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
window.log.error(
|
||||||
|
'Error ensuring that image is properly sized:',
|
||||||
|
error && error.stack ? error.stack : error
|
||||||
|
);
|
||||||
|
|
||||||
|
this.showLoadFailure();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Signal.Util.GoogleChrome.isImageTypeSupported(contentType)) {
|
||||||
|
await renderImagePreview();
|
||||||
|
} else if (Signal.Util.GoogleChrome.isVideoTypeSupported(contentType)) {
|
||||||
|
await renderVideoPreview();
|
||||||
|
} else {
|
||||||
|
this.addAttachment({
|
||||||
|
file,
|
||||||
|
size: file.size,
|
||||||
|
contentType,
|
||||||
|
fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.log.error(
|
||||||
|
`Was unable to generate thumbnail for file type ${contentType}`,
|
||||||
|
e && e.stack ? e.stack : e
|
||||||
|
);
|
||||||
|
this.addAttachment({
|
||||||
|
file,
|
||||||
|
size: file.size,
|
||||||
|
contentType,
|
||||||
|
fileName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
autoScale(attachment) {
|
||||||
|
const { contentType, file } = attachment;
|
||||||
|
if (
|
||||||
|
contentType.split('/')[0] !== 'image' ||
|
||||||
|
contentType === 'image/tiff'
|
||||||
|
) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return Promise.resolve(file);
|
return Promise.resolve(attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
@ -132,13 +446,13 @@
|
||||||
img.naturalHeight <= maxHeight &&
|
img.naturalHeight <= maxHeight &&
|
||||||
file.size <= maxSize
|
file.size <= maxSize
|
||||||
) {
|
) {
|
||||||
resolve(file);
|
resolve(attachment);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gifMaxSize = 25000 * 1024;
|
const gifMaxSize = 25000 * 1024;
|
||||||
if (file.type === 'image/gif' && file.size <= gifMaxSize) {
|
if (file.type === 'image/gif' && file.size <= gifMaxSize) {
|
||||||
resolve(file);
|
resolve(attachment);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,285 +484,47 @@
|
||||||
}
|
}
|
||||||
} while (i > 0 && blob.size > maxSize);
|
} while (i > 0 && blob.size > maxSize);
|
||||||
|
|
||||||
resolve(blob);
|
resolve({
|
||||||
|
...attachment,
|
||||||
|
file: blob,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
img.src = url;
|
img.src = url;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async previewImages() {
|
async getFile(attachment) {
|
||||||
this.clearForm();
|
if (!attachment) {
|
||||||
const file = this.file || this.$input.prop('files')[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { name } = file;
|
|
||||||
if (window.Signal.Util.isFileDangerous(name)) {
|
|
||||||
this.deleteFiles();
|
|
||||||
|
|
||||||
const toast = new Whisper.DangerousFileTypeToast();
|
|
||||||
toast.$el.insertAfter(this.$el);
|
|
||||||
toast.render();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentType = file.type;
|
|
||||||
|
|
||||||
const renderVideoPreview = async () => {
|
|
||||||
// we use the variable on this here to ensure cleanup if we're interrupted
|
|
||||||
this.previewObjectUrl = URL.createObjectURL(file);
|
|
||||||
const type = 'image/png';
|
|
||||||
const thumbnail = await VisualAttachment.makeVideoThumbnail({
|
|
||||||
size: 100,
|
|
||||||
videoObjectUrl: this.previewObjectUrl,
|
|
||||||
contentType: type,
|
|
||||||
logger: window.log,
|
|
||||||
});
|
|
||||||
URL.revokeObjectURL(this.previewObjectUrl);
|
|
||||||
|
|
||||||
const data = await VisualAttachment.blobToArrayBuffer(thumbnail);
|
|
||||||
this.previewObjectUrl = Signal.Util.arrayBufferToObjectURL({
|
|
||||||
data,
|
|
||||||
type,
|
|
||||||
});
|
|
||||||
this.addThumb(this.previewObjectUrl, { addPlayIcon: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderImagePreview = async () => {
|
|
||||||
if (!MIME.isJPEG(file.type)) {
|
|
||||||
this.previewObjectUrl = URL.createObjectURL(file);
|
|
||||||
if (!this.previewObjectUrl) {
|
|
||||||
throw new Error('Failed to create object url for image!');
|
|
||||||
}
|
|
||||||
this.addThumb(this.previewObjectUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataUrl = await window.autoOrientImage(file);
|
|
||||||
this.addThumb(dataUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (Signal.Util.GoogleChrome.isImageTypeSupported(contentType)) {
|
|
||||||
await renderImagePreview();
|
|
||||||
} else if (Signal.Util.GoogleChrome.isVideoTypeSupported(contentType)) {
|
|
||||||
await renderVideoPreview();
|
|
||||||
} else if (MIME.isAudio(contentType)) {
|
|
||||||
this.addThumb('images/audio.svg');
|
|
||||||
} else {
|
|
||||||
this.addThumb('images/file.svg');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
window.log.error(
|
|
||||||
`Was unable to generate thumbnail for file type ${contentType}`,
|
|
||||||
e && e.stack ? e.stack : e
|
|
||||||
);
|
|
||||||
this.addThumb('images/file.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const blob = await this.autoScale(file);
|
|
||||||
let limitKb = 1000000;
|
|
||||||
const blobType =
|
|
||||||
file.type === 'image/gif' ? 'gif' : contentType.split('/')[0];
|
|
||||||
|
|
||||||
switch (blobType) {
|
|
||||||
case 'image':
|
|
||||||
limitKb = 6000;
|
|
||||||
break;
|
|
||||||
case 'gif':
|
|
||||||
limitKb = 25000;
|
|
||||||
break;
|
|
||||||
case 'audio':
|
|
||||||
limitKb = 100000;
|
|
||||||
break;
|
|
||||||
case 'video':
|
|
||||||
limitKb = 100000;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
limitKb = 100000;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((blob.size / 1024).toFixed(4) >= limitKb) {
|
|
||||||
const units = ['kB', 'MB', 'GB'];
|
|
||||||
let u = -1;
|
|
||||||
let limit = limitKb * 1000;
|
|
||||||
do {
|
|
||||||
limit /= 1000;
|
|
||||||
u += 1;
|
|
||||||
} while (limit >= 1000 && u < units.length - 1);
|
|
||||||
const toast = new Whisper.FileSizeToast({
|
|
||||||
model: { limit, units: units[u] },
|
|
||||||
});
|
|
||||||
toast.$el.insertAfter(this.$el);
|
|
||||||
toast.render();
|
|
||||||
this.deleteFiles();
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
window.log.error(
|
|
||||||
'Error ensuring that image is properly sized:',
|
|
||||||
error && error.message ? error.message : error
|
|
||||||
);
|
|
||||||
|
|
||||||
this.unableToLoadAttachment();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hasFiles() {
|
|
||||||
const files = this.file ? [this.file] : this.$input.prop('files');
|
|
||||||
return files && files.length && files.length > 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFiles() {
|
|
||||||
const files = this.file
|
|
||||||
? [this.file]
|
|
||||||
: Array.from(this.$input.prop('files'));
|
|
||||||
const promise = Promise.all(files.map(file => this.getFile(file)));
|
|
||||||
this.clearForm();
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
getFile(rawFile) {
|
|
||||||
const file = rawFile || this.file || this.$input.prop('files')[0];
|
|
||||||
if (!file) {
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const attachmentFlags = this.isVoiceNote
|
const attachmentFlags = attachment.isVoiceNote
|
||||||
? textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE
|
? textsecure.protobuf.AttachmentPointer.Flags.VOICE_MESSAGE
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const setFlags = flags => attachment => {
|
const scaled = await this.autoScale(attachment);
|
||||||
const newAttachment = Object.assign({}, attachment);
|
const fileRead = await this.readFile(scaled);
|
||||||
if (flags) {
|
return {
|
||||||
newAttachment.flags = flags;
|
...fileRead,
|
||||||
}
|
url: undefined,
|
||||||
return newAttachment;
|
videoUrl: undefined,
|
||||||
|
flags: attachmentFlags || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: Temporarily allow `then` until we convert the entire file
|
|
||||||
// to `async` / `await`:
|
|
||||||
// eslint-disable-next-line more/no-then
|
|
||||||
return this.autoScale(file)
|
|
||||||
.then(this.readFile)
|
|
||||||
.then(setFlags(attachmentFlags));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async getThumbnail() {
|
readFile(attachment) {
|
||||||
// Scale and crop an image to 256px square
|
|
||||||
const size = 256;
|
|
||||||
const file = this.file || this.$input.prop('files')[0];
|
|
||||||
if (
|
|
||||||
file === undefined ||
|
|
||||||
file.type.split('/')[0] !== 'image' ||
|
|
||||||
file.type === 'image/gif'
|
|
||||||
) {
|
|
||||||
// nothing to do
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
const arrayBuffer = await VisualAttachment.makeImageThumbnail({
|
|
||||||
size,
|
|
||||||
objectUrl,
|
|
||||||
logger: window.log,
|
|
||||||
});
|
|
||||||
URL.revokeObjectURL(objectUrl);
|
|
||||||
|
|
||||||
return this.readFile(arrayBuffer);
|
|
||||||
},
|
|
||||||
|
|
||||||
// File -> Promise Attachment
|
|
||||||
readFile(file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const FR = new FileReader();
|
const FR = new FileReader();
|
||||||
FR.onload = e => {
|
FR.onload = e => {
|
||||||
resolve({
|
resolve({
|
||||||
|
...attachment,
|
||||||
data: e.target.result,
|
data: e.target.result,
|
||||||
contentType: file.type,
|
|
||||||
fileName: file.name,
|
|
||||||
size: file.size,
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
FR.onerror = reject;
|
FR.onerror = reject;
|
||||||
FR.onabort = reject;
|
FR.onabort = reject;
|
||||||
FR.readAsArrayBuffer(file);
|
FR.readAsArrayBuffer(attachment.file);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearForm() {
|
|
||||||
if (this.previewObjectUrl) {
|
|
||||||
URL.revokeObjectURL(this.previewObjectUrl);
|
|
||||||
this.previewObjectUrl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.thumb.remove();
|
|
||||||
this.$('.avatar').show();
|
|
||||||
this.$el.trigger('force-resize');
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteFiles(e) {
|
|
||||||
if (e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
this.clearForm();
|
|
||||||
this.$input
|
|
||||||
.wrap('<form>')
|
|
||||||
.parent('form')
|
|
||||||
.trigger('reset');
|
|
||||||
this.$input.unwrap();
|
|
||||||
this.file = null;
|
|
||||||
this.$input.trigger('change');
|
|
||||||
this.isVoiceNote = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
openDropped(e) {
|
|
||||||
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
this.file = e.originalEvent.dataTransfer.files[0];
|
|
||||||
this.previewImages();
|
|
||||||
this.$el.removeClass('dropoff');
|
|
||||||
},
|
|
||||||
|
|
||||||
showArea(e) {
|
|
||||||
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
this.$el.addClass('dropoff');
|
|
||||||
},
|
|
||||||
|
|
||||||
hideArea(e) {
|
|
||||||
if (e.originalEvent.dataTransfer.types[0] !== 'Files') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
this.$el.removeClass('dropoff');
|
|
||||||
},
|
|
||||||
onPaste(e) {
|
|
||||||
const { items } = e.originalEvent.clipboardData;
|
|
||||||
let imgBlob = null;
|
|
||||||
for (let i = 0; i < items.length; i += 1) {
|
|
||||||
if (items[i].type.split('/')[0] === 'image') {
|
|
||||||
imgBlob = items[i].getAsFile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (imgBlob !== null) {
|
|
||||||
this.file = imgBlob;
|
|
||||||
this.previewImages();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -30,7 +30,7 @@ function OutgoingMessage(
|
||||||
this.failoverNumbers = [];
|
this.failoverNumbers = [];
|
||||||
this.unidentifiedDeliveries = [];
|
this.unidentifiedDeliveries = [];
|
||||||
|
|
||||||
const { numberInfo, senderCertificate, online } = options;
|
const { numberInfo, senderCertificate, online } = options || {};
|
||||||
this.numberInfo = numberInfo;
|
this.numberInfo = numberInfo;
|
||||||
this.senderCertificate = senderCertificate;
|
this.senderCertificate = senderCertificate;
|
||||||
this.online = online;
|
this.online = online;
|
||||||
|
|
|
@ -2033,6 +2033,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.module-image__caption-icon {
|
.module-image__caption-icon {
|
||||||
|
@ -2041,6 +2042,14 @@
|
||||||
left: 6px;
|
left: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-image__with-click-handler {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-image--soft-corners {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.module-image--curved-top-left {
|
.module-image--curved-top-left {
|
||||||
border-top-left-radius: 16px;
|
border-top-left-radius: 16px;
|
||||||
}
|
}
|
||||||
|
@ -2143,6 +2152,17 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-image__close-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
z-index: 2;
|
||||||
|
background-image: url('../images/x-shadow-16.svg');
|
||||||
|
}
|
||||||
|
|
||||||
// Module: Image Grid
|
// Module: Image Grid
|
||||||
|
|
||||||
.module-image-grid {
|
.module-image-grid {
|
||||||
|
@ -2256,6 +2276,220 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Module: Attachments
|
||||||
|
|
||||||
|
.module-attachments {
|
||||||
|
border-top: 1px solid $color-black-015;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-attachments__header {
|
||||||
|
height: 24px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-attachments__close-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 16px;
|
||||||
|
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
z-index: 2;
|
||||||
|
@include color-svg('../images/x-16.svg', $color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-attachments__rail {
|
||||||
|
margin-top: 12px;
|
||||||
|
margin-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
max-height: 142px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-y: hidden;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module: Staged Generic Attachment
|
||||||
|
|
||||||
|
.module-staged-generic-attachment {
|
||||||
|
height: 120px;
|
||||||
|
width: 120px;
|
||||||
|
margin: 1px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: inset 0px 0px 0px 1px $color-black-015;
|
||||||
|
background-color: $color-gray-05;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-generic-attachment__close-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
z-index: 2;
|
||||||
|
@include color-svg('../images/x-16.svg', $color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-generic-attachment__icon {
|
||||||
|
margin-top: 30px;
|
||||||
|
|
||||||
|
background: url('../images/file-gradient.svg') no-repeat center;
|
||||||
|
height: 44px;
|
||||||
|
width: 56px;
|
||||||
|
margin-left: 32px;
|
||||||
|
margin-right: 32px;
|
||||||
|
margin-bottom: -4px;
|
||||||
|
|
||||||
|
// So we can center the extension text inside this icon
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-generic-attachment__icon__extension {
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 13px;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
// Along with flow layout in parent item, centers text
|
||||||
|
text-align: center;
|
||||||
|
width: 25px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
// We don't have much room for text here, cut it off without ellipse
|
||||||
|
overflow-x: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: clip;
|
||||||
|
|
||||||
|
color: $color-gray-90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-staged-generic-attachment__filename {
|
||||||
|
margin: 7px;
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
font-family: Roboto;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
height: 2.4em;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module: Caption Editor
|
||||||
|
|
||||||
|
.module-caption-editor {
|
||||||
|
background-color: $color-black;
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__close-button {
|
||||||
|
z-index: 21;
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 12px;
|
||||||
|
right: 16px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
z-index: 2;
|
||||||
|
@include color-svg('../images/x-16.svg', $color-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__media-container {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
background-color: $color-black;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.module-caption-editor__video {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
.module-caption-editor__placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__bottom-bar {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 3em;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: middle;
|
||||||
|
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-caption-editor__add-caption-button {
|
||||||
|
display: inline-block;
|
||||||
|
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 {
|
||||||
|
height: 2em;
|
||||||
|
width: 40em;
|
||||||
|
border: 1px solid $color-white;
|
||||||
|
border-radius: 1em;
|
||||||
|
color: $color-white;
|
||||||
|
background-color: $color-black;
|
||||||
|
padding: 0.5em;
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: $color-white-07;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Third-party module: react-contextmenu
|
// Third-party module: react-contextmenu
|
||||||
|
|
||||||
.react-contextmenu {
|
.react-contextmenu {
|
||||||
|
|
72
ts/components/CaptionEditor.md
Normal file
72
ts/components/CaptionEditor.md
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
## Image
|
||||||
|
|
||||||
|
```js
|
||||||
|
let caption = null;
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', width: '100%', height: 500 }}>
|
||||||
|
<CaptionEditor
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
attachment={{
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
}}
|
||||||
|
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Image with caption
|
||||||
|
|
||||||
|
```js
|
||||||
|
let caption =
|
||||||
|
"This is the user-provided caption. We show it overlaid on the image. If it's really long, then it wraps, but it doesn't get too close to the edges of the image.";
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', width: '100%', height: 500 }}>
|
||||||
|
<CaptionEditor
|
||||||
|
url="https://placekitten.com/800/600"
|
||||||
|
attachment={{
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
}}
|
||||||
|
caption={caption}
|
||||||
|
contentType="image/jpeg"
|
||||||
|
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
```js
|
||||||
|
let caption = null;
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', width: '100%', height: 500 }}>
|
||||||
|
<CaptionEditor
|
||||||
|
url="fixtures/pixabay-Soap-Bubble-7141.mp4"
|
||||||
|
attachment={{
|
||||||
|
contentType: 'video/mp4',
|
||||||
|
}}
|
||||||
|
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Video with caption
|
||||||
|
|
||||||
|
```js
|
||||||
|
let caption =
|
||||||
|
"This is the user-provided caption. We show it overlaid on the image. If it's really long, then it wraps, but it doesn't get too close to the edges of the image.";
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', width: '100%', height: 500 }}>
|
||||||
|
<CaptionEditor
|
||||||
|
url="fixtures/pixabay-Soap-Bubble-7141.mp4"
|
||||||
|
attachment={{
|
||||||
|
contentType: 'video/mp4',
|
||||||
|
}}
|
||||||
|
caption={caption}
|
||||||
|
onChangeCaption={caption => console.log('onChangeCaption', caption)}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
78
ts/components/CaptionEditor.tsx
Normal file
78
ts/components/CaptionEditor.tsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// tslint:disable:react-a11y-anchors
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import * as GoogleChrome from '../util/GoogleChrome';
|
||||||
|
|
||||||
|
import { AttachmentType } from './conversation/types';
|
||||||
|
|
||||||
|
import { Localizer } from '../types/Util';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
attachment: AttachmentType;
|
||||||
|
i18n: Localizer;
|
||||||
|
url: string;
|
||||||
|
caption?: string;
|
||||||
|
onChangeCaption?: (caption: string) => void;
|
||||||
|
close?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CaptionEditor extends React.Component<Props> {
|
||||||
|
public renderObject() {
|
||||||
|
const { url, i18n, attachment } = this.props;
|
||||||
|
const { contentType } = attachment || { contentType: null };
|
||||||
|
|
||||||
|
const isImageTypeSupported = GoogleChrome.isImageTypeSupported(contentType);
|
||||||
|
if (isImageTypeSupported) {
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className="module-caption-editor__image"
|
||||||
|
alt={i18n('imageAttachmentAlt')}
|
||||||
|
src={url}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isVideoTypeSupported = GoogleChrome.isVideoTypeSupported(contentType);
|
||||||
|
if (isVideoTypeSupported) {
|
||||||
|
return (
|
||||||
|
<video className="module-caption-editor__video" controls={true}>
|
||||||
|
<source src={url} />
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="module-caption-editor__placeholder" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
const { caption, i18n, close, onChangeCaption } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-caption-editor">
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
onClick={close}
|
||||||
|
className="module-caption-editor__close-button"
|
||||||
|
/>
|
||||||
|
<div className="module-caption-editor__media-container">
|
||||||
|
{this.renderObject()}
|
||||||
|
</div>
|
||||||
|
<div className="module-caption-editor__bottom-bar">
|
||||||
|
<div className="module-caption-editor__add-caption-button" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={caption || ''}
|
||||||
|
maxLength={200}
|
||||||
|
placeholder={i18n('addACaption')}
|
||||||
|
className="module-caption-editor__caption-input"
|
||||||
|
onChange={event => {
|
||||||
|
if (onChangeCaption) {
|
||||||
|
onChangeCaption(event.target.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
114
ts/components/conversation/AttachmentList.md
Normal file
114
ts/components/conversation/AttachmentList.md
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
### One image
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const attachments = [
|
||||||
|
{
|
||||||
|
url: util.gifObjectUrl,
|
||||||
|
contentType: 'image/gif',
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
<AttachmentList
|
||||||
|
attachments={attachments}
|
||||||
|
onClose={() => console.log('onClose')}
|
||||||
|
onClickAttachment={attachment => {
|
||||||
|
console.log('onClickAttachment', attachment);
|
||||||
|
}}
|
||||||
|
onCloseAttachment={attachment => {
|
||||||
|
console.log('onCloseAttachment', attachment);
|
||||||
|
}}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Four images
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const attachments = [
|
||||||
|
{
|
||||||
|
url: util.gifObjectUrl,
|
||||||
|
contentType: 'image/png',
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: util.pngObjectUrl,
|
||||||
|
contentType: 'image/png',
|
||||||
|
width: 800,
|
||||||
|
height: 1200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: util.landscapeObjectUrl,
|
||||||
|
contentType: 'image/png',
|
||||||
|
width: 4496,
|
||||||
|
height: 3000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: util.landscapeGreenObjectUrl,
|
||||||
|
contentType: 'image/png',
|
||||||
|
width: 1000,
|
||||||
|
height: 50,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<AttachmentList
|
||||||
|
attachments={attachments}
|
||||||
|
onClose={() => console.log('onClose')}
|
||||||
|
onClickAttachment={attachment => {
|
||||||
|
console.log('onClickAttachment', attachment);
|
||||||
|
}}
|
||||||
|
onCloseAttachment={attachment => {
|
||||||
|
console.log('onCloseAttachment', attachment);
|
||||||
|
}}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### A mix of attachment types
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
const attachments = [
|
||||||
|
{
|
||||||
|
url: util.gifObjectUrl,
|
||||||
|
contentType: 'image/gif',
|
||||||
|
width: 320,
|
||||||
|
height: 240,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
contentType: 'text/plain',
|
||||||
|
fileName: 'manifesto.txt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: util.pngObjectUrl,
|
||||||
|
contentType: 'image/png',
|
||||||
|
width: 800,
|
||||||
|
height: 1200,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<AttachmentList
|
||||||
|
attachments={attachments}
|
||||||
|
onClose={() => console.log('onClose')}
|
||||||
|
onClickAttachment={attachment => {
|
||||||
|
console.log('onClickAttachment', attachment);
|
||||||
|
}}
|
||||||
|
onCloseAttachment={attachment => {
|
||||||
|
console.log('onCloseAttachment', attachment);
|
||||||
|
}}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
|
```
|
||||||
|
|
||||||
|
### No attachments provided
|
||||||
|
|
||||||
|
Nothing is shown if attachment list is empty.
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<AttachmentList attachments={[]} i18n={util.i18n} />
|
||||||
|
```
|
106
ts/components/conversation/AttachmentList.tsx
Normal file
106
ts/components/conversation/AttachmentList.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import React from 'react';
|
||||||
|
// import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {
|
||||||
|
isImageTypeSupported,
|
||||||
|
isVideoTypeSupported,
|
||||||
|
} from '../../util/GoogleChrome';
|
||||||
|
import { AttachmentType } from './types';
|
||||||
|
import { Image } from './Image';
|
||||||
|
import { StagedGenericAttachment } from './StagedGenericAttachment';
|
||||||
|
import { Localizer } from '../../types/Util';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
attachments: Array<AttachmentType>;
|
||||||
|
i18n: Localizer;
|
||||||
|
// onError: () => void;
|
||||||
|
onClickAttachment: (attachment: AttachmentType) => void;
|
||||||
|
onCloseAttachment: (attachment: AttachmentType) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const IMAGE_WIDTH = 120;
|
||||||
|
const IMAGE_HEIGHT = 120;
|
||||||
|
|
||||||
|
export class AttachmentList extends React.Component<Props> {
|
||||||
|
// tslint:disable-next-line max-func-body-length */
|
||||||
|
public render() {
|
||||||
|
const {
|
||||||
|
attachments,
|
||||||
|
i18n,
|
||||||
|
// onError,
|
||||||
|
onClickAttachment,
|
||||||
|
onCloseAttachment,
|
||||||
|
onClose,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!attachments.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-attachments">
|
||||||
|
{attachments.length > 1 ? (
|
||||||
|
<div className="module-attachments__header">
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="module-attachments__close-button"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="module-attachments__rail">
|
||||||
|
{(attachments || []).map((attachment, index) => {
|
||||||
|
const { contentType } = attachment;
|
||||||
|
if (
|
||||||
|
isImageTypeSupported(contentType) ||
|
||||||
|
isVideoTypeSupported(contentType)
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
key={getUrl(attachment) || attachment.fileName || index}
|
||||||
|
alt={`TODO: attachment number ${index}`}
|
||||||
|
i18n={i18n}
|
||||||
|
attachment={attachment}
|
||||||
|
softCorners={true}
|
||||||
|
playIconOverlay={isVideoAttachment(attachment)}
|
||||||
|
height={IMAGE_HEIGHT}
|
||||||
|
width={IMAGE_WIDTH}
|
||||||
|
url={getUrl(attachment)}
|
||||||
|
closeButton={true}
|
||||||
|
onClick={onClickAttachment}
|
||||||
|
onClickClose={onCloseAttachment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StagedGenericAttachment
|
||||||
|
key={getUrl(attachment) || attachment.fileName || index}
|
||||||
|
attachment={attachment}
|
||||||
|
i18n={i18n}
|
||||||
|
onClose={onCloseAttachment}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isVideoAttachment(attachment?: AttachmentType) {
|
||||||
|
return (
|
||||||
|
attachment &&
|
||||||
|
attachment.contentType &&
|
||||||
|
isVideoTypeSupported(attachment.contentType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrl(attachment: AttachmentType) {
|
||||||
|
if (attachment.screenshot) {
|
||||||
|
return attachment.screenshot.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attachment.url;
|
||||||
|
}
|
|
@ -77,18 +77,21 @@
|
||||||
width="199"
|
width="199"
|
||||||
attachment={{ caption: 'dogs playing' }}
|
attachment={{ caption: 'dogs playing' }}
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
height="149"
|
height="149"
|
||||||
width="149"
|
width="149"
|
||||||
attachment={{ caption: 'dogs playing' }}
|
attachment={{ caption: 'dogs playing' }}
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
height="99"
|
height="99"
|
||||||
width="99"
|
width="99"
|
||||||
attachment={{ caption: 'dogs playing' }}
|
attachment={{ caption: 'dogs playing' }}
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -100,6 +103,7 @@
|
||||||
darkOverlay
|
darkOverlay
|
||||||
overlayText="+3"
|
overlayText="+3"
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
height="149"
|
height="149"
|
||||||
|
@ -108,6 +112,7 @@
|
||||||
darkOverlay
|
darkOverlay
|
||||||
overlayText="+3"
|
overlayText="+3"
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
<Image
|
<Image
|
||||||
height="99"
|
height="99"
|
||||||
|
@ -116,6 +121,82 @@
|
||||||
darkOverlay
|
darkOverlay
|
||||||
overlayText="+3"
|
overlayText="+3"
|
||||||
url={util.pngObjectUrl}
|
url={util.pngObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### With top-right X and soft corners
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
height="200"
|
||||||
|
width="199"
|
||||||
|
closeButton={true}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
height="149"
|
||||||
|
width="149"
|
||||||
|
closeButton={true}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
height="99"
|
||||||
|
width="99"
|
||||||
|
closeButton={true}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<Image
|
||||||
|
height="200"
|
||||||
|
width="199"
|
||||||
|
closeButton={true}
|
||||||
|
attachment={{ caption: 'dogs playing' }}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
height="149"
|
||||||
|
width="149"
|
||||||
|
closeButton={true}
|
||||||
|
attachment={{ caption: 'dogs playing' }}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
height="99"
|
||||||
|
width="99"
|
||||||
|
closeButton={true}
|
||||||
|
attachment={{ caption: 'dogs playing' }}
|
||||||
|
onClick={() => console.log('onClick')}
|
||||||
|
onClickClose={attachment => console.log('onClickClose', attachment)}
|
||||||
|
softCorners={true}
|
||||||
|
url={util.gifObjectUrl}
|
||||||
|
i18n={util.i18n}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,24 +15,29 @@ interface Props {
|
||||||
overlayText?: string;
|
overlayText?: string;
|
||||||
|
|
||||||
bottomOverlay?: boolean;
|
bottomOverlay?: boolean;
|
||||||
|
closeButton?: boolean;
|
||||||
curveBottomLeft?: boolean;
|
curveBottomLeft?: boolean;
|
||||||
curveBottomRight?: boolean;
|
curveBottomRight?: boolean;
|
||||||
curveTopLeft?: boolean;
|
curveTopLeft?: boolean;
|
||||||
curveTopRight?: boolean;
|
curveTopRight?: boolean;
|
||||||
darkOverlay?: boolean;
|
darkOverlay?: boolean;
|
||||||
playIconOverlay?: boolean;
|
playIconOverlay?: boolean;
|
||||||
|
softCorners?: boolean;
|
||||||
|
|
||||||
i18n: Localizer;
|
i18n: Localizer;
|
||||||
onClick?: (attachment: AttachmentType) => void;
|
onClick?: (attachment: AttachmentType) => void;
|
||||||
|
onClickClose?: (attachment: AttachmentType) => void;
|
||||||
onError?: () => void;
|
onError?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Image extends React.Component<Props> {
|
export class Image extends React.Component<Props> {
|
||||||
|
// tslint:disable-next-line max-func-body-length cyclomatic-complexity
|
||||||
public render() {
|
public render() {
|
||||||
const {
|
const {
|
||||||
alt,
|
alt,
|
||||||
attachment,
|
attachment,
|
||||||
bottomOverlay,
|
bottomOverlay,
|
||||||
|
closeButton,
|
||||||
curveBottomLeft,
|
curveBottomLeft,
|
||||||
curveBottomRight,
|
curveBottomRight,
|
||||||
curveTopLeft,
|
curveTopLeft,
|
||||||
|
@ -41,9 +46,11 @@ export class Image extends React.Component<Props> {
|
||||||
height,
|
height,
|
||||||
i18n,
|
i18n,
|
||||||
onClick,
|
onClick,
|
||||||
|
onClickClose,
|
||||||
onError,
|
onError,
|
||||||
overlayText,
|
overlayText,
|
||||||
playIconOverlay,
|
playIconOverlay,
|
||||||
|
softCorners,
|
||||||
url,
|
url,
|
||||||
width,
|
width,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -52,18 +59,20 @@ export class Image extends React.Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
role={onClick ? 'button' : undefined}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick(attachment);
|
onClick(attachment);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
role="button"
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'module-image',
|
'module-image',
|
||||||
|
onClick ? 'module-image__with-click-handler' : null,
|
||||||
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
|
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
|
||||||
curveBottomRight ? 'module-image--curved-bottom-right' : null,
|
curveBottomRight ? 'module-image--curved-bottom-right' : null,
|
||||||
curveTopLeft ? 'module-image--curved-top-left' : null,
|
curveTopLeft ? 'module-image--curved-top-left' : null,
|
||||||
curveTopRight ? 'module-image--curved-top-right' : null
|
curveTopRight ? 'module-image--curved-top-right' : null,
|
||||||
|
softCorners ? 'module-image--soft-corners' : null
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
@ -88,9 +97,22 @@ export class Image extends React.Component<Props> {
|
||||||
curveTopRight ? 'module-image--curved-top-right' : null,
|
curveTopRight ? 'module-image--curved-top-right' : null,
|
||||||
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
|
curveBottomLeft ? 'module-image--curved-bottom-left' : null,
|
||||||
curveBottomRight ? 'module-image--curved-bottom-right' : null,
|
curveBottomRight ? 'module-image--curved-bottom-right' : null,
|
||||||
|
softCorners ? 'module-image--soft-corners' : null,
|
||||||
darkOverlay ? 'module-image__border-overlay--dark' : null
|
darkOverlay ? 'module-image__border-overlay--dark' : null
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{closeButton ? (
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
onClick={(e: React.MouseEvent<{}>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onClickClose) {
|
||||||
|
onClickClose(attachment);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="module-image__close-button"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{bottomOverlay ? (
|
{bottomOverlay ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface Props {
|
||||||
const MAX_WIDTH = 300;
|
const MAX_WIDTH = 300;
|
||||||
const MAX_HEIGHT = MAX_WIDTH * 1.5;
|
const MAX_HEIGHT = MAX_WIDTH * 1.5;
|
||||||
const MIN_WIDTH = 200;
|
const MIN_WIDTH = 200;
|
||||||
const MIN_HEIGHT = 25;
|
const MIN_HEIGHT = 50;
|
||||||
|
|
||||||
export class ImageGrid extends React.Component<Props> {
|
export class ImageGrid extends React.Component<Props> {
|
||||||
// tslint:disable-next-line max-func-body-length */
|
// tslint:disable-next-line max-func-body-length */
|
||||||
|
|
|
@ -79,52 +79,6 @@ interface State {
|
||||||
imageBroken: boolean;
|
imageBroken: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudio(attachments?: Array<AttachmentType>) {
|
|
||||||
return (
|
|
||||||
attachments &&
|
|
||||||
attachments[0] &&
|
|
||||||
attachments[0].contentType &&
|
|
||||||
MIME.isAudio(attachments[0].contentType)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function canDisplayImage(attachments?: Array<AttachmentType>) {
|
|
||||||
const { height, width } =
|
|
||||||
attachments && attachments[0] ? attachments[0] : { height: 0, width: 0 };
|
|
||||||
|
|
||||||
return (
|
|
||||||
height &&
|
|
||||||
height > 0 &&
|
|
||||||
height <= 4096 &&
|
|
||||||
width &&
|
|
||||||
width > 0 &&
|
|
||||||
width <= 4096
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getExtension({
|
|
||||||
fileName,
|
|
||||||
contentType,
|
|
||||||
}: {
|
|
||||||
fileName: string;
|
|
||||||
contentType: MIME.MIMEType;
|
|
||||||
}): string | null {
|
|
||||||
if (fileName && fileName.indexOf('.') >= 0) {
|
|
||||||
const lastPeriod = fileName.lastIndexOf('.');
|
|
||||||
const extension = fileName.slice(lastPeriod + 1);
|
|
||||||
if (extension.length) {
|
|
||||||
return extension;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const slash = contentType.indexOf('/');
|
|
||||||
if (slash >= 0) {
|
|
||||||
return contentType.slice(slash + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXPIRATION_CHECK_MINIMUM = 2000;
|
const EXPIRATION_CHECK_MINIMUM = 2000;
|
||||||
const EXPIRED_DELAY = 600;
|
const EXPIRED_DELAY = 600;
|
||||||
|
|
||||||
|
@ -847,3 +801,49 @@ export class Message extends React.Component<Props, State> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getExtension({
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
}: {
|
||||||
|
fileName: string;
|
||||||
|
contentType: MIME.MIMEType;
|
||||||
|
}): string | null {
|
||||||
|
if (fileName && fileName.indexOf('.') >= 0) {
|
||||||
|
const lastPeriod = fileName.lastIndexOf('.');
|
||||||
|
const extension = fileName.slice(lastPeriod + 1);
|
||||||
|
if (extension.length) {
|
||||||
|
return extension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const slash = contentType.indexOf('/');
|
||||||
|
if (slash >= 0) {
|
||||||
|
return contentType.slice(slash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAudio(attachments?: Array<AttachmentType>) {
|
||||||
|
return (
|
||||||
|
attachments &&
|
||||||
|
attachments[0] &&
|
||||||
|
attachments[0].contentType &&
|
||||||
|
MIME.isAudio(attachments[0].contentType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canDisplayImage(attachments?: Array<AttachmentType>) {
|
||||||
|
const { height, width } =
|
||||||
|
attachments && attachments[0] ? attachments[0] : { height: 0, width: 0 };
|
||||||
|
|
||||||
|
return (
|
||||||
|
height &&
|
||||||
|
height > 0 &&
|
||||||
|
height <= 4096 &&
|
||||||
|
width &&
|
||||||
|
width > 0 &&
|
||||||
|
width <= 4096
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
44
ts/components/conversation/StagedGenericAttachment.md
Normal file
44
ts/components/conversation/StagedGenericAttachment.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
Text file
|
||||||
|
|
||||||
|
```js
|
||||||
|
const attachment = {
|
||||||
|
contentType: 'text/plain',
|
||||||
|
fileName: 'manifesto.txt',
|
||||||
|
};
|
||||||
|
|
||||||
|
<StagedGenericAttachment
|
||||||
|
attachment={attachment}
|
||||||
|
i18n={util.i18n}
|
||||||
|
onClose={attachment => console.log('onClose', attachment)}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
File with long name
|
||||||
|
|
||||||
|
```js
|
||||||
|
const attachment = {
|
||||||
|
contentType: 'text/plain',
|
||||||
|
fileName: 'this-is-my-very-important-manifesto-you-must-read-it.txt',
|
||||||
|
};
|
||||||
|
|
||||||
|
<StagedGenericAttachment
|
||||||
|
attachment={attachment}
|
||||||
|
i18n={util.i18n}
|
||||||
|
onClose={attachment => console.log('onClose', attachment)}
|
||||||
|
/>;
|
||||||
|
```
|
||||||
|
|
||||||
|
File with long extension
|
||||||
|
|
||||||
|
```js
|
||||||
|
const attachment = {
|
||||||
|
contentType: 'text/plain',
|
||||||
|
fileName: 'manifesto.reallylongtxt',
|
||||||
|
};
|
||||||
|
|
||||||
|
<StagedGenericAttachment
|
||||||
|
attachment={attachment}
|
||||||
|
i18n={util.i18n}
|
||||||
|
onClose={attachment => console.log('onClose', attachment)}
|
||||||
|
/>;
|
||||||
|
```
|
44
ts/components/conversation/StagedGenericAttachment.tsx
Normal file
44
ts/components/conversation/StagedGenericAttachment.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { getExtension } from './Message';
|
||||||
|
|
||||||
|
import { Localizer } from '../../types/Util';
|
||||||
|
import { AttachmentType } from './types';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
attachment: AttachmentType;
|
||||||
|
onClose: (attachment: AttachmentType) => void;
|
||||||
|
i18n: Localizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class StagedGenericAttachment extends React.Component<Props> {
|
||||||
|
public render() {
|
||||||
|
const { attachment, onClose } = this.props;
|
||||||
|
const { fileName, contentType } = attachment;
|
||||||
|
const extension = getExtension({ contentType, fileName });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="module-staged-generic-attachment">
|
||||||
|
<div
|
||||||
|
className="module-staged-generic-attachment__close-button"
|
||||||
|
role="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (onClose) {
|
||||||
|
onClose(attachment);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="module-staged-generic-attachment__icon">
|
||||||
|
{extension ? (
|
||||||
|
<div className="module-staged-generic-attachment__icon__extension">
|
||||||
|
{extension}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
<div className="module-staged-generic-attachment__filename">
|
||||||
|
{fileName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -659,468 +659,495 @@
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " template: $('#conversation').html(),",
|
"line": " template: $('#conversation').html(),",
|
||||||
"lineNumber": 73,
|
"lineNumber": 78,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-html(",
|
"rule": "jQuery-html(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " template: $('#conversation').html(),",
|
"line": " template: $('#conversation').html(),",
|
||||||
"lineNumber": 73,
|
"lineNumber": 78,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-15T00:38:04.183Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Getting the value, not setting it"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.loadingScreen.$el.prependTo(this.$('.discussion-container'));",
|
"line": " this.loadingScreen.$el.prependTo(this.$('.discussion-container'));",
|
||||||
"lineNumber": 143,
|
"lineNumber": 148,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-14T19:09:08.182Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-prependTo(",
|
"rule": "jQuery-prependTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.loadingScreen.$el.prependTo(this.$('.discussion-container'));",
|
"line": " this.loadingScreen.$el.prependTo(this.$('.discussion-container'));",
|
||||||
"lineNumber": 143,
|
"lineNumber": 148,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-14T19:07:46.079Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " el: this.$('form.send'),",
|
"line": " el: this.$('.attachment-list'),",
|
||||||
"lineNumber": 147,
|
"lineNumber": 152,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-14T19:07:46.079Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.conversation-header').append(this.titleView.el);",
|
"line": " this.$('.conversation-header').append(this.titleView.el);",
|
||||||
"lineNumber": 205,
|
"lineNumber": 209,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.conversation-header').append(this.titleView.el);",
|
"line": " this.$('.conversation-header').append(this.titleView.el);",
|
||||||
"lineNumber": 205,
|
"lineNumber": 209,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.discussion-container').append(this.view.el);",
|
"line": " this.$('.discussion-container').append(this.view.el);",
|
||||||
"lineNumber": 211,
|
"lineNumber": 215,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.discussion-container').append(this.view.el);",
|
"line": " this.$('.discussion-container').append(this.view.el);",
|
||||||
"lineNumber": 211,
|
"lineNumber": 215,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$messageField = this.$('.send-message');",
|
"line": " this.$messageField = this.$('.send-message');",
|
||||||
"lineNumber": 214,
|
"lineNumber": 218,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send-message').focus(this.focusBottomBar.bind(this));",
|
"line": " this.$('.send-message').focus(this.focusBottomBar.bind(this));",
|
||||||
"lineNumber": 232,
|
"lineNumber": 236,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$emojiPanelContainer = this.$('.emoji-panel-container');",
|
"line": " this.$emojiPanelContainer = this.$('.emoji-panel-container');",
|
||||||
"lineNumber": 235,
|
"lineNumber": 239,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:26:45.287Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-$(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " this.$('input.file-input').click();",
|
||||||
|
"lineNumber": 276,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-$(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " const fileField = this.$('input.file-input');",
|
||||||
|
"lineNumber": 279,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " const container = this.$('.discussion-container');",
|
"line": " const container = this.$('.discussion-container');",
|
||||||
"lineNumber": 421,
|
"lineNumber": 451,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " container.append(this.banner.el);",
|
"line": " container.append(this.banner.el);",
|
||||||
"lineNumber": 422,
|
"lineNumber": 452,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.typingBubbleView.$el.appendTo(this.$('.typing-container'));",
|
"line": " this.typingBubbleView.$el.appendTo(this.$('.typing-container'));",
|
||||||
"lineNumber": 459,
|
"lineNumber": 489,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-14T18:51:15.180Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "$() parameter is a hard-coded string"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.typingBubbleView.$el.appendTo(this.$('.typing-container'));",
|
"line": " this.typingBubbleView.$el.appendTo(this.$('.typing-container'));",
|
||||||
"lineNumber": 459,
|
"lineNumber": 489,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-14T18:51:15.180Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Both parameters are known elements from the DOM"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send-message').val().length > 0 ||",
|
"line": " this.$('.send-message').val().length > 0 ||",
|
||||||
"lineNumber": 468,
|
"lineNumber": 498,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.capture-audio').hide();",
|
"line": " this.$('.capture-audio').hide();",
|
||||||
"lineNumber": 471,
|
"lineNumber": 501,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.capture-audio').show();",
|
"line": " this.$('.capture-audio').show();",
|
||||||
"lineNumber": 473,
|
"lineNumber": 503,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " if (this.$('.send-message').val().length > 2000) {",
|
"line": " if (this.$('.send-message').val().length > 2000) {",
|
||||||
"lineNumber": 477,
|
"lineNumber": 507,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.android-length-warning').hide();",
|
"line": " this.$('.android-length-warning').hide();",
|
||||||
"lineNumber": 480,
|
"lineNumber": 510,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-appendTo(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
|
"lineNumber": 518,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " view.$el.appendTo(this.$('.capture-audio'));",
|
"line": " view.$el.appendTo(this.$('.capture-audio'));",
|
||||||
"lineNumber": 500,
|
"lineNumber": 537,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " view.$el.appendTo(this.$('.capture-audio'));",
|
"line": " view.$el.appendTo(this.$('.capture-audio'));",
|
||||||
"lineNumber": 500,
|
"lineNumber": 537,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send-message').attr('disabled', true);",
|
"line": " this.$('.send-message').attr('disabled', true);",
|
||||||
"lineNumber": 502,
|
"lineNumber": 539,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bottom-bar form').submit();",
|
"line": " this.$('.bottom-bar form').submit();",
|
||||||
"lineNumber": 509,
|
"lineNumber": 548,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send-message').removeAttr('disabled');",
|
"line": " this.$('.send-message').removeAttr('disabled');",
|
||||||
"lineNumber": 512,
|
"lineNumber": 551,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bottom-bar form').removeClass('active');",
|
"line": " this.$('.bottom-bar form').removeClass('active');",
|
||||||
"lineNumber": 518,
|
"lineNumber": 557,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bottom-bar form').addClass('active');",
|
"line": " this.$('.bottom-bar form').addClass('active');",
|
||||||
"lineNumber": 521,
|
"lineNumber": 560,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " const container = this.$('.discussion-container');",
|
"line": " const container = this.$('.discussion-container');",
|
||||||
"lineNumber": 609,
|
"lineNumber": 648,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " container.append(this.scrollDownButton.el);",
|
"line": " container.append(this.scrollDownButton.el);",
|
||||||
"lineNumber": 610,
|
"lineNumber": 649,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-appendTo(",
|
|
||||||
"path": "js/views/conversation_view.js",
|
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
|
||||||
"lineNumber": 637,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-appendTo(",
|
|
||||||
"path": "js/views/conversation_view.js",
|
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
|
||||||
"lineNumber": 670,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-appendTo(",
|
|
||||||
"path": "js/views/conversation_view.js",
|
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
|
||||||
"lineNumber": 674,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/conversation_view.js",
|
|
||||||
"line": " const el = this.$(`#${databaseId}`);",
|
|
||||||
"lineNumber": 681,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
"lineNumber": 684,
|
"lineNumber": 676,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-appendTo(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
|
"lineNumber": 709,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-appendTo(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
|
"lineNumber": 713,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-$(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " const el = this.$(`#${databaseId}`);",
|
||||||
|
"lineNumber": 720,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-appendTo(",
|
||||||
|
"path": "js/views/conversation_view.js",
|
||||||
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
|
"lineNumber": 723,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " lastSeenEl.insertBefore(this.$(`#${oldestUnread.get('id')}`));",
|
"line": " lastSeenEl.insertBefore(this.$(`#${oldestUnread.get('id')}`));",
|
||||||
"lineNumber": 861,
|
"lineNumber": 900,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-insertBefore(",
|
"rule": "jQuery-insertBefore(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " lastSeenEl.insertBefore(this.$(`#${oldestUnread.get('id')}`));",
|
"line": " lastSeenEl.insertBefore(this.$(`#${oldestUnread.get('id')}`));",
|
||||||
"lineNumber": 861,
|
"lineNumber": 900,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bar-container').show();",
|
"line": " this.$('.bar-container').show();",
|
||||||
"lineNumber": 916,
|
"lineNumber": 955,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bar-container').hide();",
|
"line": " this.$('.bar-container').hide();",
|
||||||
"lineNumber": 928,
|
"lineNumber": 967,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " const el = this.$(`#${message.id}`);",
|
"line": " const el = this.$(`#${message.id}`);",
|
||||||
"lineNumber": 1025,
|
"lineNumber": 1064,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-prepend(",
|
"rule": "jQuery-prepend(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$el.prepend(dialog.el);",
|
"line": " this.$el.prepend(dialog.el);",
|
||||||
"lineNumber": 1098,
|
"lineNumber": 1137,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
"lineNumber": 1121,
|
"lineNumber": 1160,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-10-11T19:22:47.331Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Operating on already-existing DOM elements"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-prepend(",
|
"rule": "jQuery-prepend(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$el.prepend(dialog.el);",
|
"line": " this.$el.prepend(dialog.el);",
|
||||||
"lineNumber": 1149,
|
"lineNumber": 1188,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " view.$el.insertBefore(this.$('.panel').first());",
|
"line": " view.$el.insertBefore(this.$('.panel').first());",
|
||||||
"lineNumber": 1283,
|
"lineNumber": 1323,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-insertBefore(",
|
"rule": "jQuery-insertBefore(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " view.$el.insertBefore(this.$('.panel').first());",
|
"line": " view.$el.insertBefore(this.$('.panel').first());",
|
||||||
"lineNumber": 1283,
|
"lineNumber": 1323,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-prepend(",
|
"rule": "jQuery-prepend(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$el.prepend(dialog.el);",
|
"line": " this.$el.prepend(dialog.el);",
|
||||||
"lineNumber": 1361,
|
"lineNumber": 1401,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send').prepend(this.quoteView.el);",
|
"line": " this.$('.send').prepend(this.quoteView.el);",
|
||||||
"lineNumber": 1531,
|
"lineNumber": 1571,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-prepend(",
|
"rule": "jQuery-prepend(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.send').prepend(this.quoteView.el);",
|
"line": " this.$('.send').prepend(this.quoteView.el);",
|
||||||
"lineNumber": 1531,
|
"lineNumber": 1571,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " toast.$el.appendTo(this.$el);",
|
"line": " toast.$el.appendTo(this.$el);",
|
||||||
"lineNumber": 1555,
|
"lineNumber": 1595,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.bottom-bar form').submit();",
|
"line": " this.$('.bottom-bar form').submit();",
|
||||||
"lineNumber": 1610,
|
"lineNumber": 1650,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " const $attachmentPreviews = this.$('.attachment-previews');",
|
"line": " const $attachmentPreviews = this.$('.attachment-previews');",
|
||||||
"lineNumber": 1619,
|
"lineNumber": 1659,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "js/views/conversation_view.js",
|
"path": "js/views/conversation_view.js",
|
||||||
"line": " this.$('.panel').css('display') === 'none'",
|
"line": " this.$('.panel').css('display') === 'none'",
|
||||||
"lineNumber": 1650,
|
"lineNumber": 1690,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T02:21:20.921Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1196,103 +1223,58 @@
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-insertAfter(",
|
||||||
"path": "js/views/file_input_view.js",
|
"path": "js/views/file_input_view.js",
|
||||||
"line": " this.$input = this.$('input[type=file]');",
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
"lineNumber": 45,
|
"lineNumber": 216,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.$('.avatar').hide();",
|
|
||||||
"lineNumber": 88,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.$('.attachment-previews').append(this.thumb.render().el);",
|
|
||||||
"lineNumber": 90,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-append(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.$('.attachment-previews').append(this.thumb.render().el);",
|
|
||||||
"lineNumber": 90,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.thumb.$('img')[0].onload = () => {",
|
|
||||||
"lineNumber": 98,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.thumb.$('img')[0].onerror = () => {",
|
|
||||||
"lineNumber": 101,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-insertAfter(",
|
"rule": "jQuery-insertAfter(",
|
||||||
"path": "js/views/file_input_view.js",
|
"path": "js/views/file_input_view.js",
|
||||||
"line": " toast.$el.insertAfter(this.$el);",
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
"lineNumber": 108,
|
"lineNumber": 222,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-insertAfter(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " toast.$el.insertAfter(this.$el);",
|
|
||||||
"lineNumber": 190,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-10-11T19:22:47.331Z",
|
|
||||||
"reasonDetail": "Operating on already-existing DOM elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-insertAfter(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " toast.$el.insertAfter(this.$el);",
|
|
||||||
"lineNumber": 284,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
|
||||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule": "jQuery-$(",
|
|
||||||
"path": "js/views/file_input_view.js",
|
|
||||||
"line": " this.$('.avatar').show();",
|
|
||||||
"lineNumber": 388,
|
|
||||||
"reasonCategory": "usageTrusted",
|
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
|
||||||
"reasonDetail": "Protected from arbitrary input"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-insertAfter(",
|
||||||
"path": "js/views/file_input_view.js",
|
"path": "js/views/file_input_view.js",
|
||||||
"line": " .wrap('<form>')",
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
"lineNumber": 398,
|
"lineNumber": 230,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
"reasonDetail": "Hard-coded value"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-insertAfter(",
|
||||||
|
"path": "js/views/file_input_view.js",
|
||||||
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
|
"lineNumber": 236,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-insertAfter(",
|
||||||
|
"path": "js/views/file_input_view.js",
|
||||||
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
|
"lineNumber": 242,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-insertAfter(",
|
||||||
|
"path": "js/views/file_input_view.js",
|
||||||
|
"line": " toast.$el.insertAfter(this.$el);",
|
||||||
|
"lineNumber": 248,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2018-12-15T03:04:48.403Z",
|
||||||
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
|
@ -3493,7 +3475,7 @@
|
||||||
"lineNumber": 4136,
|
"lineNumber": 4136,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-19T18:13:29.628Z",
|
"updated": "2018-09-19T18:13:29.628Z",
|
||||||
"reasonDetail": "<optional>"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
|
@ -4083,7 +4065,7 @@
|
||||||
"lineNumber": 483,
|
"lineNumber": 483,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-09-19T21:59:32.770Z",
|
"updated": "2018-09-19T21:59:32.770Z",
|
||||||
"reasonDetail": "<optional>"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-appendTo(",
|
"rule": "jQuery-appendTo(",
|
||||||
|
@ -5849,7 +5831,7 @@
|
||||||
"lineNumber": 1699,
|
"lineNumber": 1699,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-18T19:19:27.699Z",
|
"updated": "2018-09-18T19:19:27.699Z",
|
||||||
"reasonDetail": "<optional>"
|
"reasonDetail": "Protected from arbitrary input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue