Bugfixes for v1.29
* If focus was set to document.body during archive, focus left pane * Shortcut Guide: Add space between text and shortcut highlight * Ensure that draft attachment can be closed with click on X button * Move to keyDown event for user idle checking * Additional resiliency around avatars; check for them on on-disk * Increase timeouts to preserve websocket connection * On startup, be resilient to malformed JSON in log files * Don't crash if shell.openExternal returns an error * Whenever we request a contact/group sync, also request block list * Avatar popup: Ensure styling is mouse- and keyboard-appropriate * MainHeader: Create popperRoot on demand, not on mount * CompositionInput: Disable default Ctrl-/ shortcut * Update libphonenumber
This commit is contained in:
parent
ee9e86ab7a
commit
e9f08c3da9
19 changed files with 300 additions and 128 deletions
|
@ -105,6 +105,30 @@ exports.createReader = root => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.createDoesExist = root => {
|
||||||
|
if (!isString(root)) {
|
||||||
|
throw new TypeError("'root' must be a path");
|
||||||
|
}
|
||||||
|
|
||||||
|
return async relativePath => {
|
||||||
|
if (!isString(relativePath)) {
|
||||||
|
throw new TypeError("'relativePath' must be a string");
|
||||||
|
}
|
||||||
|
|
||||||
|
const absolutePath = path.join(root, relativePath);
|
||||||
|
const normalized = path.normalize(absolutePath);
|
||||||
|
if (!normalized.startsWith(root)) {
|
||||||
|
throw new Error('Invalid relative path');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fse.access(normalized, fse.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
exports.copyIntoAttachmentsDirectory = root => {
|
exports.copyIntoAttachmentsDirectory = root => {
|
||||||
if (!isString(root)) {
|
if (!isString(root)) {
|
||||||
throw new TypeError("'root' must be a path");
|
throw new TypeError("'root' must be a path");
|
||||||
|
|
123
app/logging.js
123
app/logging.js
|
@ -31,7 +31,7 @@ module.exports = {
|
||||||
fetch,
|
fetch,
|
||||||
};
|
};
|
||||||
|
|
||||||
function initialize() {
|
async function initialize() {
|
||||||
if (logger) {
|
if (logger) {
|
||||||
throw new Error('Already called initialize!');
|
throw new Error('Already called initialize!');
|
||||||
}
|
}
|
||||||
|
@ -40,66 +40,81 @@ function initialize() {
|
||||||
const logPath = path.join(basePath, 'logs');
|
const logPath = path.join(basePath, 'logs');
|
||||||
mkdirp.sync(logPath);
|
mkdirp.sync(logPath);
|
||||||
|
|
||||||
return cleanupLogs(logPath).then(() => {
|
try {
|
||||||
const logFile = path.join(logPath, 'log.log');
|
await cleanupLogs(logPath);
|
||||||
const loggerOptions = {
|
} catch (error) {
|
||||||
name: 'log',
|
const errorString = `Failed to clean logs; deleting all. Error: ${
|
||||||
streams: [
|
error.stack
|
||||||
|
}`;
|
||||||
|
console.error(errorString);
|
||||||
|
await deleteAllLogs(logPath);
|
||||||
|
mkdirp.sync(logPath);
|
||||||
|
|
||||||
|
// If we want this log entry to persist on disk, we need to wait until we've
|
||||||
|
// set up our logging infrastructure.
|
||||||
|
setTimeout(() => {
|
||||||
|
console.error(errorString);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = path.join(logPath, 'log.log');
|
||||||
|
const loggerOptions = {
|
||||||
|
name: 'log',
|
||||||
|
streams: [
|
||||||
|
{
|
||||||
|
type: 'rotating-file',
|
||||||
|
path: logFile,
|
||||||
|
period: '1d',
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isRunningFromConsole) {
|
||||||
|
loggerOptions.streams.push({
|
||||||
|
level: 'debug',
|
||||||
|
stream: process.stdout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger = bunyan.createLogger(loggerOptions);
|
||||||
|
|
||||||
|
LEVELS.forEach(level => {
|
||||||
|
ipc.on(`log-${level}`, (first, ...rest) => {
|
||||||
|
logger[level](...rest);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipc.on('batch-log', (first, batch) => {
|
||||||
|
batch.forEach(item => {
|
||||||
|
logger[item.level](
|
||||||
{
|
{
|
||||||
type: 'rotating-file',
|
time: new Date(item.timestamp),
|
||||||
path: logFile,
|
|
||||||
period: '1d',
|
|
||||||
count: 3,
|
|
||||||
},
|
},
|
||||||
],
|
item.logText
|
||||||
};
|
|
||||||
|
|
||||||
if (isRunningFromConsole) {
|
|
||||||
loggerOptions.streams.push({
|
|
||||||
level: 'debug',
|
|
||||||
stream: process.stdout,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logger = bunyan.createLogger(loggerOptions);
|
|
||||||
|
|
||||||
LEVELS.forEach(level => {
|
|
||||||
ipc.on(`log-${level}`, (first, ...rest) => {
|
|
||||||
logger[level](...rest);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('batch-log', (first, batch) => {
|
|
||||||
batch.forEach(item => {
|
|
||||||
logger[item.level](
|
|
||||||
{
|
|
||||||
time: new Date(item.timestamp),
|
|
||||||
},
|
|
||||||
item.logText
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ipc.on('fetch-log', event => {
|
|
||||||
fetch(logPath).then(
|
|
||||||
data => {
|
|
||||||
event.sender.send('fetched-log', data);
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
logger.error(`Problem loading log from disk: ${error.stack}`);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on('delete-all-logs', async event => {
|
ipc.on('fetch-log', event => {
|
||||||
try {
|
fetch(logPath).then(
|
||||||
await deleteAllLogs(logPath);
|
data => {
|
||||||
} catch (error) {
|
event.sender.send('fetched-log', data);
|
||||||
logger.error(`Problem deleting all logs: ${error.stack}`);
|
},
|
||||||
|
error => {
|
||||||
|
logger.error(`Problem loading log from disk: ${error.stack}`);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
event.sender.send('delete-all-logs-complete');
|
ipc.on('delete-all-logs', async event => {
|
||||||
});
|
try {
|
||||||
|
await deleteAllLogs(logPath);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Problem deleting all logs: ${error.stack}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
event.sender.send('delete-all-logs-complete');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
const ACTIVE_TIMEOUT = 15 * 1000;
|
const ACTIVE_TIMEOUT = 15 * 1000;
|
||||||
const ACTIVE_EVENTS = [
|
const ACTIVE_EVENTS = [
|
||||||
'click',
|
'click',
|
||||||
'keypress',
|
'keydown',
|
||||||
'mousedown',
|
'mousedown',
|
||||||
'mousemove',
|
'mousemove',
|
||||||
// 'scroll', // this is triggered by Timeline re-renders, can't use
|
// 'scroll', // this is triggered by Timeline re-renders, can't use
|
||||||
|
@ -193,6 +193,7 @@
|
||||||
upgradeMessageSchema,
|
upgradeMessageSchema,
|
||||||
writeNewAttachmentData,
|
writeNewAttachmentData,
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
|
doesAttachmentExist,
|
||||||
} = window.Signal.Migrations;
|
} = window.Signal.Migrations;
|
||||||
const { Views } = window.Signal;
|
const { Views } = window.Signal;
|
||||||
|
|
||||||
|
@ -1049,6 +1050,23 @@
|
||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// It's very likely that the act of archiving a conversation will set focus to
|
||||||
|
// 'none,' or the top-level body element. This resets it to the left pane,
|
||||||
|
// whether in the normal conversation list or search results.
|
||||||
|
if (document.activeElement === document.body) {
|
||||||
|
const leftPaneEl = document.querySelector('.module-left-pane__list');
|
||||||
|
if (leftPaneEl) {
|
||||||
|
leftPaneEl.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResultsEl = document.querySelector(
|
||||||
|
'.module-search-results'
|
||||||
|
);
|
||||||
|
if (searchResultsEl) {
|
||||||
|
searchResultsEl.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return;
|
return;
|
||||||
|
@ -1852,12 +1870,14 @@
|
||||||
{
|
{
|
||||||
writeNewAttachmentData,
|
writeNewAttachmentData,
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
|
doesAttachmentExist,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
conversation.set(newAttributes);
|
conversation.set(newAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Signal.Data.updateConversation(id, conversation.attributes);
|
window.Signal.Data.updateConversation(id, conversation.attributes);
|
||||||
|
|
||||||
const { expireTimer } = details;
|
const { expireTimer } = details;
|
||||||
const isValidExpireTimer = typeof expireTimer === 'number';
|
const isValidExpireTimer = typeof expireTimer === 'number';
|
||||||
if (isValidExpireTimer) {
|
if (isValidExpireTimer) {
|
||||||
|
@ -1934,6 +1954,7 @@
|
||||||
{
|
{
|
||||||
writeNewAttachmentData,
|
writeNewAttachmentData,
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
|
doesAttachmentExist,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
conversation.set(newAttributes);
|
conversation.set(newAttributes);
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
const { Conversation, Contact, Message, PhoneNumber } = window.Signal.Types;
|
const { Conversation, Contact, Message, PhoneNumber } = window.Signal.Types;
|
||||||
const {
|
const {
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
|
doesAttachmentExist,
|
||||||
getAbsoluteAttachmentPath,
|
getAbsoluteAttachmentPath,
|
||||||
loadAttachmentData,
|
loadAttachmentData,
|
||||||
readStickerData,
|
readStickerData,
|
||||||
|
@ -1746,7 +1747,6 @@
|
||||||
error && error.stack ? error.stack : error
|
error && error.stack ? error.stack : error
|
||||||
);
|
);
|
||||||
await c.dropProfileKey();
|
await c.dropProfileKey();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1814,6 +1814,7 @@
|
||||||
{
|
{
|
||||||
writeNewAttachmentData,
|
writeNewAttachmentData,
|
||||||
deleteAttachmentData,
|
deleteAttachmentData,
|
||||||
|
doesAttachmentExist,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.set(newAttributes);
|
this.set(newAttributes);
|
||||||
|
|
|
@ -114,6 +114,7 @@ function initializeMigrations({
|
||||||
createReader,
|
createReader,
|
||||||
createWriterForExisting,
|
createWriterForExisting,
|
||||||
createWriterForNew,
|
createWriterForNew,
|
||||||
|
createDoesExist,
|
||||||
getDraftPath,
|
getDraftPath,
|
||||||
getPath,
|
getPath,
|
||||||
getStickersPath,
|
getStickersPath,
|
||||||
|
@ -139,6 +140,7 @@ function initializeMigrations({
|
||||||
const copyIntoAttachmentsDirectory = Attachments.copyIntoAttachmentsDirectory(
|
const copyIntoAttachmentsDirectory = Attachments.copyIntoAttachmentsDirectory(
|
||||||
attachmentsPath
|
attachmentsPath
|
||||||
);
|
);
|
||||||
|
const doesAttachmentExist = createDoesExist(attachmentsPath);
|
||||||
|
|
||||||
const stickersPath = getStickersPath(userDataPath);
|
const stickersPath = getStickersPath(userDataPath);
|
||||||
const writeNewStickerData = createWriterForNew(stickersPath);
|
const writeNewStickerData = createWriterForNew(stickersPath);
|
||||||
|
@ -173,6 +175,7 @@ function initializeMigrations({
|
||||||
}),
|
}),
|
||||||
deleteSticker,
|
deleteSticker,
|
||||||
deleteTempFile,
|
deleteTempFile,
|
||||||
|
doesAttachmentExist,
|
||||||
getAbsoluteAttachmentPath,
|
getAbsoluteAttachmentPath,
|
||||||
getAbsoluteDraftPath,
|
getAbsoluteDraftPath,
|
||||||
getAbsoluteStickerPath,
|
getAbsoluteStickerPath,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global crypto */
|
/* global crypto, window */
|
||||||
|
|
||||||
const { isFunction, isNumber } = require('lodash');
|
const { isFunction, isNumber } = require('lodash');
|
||||||
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
|
const { createLastMessageUpdate } = require('../../../ts/types/Conversation');
|
||||||
|
@ -16,17 +16,26 @@ function buildAvatarUpdater({ field }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatar = conversation[field];
|
const avatar = conversation[field];
|
||||||
const { writeNewAttachmentData, deleteAttachmentData } = options;
|
const {
|
||||||
if (!isFunction(writeNewAttachmentData)) {
|
deleteAttachmentData,
|
||||||
throw new Error(
|
doesAttachmentExist,
|
||||||
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
|
writeNewAttachmentData,
|
||||||
);
|
} = options;
|
||||||
}
|
|
||||||
if (!isFunction(deleteAttachmentData)) {
|
if (!isFunction(deleteAttachmentData)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!isFunction(doesAttachmentExist)) {
|
||||||
|
throw new Error(
|
||||||
|
'Conversation.buildAvatarUpdater: deleteAttachmentData must be a function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!isFunction(writeNewAttachmentData)) {
|
||||||
|
throw new Error(
|
||||||
|
'Conversation.buildAvatarUpdater: writeNewAttachmentData must be a function'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const newHash = await computeHash(data);
|
const newHash = await computeHash(data);
|
||||||
|
|
||||||
|
@ -41,8 +50,14 @@ function buildAvatarUpdater({ field }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hash, path } = avatar;
|
const { hash, path } = avatar;
|
||||||
|
const exists = await doesAttachmentExist(path);
|
||||||
|
if (!exists) {
|
||||||
|
window.log.warn(
|
||||||
|
`Conversation.buildAvatarUpdater: attachment ${path} did not exist`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (hash === newHash) {
|
if (exists && hash === newHash) {
|
||||||
return conversation;
|
return conversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -556,6 +556,30 @@ MessageSender.prototype = {
|
||||||
return this.server.getStickerPackManifest(packId);
|
return this.server.getStickerPackManifest(packId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendRequestBlockSyncMessage(options) {
|
||||||
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
|
if (myDevice !== 1 && myDevice !== '1') {
|
||||||
|
const request = new textsecure.protobuf.SyncMessage.Request();
|
||||||
|
request.type = textsecure.protobuf.SyncMessage.Request.Type.BLOCKED;
|
||||||
|
const syncMessage = this.createSyncMessage();
|
||||||
|
syncMessage.request = request;
|
||||||
|
const contentMessage = new textsecure.protobuf.Content();
|
||||||
|
contentMessage.syncMessage = syncMessage;
|
||||||
|
|
||||||
|
const silent = true;
|
||||||
|
return this.sendIndividualProto(
|
||||||
|
myNumber,
|
||||||
|
contentMessage,
|
||||||
|
Date.now(),
|
||||||
|
silent,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
|
||||||
sendRequestConfigurationSyncMessage(options) {
|
sendRequestConfigurationSyncMessage(options) {
|
||||||
const myNumber = textsecure.storage.user.getNumber();
|
const myNumber = textsecure.storage.user.getNumber();
|
||||||
const myDevice = textsecure.storage.user.getDeviceId();
|
const myDevice = textsecure.storage.user.getDeviceId();
|
||||||
|
@ -1236,6 +1260,10 @@ textsecure.MessageSender = function MessageSenderWrapper(username, password) {
|
||||||
this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(
|
this.sendRequestConfigurationSyncMessage = sender.sendRequestConfigurationSyncMessage.bind(
|
||||||
sender
|
sender
|
||||||
);
|
);
|
||||||
|
this.sendRequestBlockSyncMessage = sender.sendRequestBlockSyncMessage.bind(
|
||||||
|
sender
|
||||||
|
);
|
||||||
|
|
||||||
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
this.sendMessageToNumber = sender.sendMessageToNumber.bind(sender);
|
||||||
this.sendMessage = sender.sendMessage.bind(sender);
|
this.sendMessage = sender.sendMessage.bind(sender);
|
||||||
this.resetSession = sender.resetSession.bind(sender);
|
this.resetSession = sender.resetSession.bind(sender);
|
||||||
|
|
|
@ -32,6 +32,9 @@
|
||||||
window.log.info('SyncRequest created. Sending config sync request...');
|
window.log.info('SyncRequest created. Sending config sync request...');
|
||||||
wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));
|
wrap(sender.sendRequestConfigurationSyncMessage(sendOptions));
|
||||||
|
|
||||||
|
window.log.info('SyncRequest now sending block sync request...');
|
||||||
|
wrap(sender.sendRequestBlockSyncMessage(sendOptions));
|
||||||
|
|
||||||
window.log.info('SyncRequest now sending contact sync message...');
|
window.log.info('SyncRequest now sending contact sync message...');
|
||||||
wrap(sender.sendRequestContactSyncMessage(sendOptions))
|
wrap(sender.sendRequestContactSyncMessage(sendOptions))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -191,7 +191,7 @@
|
||||||
ev.code = code;
|
ev.code = code;
|
||||||
ev.reason = reason;
|
ev.reason = reason;
|
||||||
this.dispatchEvent(ev);
|
this.dispatchEvent(ev);
|
||||||
}, 1000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
window.WebSocketResource.prototype = new textsecure.EventTarget();
|
window.WebSocketResource.prototype = new textsecure.EventTarget();
|
||||||
|
@ -227,7 +227,7 @@
|
||||||
this.disconnectTimer = setTimeout(() => {
|
this.disconnectTimer = setTimeout(() => {
|
||||||
clearTimeout(this.keepAliveTimer);
|
clearTimeout(this.keepAliveTimer);
|
||||||
this.wsr.close(3001, 'No response to keepalive request');
|
this.wsr.close(3001, 'No response to keepalive request');
|
||||||
}, 1000);
|
}, 10000);
|
||||||
} else {
|
} else {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
8
main.js
8
main.js
|
@ -166,11 +166,15 @@ function prepareURL(pathSegments, moreKeys) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUrl(event, target) {
|
async function handleUrl(event, target) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const { protocol } = url.parse(target);
|
const { protocol } = url.parse(target);
|
||||||
if (protocol === 'http:' || protocol === 'https:') {
|
if (protocol === 'http:' || protocol === 'https:') {
|
||||||
shell.openExternal(target);
|
try {
|
||||||
|
await shell.openExternal(target);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Failed to open url: ${error.stack}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
"fs-extra": "5.0.0",
|
"fs-extra": "5.0.0",
|
||||||
"fuse.js": "3.4.4",
|
"fuse.js": "3.4.4",
|
||||||
"glob": "7.1.2",
|
"glob": "7.1.2",
|
||||||
"google-libphonenumber": "3.2.2",
|
"google-libphonenumber": "3.2.6",
|
||||||
"got": "8.2.0",
|
"got": "8.2.0",
|
||||||
"he": "1.2.0",
|
"he": "1.2.0",
|
||||||
"intl-tel-input": "12.1.15",
|
"intl-tel-input": "12.1.15",
|
||||||
|
|
|
@ -6831,13 +6831,27 @@ button.module-image__border-overlay:focus {
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
|
|
||||||
@include light-theme {
|
@include light-theme {
|
||||||
&:hover,
|
&:hover {
|
||||||
|
background-color: $color-gray-05;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include keyboard-mode {
|
||||||
|
&:hover {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: $color-gray-05;
|
background-color: $color-gray-05;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@include dark-theme {
|
@include dark-theme {
|
||||||
&:hover,
|
&:hover {
|
||||||
|
background-color: $color-gray-60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@include dark-keyboard-mode {
|
||||||
|
&:hover {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: $color-gray-60;
|
background-color: $color-gray-60;
|
||||||
}
|
}
|
||||||
|
@ -7009,6 +7023,7 @@ button.module-image__border-overlay:focus {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
|
|
||||||
|
padding-left: 4px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ export interface Props {
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
imageBroken: boolean;
|
imageBroken: boolean;
|
||||||
|
lastAvatarPath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Avatar extends React.Component<Props, State> {
|
export class Avatar extends React.Component<Props, State> {
|
||||||
|
@ -35,10 +36,23 @@ export class Avatar extends React.Component<Props, State> {
|
||||||
this.handleImageErrorBound = this.handleImageError.bind(this);
|
this.handleImageErrorBound = this.handleImageError.bind(this);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
lastAvatarPath: props.avatarPath,
|
||||||
imageBroken: false,
|
imageBroken: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static getDerivedStateFromProps(props: Props, state: State): State {
|
||||||
|
if (props.avatarPath !== state.lastAvatarPath) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
lastAvatarPath: props.avatarPath,
|
||||||
|
imageBroken: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
public handleImageError() {
|
public handleImageError() {
|
||||||
// tslint:disable-next-line no-console
|
// tslint:disable-next-line no-console
|
||||||
console.log('Avatar: Image failed to load; failing over to placeholder');
|
console.log('Avatar: Image failed to load; failing over to placeholder');
|
||||||
|
|
|
@ -700,6 +700,13 @@ export const CompositionInput = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get rid of Ctrl-/, which on GNOME is bound to 'select all'
|
||||||
|
if (e.key === '/' && !e.shiftKey && e.ctrlKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return getDefaultKeyBinding(e);
|
return getDefaultKeyBinding(e);
|
||||||
},
|
},
|
||||||
[emojiResults, large]
|
[emojiResults, large]
|
||||||
|
|
|
@ -70,15 +70,6 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentDidMount() {
|
|
||||||
const popperRoot = document.createElement('div');
|
|
||||||
document.body.appendChild(popperRoot);
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
popperRoot,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public componentDidUpdate(prevProps: PropsType) {
|
public componentDidUpdate(prevProps: PropsType) {
|
||||||
const { searchConversationId, startSearchCounter } = this.props;
|
const { searchConversationId, startSearchCounter } = this.props;
|
||||||
|
|
||||||
|
@ -114,28 +105,41 @@ export class MainHeader extends React.Component<PropsType, StateType> {
|
||||||
};
|
};
|
||||||
|
|
||||||
public showAvatarPopup = () => {
|
public showAvatarPopup = () => {
|
||||||
|
const popperRoot = document.createElement('div');
|
||||||
|
document.body.appendChild(popperRoot);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showingAvatarPopup: true,
|
showingAvatarPopup: true,
|
||||||
|
popperRoot,
|
||||||
});
|
});
|
||||||
document.addEventListener('click', this.handleOutsideClick);
|
document.addEventListener('click', this.handleOutsideClick);
|
||||||
document.addEventListener('keydown', this.handleOutsideKeyDown);
|
document.addEventListener('keydown', this.handleOutsideKeyDown);
|
||||||
};
|
};
|
||||||
|
|
||||||
public hideAvatarPopup = () => {
|
public hideAvatarPopup = () => {
|
||||||
|
const { popperRoot } = this.state;
|
||||||
|
|
||||||
document.removeEventListener('click', this.handleOutsideClick);
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
document.removeEventListener('keydown', this.handleOutsideKeyDown);
|
document.removeEventListener('keydown', this.handleOutsideKeyDown);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
showingAvatarPopup: false,
|
showingAvatarPopup: false,
|
||||||
|
popperRoot: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (popperRoot) {
|
||||||
|
document.body.removeChild(popperRoot);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
const { popperRoot } = this.state;
|
const { popperRoot } = this.state;
|
||||||
|
|
||||||
|
document.removeEventListener('click', this.handleOutsideClick);
|
||||||
|
document.removeEventListener('keydown', this.handleOutsideKeyDown);
|
||||||
|
|
||||||
if (popperRoot) {
|
if (popperRoot) {
|
||||||
document.body.removeChild(popperRoot);
|
document.body.removeChild(popperRoot);
|
||||||
document.removeEventListener('click', this.handleOutsideClick);
|
|
||||||
document.removeEventListener('keydown', this.handleOutsideKeyDown);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,20 +162,6 @@ export class Image extends React.Component<Props> {
|
||||||
alt={i18n('imageCaptionIconAlt')}
|
alt={i18n('imageCaptionIconAlt')}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{closeButton ? (
|
|
||||||
<button
|
|
||||||
onClick={(e: React.MouseEvent<{}>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
if (onClickClose) {
|
|
||||||
onClickClose(attachment);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="module-image__close-button"
|
|
||||||
title={i18n('remove-attachment')}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{bottomOverlay ? (
|
{bottomOverlay ? (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
@ -199,6 +185,20 @@ export class Image extends React.Component<Props> {
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{overlay}
|
{overlay}
|
||||||
|
{closeButton ? (
|
||||||
|
<button
|
||||||
|
onClick={(e: React.MouseEvent<{}>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (onClickClose) {
|
||||||
|
onClickClose(attachment);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="module-image__close-button"
|
||||||
|
title={i18n('remove-attachment')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ export const actions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
function userChanged(attributes: {
|
function userChanged(attributes: {
|
||||||
|
interactionMode?: 'mouse' | 'keyboard';
|
||||||
ourNumber: string;
|
ourNumber: string;
|
||||||
regionCode: string;
|
regionCode: string;
|
||||||
}): UserChangedActionType {
|
}): UserChangedActionType {
|
||||||
|
|
|
@ -1292,18 +1292,26 @@
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/sync_request.js",
|
"path": "libtextsecure/sync_request.js",
|
||||||
"line": " wrap(sender.sendRequestContactSyncMessage(sendOptions))",
|
"line": " wrap(sender.sendRequestBlockSyncMessage(sendOptions));",
|
||||||
"lineNumber": 36,
|
"lineNumber": 36,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-10-05T23:12:28.961Z"
|
"updated": "2019-12-03T00:28:08.683Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-wrap(",
|
||||||
|
"path": "libtextsecure/sync_request.js",
|
||||||
|
"line": " wrap(sender.sendRequestContactSyncMessage(sendOptions))",
|
||||||
|
"lineNumber": 39,
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2019-12-03T00:28:08.683Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "libtextsecure/sync_request.js",
|
"path": "libtextsecure/sync_request.js",
|
||||||
"line": " return wrap(sender.sendRequestGroupSyncMessage(sendOptions));",
|
"line": " return wrap(sender.sendRequestGroupSyncMessage(sendOptions));",
|
||||||
"lineNumber": 39,
|
"lineNumber": 42,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-10-05T23:12:28.961Z"
|
"updated": "2019-12-03T00:28:08.683Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
|
@ -3673,7 +3681,7 @@
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " var a = !eval('\"use strict\";let x = 1; function f() { return typeof x; };f() == \"number\";');",
|
"line": " var a = !eval('\"use strict\";let x = 1; function f() { return typeof x; };f() == \"number\";');",
|
||||||
"lineNumber": 232,
|
"lineNumber": 233,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-27T01:31:13.384Z",
|
"updated": "2018-11-27T01:31:13.384Z",
|
||||||
"reasonDetail": "Hard-coded string used for testing capabilities."
|
"reasonDetail": "Hard-coded string used for testing capabilities."
|
||||||
|
@ -3682,25 +3690,16 @@
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " eval(a);",
|
"line": " eval(a);",
|
||||||
"lineNumber": 267,
|
"lineNumber": 268,
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
"reasonCategory": "notExercisedByOurApp",
|
||||||
"updated": "2018-11-27T01:31:13.384Z",
|
"updated": "2018-11-27T01:31:13.384Z",
|
||||||
"reasonDetail": "Used to load dependencies; parent function loadModuleFromSource_ is used in one place only."
|
"reasonDetail": "Used to load dependencies; parent function loadModuleFromSource_ is used in one place only."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"rule": "eval",
|
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
|
||||||
"line": " eval(g + \"\\n//# sourceURL=\" + f);",
|
|
||||||
"lineNumber": 298,
|
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
|
||||||
"updated": "2019-04-12T00:50:12.124Z",
|
|
||||||
"reasonDetail": "Used for google closure compiler transpilation scenarios"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " goog.global.eval(\"var _evalTest_ = 1;\");",
|
"line": " goog.global.eval(\"var _evalTest_ = 1;\");",
|
||||||
"lineNumber": 444,
|
"lineNumber": 445,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-04-12T00:50:12.124Z",
|
"updated": "2019-04-12T00:50:12.124Z",
|
||||||
"reasonDetail": "Hard-coded string used for testing capabilities"
|
"reasonDetail": "Hard-coded string used for testing capabilities"
|
||||||
|
@ -3709,7 +3708,7 @@
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " goog.global.eval(a);",
|
"line": " goog.global.eval(a);",
|
||||||
"lineNumber": 458,
|
"lineNumber": 459,
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
"reasonCategory": "notExercisedByOurApp",
|
||||||
"updated": "2018-11-27T01:31:13.384Z",
|
"updated": "2018-11-27T01:31:13.384Z",
|
||||||
"reasonDetail": "More transpilation logic"
|
"reasonDetail": "More transpilation logic"
|
||||||
|
@ -3717,16 +3716,16 @@
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " b && (a = a.replace(/\\{\\$([^}]+)}/g, function(a, d) {",
|
"line": " b && (a = a.replace(/\\{\\$([^}]+)}/g, function(a, c) {",
|
||||||
"lineNumber": 498,
|
"lineNumber": 500,
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-11-27T01:31:13.384Z"
|
"updated": "2019-12-03T19:24:21.611Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " return !!eval(a);",
|
"line": " return !!eval(a);",
|
||||||
"lineNumber": 640,
|
"lineNumber": 642,
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
"reasonCategory": "notExercisedByOurApp",
|
||||||
"updated": "2018-11-27T01:31:13.384Z",
|
"updated": "2018-11-27T01:31:13.384Z",
|
||||||
"reasonDetail": "More transpilation logic"
|
"reasonDetail": "More transpilation logic"
|
||||||
|
@ -3735,7 +3734,7 @@
|
||||||
"rule": "jQuery-load(",
|
"rule": "jQuery-load(",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " d.load(f);",
|
"line": " d.load(f);",
|
||||||
"lineNumber": 796,
|
"lineNumber": 794,
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
"reasonCategory": "notExercisedByOurApp",
|
||||||
"updated": "2019-04-12T00:50:12.124Z",
|
"updated": "2019-04-12T00:50:12.124Z",
|
||||||
"reasonDetail": "Part of their google closure 'debug loader'"
|
"reasonDetail": "Part of their google closure 'debug loader'"
|
||||||
|
@ -3752,11 +3751,29 @@
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
"line": " e || (d.innerHTML = a + \" \", e = d.firstChild.nodeValue.slice(0, -1));",
|
"line": " a.innerHTML = goog.html.SafeHtml.unwrapTrustedHTML(goog.html.SafeHtml.EMPTY);",
|
||||||
"lineNumber": 2045,
|
"lineNumber": 3448,
|
||||||
"reasonCategory": "notExercisedByOurApp",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2018-11-27T01:31:13.384Z",
|
"updated": "2019-12-03T19:24:21.611Z",
|
||||||
"reasonDetail": "An odd technique of unescaping content by putting it into the dom"
|
"reasonDetail": "HTML is escaped"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "DOM-innerHTML",
|
||||||
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
|
"line": " a.innerHTML = goog.html.SafeHtml.unwrapTrustedHTML(b);",
|
||||||
|
"lineNumber": 3457,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2019-12-03T19:24:21.611Z",
|
||||||
|
"reasonDetail": "HTML is escaped"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "DOM-outerHTML",
|
||||||
|
"path": "node_modules/google-libphonenumber/dist/libphonenumber.js",
|
||||||
|
"line": " a.outerHTML = goog.html.SafeHtml.unwrapTrustedHTML(b);",
|
||||||
|
"lineNumber": 3469,
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2019-12-03T19:24:21.611Z",
|
||||||
|
"reasonDetail": "HTML is escaped"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
|
@ -7560,7 +7577,7 @@
|
||||||
"rule": "React-createRef",
|
"rule": "React-createRef",
|
||||||
"path": "ts/components/MainHeader.js",
|
"path": "ts/components/MainHeader.js",
|
||||||
"line": " this.inputRef = react_1.default.createRef();",
|
"line": " this.inputRef = react_1.default.createRef();",
|
||||||
"lineNumber": 134,
|
"lineNumber": 142,
|
||||||
"reasonCategory": "usageTrusted",
|
"reasonCategory": "usageTrusted",
|
||||||
"updated": "2019-08-09T21:17:57.798Z",
|
"updated": "2019-08-09T21:17:57.798Z",
|
||||||
"reasonDetail": "Used only to set focus"
|
"reasonDetail": "Used only to set focus"
|
||||||
|
|
|
@ -4242,10 +4242,10 @@ glogg@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
sparkles "^1.0.0"
|
sparkles "^1.0.0"
|
||||||
|
|
||||||
google-libphonenumber@3.2.2:
|
google-libphonenumber@3.2.6:
|
||||||
version "3.2.2"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.2.tgz#3d9d7ba727e99a50812f21b0ed313723b76c5c54"
|
resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.6.tgz#3d725b48ff44706b80246e77f95f2c2fdc6fd729"
|
||||||
integrity sha512-ubjGeosYPeusjYbUHy76lCniGTTI0k1rIFc+uKBX+jHQLDmWOSUtlFUxaeoLJ+Y+PAMM6dWp+C1HjHx5BI8kEw==
|
integrity sha512-6QCQAaKJlSd/1dUqvdQf7zzfb3uiZHsG8yhCfOdCVRfMuPZ/VDIEB47y5SYwjPQJPs7ebfW5jj6PeobB9JJ4JA==
|
||||||
|
|
||||||
got@8.2.0:
|
got@8.2.0:
|
||||||
version "8.2.0"
|
version "8.2.0"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue