Don't create preview icon for links with no image (quotes)
This commit is contained in:
parent
35f682f4dc
commit
d4b74db05c
4 changed files with 143 additions and 14 deletions
|
@ -80,6 +80,7 @@ import {
|
|||
take,
|
||||
repeat,
|
||||
zipObject,
|
||||
collect,
|
||||
} from '../util/iterables';
|
||||
import * as universalExpireTimer from '../util/universalExpireTimer';
|
||||
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
|
||||
|
@ -3640,22 +3641,11 @@ export class ConversationModel extends window.Backbone
|
|||
}
|
||||
|
||||
if (preview && preview.length) {
|
||||
const previewsToUse = take(preview, 1);
|
||||
const previewImages = collect(preview, prev => prev.image);
|
||||
const previewImagesToUse = take(previewImages, 1);
|
||||
|
||||
return Promise.all(
|
||||
map(previewsToUse, async attachment => {
|
||||
const { image } = attachment;
|
||||
|
||||
if (!image) {
|
||||
return {
|
||||
contentType: IMAGE_JPEG,
|
||||
// Our protos library complains about these fields being undefined, so we
|
||||
// force them to null
|
||||
fileName: null,
|
||||
thumbnail: null,
|
||||
};
|
||||
}
|
||||
|
||||
map(previewImagesToUse, async image => {
|
||||
const { contentType } = image;
|
||||
|
||||
return {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { assert } from 'chai';
|
|||
import * as sinon from 'sinon';
|
||||
|
||||
import {
|
||||
collect,
|
||||
concat,
|
||||
every,
|
||||
filter,
|
||||
|
@ -251,6 +252,52 @@ describe('iterable utilities', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('collect', () => {
|
||||
it('returns an empty iterable when passed an empty iterable', () => {
|
||||
const fn = sinon.fake();
|
||||
|
||||
assert.deepEqual([...collect([], fn)], []);
|
||||
assert.deepEqual([...collect(new Set(), fn)], []);
|
||||
assert.deepEqual([...collect(new Map(), fn)], []);
|
||||
|
||||
sinon.assert.notCalled(fn);
|
||||
});
|
||||
|
||||
it('returns a new iterator with some values removed', () => {
|
||||
const getB = sinon.fake((v: { a: string; b?: number }) => v.b);
|
||||
const result = collect(
|
||||
[{ a: 'n' }, { a: 'm', b: 0 }, { a: 'o' }, { a: 'p', b: 1 }],
|
||||
getB
|
||||
);
|
||||
|
||||
sinon.assert.notCalled(getB);
|
||||
|
||||
assert.deepEqual([...result], [0, 1]);
|
||||
assert.notInstanceOf(result, Array);
|
||||
|
||||
sinon.assert.callCount(getB, 4);
|
||||
});
|
||||
|
||||
it('can collect an infinite iterable', () => {
|
||||
const everyNumber = {
|
||||
*[Symbol.iterator]() {
|
||||
for (let i = 0; true; i += 1) {
|
||||
yield { a: 'x', ...(i % 2 ? { b: i } : {}) };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const getB = sinon.fake((v: { a: string; b?: number }) => v.b);
|
||||
const result = collect(everyNumber, getB);
|
||||
const iterator = result[Symbol.iterator]();
|
||||
|
||||
assert.deepEqual(iterator.next(), { value: 1, done: false });
|
||||
assert.deepEqual(iterator.next(), { value: 3, done: false });
|
||||
assert.deepEqual(iterator.next(), { value: 5, done: false });
|
||||
assert.deepEqual(iterator.next(), { value: 7, done: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe('find', () => {
|
||||
const isOdd = (n: number) => Boolean(n % 2);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { assert } from 'chai';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
import { IMAGE_PNG } from '../../types/MIME';
|
||||
import { UUID } from '../../types/UUID';
|
||||
|
||||
describe('Conversations', () => {
|
||||
|
@ -104,4 +105,51 @@ describe('Conversations', () => {
|
|||
|
||||
assert.strictEqual(conversation.get('lastMessage'), '');
|
||||
});
|
||||
|
||||
it('only produces attachments on a quote with an image', async () => {
|
||||
// Creating a fake conversation
|
||||
const conversation = new window.Whisper.Conversation({
|
||||
avatars: [],
|
||||
id: UUID.generate().toString(),
|
||||
e164: '+15551234567',
|
||||
uuid: UUID.generate().toString(),
|
||||
type: 'private',
|
||||
inbox_position: 0,
|
||||
isPinned: false,
|
||||
markedUnread: false,
|
||||
lastMessageDeletedForEveryone: false,
|
||||
messageCount: 0,
|
||||
sentMessageCount: 0,
|
||||
profileSharing: true,
|
||||
version: 0,
|
||||
});
|
||||
|
||||
const resultNoImage = await conversation.getQuoteAttachment(
|
||||
[],
|
||||
[
|
||||
{
|
||||
url: 'https://sometest.signal.org/',
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert.deepEqual(resultNoImage, []);
|
||||
|
||||
const [resultWithImage] = await conversation.getQuoteAttachment(
|
||||
[],
|
||||
[
|
||||
{
|
||||
url: 'https://sometest.signal.org/',
|
||||
image: {
|
||||
contentType: IMAGE_PNG,
|
||||
size: 100,
|
||||
data: new Uint8Array(),
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
assert.equal(resultWithImage.contentType, 'image/png');
|
||||
assert.equal(resultWithImage.fileName, null);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -101,6 +101,50 @@ class FilterIterator<T> implements Iterator<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter and transform (map) that produces a new type
|
||||
* useful when traversing through fields that might be undefined
|
||||
*/
|
||||
export function collect<T, S>(
|
||||
iterable: Iterable<T>,
|
||||
fn: (value: T) => S | undefined
|
||||
): Iterable<S> {
|
||||
return new CollectIterable(iterable, fn);
|
||||
}
|
||||
|
||||
class CollectIterable<T, S> implements Iterable<S> {
|
||||
constructor(
|
||||
private readonly iterable: Iterable<T>,
|
||||
private readonly fn: (value: T) => S | undefined
|
||||
) {}
|
||||
|
||||
[Symbol.iterator](): Iterator<S> {
|
||||
return new CollectIterator(this.iterable[Symbol.iterator](), this.fn);
|
||||
}
|
||||
}
|
||||
|
||||
class CollectIterator<T, S> implements Iterator<S> {
|
||||
constructor(
|
||||
private readonly iterator: Iterator<T>,
|
||||
private readonly fn: (value: T) => S | undefined
|
||||
) {}
|
||||
|
||||
next(): IteratorResult<S> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const nextIteration = this.iterator.next();
|
||||
if (nextIteration.done) return nextIteration;
|
||||
const nextValue = this.fn(nextIteration.value);
|
||||
if (nextValue !== undefined) {
|
||||
return {
|
||||
done: false,
|
||||
value: nextValue,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function find<T>(
|
||||
iterable: Iterable<T>,
|
||||
predicate: (value: T) => unknown
|
||||
|
|
Loading…
Reference in a new issue