Note to Self

This commit is contained in:
Scott Nonnenberg 2019-01-30 17:45:58 -08:00
parent 681ca363fe
commit a43a78731a
15 changed files with 411 additions and 148 deletions

View file

@ -1544,6 +1544,10 @@
"message": "Dark", "message": "Dark",
"description": "Label text for dark theme" "description": "Label text for dark theme"
}, },
"noteToSelf": {
"message": "Note to Self",
"description": "Name for the conversation with your own phone number"
},
"hideMenuBar": { "hideMenuBar": {
"message": "Hide menu bar", "message": "Hide menu bar",
"description": "Label text for menu bar visibility setting" "description": "Label text for menu bar visibility setting"

1
images/note-28.svg Normal file
View file

@ -0,0 +1 @@
<svg id="Export" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28 28"><title>note-28</title><path d="M21,3H7A2,2,0,0,0,5,5V23a2,2,0,0,0,2,2H21a2,2,0,0,0,2-2V5A2,2,0,0,0,21,3ZM17,19.5H7V18H17ZM21,16H7V14.5H21Zm0-3.5H7V11H21ZM21,9H7V7.5H21Z"/></svg>

After

Width:  |  Height:  |  Size: 249 B

View file

@ -312,6 +312,7 @@
const result = { const result = {
...this.format(), ...this.format(),
isMe: this.isMe(),
conversationType: this.isPrivate() ? 'direct' : 'group', conversationType: this.isPrivate() ? 'direct' : 'group',
lastUpdated: this.get('timestamp'), lastUpdated: this.get('timestamp'),
@ -908,6 +909,25 @@
return null; return null;
} }
const attachmentsWithData = await Promise.all(
messageWithSchema.attachments.map(loadAttachmentData)
);
// Special-case the self-send case - we send only a sync message
if (this.isMe()) {
const dataMessage = await textsecure.messaging.getMessageProto(
destination,
body,
attachmentsWithData,
quote,
preview,
now,
expireTimer,
profileKey
);
return message.sendSyncMessageOnly(dataMessage);
}
const conversationType = this.get('type'); const conversationType = this.get('type');
const sendFunction = (() => { const sendFunction = (() => {
switch (conversationType) { switch (conversationType) {
@ -922,10 +942,6 @@
} }
})(); })();
const attachmentsWithData = await Promise.all(
messageWithSchema.attachments.map(loadAttachmentData)
);
const options = this.getSendOptions(); const options = this.getSendOptions();
return message.send( return message.send(
this.wrapSend( this.wrapSend(

View file

@ -736,10 +736,25 @@
const quoteWithData = await loadQuoteData(this.get('quote')); const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview')); const previewWithData = await loadPreviewData(this.get('preview'));
const conversation = this.getConversation(); // Special-case the self-send case - we send only a sync message
const options = conversation.getSendOptions(); if (numbers.length === 1 && numbers[0] === this.OUR_NUMBER) {
const [number] = numbers;
const dataMessage = await textsecure.messaging.getMessageProto(
number,
this.get('body'),
attachmentsWithData,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
);
return this.sendSyncMessageOnly(dataMessage);
}
let promise; let promise;
const conversation = this.getConversation();
const options = conversation.getSendOptions();
if (conversation.isPrivate()) { if (conversation.isPrivate()) {
const [number] = numbers; const [number] = numbers;
@ -794,7 +809,11 @@
// One caller today: ConversationView.forceSend() // One caller today: ConversationView.forceSend()
async resend(number) { async resend(number) {
const error = this.removeOutgoingErrors(number); const error = this.removeOutgoingErrors(number);
if (error) { if (!error) {
window.log.warn('resend: requested number was not present in errors');
return null;
}
const profileKey = null; const profileKey = null;
const attachmentsWithData = await Promise.all( const attachmentsWithData = await Promise.all(
(this.get('attachments') || []).map(loadAttachmentData) (this.get('attachments') || []).map(loadAttachmentData)
@ -802,6 +821,21 @@
const quoteWithData = await loadQuoteData(this.get('quote')); const quoteWithData = await loadQuoteData(this.get('quote'));
const previewWithData = await loadPreviewData(this.get('preview')); const previewWithData = await loadPreviewData(this.get('preview'));
// Special-case the self-send case - we send only a sync message
if (number === this.OUR_NUMBER) {
const dataMessage = await textsecure.messaging.getMessageProto(
number,
this.get('body'),
attachmentsWithData,
quoteWithData,
previewWithData,
this.get('sent_at'),
this.get('expireTimer'),
profileKey
);
return this.sendSyncMessageOnly(dataMessage);
}
const { wrap, sendOptions } = ConversationController.prepareForSend( const { wrap, sendOptions } = ConversationController.prepareForSend(
number number
); );
@ -817,8 +851,7 @@
sendOptions sendOptions
); );
this.send(wrap(promise)); return this.send(wrap(promise));
}
}, },
removeOutgoingErrors(number) { removeOutgoingErrors(number) {
const errors = _.partition( const errors = _.partition(
@ -912,7 +945,7 @@
this.trigger('done'); this.trigger('done');
// This is used by sendSyncMessage, then set to null // This is used by sendSyncMessage, then set to null
if (result.dataMessage) { if (!this.get('synced') && result.dataMessage) {
this.set({ dataMessage: result.dataMessage }); this.set({ dataMessage: result.dataMessage });
} }
@ -1013,6 +1046,35 @@
return false; return false;
}, },
async sendSyncMessageOnly(dataMessage) {
this.set({ dataMessage });
try {
await this.sendSyncMessage();
this.set({
delivered_to: [this.OUR_NUMBER],
read_by: [this.OUR_NUMBER],
});
} catch (result) {
const errors = (result && result.errors) || [
new Error('Unknown error'),
];
this.set({ errors });
} finally {
await window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message,
});
this.trigger('done');
const errors = this.get('errors');
if (errors) {
this.trigger('send-error', errors);
} else {
this.trigger('sent');
}
}
},
sendSyncMessage() { sendSyncMessage() {
const ourNumber = textsecure.storage.user.getNumber(); const ourNumber = textsecure.storage.user.getNumber();
const { wrap, sendOptions } = ConversationController.prepareForSend( const { wrap, sendOptions } = ConversationController.prepareForSend(
@ -1021,7 +1083,7 @@
); );
this.syncPromise = this.syncPromise || Promise.resolve(); this.syncPromise = this.syncPromise || Promise.resolve();
this.syncPromise = this.syncPromise.then(() => { const next = () => {
const dataMessage = this.get('dataMessage'); const dataMessage = this.get('dataMessage');
if (this.get('synced') || !dataMessage) { if (this.get('synced') || !dataMessage) {
return Promise.resolve(); return Promise.resolve();
@ -1036,16 +1098,20 @@
this.get('unidentifiedDeliveries'), this.get('unidentifiedDeliveries'),
sendOptions sendOptions
) )
).then(() => { ).then(result => {
this.set({ this.set({
synced: true, synced: true,
dataMessage: null, dataMessage: null,
}); });
return window.Signal.Data.saveMessage(this.attributes, { return window.Signal.Data.saveMessage(this.attributes, {
Message: Whisper.Message, Message: Whisper.Message,
}).then(() => result);
}); });
}); };
});
this.syncPromise = this.syncPromise.then(next, next);
return this.syncPromise;
}, },
async saveErrors(providedErrors) { async saveErrors(providedErrors) {
@ -1312,6 +1378,14 @@
}); });
} }
// A sync'd message to ourself is automatically considered read and delivered
if (conversation.isMe()) {
message.set({
read_by: conversation.getRecipients(),
delivered_to: conversation.getRecipients(),
});
}
message.set({ recipients: conversation.getRecipients() }); message.set({ recipients: conversation.getRecipients() });
} }

View file

@ -1,6 +1,4 @@
/* global ConversationController: false */ /* global ConversationController, i18n, textsecure, Whisper */
/* global i18n: false */
/* global Whisper: false */
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
(function() { (function() {
@ -81,9 +79,19 @@
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */
this.pending = this.pending.then(() => this.pending = this.pending.then(() =>
this.typeahead.search(query).then(() => { this.typeahead.search(query).then(() => {
this.typeahead_view.collection.reset( let results = this.typeahead.filter(isSearchable);
this.typeahead.filter(isSearchable) const noteToSelf = i18n('noteToSelf');
); if (noteToSelf.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
const ourNumber = textsecure.storage.user.getNumber();
const conversation = ConversationController.get(ourNumber);
if (conversation) {
// ensure that we don't have duplicates in our results
results = results.filter(item => item.id !== ourNumber);
results.unshift(conversation);
}
}
this.typeahead_view.collection.reset(results);
}) })
); );
/* eslint-enable more/no-then */ /* eslint-enable more/no-then */

View file

@ -759,6 +759,37 @@ MessageSender.prototype = {
}); });
}, },
async getMessageProto(
number,
body,
attachments,
quote,
preview,
timestamp,
expireTimer,
profileKey
) {
const attributes = {
recipients: [number],
body,
timestamp,
attachments,
quote,
preview,
expireTimer,
profileKey,
};
const message = new Message(attributes);
await Promise.all([
this.uploadAttachments(message),
this.uploadThumbnails(message),
this.uploadLinkPreviews(message),
]);
return message.toArrayBuffer();
},
sendMessageToNumber( sendMessageToNumber(
number, number,
messageText, messageText,
@ -1110,6 +1141,7 @@ textsecure.MessageSender = function MessageSenderWrapper(
this.sendReadReceipts = sender.sendReadReceipts.bind(sender); this.sendReadReceipts = sender.sendReadReceipts.bind(sender);
this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender); this.makeProxiedRequest = sender.makeProxiedRequest.bind(sender);
this.getProxiedSize = sender.getProxiedSize.bind(sender); this.getProxiedSize = sender.getProxiedSize.bind(sender);
this.getMessageProto = sender.getMessageProto.bind(sender);
}; };
textsecure.MessageSender.prototype = { textsecure.MessageSender.prototype = {

View file

@ -1348,7 +1348,7 @@
} }
.module-conversation-header__title { .module-conversation-header__title {
margin-left: 8px; margin-left: 6px;
min-width: 0; min-width: 0;
font-size: 16px; font-size: 16px;
@ -1356,8 +1356,8 @@
font-weight: 300; font-weight: 300;
color: $color-gray-90; color: $color-gray-90;
// width of avatar (28px) and our 8px left margin // width of avatar (28px) and our 6px left margin
max-width: calc(100% - 36px); max-width: calc(100% - 34px);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -2036,6 +2036,12 @@
width: 42px; width: 42px;
} }
.module-avatar__icon--note-to-self {
width: 70%;
height: 70%;
@include color-svg('../images/note-28.svg', $color-white);
}
.module-avatar--no-image { .module-avatar--no-image {
background-color: $color-conversation-grey; background-color: $color-conversation-grey;
} }

View file

@ -1098,6 +1098,10 @@ body.dark-theme {
color: $color-dark-05; color: $color-dark-05;
} }
.module-conversation-header__note-to-self {
color: $color-dark-05;
}
.module-conversation-header__title__verified-icon { .module-conversation-header__title__verified-icon {
@include color-svg('../images/verified-check.svg', $color-dark-05); @include color-svg('../images/verified-check.svg', $color-dark-05);
} }
@ -1262,11 +1266,15 @@ body.dark-theme {
} }
.module-avatar__icon--group { .module-avatar__icon--group {
@include color-svg('../images/profile-group.svg', $color-gray-05); background-color: $color-gray-05;
} }
.module-avatar__icon--direct { .module-avatar__icon--direct {
@include color-svg('../images/profile-individual.svg', $color-gray-05); background-color: $color-gray-05;
}
.module-avatar__icon--note-to-self {
background-color: $color-gray-05;
} }
.module-avatar--no-image { .module-avatar--no-image {

View file

@ -63,6 +63,45 @@
</util.ConversationContext> </util.ConversationContext>
``` ```
### Note to self
```jsx
<util.ConversationContext theme={util.theme} ios={util.ios}>
<Avatar
size={80}
color="pink"
noteToSelf={true}
phoneNumber="(555) 353-3433"
conversationType="direct"
i18n={util.i18n}
/>
<Avatar
size={48}
color="pink"
noteToSelf={true}
phoneNumber="(555) 353-3433"
conversationType="direct"
i18n={util.i18n}
/>
<Avatar
size={36}
color="pink"
noteToSelf={true}
phoneNumber="(555) 353-3433"
conversationType="direct"
i18n={util.i18n}
/>
<Avatar
size={28}
color="pink"
noteToSelf={true}
phoneNumber="(555) 353-3433"
conversationType="direct"
i18n={util.i18n}
/>
</util.ConversationContext>
```
### All colors ### All colors
```jsx ```jsx

View file

@ -9,6 +9,7 @@ interface Props {
color?: string; color?: string;
conversationType: 'group' | 'direct'; conversationType: 'group' | 'direct';
i18n: Localizer; i18n: Localizer;
noteToSelf?: boolean;
name?: string; name?: string;
phoneNumber?: string; phoneNumber?: string;
profileName?: string; profileName?: string;
@ -63,11 +64,23 @@ export class Avatar extends React.Component<Props, State> {
} }
public renderNoImage() { public renderNoImage() {
const { conversationType, name, size } = this.props; const { conversationType, name, noteToSelf, size } = this.props;
const initials = getInitials(name); const initials = getInitials(name);
const isGroup = conversationType === 'group'; const isGroup = conversationType === 'group';
if (noteToSelf) {
return (
<div
className={classNames(
'module-avatar__icon',
'module-avatar__icon--note-to-self',
`module-avatar__icon--${size}`
)}
/>
);
}
if (!isGroup && initials) { if (!isGroup && initials) {
return ( return (
<div <div
@ -93,10 +106,10 @@ export class Avatar extends React.Component<Props, State> {
} }
public render() { public render() {
const { avatarPath, color, size } = this.props; const { avatarPath, color, size, noteToSelf } = this.props;
const { imageBroken } = this.state; const { imageBroken } = this.state;
const hasImage = avatarPath && !imageBroken; const hasImage = !noteToSelf && avatarPath && !imageBroken;
if (size !== 28 && size !== 36 && size !== 48 && size !== 80) { if (size !== 28 && size !== 36 && size !== 48 && size !== 80) {
throw new Error(`Size ${size} is not supported!`); throw new Error(`Size ${size} is not supported!`);

View file

@ -38,6 +38,27 @@
</util.LeftPaneContext> </util.LeftPaneContext>
``` ```
#### Conversation with yourself
```jsx
<util.LeftPaneContext theme={util.theme}>
<ConversationListItem
isMe={true}
phoneNumber="(202) 555-0011"
conversationType={'direct'}
name="Mr. Fire🔥"
color="green"
lastUpdated={Date.now() - 5 * 60 * 1000}
lastMessage={{
text: 'Just a second',
status: 'read',
}}
onClick={() => console.log('onClick')}
i18n={util.i18n}
/>
</util.LeftPaneContext>
```
#### All types of status #### All types of status
```jsx ```jsx

View file

@ -16,6 +16,7 @@ interface Props {
color?: string; color?: string;
conversationType: 'group' | 'direct'; conversationType: 'group' | 'direct';
avatarPath?: string; avatarPath?: string;
isMe: boolean;
lastUpdated: number; lastUpdated: number;
unreadCount: number; unreadCount: number;
@ -38,6 +39,7 @@ export class ConversationListItem extends React.Component<Props> {
color, color,
conversationType, conversationType,
i18n, i18n,
isMe,
name, name,
phoneNumber, phoneNumber,
profileName, profileName,
@ -48,6 +50,7 @@ export class ConversationListItem extends React.Component<Props> {
<Avatar <Avatar
avatarPath={avatarPath} avatarPath={avatarPath}
color={color} color={color}
noteToSelf={isMe}
conversationType={conversationType} conversationType={conversationType}
i18n={i18n} i18n={i18n}
name={name} name={name}
@ -78,6 +81,7 @@ export class ConversationListItem extends React.Component<Props> {
const { const {
unreadCount, unreadCount,
i18n, i18n,
isMe,
lastUpdated, lastUpdated,
name, name,
phoneNumber, phoneNumber,
@ -94,12 +98,16 @@ export class ConversationListItem extends React.Component<Props> {
: null : null
)} )}
> >
{isMe ? (
i18n('noteToSelf')
) : (
<ContactName <ContactName
phoneNumber={phoneNumber} phoneNumber={phoneNumber}
name={name} name={name}
profileName={profileName} profileName={profileName}
i18n={i18n} i18n={i18n}
/> />
)}
</div> </div>
<div <div
className={classNames( className={classNames(

View file

@ -5,7 +5,8 @@ Note the five items in gear menu, and the second-level menu with disappearing me
#### With name and profile, verified #### With name and profile, verified
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
i18n={util.i18n} i18n={util.i18n}
color="red" color="red"
isVerified={true} isVerified={true}
@ -23,57 +24,67 @@ Note the five items in gear menu, and the second-level menu with disappearing me
onShowAllMedia={() => console.log('onShowAllMedia')} onShowAllMedia={() => console.log('onShowAllMedia')}
onShowGroupMembers={() => console.log('onShowGroupMembers')} onShowGroupMembers={() => console.log('onShowGroupMembers')}
onGoBack={() => console.log('onGoBack')} onGoBack={() => console.log('onGoBack')}
/> />
</util.ConversationContext>
``` ```
#### With name, not verified, no avatar #### With name, not verified, no avatar
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
i18n={util.i18n} i18n={util.i18n}
color="blue" color="blue"
isVerified={false} isVerified={false}
name="Someone 🔥 Somewhere" name="Someone 🔥 Somewhere"
phoneNumber="(202) 555-0002" phoneNumber="(202) 555-0002"
id="2" id="2"
/> />
</util.ConversationContext>
``` ```
#### Profile, no name #### Profile, no name
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
i18n={util.i18n} i18n={util.i18n}
color="teal" color="teal"
isVerified={false} isVerified={false}
phoneNumber="(202) 555-0003" phoneNumber="(202) 555-0003"
id="3" id="3"
profileName="🔥Flames🔥" profileName="🔥Flames🔥"
/> />
</util.ConversationContext>
``` ```
#### No name, no profile, no color #### No name, no profile, no color
```jsx ```jsx
<ConversationHeader i18n={util.i18n} phoneNumber="(202) 555-0011" id="11" /> <util.ConversationContext theme={util.theme}>
<ConversationHeader i18n={util.i18n} phoneNumber="(202) 555-0011" id="11" />
</util.ConversationContext>
``` ```
### With back button ### With back button
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
showBackButton={true} showBackButton={true}
color="deep_orange" color="deep_orange"
i18n={util.i18n} i18n={util.i18n}
phoneNumber="(202) 555-0004" phoneNumber="(202) 555-0004"
id="4" id="4"
/> />
</util.ConversationContext>
``` ```
### Disappearing messages set ### Disappearing messages set
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
color="indigo" color="indigo"
i18n={util.i18n} i18n={util.i18n}
phoneNumber="(202) 555-0005" phoneNumber="(202) 555-0005"
@ -98,7 +109,8 @@ Note the five items in gear menu, and the second-level menu with disappearing me
onShowAllMedia={() => console.log('onShowAllMedia')} onShowAllMedia={() => console.log('onShowAllMedia')}
onShowGroupMembers={() => console.log('onShowGroupMembers')} onShowGroupMembers={() => console.log('onShowGroupMembers')}
onGoBack={() => console.log('onGoBack')} onGoBack={() => console.log('onGoBack')}
/> />
</util.ConversationContext>
``` ```
### In a group ### In a group
@ -106,7 +118,8 @@ Note the five items in gear menu, and the second-level menu with disappearing me
Note that the menu should includes 'Show Members' instead of 'Show Safety Number' Note that the menu should includes 'Show Members' instead of 'Show Safety Number'
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
i18n={util.i18n} i18n={util.i18n}
color="green" color="green"
phoneNumber="(202) 555-0006" phoneNumber="(202) 555-0006"
@ -121,19 +134,22 @@ Note that the menu should includes 'Show Members' instead of 'Show Safety Number
onShowAllMedia={() => console.log('onShowAllMedia')} onShowAllMedia={() => console.log('onShowAllMedia')}
onShowGroupMembers={() => console.log('onShowGroupMembers')} onShowGroupMembers={() => console.log('onShowGroupMembers')}
onGoBack={() => console.log('onGoBack')} onGoBack={() => console.log('onGoBack')}
/> />
</util.ConversationContext>
``` ```
### In chat with yourself ### In chat with yourself
Note that the menu should not have a 'Show Safety Number' entry. This is the 'Note to self' conversation. Note that the menu should not have a 'Show Safety Number' entry.
```jsx ```jsx
<ConversationHeader <util.ConversationContext theme={util.theme}>
<ConversationHeader
color="cyan" color="cyan"
i18n={util.i18n} i18n={util.i18n}
phoneNumber="(202) 555-0007" phoneNumber="(202) 555-0007"
id="7" id="7"
isMe={true} isMe={true}
/> />
</util.ConversationContext>
``` ```

View file

@ -84,7 +84,22 @@ export class ConversationHeader extends React.Component<Props> {
} }
public renderTitle() { public renderTitle() {
const { name, phoneNumber, i18n, profileName, isVerified } = this.props; const {
name,
phoneNumber,
i18n,
isMe,
profileName,
isVerified,
} = this.props;
if (isMe) {
return (
<div className="module-conversation-header__title">
{i18n('noteToSelf')}
</div>
);
}
return ( return (
<div className="module-conversation-header__title"> <div className="module-conversation-header__title">
@ -113,6 +128,7 @@ export class ConversationHeader extends React.Component<Props> {
color, color,
i18n, i18n,
isGroup, isGroup,
isMe,
name, name,
phoneNumber, phoneNumber,
profileName, profileName,
@ -125,6 +141,7 @@ export class ConversationHeader extends React.Component<Props> {
color={color} color={color}
conversationType={isGroup ? 'group' : 'direct'} conversationType={isGroup ? 'group' : 'direct'}
i18n={i18n} i18n={i18n}
noteToSelf={isMe}
name={name} name={name}
phoneNumber={phoneNumber} phoneNumber={phoneNumber}
profileName={profileName} profileName={profileName}

View file

@ -206,8 +206,8 @@
{ {
"rule": "jQuery-wrap(", "rule": "jQuery-wrap(",
"path": "js/models/messages.js", "path": "js/models/messages.js",
"line": " this.send(wrap(promise));", "line": " return this.send(wrap(promise));",
"lineNumber": 820, "lineNumber": 854,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2018-10-05T23:12:28.961Z" "updated": "2018-10-05T23:12:28.961Z"
}, },
@ -215,7 +215,7 @@
"rule": "jQuery-wrap(", "rule": "jQuery-wrap(",
"path": "js/models/messages.js", "path": "js/models/messages.js",
"line": " return wrap(", "line": " return wrap(",
"lineNumber": 1029, "lineNumber": 1091,
"reasonCategory": "falseMatch", "reasonCategory": "falseMatch",
"updated": "2018-10-05T23:12:28.961Z" "updated": "2018-10-05T23:12:28.961Z"
}, },
@ -527,7 +527,7 @@
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "js/views/conversation_search_view.js", "path": "js/views/conversation_search_view.js",
"line": " this.$new_contact = this.$('.new-contact');", "line": " this.$new_contact = this.$('.new-contact');",
"lineNumber": 42, "lineNumber": 40,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z", "updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input" "reasonDetail": "Protected from arbitrary input"
@ -536,7 +536,7 @@
"rule": "jQuery-append(", "rule": "jQuery-append(",
"path": "js/views/conversation_search_view.js", "path": "js/views/conversation_search_view.js",
"line": " this.$el.append(this.typeahead_view.el);", "line": " this.$el.append(this.typeahead_view.el);",
"lineNumber": 59, "lineNumber": 57,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z", "updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes" "reasonDetail": "Interacting with already-existing DOM nodes"
@ -545,7 +545,7 @@
"rule": "jQuery-$(", "rule": "jQuery-$(",
"path": "js/views/conversation_search_view.js", "path": "js/views/conversation_search_view.js",
"line": " this.new_contact_view.$('.number').text(i18n('invalidNumberError'));", "line": " this.new_contact_view.$('.number').text(i18n('invalidNumberError'));",
"lineNumber": 111, "lineNumber": 119,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T21:59:32.770Z", "updated": "2018-09-19T21:59:32.770Z",
"reasonDetail": "Protected from arbitrary input" "reasonDetail": "Protected from arbitrary input"
@ -554,7 +554,7 @@
"rule": "jQuery-insertAfter(", "rule": "jQuery-insertAfter(",
"path": "js/views/conversation_search_view.js", "path": "js/views/conversation_search_view.js",
"line": " this.hintView.$el.insertAfter(this.$input);", "line": " this.hintView.$el.insertAfter(this.$input);",
"lineNumber": 147, "lineNumber": 155,
"reasonCategory": "usageTrusted", "reasonCategory": "usageTrusted",
"updated": "2018-09-19T18:13:29.628Z", "updated": "2018-09-19T18:13:29.628Z",
"reasonDetail": "Interacting with already-existing DOM nodes" "reasonDetail": "Interacting with already-existing DOM nodes"