List navigation for stickers helper
This commit is contained in:
parent
10d97d9969
commit
197da96325
32
src/components/chat/autocompleteHelper.ts
Normal file
32
src/components/chat/autocompleteHelper.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventListenerBase from "../../helpers/eventListenerBase";
|
||||||
|
import rootScope from "../../lib/rootScope";
|
||||||
|
import SetTransition from "../singleTransition";
|
||||||
|
|
||||||
|
export default class AutocompleteHelper extends EventListenerBase<{
|
||||||
|
hidden: () => void,
|
||||||
|
visible: () => void,
|
||||||
|
}> {
|
||||||
|
protected container: HTMLElement;
|
||||||
|
|
||||||
|
constructor(appendTo: HTMLElement) {
|
||||||
|
super(false);
|
||||||
|
|
||||||
|
this.container = document.createElement('div');
|
||||||
|
this.container.classList.add('autocomplete-helper', 'z-depth-1');
|
||||||
|
|
||||||
|
appendTo.append(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggle(hide?: boolean) {
|
||||||
|
hide = hide === undefined ? this.container.classList.contains('is-visible') : hide;
|
||||||
|
SetTransition(this.container, 'is-visible', !hide, rootScope.settings.animationsEnabled ? 200 : 0, () => {
|
||||||
|
this.dispatchEvent(hide ? 'hidden' : 'visible');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -51,10 +51,13 @@ import blurActiveElement from '../../helpers/dom/blurActiveElement';
|
|||||||
import { cancelEvent } from '../../helpers/dom/cancelEvent';
|
import { cancelEvent } from '../../helpers/dom/cancelEvent';
|
||||||
import cancelSelection from '../../helpers/dom/cancelSelection';
|
import cancelSelection from '../../helpers/dom/cancelSelection';
|
||||||
import { attachClickEvent } from '../../helpers/dom/clickEvent';
|
import { attachClickEvent } from '../../helpers/dom/clickEvent';
|
||||||
import getRichValue, { MarkdownType, markdownTags } from '../../helpers/dom/getRichValue';
|
import getRichValue from '../../helpers/dom/getRichValue';
|
||||||
import isInputEmpty from '../../helpers/dom/isInputEmpty';
|
import isInputEmpty from '../../helpers/dom/isInputEmpty';
|
||||||
import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed';
|
import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed';
|
||||||
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
||||||
|
import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValue';
|
||||||
|
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret';
|
||||||
|
import searchIndexManager from '../../lib/searchIndexManager';
|
||||||
|
|
||||||
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.';
|
||||||
@ -62,6 +65,7 @@ const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this
|
|||||||
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
||||||
|
|
||||||
export default class ChatInput {
|
export default class ChatInput {
|
||||||
|
public static AUTO_COMPLETE_REG_EXP = /(\s|^)(:|@|\/)([\S]*)$/;
|
||||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||||
public messageInput: HTMLElement;
|
public messageInput: HTMLElement;
|
||||||
public messageInputField: InputField;
|
public messageInputField: InputField;
|
||||||
@ -137,6 +141,8 @@ export default class ChatInput {
|
|||||||
public fakeRowsWrapper: HTMLDivElement;
|
public fakeRowsWrapper: HTMLDivElement;
|
||||||
private fakePinnedControlBtn: HTMLElement;
|
private fakePinnedControlBtn: HTMLElement;
|
||||||
|
|
||||||
|
public previousQuery: string;
|
||||||
|
|
||||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager, private appDraftsManager: AppDraftsManager, private serverTimeManager: ServerTimeManager, private appNotificationsManager: AppNotificationsManager) {
|
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager, private appDraftsManager: AppDraftsManager, private serverTimeManager: ServerTimeManager, private appNotificationsManager: AppNotificationsManager) {
|
||||||
this.listenerSetter = new ListenerSetter();
|
this.listenerSetter = new ListenerSetter();
|
||||||
}
|
}
|
||||||
@ -595,15 +601,14 @@ export default class ChatInput {
|
|||||||
public saveDraft() {
|
public saveDraft() {
|
||||||
if(!this.chat.peerId || this.editMsgId || this.chat.type === 'scheduled') return;
|
if(!this.chat.peerId || this.editMsgId || this.chat.type === 'scheduled') return;
|
||||||
|
|
||||||
const entities: MessageEntity[] = [];
|
const {value, entities} = getRichValue(this.messageInputField.input);
|
||||||
const str = getRichValue(this.messageInputField.input, entities);
|
|
||||||
|
|
||||||
let draft: DraftMessage.draftMessage;
|
let draft: DraftMessage.draftMessage;
|
||||||
if(str.length || this.replyToMsgId) {
|
if(value.length || this.replyToMsgId) {
|
||||||
draft = {
|
draft = {
|
||||||
_: 'draftMessage',
|
_: 'draftMessage',
|
||||||
date: tsNow(true) + this.serverTimeManager.serverTimeOffset,
|
date: tsNow(true) + this.serverTimeManager.serverTimeOffset,
|
||||||
message: str,
|
message: value,
|
||||||
entities: entities.length ? entities : undefined,
|
entities: entities.length ? entities : undefined,
|
||||||
pFlags: {
|
pFlags: {
|
||||||
no_webpage: this.noWebPage
|
no_webpage: this.noWebPage
|
||||||
@ -1021,35 +1026,27 @@ export default class ChatInput {
|
|||||||
|
|
||||||
//console.log('messageInput input', this.messageInput.innerText);
|
//console.log('messageInput input', this.messageInput.innerText);
|
||||||
//const value = this.messageInput.innerText;
|
//const value = this.messageInput.innerText;
|
||||||
const markdownEntities: MessageEntity[] = [];
|
const {value: richValue, entities: markdownEntities, caretPos} = getRichValueWithCaret(this.messageInputField.input);
|
||||||
const richValue = getRichValue(this.messageInputField.input, markdownEntities);
|
|
||||||
|
|
||||||
//const entities = RichTextProcessor.parseEntities(value);
|
//const entities = RichTextProcessor.parseEntities(value);
|
||||||
const value = RichTextProcessor.parseMarkdown(richValue, markdownEntities);
|
const value = RichTextProcessor.parseMarkdown(richValue, markdownEntities);
|
||||||
const entities = RichTextProcessor.mergeEntities(markdownEntities, RichTextProcessor.parseEntities(value));
|
const entities = RichTextProcessor.mergeEntities(markdownEntities, RichTextProcessor.parseEntities(value));
|
||||||
|
|
||||||
//this.chat.log('messageInput entities', richValue, value, markdownEntities);
|
this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
|
||||||
|
|
||||||
if(this.stickersHelper &&
|
if(this.stickersHelper &&
|
||||||
rootScope.settings.stickers.suggest &&
|
rootScope.settings.stickers.suggest &&
|
||||||
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers'))) {
|
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers'))) {
|
||||||
let emoticon = '';
|
let emoticon = '';
|
||||||
if(entities.length && entities[0]._ === 'messageEntityEmoji') {
|
const entity = entities[0];
|
||||||
const entity = entities[0];
|
if(entity?._ === 'messageEntityEmoji' && entity.length === richValue.length && !entity.offset) {
|
||||||
if(entity.length === richValue.length && !entity.offset) {
|
emoticon = richValue;
|
||||||
emoticon = richValue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stickersHelper.checkEmoticon(emoticon);
|
this.stickersHelper.checkEmoticon(emoticon);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!richValue.trim()) {
|
if(this.canRedoFromHTML && !this.lockRedo && this.messageInput.innerHTML !== this.canRedoFromHTML) {
|
||||||
this.appImManager.markupTooltip.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
const html = this.messageInput.innerHTML;
|
|
||||||
if(this.canRedoFromHTML && html !== this.canRedoFromHTML && !this.lockRedo) {
|
|
||||||
this.canRedoFromHTML = '';
|
this.canRedoFromHTML = '';
|
||||||
this.undoHistory.length = 0;
|
this.undoHistory.length = 0;
|
||||||
}
|
}
|
||||||
@ -1104,10 +1101,12 @@ export default class ChatInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.isInputEmpty()) {
|
if(!richValue.trim()) {
|
||||||
if(this.lastTimeType) {
|
if(this.lastTimeType) {
|
||||||
this.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'});
|
this.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.appImManager.markupTooltip.hide();
|
||||||
} else {
|
} else {
|
||||||
const time = Date.now();
|
const time = Date.now();
|
||||||
if(time - this.lastTimeType >= 6000) {
|
if(time - this.lastTimeType >= 6000) {
|
||||||
@ -1346,17 +1345,16 @@ export default class ChatInput {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const entities: MessageEntity[] = [];
|
const {value, entities} = getRichValue(this.messageInputField.input);
|
||||||
const str = getRichValue(this.messageInputField.input, entities);
|
|
||||||
|
|
||||||
//return;
|
//return;
|
||||||
if(this.editMsgId) {
|
if(this.editMsgId) {
|
||||||
this.appMessagesManager.editMessage(this.chat.getMessage(this.editMsgId), str, {
|
this.appMessagesManager.editMessage(this.chat.getMessage(this.editMsgId), value, {
|
||||||
entities,
|
entities,
|
||||||
noWebPage: this.noWebPage
|
noWebPage: this.noWebPage
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.appMessagesManager.sendText(this.chat.peerId, str, {
|
this.appMessagesManager.sendText(this.chat.peerId, value, {
|
||||||
entities,
|
entities,
|
||||||
replyToMsgId: this.replyToMsgId,
|
replyToMsgId: this.replyToMsgId,
|
||||||
threadId: this.chat.threadId,
|
threadId: this.chat.threadId,
|
||||||
|
@ -14,9 +14,9 @@ import appNavigationController from "../appNavigationController";
|
|||||||
import { _i18n } from "../../lib/langPack";
|
import { _i18n } from "../../lib/langPack";
|
||||||
import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
||||||
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||||||
import { MarkdownType, markdownTags } from "../../helpers/dom/getRichValue";
|
|
||||||
import getSelectedNodes from "../../helpers/dom/getSelectedNodes";
|
import getSelectedNodes from "../../helpers/dom/getSelectedNodes";
|
||||||
import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty";
|
import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty";
|
||||||
|
import { MarkdownType, markdownTags } from "../../helpers/dom/getRichElementValue";
|
||||||
//import { logger } from "../../lib/logger";
|
//import { logger } from "../../lib/logger";
|
||||||
|
|
||||||
export default class MarkupTooltip {
|
export default class MarkupTooltip {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import findUpClassName from "../../helpers/dom/findUpClassName";
|
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
|
||||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||||
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||||
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
||||||
@ -12,21 +12,36 @@ import { EmoticonsDropdown } from "../emoticonsDropdown";
|
|||||||
import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
|
import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
|
||||||
import LazyLoadQueue from "../lazyLoadQueue";
|
import LazyLoadQueue from "../lazyLoadQueue";
|
||||||
import Scrollable from "../scrollable";
|
import Scrollable from "../scrollable";
|
||||||
import SetTransition from "../singleTransition";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
|
||||||
export default class StickersHelper {
|
export default class StickersHelper extends AutocompleteHelper {
|
||||||
private container: HTMLElement;
|
|
||||||
private stickersContainer: HTMLElement;
|
private stickersContainer: HTMLElement;
|
||||||
private scrollable: Scrollable;
|
private scrollable: Scrollable;
|
||||||
private superStickerRenderer: SuperStickerRenderer;
|
private superStickerRenderer: SuperStickerRenderer;
|
||||||
private lazyLoadQueue: LazyLoadQueue;
|
private lazyLoadQueue: LazyLoadQueue;
|
||||||
private lastEmoticon = '';
|
private lastEmoticon = '';
|
||||||
|
|
||||||
constructor(private appendTo: HTMLElement) {
|
constructor(appendTo: HTMLElement) {
|
||||||
this.container = document.createElement('div');
|
super(appendTo);
|
||||||
this.container.classList.add('stickers-helper', 'z-depth-1');
|
|
||||||
|
|
||||||
this.appendTo.append(this.container);
|
this.container.classList.add('stickers-helper');
|
||||||
|
|
||||||
|
this.addEventListener('visible', () => {
|
||||||
|
const list = this.stickersContainer;
|
||||||
|
const {detach} = attachListNavigation({
|
||||||
|
list,
|
||||||
|
type: 'xy',
|
||||||
|
onSelect: (target) => {
|
||||||
|
EmoticonsDropdown.onMediaClick({target}, true);
|
||||||
|
},
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addEventListener('hidden', () => {
|
||||||
|
list.innerHTML = '';
|
||||||
|
detach();
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkEmoticon(emoticon: string) {
|
public checkEmoticon(emoticon: string) {
|
||||||
@ -34,11 +49,7 @@ export default class StickersHelper {
|
|||||||
|
|
||||||
if(this.lastEmoticon && !emoticon) {
|
if(this.lastEmoticon && !emoticon) {
|
||||||
if(this.container) {
|
if(this.container) {
|
||||||
SetTransition(this.container, 'is-visible', false, 200, () => {
|
this.toggle(true);
|
||||||
if(this.stickersContainer) {
|
|
||||||
this.stickersContainer.innerHTML = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,21 +95,13 @@ export default class StickersHelper {
|
|||||||
this.stickersContainer.replaceWith(container);
|
this.stickersContainer.replaceWith(container);
|
||||||
this.stickersContainer = container;
|
this.stickersContainer = container;
|
||||||
|
|
||||||
SetTransition(this.container, 'is-visible', !!stickers.length, 200);
|
this.toggle(!stickers.length);
|
||||||
this.scrollable.scrollTop = 0;
|
this.scrollable.scrollTop = 0;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
this.container.addEventListener('click', (e) => {
|
|
||||||
if(!findUpClassName(e.target, 'super-sticker')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EmoticonsDropdown.onMediaClick(e, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.stickersContainer = document.createElement('div');
|
this.stickersContainer = document.createElement('div');
|
||||||
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
|
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@ export class EmoticonsDropdown {
|
|||||||
return stickyIntersector;
|
return stickyIntersector;
|
||||||
};
|
};
|
||||||
|
|
||||||
public static onMediaClick = (e: MouseEvent, clearDraft = false) => {
|
public static onMediaClick = (e: {target: EventTarget | Element}, clearDraft = false) => {
|
||||||
let target = e.target as HTMLElement;
|
let target = e.target as HTMLElement;
|
||||||
target = findUpTag(target, 'DIV');
|
target = findUpTag(target, 'DIV');
|
||||||
|
|
||||||
@ -406,9 +406,11 @@ export class EmoticonsDropdown {
|
|||||||
if(appImManager.chat.input.sendMessageWithDocument(fileId, undefined, clearDraft)) {
|
if(appImManager.chat.input.sendMessageWithDocument(fileId, undefined, clearDraft)) {
|
||||||
/* dropdown.classList.remove('active');
|
/* dropdown.classList.remove('active');
|
||||||
toggleEl.classList.remove('active'); */
|
toggleEl.classList.remove('active'); */
|
||||||
emoticonsDropdown.forceClose = true;
|
if(emoticonsDropdown.container) {
|
||||||
emoticonsDropdown.container.classList.add('disable-hover');
|
emoticonsDropdown.forceClose = true;
|
||||||
emoticonsDropdown.toggle(false);
|
emoticonsDropdown.container.classList.add('disable-hover');
|
||||||
|
emoticonsDropdown.toggle(false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('got no doc by id:', fileId);
|
console.warn('got no doc by id:', fileId);
|
||||||
}
|
}
|
||||||
|
@ -182,7 +182,7 @@ class InputField {
|
|||||||
processInput = () => {
|
processInput = () => {
|
||||||
const wasError = input.classList.contains('error');
|
const wasError = input.classList.contains('error');
|
||||||
// * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol
|
// * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol
|
||||||
const inputLength = plainText ? (input as HTMLInputElement).value.length : [...getRichValue(input)].length;
|
const inputLength = plainText ? (input as HTMLInputElement).value.length : [...getRichValue(input, false).value].length;
|
||||||
const diff = maxLength - inputLength;
|
const diff = maxLength - inputLength;
|
||||||
const isError = diff < 0;
|
const isError = diff < 0;
|
||||||
input.classList.toggle('error', isError);
|
input.classList.toggle('error', isError);
|
||||||
@ -232,7 +232,7 @@ class InputField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.options.plainText ? (this.input as HTMLInputElement).value : getRichValue(this.input);
|
return this.options.plainText ? (this.input as HTMLInputElement).value : getRichValue(this.input, false).value;
|
||||||
//return getRichValue(this.input);
|
//return getRichValue(this.input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||||||
private getFilledAnswers() {
|
private getFilledAnswers() {
|
||||||
const answers = Array.from(this.questions.children).map((el, idx) => {
|
const answers = Array.from(this.questions.children).map((el, idx) => {
|
||||||
const input = el.querySelector('.input-field-input') as HTMLElement;
|
const input = el.querySelector('.input-field-input') as HTMLElement;
|
||||||
return input instanceof HTMLInputElement ? input.value : getRichValue(input);
|
return input instanceof HTMLInputElement ? input.value : getRichValue(input, false).value;
|
||||||
}).filter(v => !!v.trim());
|
}).filter(v => !!v.trim());
|
||||||
|
|
||||||
return answers;
|
return answers;
|
||||||
@ -219,9 +219,8 @@ export default class PopupCreatePoll extends PopupElement {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const quizSolutionEntities: MessageEntity[] = [];
|
const {value: quizSolution} = getRichValue(this.quizSolutionField.input, false);
|
||||||
const quizSolution = getRichValue(this.quizSolutionField.input, quizSolutionEntities) || undefined;
|
if(quizSolution.length > MAX_LENGTH_SOLUTION) {
|
||||||
if(quizSolution?.length > MAX_LENGTH_SOLUTION) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,8 +237,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||||||
|
|
||||||
const answers = this.getFilledAnswers();
|
const answers = this.getFilledAnswers();
|
||||||
|
|
||||||
const quizSolutionEntities: MessageEntity[] = [];
|
const {value: quizSolution, entities: quizSolutionEntities} = getRichValue(this.quizSolutionField.input);
|
||||||
const quizSolution = getRichValue(this.quizSolutionField.input, quizSolutionEntities) || undefined;
|
|
||||||
|
|
||||||
if(this.chat.type === 'scheduled' && !force) {
|
if(this.chat.type === 'scheduled' && !force) {
|
||||||
this.chat.input.scheduleSending(() => {
|
this.chat.input.scheduleSending(() => {
|
||||||
|
160
src/helpers/dom/attachListNavigation.ts
Normal file
160
src/helpers/dom/attachListNavigation.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fastSmoothScroll from "../fastSmoothScroll";
|
||||||
|
import { cancelEvent } from "./cancelEvent";
|
||||||
|
import { attachClickEvent, detachClickEvent } from "./clickEvent";
|
||||||
|
import findUpAsChild from "./findUpAsChild";
|
||||||
|
import findUpClassName from "./findUpClassName";
|
||||||
|
|
||||||
|
type ArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight';
|
||||||
|
const HANDLE_EVENT = 'keydown';
|
||||||
|
const ACTIVE_CLASS_NAME = 'active';
|
||||||
|
|
||||||
|
export default function attachListNavigation({list, type, onSelect, once}: {
|
||||||
|
list: HTMLElement,
|
||||||
|
type: 'xy' | 'x' | 'y',
|
||||||
|
onSelect: (target: Element) => void | boolean,
|
||||||
|
once: boolean,
|
||||||
|
}) {
|
||||||
|
const keyNames: Set<ArrowKey> = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']);
|
||||||
|
|
||||||
|
let target: Element;
|
||||||
|
const getCurrentTarget = () => {
|
||||||
|
return target || list.querySelector('.' + ACTIVE_CLASS_NAME) || list.firstElementChild;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setCurrentTarget = (_target: Element) => {
|
||||||
|
if(target === _target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hadTarget = false;
|
||||||
|
if(target) {
|
||||||
|
hadTarget = true;
|
||||||
|
target.classList.remove(ACTIVE_CLASS_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
target = _target;
|
||||||
|
target.classList.add(ACTIVE_CLASS_NAME);
|
||||||
|
|
||||||
|
if(hadTarget && scrollable) {
|
||||||
|
fastSmoothScroll(scrollable, target as HTMLElement, 'center', undefined, undefined, undefined, 100, type === 'x' ? 'x' : 'y');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextTargetX = (currentTarget: Element, isNext: boolean) => {
|
||||||
|
let nextTarget: Element;
|
||||||
|
if(isNext) nextTarget = currentTarget.nextElementSibling || list.firstElementChild;
|
||||||
|
else nextTarget = currentTarget.previousElementSibling || list.lastElementChild;
|
||||||
|
|
||||||
|
return nextTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getNextTargetY = (currentTarget: Element, isNext: boolean) => {
|
||||||
|
const property = isNext ? 'nextElementSibling' : 'previousElementSibling';
|
||||||
|
const endProperty = isNext ? 'firstElementChild' : 'lastElementChild';
|
||||||
|
const currentRect = currentTarget.getBoundingClientRect();
|
||||||
|
|
||||||
|
let nextTarget = currentTarget[property] || list[endProperty];
|
||||||
|
while(nextTarget !== currentTarget) {
|
||||||
|
const targetRect = nextTarget.getBoundingClientRect();
|
||||||
|
if(targetRect.x === currentRect.x && targetRect.y !== currentRect.y) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTarget = nextTarget[property] || list[endProperty];
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextTarget;
|
||||||
|
};
|
||||||
|
|
||||||
|
let handleArrowKey: (currentTarget: Element, key: ArrowKey) => Element;
|
||||||
|
if(type === 'xy') { // flex-direction: row; flex-wrap: wrap;
|
||||||
|
handleArrowKey = (currentTarget, key) => {
|
||||||
|
if(key === 'ArrowUp' || key === 'ArrowDown') return getNextTargetY(currentTarget, key === 'ArrowDown');
|
||||||
|
else return getNextTargetX(currentTarget, key === 'ArrowRight');
|
||||||
|
};
|
||||||
|
} else { // flex-direction: row | column;
|
||||||
|
handleArrowKey = (currentTarget, key) => getNextTargetX(currentTarget, key === 'ArrowRight' || key === 'ArrowDown');
|
||||||
|
}
|
||||||
|
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if(!keyNames.has(e.key as any)) {
|
||||||
|
if(e.key === 'Enter') {
|
||||||
|
cancelEvent(e);
|
||||||
|
fireSelect(getCurrentTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelEvent(e);
|
||||||
|
|
||||||
|
if(list.childElementCount > 1) {
|
||||||
|
let currentTarget = getCurrentTarget();
|
||||||
|
currentTarget = handleArrowKey(currentTarget, e.key as any);
|
||||||
|
setCurrentTarget(currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollable = findUpClassName(list, 'scrollable');
|
||||||
|
list.classList.add('navigable-list');
|
||||||
|
|
||||||
|
const onMouseMove = (e: MouseEvent) => {
|
||||||
|
const target = findUpAsChild(e.target, list) as HTMLElement;
|
||||||
|
if(!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentTarget(target);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = (e: Event) => {
|
||||||
|
cancelEvent(e); // cancel keyboard closening
|
||||||
|
|
||||||
|
const target = findUpAsChild(e.target, list) as HTMLElement;
|
||||||
|
if(!target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentTarget(target);
|
||||||
|
fireSelect(getCurrentTarget());
|
||||||
|
};
|
||||||
|
|
||||||
|
const fireSelect = (target: Element) => {
|
||||||
|
const canContinue = onSelect(target);
|
||||||
|
if(canContinue !== undefined ? !canContinue : once) {
|
||||||
|
detach();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const detach = () => {
|
||||||
|
// input.removeEventListener(HANDLE_EVENT, onKeyDown, {capture: true});
|
||||||
|
document.removeEventListener(HANDLE_EVENT, onKeyDown, {capture: true});
|
||||||
|
list.removeEventListener('mousemove', onMouseMove);
|
||||||
|
detachClickEvent(list, onClick);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTarget = () => {
|
||||||
|
setCurrentTarget(list.firstElementChild);
|
||||||
|
};
|
||||||
|
|
||||||
|
resetTarget();
|
||||||
|
|
||||||
|
// const input = document.activeElement as HTMLElement;
|
||||||
|
// input.addEventListener(HANDLE_EVENT, onKeyDown, {capture: true, passive: false});
|
||||||
|
document.addEventListener(HANDLE_EVENT, onKeyDown, {capture: true, passive: false});
|
||||||
|
list.addEventListener('mousemove', onMouseMove, {passive: true});
|
||||||
|
attachClickEvent(list, onClick);
|
||||||
|
|
||||||
|
return {
|
||||||
|
detach,
|
||||||
|
resetTarget
|
||||||
|
};
|
||||||
|
}
|
123
src/helpers/dom/getRichElementValue.ts
Normal file
123
src/helpers/dom/getRichElementValue.ts
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*
|
||||||
|
* Originally from:
|
||||||
|
* https://github.com/zhukov/webogram
|
||||||
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
|
||||||
|
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MessageEntity } from "../../layer";
|
||||||
|
|
||||||
|
export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link';
|
||||||
|
export type MarkdownTag = {
|
||||||
|
match: string,
|
||||||
|
entityName: 'messageEntityBold' | 'messageEntityUnderline' | 'messageEntityItalic' | 'messageEntityPre' | 'messageEntityStrike' | 'messageEntityTextUrl';
|
||||||
|
};
|
||||||
|
export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
|
||||||
|
bold: {
|
||||||
|
match: '[style*="font-weight"], b',
|
||||||
|
entityName: 'messageEntityBold'
|
||||||
|
},
|
||||||
|
underline: {
|
||||||
|
match: '[style*="underline"], u',
|
||||||
|
entityName: 'messageEntityUnderline'
|
||||||
|
},
|
||||||
|
italic: {
|
||||||
|
match: '[style*="italic"], i',
|
||||||
|
entityName: 'messageEntityItalic'
|
||||||
|
},
|
||||||
|
monospace: {
|
||||||
|
match: '[style*="monospace"], [face="monospace"]',
|
||||||
|
entityName: 'messageEntityPre'
|
||||||
|
},
|
||||||
|
strikethrough: {
|
||||||
|
match: '[style*="line-through"], strike',
|
||||||
|
entityName: 'messageEntityStrike'
|
||||||
|
},
|
||||||
|
link: {
|
||||||
|
match: 'A',
|
||||||
|
entityName: 'messageEntityTextUrl'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number, entities?: MessageEntity[], offset = {offset: 0}) {
|
||||||
|
if(node.nodeType === 3) { // TEXT
|
||||||
|
if(selNode === node) {
|
||||||
|
const value = node.nodeValue;
|
||||||
|
line.push(value.substr(0, selOffset) + '\x01' + value.substr(selOffset));
|
||||||
|
} else {
|
||||||
|
const nodeValue = node.nodeValue;
|
||||||
|
line.push(nodeValue);
|
||||||
|
|
||||||
|
if(entities && nodeValue.trim()) {
|
||||||
|
if(node.parentNode) {
|
||||||
|
const parentElement = node.parentElement;
|
||||||
|
|
||||||
|
for(const type in markdownTags) {
|
||||||
|
const tag = markdownTags[type as MarkdownType];
|
||||||
|
const closest = parentElement.closest(tag.match + ', [contenteditable]');
|
||||||
|
if(closest && closest.getAttribute('contenteditable') === null) {
|
||||||
|
if(tag.entityName === 'messageEntityTextUrl') {
|
||||||
|
entities.push({
|
||||||
|
_: tag.entityName as any,
|
||||||
|
url: (parentElement as HTMLAnchorElement).href,
|
||||||
|
offset: offset.offset,
|
||||||
|
length: nodeValue.length
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
entities.push({
|
||||||
|
_: tag.entityName as any,
|
||||||
|
offset: offset.offset,
|
||||||
|
length: nodeValue.length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset.offset += nodeValue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.nodeType !== 1) { // NON-ELEMENT
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSelected = (selNode === node);
|
||||||
|
const isBlock = node.tagName === 'DIV' || node.tagName === 'P';
|
||||||
|
if(isBlock && line.length || node.tagName === 'BR') {
|
||||||
|
lines.push(line.join(''));
|
||||||
|
line.splice(0, line.length);
|
||||||
|
} else if(node.tagName === 'IMG') {
|
||||||
|
const alt = (node as HTMLImageElement).alt;
|
||||||
|
if(alt) {
|
||||||
|
line.push(alt);
|
||||||
|
offset.offset += alt.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isSelected && !selOffset) {
|
||||||
|
line.push('\x01');
|
||||||
|
}
|
||||||
|
|
||||||
|
let curChild = node.firstChild as HTMLElement;
|
||||||
|
while(curChild) {
|
||||||
|
getRichElementValue(curChild, lines, line, selNode, selOffset, entities, offset);
|
||||||
|
curChild = curChild.nextSibling as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isSelected && selOffset) {
|
||||||
|
line.push('\x01');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isBlock && line.length) {
|
||||||
|
lines.push(line.join(''));
|
||||||
|
line.splice(0, line.length);
|
||||||
|
}
|
||||||
|
}
|
@ -12,15 +12,13 @@
|
|||||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||||
import { MessageEntity } from "../../layer";
|
import { MessageEntity } from "../../layer";
|
||||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||||
|
import getRichElementValue from "./getRichElementValue";
|
||||||
|
|
||||||
export default function getRichValue(field: HTMLElement, entities?: MessageEntity[]) {
|
export default function getRichValue(field: HTMLElement, withEntities = true) {
|
||||||
if(!field) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
const line: string[] = [];
|
const line: string[] = [];
|
||||||
|
|
||||||
|
const entities: MessageEntity[] = withEntities ? [] : undefined;
|
||||||
getRichElementValue(field, lines, line, undefined, undefined, entities);
|
getRichElementValue(field, lines, line, undefined, undefined, entities);
|
||||||
if(line.length) {
|
if(line.length) {
|
||||||
lines.push(line.join(''));
|
lines.push(line.join(''));
|
||||||
@ -35,118 +33,7 @@ export default function getRichValue(field: HTMLElement, entities?: MessageEntit
|
|||||||
|
|
||||||
//console.log('getRichValue:', value, entities);
|
//console.log('getRichValue:', value, entities);
|
||||||
|
|
||||||
return value;
|
return {value, entities};
|
||||||
}
|
}
|
||||||
|
|
||||||
MOUNT_CLASS_TO.getRichValue = getRichValue;
|
MOUNT_CLASS_TO.getRichValue = getRichValue;
|
||||||
|
|
||||||
export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link';
|
|
||||||
export type MarkdownTag = {
|
|
||||||
match: string,
|
|
||||||
entityName: 'messageEntityBold' | 'messageEntityUnderline' | 'messageEntityItalic' | 'messageEntityPre' | 'messageEntityStrike' | 'messageEntityTextUrl';
|
|
||||||
};
|
|
||||||
export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
|
|
||||||
bold: {
|
|
||||||
match: '[style*="font-weight"], b',
|
|
||||||
entityName: 'messageEntityBold'
|
|
||||||
},
|
|
||||||
underline: {
|
|
||||||
match: '[style*="underline"], u',
|
|
||||||
entityName: 'messageEntityUnderline'
|
|
||||||
},
|
|
||||||
italic: {
|
|
||||||
match: '[style*="italic"], i',
|
|
||||||
entityName: 'messageEntityItalic'
|
|
||||||
},
|
|
||||||
monospace: {
|
|
||||||
match: '[style*="monospace"], [face="monospace"]',
|
|
||||||
entityName: 'messageEntityPre'
|
|
||||||
},
|
|
||||||
strikethrough: {
|
|
||||||
match: '[style*="line-through"], strike',
|
|
||||||
entityName: 'messageEntityStrike'
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
match: 'A',
|
|
||||||
entityName: 'messageEntityTextUrl'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number, entities?: MessageEntity[], offset = {offset: 0}) {
|
|
||||||
if(node.nodeType === 3) { // TEXT
|
|
||||||
if(selNode === node) {
|
|
||||||
const value = node.nodeValue;
|
|
||||||
line.push(value.substr(0, selOffset) + '\x01' + value.substr(selOffset));
|
|
||||||
} else {
|
|
||||||
const nodeValue = node.nodeValue;
|
|
||||||
line.push(nodeValue);
|
|
||||||
|
|
||||||
if(entities && nodeValue.trim()) {
|
|
||||||
if(node.parentNode) {
|
|
||||||
const parentElement = node.parentElement;
|
|
||||||
|
|
||||||
for(const type in markdownTags) {
|
|
||||||
const tag = markdownTags[type as MarkdownType];
|
|
||||||
const closest = parentElement.closest(tag.match + ', [contenteditable]');
|
|
||||||
if(closest && closest.getAttribute('contenteditable') === null) {
|
|
||||||
if(tag.entityName === 'messageEntityTextUrl') {
|
|
||||||
entities.push({
|
|
||||||
_: tag.entityName as any,
|
|
||||||
url: (parentElement as HTMLAnchorElement).href,
|
|
||||||
offset: offset.offset,
|
|
||||||
length: nodeValue.length
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
entities.push({
|
|
||||||
_: tag.entityName as any,
|
|
||||||
offset: offset.offset,
|
|
||||||
length: nodeValue.length
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
offset.offset += nodeValue.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(node.nodeType !== 1) { // NON-ELEMENT
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSelected = (selNode === node);
|
|
||||||
const isBlock = node.tagName === 'DIV' || node.tagName === 'P';
|
|
||||||
if(isBlock && line.length || node.tagName === 'BR') {
|
|
||||||
lines.push(line.join(''));
|
|
||||||
line.splice(0, line.length);
|
|
||||||
} else if(node.tagName === 'IMG') {
|
|
||||||
const alt = (node as HTMLImageElement).alt;
|
|
||||||
if(alt) {
|
|
||||||
line.push(alt);
|
|
||||||
offset.offset += alt.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isSelected && !selOffset) {
|
|
||||||
line.push('\x01');
|
|
||||||
}
|
|
||||||
|
|
||||||
let curChild = node.firstChild as HTMLElement;
|
|
||||||
while(curChild) {
|
|
||||||
getRichElementValue(curChild, lines, line, selNode, selOffset, entities, offset);
|
|
||||||
curChild = curChild.nextSibling as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isSelected && selOffset) {
|
|
||||||
line.push('\x01');
|
|
||||||
}
|
|
||||||
|
|
||||||
if(isBlock && line.length) {
|
|
||||||
lines.push(line.join(''));
|
|
||||||
line.splice(0, line.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
52
src/helpers/dom/getRichValueWithCaret.ts
Normal file
52
src/helpers/dom/getRichValueWithCaret.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*
|
||||||
|
* Originally from:
|
||||||
|
* https://github.com/zhukov/webogram
|
||||||
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
|
||||||
|
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MessageEntity } from "../../layer";
|
||||||
|
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||||
|
import getRichElementValue from "./getRichElementValue";
|
||||||
|
|
||||||
|
export default function getRichValueWithCaret(field: HTMLElement, withEntities = true) {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const line: string[] = [];
|
||||||
|
|
||||||
|
const sel = window.getSelection();
|
||||||
|
var selNode
|
||||||
|
var selOffset
|
||||||
|
if(sel && sel.rangeCount) {
|
||||||
|
const range = sel.getRangeAt(0);
|
||||||
|
if(range.startContainer &&
|
||||||
|
range.startContainer == range.endContainer &&
|
||||||
|
range.startOffset == range.endOffset) {
|
||||||
|
selNode = range.startContainer;
|
||||||
|
selOffset = range.startOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const entities: MessageEntity[] = withEntities ? [] : undefined;
|
||||||
|
getRichElementValue(field, lines, line, selNode, selOffset, entities);
|
||||||
|
|
||||||
|
if(line.length) {
|
||||||
|
lines.push(line.join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = lines.join('\n');
|
||||||
|
const caretPos = value.indexOf('\x01');
|
||||||
|
if(caretPos != -1) {
|
||||||
|
value = value.substr(0, caretPos) + value.substr(caretPos + 1);
|
||||||
|
}
|
||||||
|
value = value.replace(/\u00A0/g, ' ');
|
||||||
|
|
||||||
|
if(entities) {
|
||||||
|
RichTextProcessor.combineSameEntities(entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {value, entities, caretPos};
|
||||||
|
}
|
@ -11,7 +11,7 @@ export default function isInputEmpty(element: HTMLElement) {
|
|||||||
/* const value = element.innerText;
|
/* const value = element.innerText;
|
||||||
|
|
||||||
return !value.trim() && !serializeNodes(Array.from(element.childNodes)).trim(); */
|
return !value.trim() && !serializeNodes(Array.from(element.childNodes)).trim(); */
|
||||||
return !getRichValue(element).trim();
|
return !getRichValue(element, false).value.trim();
|
||||||
} else {
|
} else {
|
||||||
return !(element as HTMLInputElement).value.trim();
|
return !(element as HTMLInputElement).value.trim();
|
||||||
}
|
}
|
||||||
|
@ -148,6 +148,8 @@ export class AppPollsManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
solution = RichTextProcessor.parseMarkdown(solution, solutionEntities);
|
solution = RichTextProcessor.parseMarkdown(solution, solutionEntities);
|
||||||
|
} else {
|
||||||
|
solution = undefined; // can be string here
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
28
src/scss/partials/_autocompleteHelper.scss
Normal file
28
src/scss/partials/_autocompleteHelper.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
.autocomplete-helper {
|
||||||
|
--border-radius: #{$border-radius-medium};
|
||||||
|
position: absolute !important;
|
||||||
|
bottom: calc(100% + .625rem);
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0 !important;
|
||||||
|
border-radius: var(--border-radius) !important;
|
||||||
|
|
||||||
|
&:not(.is-visible) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include animation-level(2) {
|
||||||
|
&.is-visible {
|
||||||
|
animation: fade-out-opacity .2s ease-in-out forwards;
|
||||||
|
|
||||||
|
&:not(.backwards) {
|
||||||
|
animation: fade-in-opacity .2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,47 +5,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.stickers-helper {
|
.stickers-helper {
|
||||||
position: absolute !important;
|
|
||||||
bottom: calc(100% + 10px);
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0 !important;
|
|
||||||
border-radius: 10px !important;
|
|
||||||
|
|
||||||
> .scrollable {
|
> .scrollable {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-height: 220px;
|
max-height: 13.75rem;
|
||||||
min-height: var(--esg-sticker-size);
|
min-height: var(--esg-sticker-size);
|
||||||
padding: 7px;
|
padding: .4375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-stickers {
|
&-stickers {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-sticker {
|
.super-sticker:not(.active) {
|
||||||
position: relative;
|
@include hover() {
|
||||||
width: var(--esg-sticker-size);
|
background: none;
|
||||||
height: var(--esg-sticker-size);
|
|
||||||
margin: 5px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(.is-visible) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include animation-level(2) {
|
|
||||||
&.is-visible {
|
|
||||||
animation: fade-out-opacity .2s ease-in-out forwards;
|
|
||||||
|
|
||||||
&:not(.backwards) {
|
|
||||||
animation: fade-in-opacity .2s ease-in-out forwards;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -225,6 +225,7 @@ html.night {
|
|||||||
@import "partials/input";
|
@import "partials/input";
|
||||||
@import "partials/button";
|
@import "partials/button";
|
||||||
@import "partials/animatedIcon";
|
@import "partials/animatedIcon";
|
||||||
|
@import "partials/autocompleteHelper";
|
||||||
@import "partials/badge";
|
@import "partials/badge";
|
||||||
@import "partials/checkbox";
|
@import "partials/checkbox";
|
||||||
@import "partials/chatlist";
|
@import "partials/chatlist";
|
||||||
@ -1205,3 +1206,10 @@ middle-ellipsis-element {
|
|||||||
.verified-background {
|
.verified-background {
|
||||||
fill: #33a8e5;
|
fill: #33a8e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigable-list {
|
||||||
|
.active {
|
||||||
|
background-color: var(--light-secondary-text-color);
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user