Browse Source

Restrict forwarding outgoing message

Verify attach menu buttons availability
Fix peer typing flickering
Colored replies
master
Eduard Kuzmenko 2 years ago
parent
commit
02f0c9db0f
  1. 24
      src/components/chat/contextMenu.ts
  2. 33
      src/components/chat/input.ts
  3. 2
      src/components/chat/selection.ts
  4. 2
      src/components/chat/sendAs.ts
  5. 2
      src/components/poll.ts
  6. 8
      src/components/wrappers/reply.ts
  7. 5
      src/index.ts
  8. 28
      src/lib/appManagers/appImManager.ts
  9. 1
      src/lib/appManagers/appManagersManager.ts
  10. 6
      src/lib/idb.ts
  11. 5
      src/lib/mtproto/mtproto.worker.ts
  12. 1
      src/lib/mtproto/mtprotoMessagePort.ts
  13. 52
      src/lib/mtproto/mtprotoworker.ts
  14. 4
      src/lib/richTextProcessor/wrapRichText.ts
  15. 5
      src/scss/partials/_avatar.scss
  16. 2
      src/scss/partials/_button.scss
  17. 14
      src/scss/partials/_chatBubble.scss
  18. 5
      src/scss/partials/_scrollable.scss

24
src/components/chat/contextMenu.ts

@ -38,6 +38,7 @@ import contextMenuController from "../../helpers/contextMenuController";
import { attachContextMenuListener } from "../../helpers/dom/attachContextMenuListener"; import { attachContextMenuListener } from "../../helpers/dom/attachContextMenuListener";
import filterAsync from "../../helpers/array/filterAsync"; import filterAsync from "../../helpers/array/filterAsync";
import appDownloadManager from "../../lib/appManagers/appDownloadManager"; import appDownloadManager from "../../lib/appManagers/appDownloadManager";
import { SERVICE_PEER_ID } from "../../lib/mtproto/mtproto_config";
export default class ChatContextMenu { export default class ChatContextMenu {
private buttons: (ButtonMenuItemOptions & {verify: () => boolean | Promise<boolean>, notDirect?: () => boolean, withSelection?: true, isSponsored?: true})[]; private buttons: (ButtonMenuItemOptions & {verify: () => boolean | Promise<boolean>, notDirect?: () => boolean, withSelection?: true, isSponsored?: true})[];
@ -178,6 +179,10 @@ export default class ChatContextMenu {
this.canOpenReactedList = undefined; this.canOpenReactedList = undefined;
const initResult = await this.init(); const initResult = await this.init();
if(!initResult) {
return;
}
element = initResult.element; element = initResult.element;
const {cleanup, destroy, menuPadding, reactionsMenu, reactionsMenuPosition} = initResult; const {cleanup, destroy, menuPadding, reactionsMenu, reactionsMenuPosition} = initResult;
let isReactionsMenuVisible = false; let isReactionsMenuVisible = false;
@ -214,7 +219,7 @@ export default class ChatContextMenu {
if(isReactionsMenuVisible) void reactionsMenu.container.offsetLeft; // reflow if(isReactionsMenuVisible) void reactionsMenu.container.offsetLeft; // reflow
} }
openBtnMenu(element, () => { contextMenuController.openBtnMenu(element, () => {
if(reactionsMenu) { if(reactionsMenu) {
reactionsMenu.container.classList.remove('is-visible'); reactionsMenu.container.classList.remove('is-visible');
} }
@ -224,6 +229,7 @@ export default class ChatContextMenu {
this.target = null; this.target = null;
this.viewerPeerId = undefined; this.viewerPeerId = undefined;
this.canOpenReactedList = undefined; this.canOpenReactedList = undefined;
cleanup();
setTimeout(() => { setTimeout(() => {
destroy(); destroy();
@ -235,6 +241,9 @@ export default class ChatContextMenu {
} }
}; };
r();
};
public cleanup() { public cleanup() {
this.listenerSetter.removeAll(); this.listenerSetter.removeAll();
this.reactionsMenu && this.reactionsMenu.cleanup(); this.reactionsMenu && this.reactionsMenu.cleanup();
@ -425,7 +434,7 @@ export default class ChatContextMenu {
icon: 'forward', icon: 'forward',
text: 'Forward', text: 'Forward',
onClick: this.onForwardClick, // let forward the message if it's outgoing but not ours (like a changelog) onClick: this.onForwardClick, // let forward the message if it's outgoing but not ours (like a changelog)
verify: () => !this.noForwards && this.chat.type !== 'scheduled' && (!this.message.pFlags.is_outgoing || !this.message.pFlags.out) && this.message._ !== 'messageService' verify: () => !this.noForwards && this.chat.type !== 'scheduled' && (!this.message.pFlags.is_outgoing || this.message.fromId === SERVICE_PEER_ID) && this.message._ !== 'messageService'
}, { }, {
icon: 'forward', icon: 'forward',
text: 'Message.Context.Selection.Forward', text: 'Message.Context.Selection.Forward',
@ -500,6 +509,10 @@ export default class ChatContextMenu {
this.setButtons(); this.setButtons();
const filteredButtons = await this.filterButtons(this.buttons); const filteredButtons = await this.filterButtons(this.buttons);
if(!filteredButtons.length) {
return;
}
const element = this.element = ButtonMenu(filteredButtons, this.listenerSetter); const element = this.element = ButtonMenu(filteredButtons, this.listenerSetter);
element.id = 'bubble-contextmenu'; element.id = 'bubble-contextmenu';
element.classList.add('contextmenu'); element.classList.add('contextmenu');
@ -540,8 +553,9 @@ export default class ChatContextMenu {
fakeText.classList.add('btn-menu-item-text-fake'); fakeText.classList.add('btn-menu-item-text-fake');
viewsButton.element.append(fakeText); viewsButton.element.append(fakeText);
const AVATAR_SIZE = 22;
const MAX_AVATARS = 3; const MAX_AVATARS = 3;
const PADDING_PER_AVATAR = .875; const PADDING_PER_AVATAR = 1.125;
i18nElem.element.style.visibility = 'hidden'; i18nElem.element.style.visibility = 'hidden';
i18nElem.element.style.paddingRight = isViewingReactions ? PADDING_PER_AVATAR * Math.min(MAX_AVATARS, recentReactions.length) + 'rem' : '1rem'; i18nElem.element.style.paddingRight = isViewingReactions ? PADDING_PER_AVATAR * Math.min(MAX_AVATARS, recentReactions.length) + 'rem' : '1rem';
const middleware = this.middleware.get(); const middleware = this.middleware.get();
@ -595,7 +609,7 @@ export default class ChatContextMenu {
} }
if(reactions.length) { if(reactions.length) {
const avatars = new StackedAvatars({avatarSize: 24}); const avatars = new StackedAvatars({avatarSize: AVATAR_SIZE});
avatars.render(recentReactions ? recentReactions.map((r) => getPeerId(r.peer_id)) : reactions.map((reaction) => reaction.peerId)); avatars.render(recentReactions ? recentReactions.map((r) => getPeerId(r.peer_id)) : reactions.map((reaction) => reaction.peerId));
viewsButton.element.append(avatars.container); viewsButton.element.append(avatars.container);
@ -645,7 +659,7 @@ export default class ChatContextMenu {
}, },
destroy: () => { destroy: () => {
element.remove(); element.remove();
reactionsMenu.widthContainer.remove(); reactionsMenu && reactionsMenu.widthContainer.remove();
}, },
menuPadding, menuPadding,
reactionsMenu, reactionsMenu,

33
src/components/chat/input.ts

@ -92,6 +92,7 @@ import contextMenuController from "../../helpers/contextMenuController";
import { emojiFromCodePoints } from "../../vendor/emoji"; import { emojiFromCodePoints } from "../../vendor/emoji";
import { modifyAckedPromise } from "../../helpers/modifyAckedResult"; import { modifyAckedPromise } from "../../helpers/modifyAckedResult";
import ChatSendAs from "./sendAs"; import ChatSendAs from "./sendAs";
import filterAsync from "../../helpers/array/filterAsync";
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -1211,7 +1212,17 @@ export default class ChatInput {
const previousSendAs = this.sendAs; const previousSendAs = this.sendAs;
const sendAs = this.createSendAs(); const sendAs = this.createSendAs();
const [isBroadcast, canPinMessage, isBot, canSend, neededFakeContainer, ackedPeerFull, ackedScheduledMids, setSendAsCallback] = await Promise.all([ const [
isBroadcast,
canPinMessage,
isBot,
canSend,
neededFakeContainer,
ackedPeerFull,
ackedScheduledMids,
setSendAsCallback,
filteredAttachMenuButtons
] = await Promise.all([
this.managers.appPeersManager.isBroadcast(peerId), this.managers.appPeersManager.isBroadcast(peerId),
this.managers.appPeersManager.canPinMessage(peerId), this.managers.appPeersManager.canPinMessage(peerId),
this.managers.appPeersManager.isBot(peerId), this.managers.appPeersManager.isBot(peerId),
@ -1219,7 +1230,8 @@ export default class ChatInput {
this.getNeededFakeContainer(), this.getNeededFakeContainer(),
modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)), modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)),
btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined, btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined,
sendAs ? (sendAs.setPeerId(this.chat.peerId), sendAs.updateManual(true)) : undefined sendAs ? (sendAs.setPeerId(this.chat.peerId), sendAs.updateManual(true)) : undefined,
this.filterAttachMenuButtons()
]); ]);
const placeholderKey = this.messageInput ? await this.getPlaceholderKey() : undefined; const placeholderKey = this.messageInput ? await this.getPlaceholderKey() : undefined;
@ -1291,7 +1303,7 @@ export default class ChatInput {
} }
if(this.messageInput) { if(this.messageInput) {
this.updateMessageInput(canSend, placeholderKey); this.updateMessageInput(canSend, placeholderKey, filteredAttachMenuButtons);
} else if(this.pinnedControlBtn) { } else if(this.pinnedControlBtn) {
this.pinnedControlBtn.append(i18n(canPinMessage ? 'Chat.Input.UnpinAll' : 'Chat.Pinned.DontShow')); this.pinnedControlBtn.append(i18n(canPinMessage ? 'Chat.Input.UnpinAll' : 'Chat.Pinned.DontShow'));
} }
@ -1373,7 +1385,14 @@ export default class ChatInput {
i.compareAndUpdate({key}); i.compareAndUpdate({key});
} }
public updateMessageInput(canSend: boolean, placeholderKey: LangPackKey) { private filterAttachMenuButtons() {
const {peerId, threadId} = this.chat;
return filterAsync(this.attachMenuButtons, (button) => {
return button.verify(peerId, threadId);
});
}
public updateMessageInput(canSend: boolean, placeholderKey: LangPackKey, visible: ChatInput['attachMenuButtons']) {
const {chatInput, attachMenu, messageInput} = this; const {chatInput, attachMenu, messageInput} = this;
const {peerId, threadId} = this.chat; const {peerId, threadId} = this.chat;
const isHidden = chatInput.classList.contains('is-hidden'); const isHidden = chatInput.classList.contains('is-hidden');
@ -1387,10 +1406,8 @@ export default class ChatInput {
this.updateMessageInputPlaceholder(placeholderKey); this.updateMessageInputPlaceholder(placeholderKey);
const visible = this.attachMenuButtons.filter((button) => { this.attachMenuButtons.forEach((button) => {
const good = button.verify(peerId, threadId); button.element.classList.toggle('hide', !visible.includes(button));
button.element.classList.toggle('hide', !good);
return good;
}); });
if(!canSend) { if(!canSend) {

2
src/components/chat/selection.ts

@ -333,6 +333,7 @@ class AppSelection extends EventListenerBase<{
this.appendCheckbox(element, checkboxField); this.appendCheckbox(element, checkboxField);
} else if(hasCheckbox) { } else if(hasCheckbox) {
this.getCheckboxInputFromElement(element).parentElement.remove(); this.getCheckboxInputFromElement(element).parentElement.remove();
SetTransition(element, 'is-selected', false, 200);
} }
return true; return true;
@ -976,6 +977,7 @@ export default class ChatSelection extends AppSelection {
}; };
protected onCancelSelection = async() => { protected onCancelSelection = async() => {
return;
const promises: Promise<HTMLElement>[] = []; const promises: Promise<HTMLElement>[] = [];
for(const [peerId, mids] of this.selectedMids) { for(const [peerId, mids] of this.selectedMids) {
for(const mid of mids) { for(const mid of mids) {

2
src/components/chat/sendAs.ts

@ -141,7 +141,7 @@ export default class ChatSendAs {
buttons.forEach((button, idx) => { buttons.forEach((button, idx) => {
const peerId = peerIds[idx]; const peerId = peerIds[idx];
const avatar = new AvatarElement(); const avatar = new AvatarElement();
avatar.classList.add('avatar-32', 'btn-menu-item-icon'); avatar.classList.add('avatar-26', 'btn-menu-item-icon');
avatar.updateWithOptions({peerId}); avatar.updateWithOptions({peerId});
if(!idx) { if(!idx) {

2
src/components/poll.ts

@ -294,7 +294,7 @@ export default class PollElement extends HTMLElement {
setInnerHTML(this.firstElementChild, wrapEmojiText(poll.question)); setInnerHTML(this.firstElementChild, wrapEmojiText(poll.question));
Array.from(this.querySelectorAll('.poll-answer-text')).forEach((el, idx) => { Array.from(this.querySelectorAll('.poll-answer-text')).forEach((el, idx) => {
setInnerHTML(el, RichTextProcessor.wrapEmojiText(poll.answers[idx].text)); setInnerHTML(el, wrapEmojiText(poll.answers[idx].text));
}); });
this.descDiv = this.firstElementChild.nextElementSibling as HTMLElement; this.descDiv = this.firstElementChild.nextElementSibling as HTMLElement;

8
src/components/wrappers/reply.ts

@ -4,6 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { hexToRgb } from "../../helpers/color";
import { Message } from "../../layer"; import { Message } from "../../layer";
import getPeerColorById from "../../lib/appManagers/utils/peers/getPeerColorById"; import getPeerColorById from "../../lib/appManagers/utils/peers/getPeerColorById";
import ReplyContainer from "../chat/replyContainer"; import ReplyContainer from "../chat/replyContainer";
@ -19,8 +20,11 @@ export default function wrapReply(
if(setColorPeerId) { if(setColorPeerId) {
const hex = getPeerColorById(setColorPeerId, false); const hex = getPeerColorById(setColorPeerId, false);
replyContainer.border.style.backgroundColor = hex; const [r, g, b] = hexToRgb(hex);
replyContainer.title.style.color = hex; replyContainer.container.style.setProperty('--override-color', `${r}, ${g}, ${b}`);
replyContainer.container.classList.add('is-overriding-color');
// replyContainer.border.style.backgroundColor = hex;
// replyContainer.title.style.color = hex;
} }
return {container: replyContainer.container, fillPromise}; return {container: replyContainer.container, fillPromise};

5
src/index.ts

@ -22,7 +22,6 @@ import I18n from './lib/langPack';
import './helpers/peerIdPolyfill'; import './helpers/peerIdPolyfill';
import './lib/polyfill'; import './lib/polyfill';
import apiManagerProxy from './lib/mtproto/mtprotoworker'; import apiManagerProxy from './lib/mtproto/mtprotoworker';
import loadState from './lib/appManagers/utils/state/loadState';
import getProxiedManagers from './lib/appManagers/getProxiedManagers'; import getProxiedManagers from './lib/appManagers/getProxiedManagers';
import themeController from './helpers/themeController'; import themeController from './helpers/themeController';
import overlayCounter from './helpers/overlayCounter'; import overlayCounter from './helpers/overlayCounter';
@ -32,7 +31,6 @@ document.addEventListener('DOMContentLoaded', async() => {
toggleAttributePolyfill(); toggleAttributePolyfill();
rootScope.managers = getProxiedManagers(); rootScope.managers = getProxiedManagers();
apiManagerProxy;
singleInstance.start(); singleInstance.start();
@ -193,7 +191,8 @@ document.addEventListener('DOMContentLoaded', async() => {
const langPromise = I18n.getCacheLangPack(); const langPromise = I18n.getCacheLangPack();
const [stateResult, langPack] = await Promise.all([ const [stateResult, langPack] = await Promise.all([
loadState(), // loadState(),
apiManagerProxy.sendState().then(([stateResult]) => stateResult),
langPromise langPromise
]); ]);
I18n.setTimeFormat(stateResult.state.settings.timeFormat); I18n.setTimeFormat(stateResult.state.settings.timeFormat);

28
src/lib/appManagers/appImManager.ts

@ -1842,6 +1842,19 @@ export class AppImManager extends EventListenerBase<{
return; return;
} }
let peerTitlePromise: Promise<any>;
let args: any[];
if(peerId.isAnyChat()) {
const peerTitle = new PeerTitle();
peerTitlePromise = peerTitle.update({peerId: typing.userId.toPeerId(false), onlyFirstName: true});
args = [
peerTitle.element,
typings.length - 1
];
await peerTitlePromise;
}
if(!container) { if(!container) {
container = document.createElement('span'); container = document.createElement('span');
container.classList.add('online', 'peer-typing-container'); container.classList.add('online', 'peer-typing-container');
@ -1859,17 +1872,6 @@ export class AppImManager extends EventListenerBase<{
} }
} }
let peerTitlePromise: Promise<any>;
let args: any[];
if(peerId.isAnyChat()) {
const peerTitle = new PeerTitle();
peerTitlePromise = peerTitle.update({peerId: typing.userId.toPeerId(false), onlyFirstName: true});
args = [
peerTitle.element,
typings.length - 1
];
}
if(action._ === 'sendMessageEmojiInteractionSeen') { if(action._ === 'sendMessageEmojiInteractionSeen') {
if(args) { if(args) {
args.pop(); args.pop();
@ -1887,10 +1889,6 @@ export class AppImManager extends EventListenerBase<{
if(container.childElementCount > 1) container.lastElementChild.replaceWith(descriptionElement); if(container.childElementCount > 1) container.lastElementChild.replaceWith(descriptionElement);
else container.append(descriptionElement); else container.append(descriptionElement);
if(peerTitlePromise) {
await peerTitlePromise;
}
// log('returning typing'); // log('returning typing');
return container; return container;
} }

1
src/lib/appManagers/appManagersManager.ts

@ -54,6 +54,7 @@ export class AppManagersManager {
const appStoragesManager = new AppStoragesManager(); const appStoragesManager = new AppStoragesManager();
await Promise.all([ await Promise.all([
// new Promise(() => {}),
appStoragesManager.loadStorages(), appStoragesManager.loadStorages(),
this.cryptoPortPromise this.cryptoPortPromise
]); ]);

6
src/lib/idb.ts

@ -369,6 +369,10 @@ export default class IDBStorage<T extends Database<any>, StoreName extends strin
entryName = [].concat(entryName); entryName = [].concat(entryName);
} }
if(!entryName.length) {
return Promise.resolve([]) as any;
}
return this.getObjectStore<T>('readonly', (objectStore) => { return this.getObjectStore<T>('readonly', (objectStore) => {
return (entryName as string[]).map((entryName) => objectStore.get(entryName)); return (entryName as string[]).map((entryName) => objectStore.get(entryName));
}, DEBUG ? 'get: ' + entryName.join(', ') : '', storeName); }, DEBUG ? 'get: ' + entryName.join(', ') : '', storeName);
@ -421,7 +425,7 @@ export default class IDBStorage<T extends Database<any>, StoreName extends strin
// transaction.oncomplete = () => onComplete('transaction'); // transaction.oncomplete = () => onComplete('transaction');
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
this.log.error('transaction not finished', transaction); this.log.error('transaction not finished', transaction, log);
}, 10000); }, 10000);
/* transaction.addEventListener('abort', (e) => { /* transaction.addEventListener('abort', (e) => {

5
src/lib/mtproto/mtproto.worker.ts

@ -31,11 +31,6 @@ port.addMultipleEventsListeners({
transportController.waitForWebSocket(); transportController.waitForWebSocket();
}, },
// windowSize: ({width, height}) => {
// windowSize.width = width;
// windowSize.height = height;
// },
crypto: ({method, args}) => { crypto: ({method, args}) => {
return cryptoWorker.invokeCrypto(method as any, ...args as any); return cryptoWorker.invokeCrypto(method as any, ...args as any);
}, },

1
src/lib/mtproto/mtprotoMessagePort.ts

@ -21,7 +21,6 @@ type MTProtoBroadcastEvent = {
export default class MTProtoMessagePort<Master extends boolean = true> extends SuperMessagePort<{ export default class MTProtoMessagePort<Master extends boolean = true> extends SuperMessagePort<{
environment: (environment: ReturnType<typeof getEnvironment>) => void, environment: (environment: ReturnType<typeof getEnvironment>) => void,
// windowSize: (payload: {width: number, height: number}) => void,
crypto: (payload: {method: string, args: any[]}) => Promise<any>, crypto: (payload: {method: string, args: any[]}) => Promise<any>,
state: (payload: {userId: UserId} & Awaited<ReturnType<typeof loadState>> & {storagesResults?: StoragesResults}) => void, state: (payload: {userId: UserId} & Awaited<ReturnType<typeof loadState>> & {storagesResults?: StoragesResults}) => void,
manager: (payload: MTProtoManagerTaskPayload) => any, manager: (payload: MTProtoManagerTaskPayload) => any,

52
src/lib/mtproto/mtprotoworker.ts

@ -172,10 +172,7 @@ class ApiManagerProxy extends MTProtoMessagePort {
this.log('Passing environment:', ENVIRONMENT); this.log('Passing environment:', ENVIRONMENT);
this.invoke('environment', ENVIRONMENT); this.invoke('environment', ENVIRONMENT);
this.sendState(); // this.sendState();
// setTimeout(() => {
// this.getConfig();
// }, 5000);
} }
private registerServiceWorker() { private registerServiceWorker() {
@ -318,61 +315,38 @@ class ApiManagerProxy extends MTProtoMessagePort {
} }
} }
// protected s() {
// const originalPostMessage = this.postMessage;
// const postQueue: any[] = [];
// this.postMessage = (source, task) => {
// if(task.type === 'invoke' && task.payload.type === 'state') {
// this.postMessage = originalPostMessage;
// postQueue.unshift(task);
// postQueue.forEach((task) => this.postMessage(undefined, task));
// postQueue.length = 0;
// } else {
// postQueue.push(task);
// }
// };
// }
private onWorkerFirstMessage(worker: any) { private onWorkerFirstMessage(worker: any) {
this.log('set webWorker'); this.log('set webWorker');
// return;
this.worker = worker; this.worker = worker;
/// #if MTPROTO_SW /// #if MTPROTO_SW
this.attachSendPort(worker); this.attachSendPort(worker);
/// #else /// #else
this.attachWorkerToPort(worker, this, 'mtproto'); this.attachWorkerToPort(worker, this, 'mtproto');
// this.s();
// port.addEventListener('message', () => {
// this.registerServiceWorker();
// }, {once: true});
/// #endif /// #endif
// this.startSendingWindowSize();
} }
public addServiceWorkerTaskListener(name: keyof ApiManagerProxy['taskListenersSW'], callback: ApiManagerProxy['taskListenersSW'][typeof name]) { public addServiceWorkerTaskListener(name: keyof ApiManagerProxy['taskListenersSW'], callback: ApiManagerProxy['taskListenersSW'][typeof name]) {
this.taskListenersSW[name] = callback; this.taskListenersSW[name] = callback;
} }
// private startSendingWindowSize() { private loadState() {
// const sendWindowSize = () => {
// this.invoke('windowSize', {width: windowSize.width, height: windowSize.height});
// };
// mediaSizes.addEventListener('resize', sendWindowSize);
// sendWindowSize();
// }
private sendState() {
return Promise.all([ return Promise.all([
loadState(), loadState().then((stateResult) => {
// loadStorages(createStorages()),
]).then(([stateResult/* , storagesResults */]) => {
this.newVersion = stateResult.newVersion; this.newVersion = stateResult.newVersion;
this.oldVersion = stateResult.oldVersion; this.oldVersion = stateResult.oldVersion;
this.mirrors['state'] = stateResult.state; this.mirrors['state'] = stateResult.state;
return stateResult;
}),
// loadStorages(createStorages()),
]);
}
public sendState() {
return this.loadState().then((result) => {
const [stateResult] = result;
this.invoke('state', {...stateResult, userId: rootScope.myId.toUserId()}); this.invoke('state', {...stateResult, userId: rootScope.myId.toUserId()});
return result;
}); });
} }

4
src/lib/richTextProcessor/wrapRichText.ts

@ -304,8 +304,8 @@ export default function wrapRichText(text: string, options: Partial<{
} }
const href = (currentContext || typeof electronHelpers === 'undefined') const href = (currentContext || typeof electronHelpers === 'undefined')
? encodeEntities(url) ? url
: `javascript:electronHelpers.openExternal('${encodeEntities(url)}');`; : `javascript:electronHelpers.openExternal('${url}');`;
element = document.createElement('a'); element = document.createElement('a');
element.className = 'anchor-url'; element.className = 'anchor-url';

5
src/scss/partials/_avatar.scss

@ -219,6 +219,11 @@ avatar-element {
--multiplier: 2.25; --multiplier: 2.25;
} }
&.avatar-22 {
--size: 22px;
--multiplier: 2.454545;
}
&.avatar-18 { &.avatar-18 {
--size: 18px; --size: 18px;
--multiplier: 3; --multiplier: 3;

2
src/scss/partials/_button.scss

@ -296,7 +296,7 @@
.stacked-avatars { .stacked-avatars {
--margin-right: -.6875rem; --margin-right: -.6875rem;
flex: 0 0 auto; flex: 0 0 auto;
right: 1rem; right: .5rem;
// margin-right: -1.5rem; // margin-right: -1.5rem;
// margin-left: 1rem; // margin-left: 1rem;
position: absolute; position: absolute;

14
src/scss/partials/_chatBubble.scss

@ -2359,6 +2359,20 @@ $bubble-beside-button-width: 38px;
code { code {
color: var(--monospace-text-color); color: var(--monospace-text-color);
} }
.reply.is-overriding-color {
.reply-border {
background-color: rgb(var(--override-color));
}
.reply-title {
color: rgb(var(--override-color));
}
@include hover() {
background-color: rgba(var(--override-color), #{$hover-alpha});
}
}
} }
.bubble.is-out { .bubble.is-out {

5
src/scss/partials/_scrollable.scss

@ -94,10 +94,13 @@ html:not(.is-safari):not(.is-ios) {
overflow-y: overlay; overflow-y: overlay;
scrollbar-width: thin; // Firefox only scrollbar-width: thin; // Firefox only
scrollbar-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0); scrollbar-color: rgba(0, 0, 0, 0) rgba(0, 0, 0, 0);
transition: scrollbar-color .3s ease;
-ms-overflow-style: none; -ms-overflow-style: none;
transform: translateZ(0); transform: translateZ(0);
// html.is-firefox & {
// transition: scrollbar-color .3s ease;
// }
/* html.is-safari & { /* html.is-safari & {
overflow-y: scroll; overflow-y: scroll;
} */ } */

Loading…
Cancel
Save