Browse Source

Prepare for chat multiselect:

Chat selection by context menu
Clear selection
Fix highlighting bubbles
Fix highlight first unread bubble
master
morethanwords 4 years ago
parent
commit
1909fe5c60
  1. 72
      src/components/chat/contextMenu.ts
  2. 106
      src/components/chat/selection.ts
  3. 9
      src/components/checkbox.ts
  4. 2
      src/components/wrappers.ts
  5. 2
      src/lib/appManagers/appDocsManager.ts
  6. 29
      src/lib/appManagers/appImManager.ts
  7. 10
      src/lib/appManagers/appMessagesManager.ts
  8. 2
      src/lib/appManagers/appPhotosManager.ts
  9. 64
      src/scss/partials/_chat.scss
  10. 96
      src/scss/partials/_chatBubble.scss
  11. 195
      src/scss/partials/_checkbox.scss
  12. 1
      src/scss/partials/_document.scss
  13. 36
      src/scss/partials/_leftSidebar.scss
  14. 171
      src/scss/style.scss

72
src/components/chat/contextMenu.ts

@ -11,8 +11,8 @@ import { PopupButton } from "../popup";
import PopupPeer from "../popupPeer"; import PopupPeer from "../popupPeer";
import appSidebarRight from "../sidebarRight"; import appSidebarRight from "../sidebarRight";
export class ChatContextMenu { export default class ChatContextMenu {
private buttons: (ButtonMenuItemOptions & {verify: (peerID: number, msgID: number, target: HTMLElement) => boolean})[]; private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean})[];
private element: HTMLElement; private element: HTMLElement;
private target: HTMLElement; private target: HTMLElement;
@ -26,22 +26,19 @@ export class ChatContextMenu {
this.init = null; this.init = null;
} }
let bubble: HTMLElement = null; let bubble: HTMLElement, bubbleContainer: HTMLElement;
try { try {
bubble = findUpClassName(e.target, 'bubble__container'); bubbleContainer = findUpClassName(e.target, 'bubble__container');
bubble = bubbleContainer ? bubbleContainer.parentElement : findUpClassName(e.target, 'bubble');
} catch(e) {} } catch(e) {}
if(!bubble) return;
if(e instanceof MouseEvent) e.preventDefault(); if(e instanceof MouseEvent) e.preventDefault();
if(this.element.classList.contains('active')) { if(this.element.classList.contains('active')) {
return false; return false;
} }
if(e instanceof MouseEvent) e.cancelBubble = true; if(e instanceof MouseEvent) e.cancelBubble = true;
bubble = bubble.parentElement as HTMLDivElement; // bc container
const msgID = +bubble.dataset.mid; const msgID = +bubble.dataset.mid;
if(!msgID) return; if(!msgID) return;
@ -57,7 +54,9 @@ export class ChatContextMenu {
} }
this.buttons.forEach(button => { this.buttons.forEach(button => {
const good = button.verify(this.peerID, this.msgID, this.target); const good = bubbleContainer ?
button.verify() :
button.notDirect && button.notDirect() && button.verify();
button.element.classList.toggle('hide', !good); button.element.classList.toggle('hide', !good);
}); });
@ -77,58 +76,75 @@ export class ChatContextMenu {
icon: 'reply', icon: 'reply',
text: 'Reply', text: 'Reply',
onClick: this.onReplyClick, onClick: this.onReplyClick,
verify: (peerID: number, msgID: number) => (peerID > 0 || appChatsManager.hasRights(-peerID, 'send')) && msgID > 0 verify: () => (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0
}, { }, {
icon: 'edit', icon: 'edit',
text: 'Edit', text: 'Edit',
onClick: this.onEditClick, onClick: this.onEditClick,
verify: (peerID: number, msgID: number) => appMessagesManager.canEditMessage(msgID, 'text') verify: () => appMessagesManager.canEditMessage(this.msgID, 'text')
}, { }, {
icon: 'copy', icon: 'copy',
text: 'Copy', text: 'Copy',
onClick: this.onCopyClick, onClick: this.onCopyClick,
verify: (peerID: number, msgID: number) => !!appMessagesManager.getMessage(msgID).message verify: () => !!appMessagesManager.getMessage(this.msgID).message
}, { }, {
icon: 'pin', icon: 'pin',
text: 'Pin', text: 'Pin',
onClick: this.onPinClick, onClick: this.onPinClick,
verify: (peerID: number, msgID: number) => { verify: () => {
const message = appMessagesManager.getMessage(msgID); const message = appMessagesManager.getMessage(this.msgID);
return msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))); return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID == $rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
} }
}, { }, {
icon: 'unpin', icon: 'unpin',
text: 'Unpin', text: 'Unpin',
onClick: this.onUnpinClick, onClick: this.onUnpinClick,
verify: (peerID: number, msgID: number) => appImManager.pinnedMsgID == msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) verify: () => appImManager.pinnedMsgID == this.msgID && (this.peerID == $rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')))
}, { }, {
icon: 'revote', icon: 'revote',
text: 'Revote', text: 'Revote',
onClick: this.onRetractVote, onClick: this.onRetractVote,
verify: (peerID: number, msgID) => { verify: () => {
const message = appMessagesManager.getMessage(msgID); const message = appMessagesManager.getMessage(this.msgID);
const poll = message.media?.poll as Poll; const poll = message.media?.poll as Poll;
return poll && poll.chosenIndexes.length && !poll.pFlags.closed && !poll.pFlags.quiz; return poll && poll.chosenIndexes.length && !poll.pFlags.closed && !poll.pFlags.quiz;
} }
}, { }, {
icon: 'lock', icon: 'stop',
text: 'Stop poll', text: 'Stop poll',
onClick: this.onStopPoll, onClick: this.onStopPoll,
verify: (peerID: number, msgID) => { verify: () => {
const message = appMessagesManager.getMessage(msgID); const message = appMessagesManager.getMessage(this.msgID);
const poll = message.media?.poll; const poll = message.media?.poll;
return appMessagesManager.canEditMessage(msgID, 'poll') && poll && !poll.pFlags.closed && msgID > 0; return appMessagesManager.canEditMessage(this.msgID, 'poll') && poll && !poll.pFlags.closed && this.msgID > 0;
} }
}, { }, {
icon: 'forward', icon: 'forward',
text: 'Forward', text: 'Forward',
onClick: this.onForwardClick, onClick: this.onForwardClick,
verify: (peerID: number, msgID: number) => msgID > 0 verify: () => this.msgID > 0
}, {
icon: 'revote',
text: 'Select',
onClick: this.onSelectClick,
verify: () => {
const message = appMessagesManager.getMessage(this.msgID);
return !message.action && !appImManager.chatSelection.selectedMids.has(this.msgID);
},
notDirect: () => true
}, {
icon: 'revote',
text: 'Clear selection',
onClick: this.onClearSelectionClick,
verify: () => {
return appImManager.chatSelection.selectedMids.has(this.msgID);
},
notDirect: () => appImManager.chatSelection.selectedMids.has(this.msgID)
}, { }, {
icon: 'delete danger', icon: 'delete danger',
text: 'Delete', text: 'Delete',
onClick: this.onDeleteClick, onClick: this.onDeleteClick,
verify: (peerID: number, msgID: number) => peerID > 0 || appMessagesManager.getMessage(msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-peerID, 'deleteRevoke') verify: () => this.peerID > 0 || appMessagesManager.getMessage(this.msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-this.peerID, 'deleteRevoke')
}]; }];
this.element = ButtonMenu(this.buttons); this.element = ButtonMenu(this.buttons);
@ -193,6 +209,14 @@ export class ChatContextMenu {
appSidebarRight.forwardTab.open([this.msgID]); appSidebarRight.forwardTab.open([this.msgID]);
}; };
private onSelectClick = () => {
appImManager.chatSelection.toggleByBubble(findUpClassName(this.target, 'bubble'));
};
private onClearSelectionClick = () => {
appImManager.chatSelection.cancelSelection();
};
private onDeleteClick = () => { private onDeleteClick = () => {
const peerID = $rootScope.selectedPeerID; const peerID = $rootScope.selectedPeerID;
const firstName = appPeersManager.getPeerTitle(peerID, false, true); const firstName = appPeersManager.getPeerTitle(peerID, false, true);

106
src/components/chat/selection.ts

@ -0,0 +1,106 @@
import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import CheckboxField from "../checkbox";
export default class ChatSelection {
public selectedMids: Set<number> = new Set();
public isSelecting = false;
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
}
public toggleBubbleCheckbox(bubble: HTMLElement, show: boolean) {
const hasCheckbox = !!this.getCheckboxInputFromBubble(bubble);
if(show) {
if(hasCheckbox) return;
const checkboxField = CheckboxField('', bubble.dataset.mid, true);
checkboxField.label.classList.add('bubble-select-checkbox');
// * if it is a render of new message
const mid = +bubble.dataset.mid;
if(this.selectedMids.has(mid)) {
checkboxField.input.checked = true;
bubble.classList.add('is-selected');
}
bubble.append(checkboxField.label);
} else if(hasCheckbox) {
bubble.lastElementChild.remove();
}
}
public getCheckboxInputFromBubble(bubble: HTMLElement) {
return bubble.lastElementChild.tagName == 'LABEL' && bubble.lastElementChild.firstElementChild as HTMLInputElement;
}
public toggleSelection() {
const wasSelecting = this.isSelecting;
this.isSelecting = this.selectedMids.size > 0;
if(wasSelecting == this.isSelecting) return;
this.appImManager.bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size);
for(const mid in this.appImManager.bubbles) {
const bubble = this.appImManager.bubbles[mid];
this.toggleBubbleCheckbox(bubble, this.isSelecting);
}
}
public cancelSelection() {
for(const mid of this.selectedMids) {
const bubble = this.appImManager.bubbles[mid];
if(bubble) {
this.toggleByBubble(bubble);
}
}
this.selectedMids.clear();
this.toggleSelection();
}
public cleanup() {
this.isSelecting = false;
this.selectedMids.clear();
this.appImManager.bubblesContainer.classList.remove('is-selecting');
}
public toggleByBubble(bubble: HTMLElement) {
const mid = +bubble.dataset.mid;
const mids = this.appMessagesManager.getMidsByMid(mid);
const found = mids.find(mid => this.selectedMids.has(mid));
if(found) {
mids.forEach(mid => this.selectedMids.delete(mid));
} else {
mids.forEach(mid => this.selectedMids.add(mid));
}
this.toggleBubbleCheckbox(bubble, true);
const input = this.getCheckboxInputFromBubble(bubble);
input.checked = !found;
this.toggleSelection();
if(found) {
bubble.classList.add('backwards');
bubble.dataset.timeout = '' + setTimeout(() => {
delete bubble.dataset.timeout;
bubble.classList.remove('backwards', 'is-selected');
}, 200);
} else {
bubble.classList.remove('backwards');
const timeout = bubble.dataset.timeout;
if(timeout !== undefined) {
clearTimeout(+timeout);
}
bubble.classList.add('is-selected');
}
}
public selectMessage(mid: number) {
}
}

9
src/components/checkbox.ts

@ -1,13 +1,16 @@
const CheckboxField = (text: string, name: string) => { const CheckboxField = (text: string, name: string, round = false) => {
const label = document.createElement('label'); const label = document.createElement('label');
label.classList.add('checkbox-field'); label.classList.add(round ? 'checkbox-field-round' : 'checkbox-field');
const input = document.createElement('input'); const input = document.createElement('input');
input.type = 'checkbox'; input.type = 'checkbox';
input.id = 'input-' + name; input.id = 'input-' + name;
const span = document.createElement('span'); const span = document.createElement('span');
span.innerText = text; span.classList.add('checkbox-caption');
if(text) {
span.innerText = text;
}
label.append(input, span); label.append(input, span);

2
src/components/wrappers.ts

@ -716,7 +716,7 @@ export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLo
const items: {size: PhotoSize.photoSize, media: any, message: any}[] = []; const items: {size: PhotoSize.photoSize, media: any, message: any}[] = [];
// !higher msgID will be the FIRST in album // !higher msgID will be the FIRST in album
const storage = Object.keys(appMessagesManager.groupedMessagesStorage[groupID]).map(id => +id).sort((a, b) => a - b); const storage = appMessagesManager.getMidsByAlbum(groupID);
for(const mid of storage) { for(const mid of storage) {
const m = appMessagesManager.getMessage(mid); const m = appMessagesManager.getMessage(mid);
const media = m.media.photo || m.media.document; const media = m.media.photo || m.media.document;

2
src/lib/appManagers/appDocsManager.ts

@ -238,6 +238,8 @@ class AppDocsManager {
if(!thumb.url) { if(!thumb.url) {
if('bytes' in thumb) { if('bytes' in thumb) {
// * exclude from state
defineNotNumerableProperties(thumb, ['url']);
thumb.url = appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker); thumb.url = appPhotosManager.getPreviewURLFromBytes(thumb.bytes, !!doc.sticker);
} else { } else {
//return this.getFileURL(doc, false, thumb); //return this.getFileURL(doc, false, thumb);

29
src/lib/appManagers/appImManager.ts

@ -5,12 +5,14 @@ import AudioElement from '../../components/audio';
import AvatarElement from '../../components/avatar'; import AvatarElement from '../../components/avatar';
import BubbleGroups from '../../components/bubbleGroups'; import BubbleGroups from '../../components/bubbleGroups';
import { ChatAudio } from '../../components/chat/audio'; import { ChatAudio } from '../../components/chat/audio';
import { ChatContextMenu } from '../../components/chat/contextMenu'; import ChatContextMenu from '../../components/chat/contextMenu';
import { ChatInput } from '../../components/chat/input'; import { ChatInput } from '../../components/chat/input';
import { MessageRender } from '../../components/chat/messageRender'; import { MessageRender } from '../../components/chat/messageRender';
import PinnedContainer from '../../components/chat/pinnedContainer'; import PinnedContainer from '../../components/chat/pinnedContainer';
import ReplyContainer from '../../components/chat/replyContainer'; import ReplyContainer from '../../components/chat/replyContainer';
import { ChatSearch } from '../../components/chat/search'; import { ChatSearch } from '../../components/chat/search';
import ChatSelection from '../../components/chat/selection';
import CheckboxField from '../../components/checkbox';
import { horizontalMenu } from '../../components/horizontalMenu'; import { horizontalMenu } from '../../components/horizontalMenu';
import LazyLoadQueue from '../../components/lazyLoadQueue'; import LazyLoadQueue from '../../components/lazyLoadQueue';
import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc'; import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc';
@ -74,6 +76,7 @@ export class AppImManager {
public chatInputC: ChatInput; public chatInputC: ChatInput;
public chatAudio: ChatAudio; public chatAudio: ChatAudio;
public chatSelection: ChatSelection;
private menuButtons: { private menuButtons: {
search: HTMLButtonElement search: HTMLButtonElement
@ -168,6 +171,8 @@ export class AppImManager {
parseMenuButtonsTo(this.menuButtons, this.columnEl.querySelector('.chat-more-button').firstElementChild.children); parseMenuButtonsTo(this.menuButtons, this.columnEl.querySelector('.chat-more-button').firstElementChild.children);
this.chatSelection = new ChatSelection(this, appMessagesManager/* this.bubblesContainer, this.bubbles */);
this.chatAudio = new ChatAudio(); this.chatAudio = new ChatAudio();
this.chatInfo.nextElementSibling.prepend(this.chatAudio.divAndCaption.container); this.chatInfo.nextElementSibling.prepend(this.chatAudio.divAndCaption.container);
@ -430,6 +435,14 @@ export class AppImManager {
return; return;
} }
// ! Trusted - due to audio autoclick
if(this.chatSelection.isSelecting && !bubble.classList.contains('service') && e.isTrusted) {
cancelEvent(e);
//console.log('bubble click', e);
this.chatSelection.toggleByBubble(bubble);
return;
}
let contactDiv: HTMLElement = findUpClassName(target, 'contact'); let contactDiv: HTMLElement = findUpClassName(target, 'contact');
if(contactDiv) { if(contactDiv) {
this.setPeer(+contactDiv.dataset.peerID); this.setPeer(+contactDiv.dataset.peerID);
@ -1261,6 +1274,8 @@ export class AppImManager {
let peerID = this.peerID; let peerID = this.peerID;
this.peerChanged = true; this.peerChanged = true;
this.chatSelection.cleanup();
this.avatarEl.setAttribute('peer', '' + this.peerID); this.avatarEl.setAttribute('peer', '' + this.peerID);
this.avatarEl.update(); this.avatarEl.update();
@ -1381,13 +1396,13 @@ export class AppImManager {
public highlightBubble(element: HTMLElement) { public highlightBubble(element: HTMLElement) {
if(element.dataset.timeout) { if(element.dataset.timeout) {
clearTimeout(+element.dataset.timeout); clearTimeout(+element.dataset.timeout);
element.classList.remove('is-selected'); element.classList.remove('is-highlighted');
void element.offsetWidth; // reflow void element.offsetWidth; // reflow
} }
element.classList.add('is-selected'); element.classList.add('is-highlighted');
element.dataset.timeout = '' + setTimeout(() => { element.dataset.timeout = '' + setTimeout(() => {
element.classList.remove('is-selected'); element.classList.remove('is-highlighted');
delete element.dataset.timeout; delete element.dataset.timeout;
}, 2000); }, 2000);
} }
@ -1575,7 +1590,7 @@ export class AppImManager {
bubble.appendChild(bubbleContainer); bubble.appendChild(bubbleContainer);
this.bubbles[+message.mid] = bubble; this.bubbles[+message.mid] = bubble;
} else { } else {
const save = ['is-selected']; const save = ['is-highlighted'];
const wasClassNames = bubble.className.split(' '); const wasClassNames = bubble.className.split(' ');
const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c))); const classNames = ['bubble'].concat(save.filter(c => wasClassNames.includes(c)));
bubble.className = classNames.join(' '); bubble.className = classNames.join(' ');
@ -1592,6 +1607,10 @@ export class AppImManager {
bubble.dataset.mid = message.mid; bubble.dataset.mid = message.mid;
if(this.chatSelection.isSelecting) {
this.chatSelection.toggleBubbleCheckbox(bubble, true);
}
if(message._ == 'messageService') { if(message._ == 'messageService') {
let action = message.action; let action = message.action;
let _ = action._; let _ = action._;

10
src/lib/appManagers/appMessagesManager.ts

@ -2195,6 +2195,16 @@ export class AppMessagesManager {
}); });
} }
public getMidsByAlbum(grouped_id: string) {
return Object.keys(this.groupedMessagesStorage[grouped_id]).map(id => +id).sort((a, b) => a - b);
}
public getMidsByMid(mid: number) {
const message = this.messagesStorage[mid];
if(message?.grouped_id) return this.getMidsByAlbum(message.grouped_id);
else return [mid];
}
public saveMessages(messages: any[], options: { public saveMessages(messages: any[], options: {
isEdited?: boolean isEdited?: boolean
} = {}) { } = {}) {

2
src/lib/appManagers/appPhotosManager.ts

@ -154,7 +154,7 @@ export class AppPhotosManager {
} }
public getPreviewURLFromThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) { public getPreviewURLFromThumb(thumb: PhotoSize.photoCachedSize | PhotoSize.photoStrippedSize, isSticker = false) {
return thumb.url ?? (thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker)); return thumb.url ?? (defineNotNumerableProperties(thumb, ['url']), thumb.url = this.getPreviewURLFromBytes(thumb.bytes, isSticker));
} }
public setAttachmentPreview(bytes: Uint8Array | number[], element: HTMLElement | SVGForeignObjectElement, isSticker = false, background = false) { public setAttachmentPreview(bytes: Uint8Array | number[], element: HTMLElement | SVGForeignObjectElement, isSticker = false, background = false) {

64
src/scss/partials/_chat.scss

@ -1,8 +1,12 @@
$btn-send-margin: .5625rem;
$chat-input-size: 3rem;
$chat-input-handhelds-size: 2.875rem;
#bubble-contextmenu > div { #bubble-contextmenu > div {
padding: 0 84px 0 16px; padding: 0 5.25 0 1rem;
@include respond-to(handhelds) { @include respond-to(handhelds) {
padding: 0 60px 0 16px; padding: 0 3.75rem 0 1rem;
} }
} }
@ -10,10 +14,10 @@
width: 100%; width: 100%;
background-color: #fff; background-color: #fff;
user-select: none; user-select: none;
box-shadow: 0px 1px 5px -1px rgba(0,0,0,0.21); box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21);
z-index: 1; z-index: 1;
min-height: 56px; min-height: 3.5rem;
max-height: 56px; max-height: 3.5rem;
// border-bottom: 1px solid #DADCE0; // border-bottom: 1px solid #DADCE0;
@include respond-to(handhelds) { @include respond-to(handhelds) {
@ -248,6 +252,7 @@
width: 100%; width: 100%;
padding: 0 .5rem; padding: 0 .5rem;
flex: 0 0 auto; flex: 0 0 auto;
position: relative;
@include respond-to(not-handhelds) { @include respond-to(not-handhelds) {
padding-left: 1rem; padding-left: 1rem;
@ -311,9 +316,19 @@
#btn-record-cancel { #btn-record-cancel {
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
transition: width .1s .1s, margin-right .1s .1s, visibility 0s .1s, opacity .1s 0s; transition: visibility 0s .1s, opacity .1s 0s;
padding: 0; padding: 0;
z-index: 3; z-index: 3;
position: absolute;
right: 0;
top: 0;
// here percents can be used since there are no other transforms
transform: translateX(calc(-100% + #{-1rem + -$btn-send-margin}));
@include respond-to(handhelds) {
transform: translateX(calc(-100% + #{-.5rem + -$btn-send-margin}));
}
} }
.btn-send-container { .btn-send-container {
@ -404,8 +419,7 @@
#btn-record-cancel { #btn-record-cancel {
opacity: 1; opacity: 1;
visibility: visible; visibility: visible;
margin-right: 9px; transition: visibility 0s .1s, opacity .1s .1s;
transition: width .1s, margin-right .1s, visibility 0s .1s, opacity .1s .1s;
} }
// unlock // unlock
@ -413,6 +427,14 @@
pointer-events: all; pointer-events: all;
} }
.input-message {
width: calc(100% - #{$chat-input-size * 2 + $btn-send-margin * 3 + $btn-send-margin / 2});
@include respond-to(handhelds) {
width: calc(100% - #{$chat-input-handhelds-size * 2 + $btn-send-margin * 2});
}
}
#attach-file { #attach-file {
display: none; display: none;
} }
@ -544,29 +566,30 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
width: calc(100% - 3.75rem); width: calc(100% - #{$chat-input-size + $btn-send-margin + $btn-send-margin / 2});
justify-content: center; justify-content: center;
background-color: #fff; background-color: #fff;
border-radius: 12px; border-radius: 12px;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, .07); box-shadow: 0 1px 2px 0 rgba(16, 35, 47, .07);
margin-right: 9px;
padding: 4.5px .5rem; padding: 4.5px .5rem;
/* padding: 3px .5rem 6px .5rem; */ /* padding: 3px .5rem 6px .5rem; */
min-height: 54px; min-height: $chat-input-size;
max-height: 30rem; max-height: 30rem;
caret-color: $button-primary-background; caret-color: $button-primary-background;
flex: 1; flex: 0 0 auto;
position: relative; position: relative;
z-index: 3; z-index: 3;
transition: width .1s;
@include respond-to(handhelds) { @include respond-to(handhelds) {
min-height: 46px; width: calc(100% - #{$chat-input-handhelds-size + $btn-send-margin});
min-height: $chat-input-handhelds-size;
padding: .5px .5rem; padding: .5px .5rem;
} }
@include respond-to(esg-bottom) { @include respond-to(esg-bottom) {
min-height: 46px; min-height: $chat-input-handhelds-size;
padding: .5px .5rem; padding: .5px .5rem;
} }
@ -984,6 +1007,19 @@
} }
} }
&.is-selecting {
cursor: default !important;
user-select: none;
.is-in .bubble__container {
transform: translateX(2.5rem);
}
}
&.is-selecting > .scrollable::-webkit-scrollbar {
display: none;
}
> .scrollable { > .scrollable {
height: auto; height: auto;
/* position: absolute; /* position: absolute;

96
src/scss/partials/_chatBubble.scss

@ -45,21 +45,42 @@ $bubble-margin: .25rem;
z-index: 1; z-index: 1;
margin: 0 auto; margin: 0 auto;
&.is-selected { &.is-highlighted, &.is-selected, /* #bubbles.is-selecting */ & {
&:after { &:after {
position: absolute; position: absolute;
left: -50%; left: -50%;
top: 0; /* top: 0;
height: 100%; bottom: 0; */
top: #{$bubble-margin / 2};
bottom: -#{$bubble-margin / 2};
content: " "; content: " ";
background-color: rgba(0, 132, 255, .3);
animation: bubbleSelected 2s linear;
z-index: 1; z-index: 1;
} }
}
/* &.is-highlighted, &.is-selected {
&:not(.is-group-last):after { &:not(.is-group-last):after {
height: calc(100% + $bubble-margin); height: calc(100% + #{$bubble-margin / 2}) !important;
} }
& + &:not(.is-group-last) {
&:after {
top: .125rem !important;
height: calc(100% - #{$bubble-margin / 2}) !important;
}
}
} */
// ! if turn this on, there will be an empty space
/* &.is-highlighted, &.is-selected {
&.is-group-last:after {
bottom: #{$bubble-margin / 2} !important;
}
} */
&.is-highlighted:after {
background-color: rgba(0, 132, 255, .3);
animation: bubbleSelected 2s linear;
} }
&.is-first-unread { &.is-first-unread {
@ -78,10 +99,30 @@ $bubble-margin: .25rem;
font-weight: 500; font-weight: 500;
font-size: 15px; font-size: 15px;
background-color: rgba(255, 255, 255, 0.95); background-color: rgba(255, 255, 255, 0.95);
z-index: 2;
position: relative;
}
&.is-highlighted, &.is-selected {
&:after {
top: #{2rem + $bubble-margin} !important;
}
} }
} }
&.is-selected:after, &.is-first-unread:before { &.is-selected {
&:after {
background-color: rgba(77, 142, 80, .4);
animation: fade-in-opacity .2s linear forwards;
}
&.backwards:after {
animation: fade-in-backwards-opacity .2s linear forwards;
}
}
//&.is-highlighted:after, &.is-first-unread:before, &.is-selected:after {
&:after, &:before {
width: 200%; width: 200%;
display: block; display: block;
} }
@ -115,6 +156,45 @@ $bubble-margin: .25rem;
} }
} }
&-select-checkbox {
z-index: 2;
position: absolute;
left: 0;
//bottom: .25rem; // * by avatar
left: 0;
top: 50%;
transform: translateY(-50%);
display: flex;
[type="checkbox"] {
&:not(:checked) + .checkbox-caption:after {
}
&:checked + .checkbox-caption {
&:after {
background-color: #61c642;
}
}
}
.checkbox-caption {
&:before {
top: 7px !important;
left: 3px !important;
width: 6px !important;
height: 11px !important;
}
&:after {
box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, .4);
border: 2px solid #fff !important;
width: 22px;
height: 22px;
}
}
}
&__container { &__container {
//min-width: 60px; //min-width: 60px;
min-width: 56px; min-width: 56px;
@ -128,6 +208,7 @@ $bubble-margin: .25rem;
width: max-content; width: max-content;
height: fit-content; height: fit-content;
z-index: 2; z-index: 2;
transition: .2s transform;
@include respond-to(not-handhelds) { @include respond-to(not-handhelds) {
max-width: 85%; max-width: 85%;
@ -408,6 +489,7 @@ $bubble-margin: .25rem;
max-width: 100%; max-width: 100%;
border-radius: inherit; border-radius: inherit;
overflow: hidden; overflow: hidden;
user-select: none;
display: flex; // lol display: flex; // lol
justify-content: center; justify-content: center;

195
src/scss/partials/_checkbox.scss

@ -0,0 +1,195 @@
.checkbox-field {
margin: 1.25rem 0;
display: block;
text-align: left;
padding: 0 1.125rem;
/* font-weight: 500; */
position: relative;
@include respond-to(handhelds) {
margin-bottom: 27px;
}
}
.checkbox-field-round {
display: block;
text-align: left;
[type="checkbox"] {
&:checked + .checkbox-caption {
&:before {
top: 5px;
left: 0px;
}
&:after {
background-color: #4EA4F6;
border: none;
}
}
}
.checkbox-caption:after {
border-radius: 50%;
height: 20px;
width: 20px;
border-color: #dadbdc;
}
}
.radio-field {
display: block;
position: relative;
padding-left: 3.5rem;
text-align: left;
margin: 1.25rem 0;
line-height: 1.5rem;
cursor: pointer;
&.hidden-widget {
cursor: default;
.radio-field-main {
&::before, &::after {
visibility: hidden;
}
}
}
> input {
&:checked {
& ~ .radio-field-main {
&::before {
border-color: $button-primary-background;
}
&::after {
opacity: 1;
}
}
}
}
.radio-field-main {
&::before, &::after {
content: '';
display: block;
position: absolute;
left: .25rem;
top: 50%;
width: 1.25rem;
height: 1.25rem;
transform: translateY(-50%);
}
&::before {
border: 2px solid #8d969c;
border-radius: 50%;
background-color: white;
opacity: 1;
transition: border-color .1s ease, opacity .1s ease;
}
&::after {
left: .5625rem;
width: .625rem;
height: .625rem;
border-radius: 50%;
background: $button-primary-background;
opacity: 0;
transition: opacity .1s ease;
}
/* .label {
display: block;
word-break: break-word;
}
.subLabel {
display: block;
font-size: 0.875rem;
line-height: 1rem;
color: var(--color-text-secondary);
} */
}
}
[type="checkbox"], [type="radio"] {
box-sizing: border-box;
padding: 0;
opacity: 0;
z-index: var(--z-below);
position: absolute;
}
[type="checkbox"] {
& + span {
position: relative;
padding-left: 3.5rem;
cursor: pointer;
display: inline-block;
height: 25px;
line-height: 25px;
user-select: none;
transition: .2s opacity;
&:before, &:after {
content: '';
left: 0;
position: absolute;
transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;
}
&:before {
border-radius: 2px;
z-index: 1;
}
&:after {
height: 18px;
width: 18px;
z-index: 0;
border: 2px solid $button-primary-background;
border-radius: 3px;
top: 50%;
transform: translateY(-50%);
}
}
&:not(:checked) + span:before {
width: 0;
height: 0;
border: 2px solid transparent;
left: 6px;
top: 10px;
transform: rotateZ(45deg);
transform-origin: 100% 100%;
}
&:checked + span:before {
top: 4px;
left: -1px;
width: 8px;
height: 14px;
border-top: 2px solid transparent;
border-left: 2px solid transparent;
border-right: 2px solid #fff;
border-bottom: 2px solid #fff;
transform: rotateZ(45deg);
transform-origin: 100% 100%;
}
&:not(:checked) + span:after {
background-color: transparent;
border-color: #8d969c;
}
&:checked + span:after {
background-color: $button-primary-background;
}
&:disabled + span {
cursor: default;
opacity: .25;
}
}

1
src/scss/partials/_document.scss

@ -105,6 +105,7 @@
justify-content: center; justify-content: center;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
user-select: none;
&-ico, &-download { &-ico, &-download {
position: absolute; position: absolute;

36
src/scss/partials/_leftSidebar.scss

@ -582,11 +582,6 @@
padding-left: 38px; padding-left: 38px;
} }
} }
[type="checkbox"]:checked+span:before {
top: 5px;
left: 0px;
}
} }
} }
@ -670,22 +665,33 @@
margin-top: 10px; margin-top: 10px;
} }
[type="checkbox"]+span { [type="checkbox"] + span {
padding-left: 26px; padding-left: 26px;
} }
} }
} }
.checkbox [type="checkbox"]+span:after { .checkbox [type="checkbox"] {
border-radius: 50%; & + span:after {
height: 20px; border-radius: 50%;
width: 20px; height: 20px;
border-color: #dadbdc; width: 20px;
} border-color: #dadbdc;
}
.checkbox [type="checkbox"]:checked+span:after { &:checked {
background-color: #4EA4F6; & + span {
border: none; &:before {
top: 5px;
left: 0px;
}
&:after {
background-color: #4EA4F6;
border: none;
}
}
}
} }
.folder-category-button { .folder-category-button {

171
src/scss/style.scss

@ -100,6 +100,7 @@ $floating-left-sidebar: 925px;
} }
@import "partials/ico"; @import "partials/ico";
@import "partials/checkbox";
@import "partials/chatlist"; @import "partials/chatlist";
@import "partials/chat"; @import "partials/chat";
@import "partials/chatBubble"; @import "partials/chatBubble";
@ -707,176 +708,6 @@ hr {
} }
} }
.checkbox-field {
margin: 1.25rem 0;
display: block;
text-align: left;
padding: 0 1.125rem;
/* font-weight: 500; */
position: relative;
@include respond-to(handhelds) {
margin-bottom: 27px;
}
}
.radio-field {
display: block;
position: relative;
padding-left: 3.5rem;
text-align: left;
margin: 1.25rem 0;
line-height: 1.5rem;
cursor: pointer;
&.hidden-widget {
cursor: default;
.radio-field-main {
&::before, &::after {
visibility: hidden;
}
}
}
> input {
&:checked {
& ~ .radio-field-main {
&::before {
border-color: $button-primary-background;
}
&::after {
opacity: 1;
}
}
}
}
.radio-field-main {
&::before, &::after {
content: '';
display: block;
position: absolute;
left: .25rem;
top: 50%;
width: 1.25rem;
height: 1.25rem;
transform: translateY(-50%);
}
&::before {
border: 2px solid #8d969c;
border-radius: 50%;
background-color: white;
opacity: 1;
transition: border-color .1s ease, opacity .1s ease;
}
&::after {
left: .5625rem;
width: .625rem;
height: .625rem;
border-radius: 50%;
background: $button-primary-background;
opacity: 0;
transition: opacity .1s ease;
}
/* .label {
display: block;
word-break: break-word;
}
.subLabel {
display: block;
font-size: 0.875rem;
line-height: 1rem;
color: var(--color-text-secondary);
} */
}
}
[type="checkbox"], [type="radio"] {
box-sizing: border-box;
padding: 0;
opacity: 0;
z-index: var(--z-below);
position: absolute;
}
[type="checkbox"] {
& + span {
position: relative;
padding-left: 3.5rem;
cursor: pointer;
display: inline-block;
height: 25px;
line-height: 25px;
user-select: none;
transition: .2s opacity;
&:before, &:after {
content: '';
left: 0;
position: absolute;
transition: border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;
}
&:before {
border-radius: 2px;
z-index: 1;
}
&:after {
height: 18px;
width: 18px;
z-index: 0;
border: 2px solid $button-primary-background;
border-radius: 3px;
top: 50%;
transform: translateY(-50%);
}
}
&:not(:checked) + span:before {
width: 0;
height: 0;
border: 2px solid transparent;
left: 6px;
top: 10px;
transform: rotateZ(45deg);
transform-origin: 100% 100%;
}
&:checked + span:before {
top: 4px;
left: -1px;
width: 8px;
height: 14px;
border-top: 2px solid transparent;
border-left: 2px solid transparent;
border-right: 2px solid #fff;
border-bottom: 2px solid #fff;
transform: rotateZ(45deg);
transform-origin: 100% 100%;
}
&:not(:checked) + span:after {
background-color: transparent;
border-color: #8d969c;
}
&:checked + span:after {
background-color: $button-primary-background;
}
&:disabled + span {
cursor: default;
opacity: .25;
}
}
.input-wrapper > * + * { .input-wrapper > * + * {
margin-top: 1.5rem; margin-top: 1.5rem;
} }

Loading…
Cancel
Save