diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 2a1aaa77..30193904 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -3148,14 +3148,15 @@ export default class ChatBubbles { } private renderEmptyPlaceholder(type: 'group' | 'saved' | 'noMessages' | 'noScheduledMessages' | 'greeting', bubble: HTMLElement, message: any, elements: (Node | string)[]) { - bubble.classList.add('empty-placeholder', 'empty-placeholder-' + type); + const BASE_CLASS = 'empty-bubble-placeholder'; + bubble.classList.add(BASE_CLASS, BASE_CLASS + '-' + type); let title: HTMLElement; if(type === 'group') title = i18n('GroupEmptyTitle1'); else if(type === 'saved') title = i18n('ChatYourSelfTitle'); else if(type === 'noMessages' || type === 'greeting') title = i18n('NoMessages'); else if(type === 'noScheduledMessages') title = i18n('NoScheduledMessages'); - title.classList.add('center', 'empty-placeholder-title'); + title.classList.add('center', BASE_CLASS + '-title'); elements.push(title); @@ -3177,12 +3178,12 @@ export default class ChatBubbles { ]; } else if(type === 'greeting') { const subtitle = i18n('NoMessagesGreetingsDescription'); - subtitle.classList.add('center', 'empty-placeholder-subtitle'); + subtitle.classList.add('center', BASE_CLASS + '-subtitle'); this.messagesQueue.findAndSplice(q => q.bubble === bubble); const stickerDiv = document.createElement('div'); - stickerDiv.classList.add('empty-placeholder-sticker'); + stickerDiv.classList.add(BASE_CLASS + '-sticker'); const middleware = this.getMiddleware(); @@ -3221,7 +3222,7 @@ export default class ChatBubbles { elements.push( ...listElements.map(elem => { const span = document.createElement('span'); - span.classList.add('empty-placeholder-list-item'); + span.classList.add(BASE_CLASS + '-list-item'); span.append(elem); return span; }) @@ -3236,7 +3237,7 @@ export default class ChatBubbles { } else if(type === 'saved') { listElements.forEach(elem => { const i = document.createElement('span'); - i.classList.add('empty-placeholder-list-bullet'); + i.classList.add(BASE_CLASS + '-list-bullet'); i.innerText = '•'; elem.prepend(i); }); @@ -3247,7 +3248,7 @@ export default class ChatBubbles { bubble.classList.add('has-description'); } - elements.forEach((element: any) => element.classList.add('empty-placeholder-line')); + elements.forEach((element: any) => element.classList.add(BASE_CLASS + '-line')); } private processLocalMessageRender(message: Message.message | Message.messageService) { diff --git a/src/config/app.ts b/src/config/app.ts index 0a69d339..74f9cf43 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -17,7 +17,7 @@ const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', version: '0.6.1', - langPackVersion: '0.3.1', + langPackVersion: '0.3.2', langPack: 'macos', langPackCode: 'en', domains: [MAIN_DOMAIN] as string[], diff --git a/src/helpers/dom/renderImageFromUrl.ts b/src/helpers/dom/renderImageFromUrl.ts index 1c7f3f72..3656c510 100644 --- a/src/helpers/dom/renderImageFromUrl.ts +++ b/src/helpers/dom/renderImageFromUrl.ts @@ -12,7 +12,8 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE }; // проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз. -export default async function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = true) { +export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, + url: string, callback?: (err?: Event) => void, useCache = true) { if(!url) { console.error('renderImageFromUrl: no url?', elem, url); callback && callback(); @@ -54,3 +55,9 @@ export default async function renderImageFromUrl(elem: HTMLElement | HTMLImageEl } } } + +export function renderImageFromUrlPromise(elem: Parameters[0], url: string, useCache?: boolean) { + return new Promise((resolve) => { + renderImageFromUrl(elem, url, resolve, useCache); + }); +} diff --git a/src/lang.ts b/src/lang.ts index 4e126c64..c0ab5e14 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -43,6 +43,9 @@ const lang = { }, "Chat.Search.NoMessagesFound": "No messages found", "Chat.Search.PrivateSearch": "Private Search", + "ChatList.Main.EmptyPlaceholder.Title": "Your chats will appear here", + "ChatList.Main.EmptyPlaceholder.Subtitle": "You have %s on Telegram", + "ChatList.Main.EmptyPlaceholder.SubtitleNoContacts": "Use Telegram app on your [Android](https://telegram.org/android) or [iOS](https://telegram.org/dl/ios) device to sync your contacts", //"ChatList.Menu.Archived": "Archived", "ChatList.Menu.SwitchTo.Webogram": "Switch to Old Version", "ChatList.Menu.SwitchTo.Z": "Switch to Z version", @@ -54,6 +57,10 @@ const lang = { "ConnectionStatus.Reconnecting": "Reconnecting...", "ConnectionStatus.TimedOut": "Request timed out, %s", "ConnectionStatus.Waiting": "Waiting for network...", + "Contacts.Count": { + "one_value": "%d contact", + "other_value": "%d contacts", + }, "Deactivated.Title": "Too many tabs...", "Deactivated.Subtitle": "Telegram supports only one active tab with the app.\nClick anywhere to continue using this tab.", /* "Drafts": { diff --git a/src/lib/appManagers/appAvatarsManager.ts b/src/lib/appManagers/appAvatarsManager.ts index d9f5a7dd..a6c915ba 100644 --- a/src/lib/appManagers/appAvatarsManager.ts +++ b/src/lib/appManagers/appAvatarsManager.ts @@ -4,7 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; +import renderImageFromUrl, { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import replaceContent from "../../helpers/dom/replaceContent"; import sequentialDom from "../../helpers/sequentialDom"; import { UserProfilePhoto, ChatPhoto, InputFileLocation } from "../../layer"; @@ -103,7 +103,7 @@ export class AppAvatarsManager { thumbImage.classList.add('avatar-photo', 'avatar-photo-thumbnail'); img.classList.add('avatar-photo'); const url = appPhotosManager.getPreviewURLFromBytes(photo.stripped_thumb); - renderThumbPromise = renderImageFromUrl(thumbImage, url).then(() => { + renderThumbPromise = renderImageFromUrlPromise(thumbImage, url).then(() => { replaceContent(div, thumbImage); }); } @@ -134,7 +134,7 @@ export class AppAvatarsManager { } const renderPromise = loadPromise - .then((url) => renderImageFromUrl(img, url/* , false */)) + .then((url) => renderImageFromUrlPromise(img, url/* , false */)) .then(() => callback()); return {cached, loadPromise: renderThumbPromise || renderPromise}; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index d9741962..525edb54 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -29,7 +29,7 @@ import appDraftsManager, { MyDraftMessage } from "./appDraftsManager"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import appNotificationsManager from "./appNotificationsManager"; import PeerTitle from "../../components/peerTitle"; -import { i18n, LangPackKey, _i18n } from "../langPack"; +import I18n, { FormatterArguments, i18n, LangPackKey, _i18n } from "../langPack"; import findUpTag from "../../helpers/dom/findUpTag"; import { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue"; import lottieLoader from "../lottieLoader"; @@ -41,6 +41,8 @@ import positionElementByIndex from "../../helpers/dom/positionElementByIndex"; import replaceContent from "../../helpers/dom/replaceContent"; import ConnectionStatusComponent from "../../components/connectionStatus"; import appChatsManager from "./appChatsManager"; +import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; +import { fastRafPromise } from "../../helpers/schedulers"; export type DialogDom = { avatarEl: AvatarElement, @@ -754,12 +756,13 @@ export class AppDialogsManager { private generateEmptyPlaceholder(options: { title: LangPackKey, - subtitle: LangPackKey, + subtitle?: LangPackKey, + subtitleArgs?: FormatterArguments, classNameType: string }) { const BASE_CLASS = 'empty-placeholder'; - const div = document.createElement('div'); - div.classList.add(BASE_CLASS, BASE_CLASS + '-' + options.classNameType); + const container = document.createElement('div'); + container.classList.add(BASE_CLASS, BASE_CLASS + '-' + options.classNameType); const header = document.createElement('div'); header.classList.add(BASE_CLASS + '-header'); @@ -767,31 +770,82 @@ export class AppDialogsManager { const subtitle = document.createElement('div'); subtitle.classList.add(BASE_CLASS + '-subtitle'); - _i18n(subtitle, options.subtitle); + if(options.subtitle) { + _i18n(subtitle, options.subtitle, options.subtitleArgs); + } - div.append(header, subtitle); + container.append(header, subtitle); - return div; + return {container, header, subtitle}; } private onListLengthChange = () => { //return; + if(this.filterId === 1) { + return; + } - if(this.filterId < 2) return; + let placeholderContainer = (Array.from(this.chatList.parentElement.children) as HTMLElement[]).find(el => el.matches('.empty-placeholder')); + const needPlaceholder = this.scroll.loadedAll.bottom && !this.chatList.childElementCount/* || true */; + // this.chatList.style.display = 'none'; - const emptyPlaceholder = this.chatList.parentElement.querySelector('.empty-placeholder'); - if(this.scroll.loadedAll.bottom && !this.chatList.childElementCount) { - if(emptyPlaceholder) { - return; + if(needPlaceholder && placeholderContainer) { + return; + } else if(!needPlaceholder) { + if(placeholderContainer) { + placeholderContainer.remove(); } - const d = this.generateEmptyPlaceholder({ + return; + } + + let placeholder: ReturnType; + if(!this.filterId) { + placeholder = this.generateEmptyPlaceholder({ + title: 'ChatList.Main.EmptyPlaceholder.Title', + classNameType: 'dialogs' + }); + + placeholderContainer = placeholder.container; + + const img = document.createElement('img'); + img.classList.add('empty-placeholder-dialogs-icon'); + + Promise.all([ + appUsersManager.getContacts().then(users => { + let key: LangPackKey, args: FormatterArguments; + + if(users.length) { + key = 'ChatList.Main.EmptyPlaceholder.Subtitle'; + args = [i18n('Contacts.Count', [users.length])]; + } else { + key = 'ChatList.Main.EmptyPlaceholder.SubtitleNoContacts'; + args = []; + } + + const subtitleEl = new I18n.IntlElement({ + key, + args, + element: placeholder.subtitle + }); + }), + renderImageFromUrlPromise(img, 'assets/img/EmptyChats.svg'), + fastRafPromise() + ]).then(() => { + placeholderContainer.classList.add('visible'); + }); + + placeholderContainer.prepend(img); + } else { + placeholder = this.generateEmptyPlaceholder({ title: 'FilterNoChatsToDisplay', subtitle: 'FilterNoChatsToDisplayInfo', classNameType: 'folder' }); - d.prepend(wrapLocalSticker({ + placeholderContainer = placeholder.container; + + placeholderContainer.prepend(wrapLocalSticker({ emoji: '📂', width: 128, height: 128 @@ -806,12 +860,10 @@ export class AppDialogsManager { new AppEditFolderTab(appSidebarLeft).open(appMessagesManager.filtersStorage.filters[this.filterId]); }); - d.append(button); - - this.chatList.parentElement.append(d); - } else if(emptyPlaceholder) { - emptyPlaceholder.remove(); + placeholderContainer.append(button); } + + this.chatList.parentElement.append(placeholderContainer); }; public onChatsRegularScroll = () => { diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index 6d2a23ff..1d933a33 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -23,7 +23,7 @@ import appDownloadManager, { ThumbCache } from "./appDownloadManager"; import appUsersManager from "./appUsersManager"; import blur from "../../helpers/blur"; import { MOUNT_CLASS_TO } from "../../config/debug"; -import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; +import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import calcImageInBox from "../../helpers/calcImageInBox"; import { makeMediaSize, MediaSize } from "../../helpers/mediaSizes"; @@ -208,9 +208,7 @@ export class AppPhotosManager { image.classList.add('thumbnail'); const loadPromise = (useBlur ? blur(url) : Promise.resolve(url)).then(url => { - return new Promise((resolve) => { - renderImageFromUrl(image, url, resolve); - }); + return renderImageFromUrlPromise(image, url); }); return {image, loadPromise}; diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 6db5b3aa..30641f4a 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -14,6 +14,7 @@ import apiManager from "./mtproto/mtprotoworker"; import stateStorage from "./stateStorage"; import App from "../config/app"; import rootScope from "./rootScope"; +import RichTextProcessor from "./richtextprocessor"; export const langPack: {[actionType: string]: LangPackKey} = { "messageActionChatCreate": "ActionCreateGroup", @@ -59,6 +60,9 @@ export const langPack: {[actionType: string]: LangPackKey} = { export type LangPackKey = /* string | */keyof typeof lang | keyof typeof langSign; +export type FormatterArgument = string | Node; +export type FormatterArguments = FormatterArgument[]; + namespace I18n { export const strings: Map = new Map(); let pluralRules: Intl.PluralRules; @@ -230,12 +234,12 @@ namespace I18n { }); } - export function superFormatter(input: string, args?: any[], indexHolder = {i: 0}) { - let out: (string | HTMLElement)[] = []; - const regExp = /(\*\*)(.+?)\1|(\n)|un\d|%\d\$.|%./g; + export function superFormatter(input: string, args?: FormatterArguments, indexHolder = {i: 0}) { + let out: FormatterArguments = []; + const regExp = /(\*\*)(.+?)\1|(\n)|(\[.+?\]\(.*?\))|un\d|%\d\$.|%./g; let lastIndex = 0; - input.replace(regExp, (match, p1: any, p2: any, p3: any, offset: number, string: string) => { + input.replace(regExp, (match, p1: any, p2: any, p3: any, p4: string, offset: number, string: string) => { //console.table({match, p1, p2, offset, string}); out.push(string.slice(lastIndex, offset)); @@ -252,6 +256,23 @@ namespace I18n { } } else if(p3) { out.push(document.createElement('br')); + } else if(p4) { + const a = document.createElement('a'); + + const idx = p4.lastIndexOf(']'); + const text = p4.slice(1, idx); + a.append(...superFormatter(text, args, indexHolder)); + + const url = p4.slice(idx + 2, p4.length - 1); + if(url) { + const wrappedUrl = RichTextProcessor.wrapUrl(url); + a.href = wrappedUrl.url; + if(wrappedUrl.onclick) a.setAttribute('onclick', wrappedUrl.onclick); + a.target = '_blank'; + } + + out.push(a); + console.log('p4', p4); } else if(args) { out.push(args[indexHolder.i++]); } @@ -268,8 +289,8 @@ namespace I18n { } export function format(key: LangPackKey, plain: true, args?: any[]): string; - export function format(key: LangPackKey, plain?: false, args?: any[]): (string | HTMLElement)[]; - export function format(key: LangPackKey, plain = false, args?: any[]): (string | HTMLElement)[] | string { + export function format(key: LangPackKey, plain?: false, args?: any[]): FormatterArguments; + export function format(key: LangPackKey, plain = false, args?: any[]): FormatterArguments | string { const str = strings.get(key); let input: string; if(str) { @@ -329,7 +350,7 @@ namespace I18n { export type IntlElementOptions = IntlElementBaseOptions & { key: LangPackKey, - args?: any[] + args?: FormatterArguments }; export class IntlElement extends IntlElementBase { public key: IntlElementOptions['key']; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 51ee2a57..ad24e931 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -733,7 +733,7 @@ $bubble-margin: .25rem; max-width: 100%; } - &.empty-placeholder { + &.empty-bubble-placeholder { position: absolute; top: 50%; left: 50%; @@ -750,7 +750,7 @@ $bubble-margin: .25rem; align-self: center; } - .empty-placeholder-title { + .empty-bubble-placeholder-title { font-weight: 500; font-size: 1rem !important; } @@ -760,7 +760,11 @@ $bubble-margin: .25rem; } } - .empty-placeholder-line + .empty-placeholder-line { + .empty-bubble-placeholder-line { + color: #fff; + } + + .empty-bubble-placeholder-line + .empty-bubble-placeholder-line { margin-top: .5rem; } @@ -771,7 +775,7 @@ $bubble-margin: .25rem; margin-left: -.1875rem; } - .empty-placeholder-list-bullet { + .empty-bubble-placeholder-list-bullet { margin-right: .3125rem; } @@ -784,23 +788,23 @@ $bubble-margin: .25rem; } } - &.empty-placeholder-group { - .empty-placeholder-list-item { + &.empty-bubble-placeholder-group { + .empty-bubble-placeholder-list-item { margin-top: .4375rem !important; } } - &.empty-placeholder-greeting { + &.empty-bubble-placeholder-greeting { .service-msg { max-width: 232px; } - .empty-placeholder-subtitle { + .empty-bubble-placeholder-subtitle { margin-top: .25rem !important; } } - .empty-placeholder-sticker { + .empty-bubble-placeholder-sticker { margin-top: .75rem !important; position: relative; width: 200px; diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 5cc22311..b8316725 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -1187,6 +1187,9 @@ text-align: center; line-height: var(--line-height); user-select: none; + width: 21rem !important; + margin: 0 auto; + padding: 0 1rem; .media-sticker-wrapper { width: 128px; @@ -1203,7 +1206,7 @@ &-subtitle { color: var(--secondary-text-color); font-size: .875rem; - margin-top: .3125rem; + margin-top: .375rem; } .btn-control { @@ -1214,4 +1217,20 @@ margin-right: .625rem; } } + + &-dialogs { + opacity: 0; + + @include animation-level(2) { + transition: opacity .2s ease-in-out; + } + + &-icon { + margin-bottom: 1.0625rem; + } + + &.visible { + opacity: 1; + } + } }