Refactor state & Custom editor (#1190)

* Fix eslint

* Enable ts strict mode

* install folds, jotai & immer

* Enable immer map/set

* change cross-signing alert anim to 30 iteration

* Add function to access matrix client

* Add new types

* Add disposable util

* Add room utils

* Add mDirect list atom

* Add invite list atom

* add room list atom

* add utils for jotai atoms

* Add room id to parents atom

* Add mute list atom

* Add room to unread atom

* Use hook to bind atoms with sdk

* Add settings atom

* Add settings hook

* Extract set settings hook

* Add Sidebar components

* WIP

* Add bind atoms hook

* Fix init muted room list atom

* add navigation atoms

* Add custom editor

* Fix hotkeys

* Update folds

* Add editor output function

* Add matrix client context

* Add tooltip to editor toolbar items

* WIP - Add editor to room input

* Refocus editor on toolbar item click

* Add Mentions - WIP

* update folds

* update mention focus outline

* rename emoji element type

* Add auto complete menu

* add autocomplete query functions

* add index file for editor

* fix bug in getPrevWord function

* Show room mention autocomplete

* Add async search function

* add use async search hook

* use async search in room mention autocomplete

* remove folds prefer font for now

* allow number array in async search

* reset search with empty query

* Autocomplete unknown room mention

* Autocomplete first room mention on tab

* fix roomAliasFromQueryText

* change mention color to primary

* add isAlive hook

* add getMxIdLocalPart to mx utils

* fix getRoomAvatarUrl size

* fix types

* add room members hook

* fix bug in room mention

* add user mention autocomplete

* Fix async search giving prev result after no match

* update folds

* add twemoji font

* add use state provider hook

* add prevent scroll with arrow key util

* add ts to custom-emoji and emoji files

* add types

* add hook for emoji group labels

* add hook for emoji group icons

* add emoji board with basic emoji

* add emojiboard in room input

* select multiple emoji with shift press

* display custom emoji in emojiboard

* Add emoji preview

* focus element on hover

* update folds

* position emojiboard properly

* convert recent-emoji.js to ts

* add use recent emoji hook

* add io.element.recent_emoji to account data evt

* Render recent emoji in emoji board

* show custom emoji from parent spaces

* show room emoji

* improve emoji sidebar

* update folds

* fix pack avatar and name fallback in emoji board

* add stickers to emoji board

* fix bug in emoji preview

* Add sticker icon in room input

* add debounce hook

* add search in emoji board

* Optimize emoji board

* fix emoji board sidebar divider

* sync emojiboard sidebar with scroll & update ui

* Add use throttle hook

* support custom emoji in editor

* remove duplicate emoji selection function

* fix emoji and mention spacing

* add emoticon autocomplete in editor

* fix string

* makes emoji size relative to font size in editor

* add option to render link element

* add spoiler in editor

* fix sticker in emoji board search using wrong type

* render custom placeholder

* update hotkey for block quote and block code

* add terminate search function in async search

* add getImageInfo to matrix utils

* send stickers

* add resize observer hook

* move emoji board component hooks in hooks dir

* prevent editor expand hides room timeline

* send typing notifications

* improve emoji style and performance

* fix imports

* add on paste param to editor

* add selectFile utils

* add file picker hook

* add file paste handler hook

* add file drop handler

* update folds

* Add file upload card

* add bytes to size util

* add blurHash util

* add await to js lib

* add browser-encrypt-attachment types

* add list atom

* convert mimetype file to ts

* add matrix types

* add matrix file util

* add file related dom utils

* add common utils

* add upload atom

* add room input draft atom

* add upload card renderer component

* add upload board component

* add support for file upload in editor

* send files with message / enter

* fix circular deps

* store editor toolbar state in local store

* move msg content util to separate file

* store msg draft on room switch

* fix following member not updating on msg sent

* add theme for folds component

* fix system default theme

* Add reply support in editor

* prevent initMatrix to init multiple time

* add state event hooks

* add async callback hook

* Show tombstone info for tombstone room

* fix room tombstone component border

* add power level hook

* Add room input placeholder component

* Show input placeholder for muted member
This commit is contained in:
Ajay Bura 2023-06-12 21:15:23 +10:00 committed by GitHub
parent 2055d7a07f
commit 0b06bed1db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
128 changed files with 8799 additions and 409 deletions

133
src/app/utils/dom.ts Normal file
View file

@ -0,0 +1,133 @@
export const targetFromEvent = (evt: Event, selector: string): Element | undefined => {
const targets = evt.composedPath() as Element[];
return targets.find((target) => target.matches?.(selector));
};
export const editableActiveElement = (): boolean =>
!!document.activeElement &&
/^(input)|(textarea)$/.test(document.activeElement.nodeName.toLowerCase());
export const inVisibleScrollArea = (
scrollElement: HTMLElement,
childElement: HTMLElement
): boolean => {
const scrollTop = scrollElement.offsetTop + scrollElement.scrollTop;
const scrollBottom = scrollTop + scrollElement.offsetHeight;
const childTop = childElement.offsetTop;
const childBottom = childTop + childElement.clientHeight;
if (childTop >= scrollTop && childTop < scrollBottom) return true;
if (childTop < scrollTop && childBottom > scrollTop) return true;
return false;
};
export type FilesOrFile<T extends boolean | undefined = undefined> = T extends true ? File[] : File;
export const selectFile = <M extends boolean | undefined = undefined>(
accept: string,
multiple?: M
): Promise<FilesOrFile<M> | undefined> =>
new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
if (accept) input.accept = accept;
if (multiple) input.multiple = true;
const changeHandler = () => {
const fileList = input.files;
if (!fileList) {
resolve(undefined);
} else {
const files: File[] = [...fileList].filter((file) => file);
resolve((multiple ? files : files[0]) as FilesOrFile<M>);
}
input.removeEventListener('change', changeHandler);
};
input.addEventListener('change', changeHandler);
input.click();
});
export const getDataTransferFiles = (dataTransfer: DataTransfer): File[] | undefined => {
const fileList = dataTransfer.files;
const files = [...fileList].filter((file) => file);
if (files.length === 0) return undefined;
return files;
};
export const getImageUrlBlob = async (url: string) => {
const res = await fetch(url);
const blob = await res.blob();
return blob;
};
export const getImageFileUrl = (fileOrBlob: File | Blob) => URL.createObjectURL(fileOrBlob);
export const getVideoFileUrl = (fileOrBlob: File | Blob) => URL.createObjectURL(fileOrBlob);
export const loadImageElement = (url: string): Promise<HTMLImageElement> =>
new Promise((resolve, reject) => {
const img = document.createElement('img');
img.onload = () => resolve(img);
img.onerror = (err) => reject(err);
img.src = url;
});
export const loadVideoElement = (url: string): Promise<HTMLVideoElement> =>
new Promise((resolve, reject) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.playsInline = true;
video.muted = true;
video.onloadeddata = () => {
resolve(video);
video.pause();
};
video.onerror = (e) => {
reject(e);
};
video.src = url;
video.load();
video.play();
});
export const getThumbnailDimensions = (width: number, height: number): [number, number] => {
const MAX_WIDTH = 400;
const MAX_HEIGHT = 300;
let targetWidth = width;
let targetHeight = height;
if (targetHeight > MAX_HEIGHT) {
targetWidth = Math.floor(targetWidth * (MAX_HEIGHT / targetHeight));
targetHeight = MAX_HEIGHT;
}
if (targetWidth > MAX_WIDTH) {
targetHeight = Math.floor(targetHeight * (MAX_WIDTH / targetWidth));
targetWidth = MAX_WIDTH;
}
return [targetWidth, targetHeight];
};
export const getThumbnail = (
img: HTMLImageElement | SVGImageElement | HTMLVideoElement,
width: number,
height: number,
thumbnailMimeType?: string
): Promise<Blob | undefined> =>
new Promise((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
if (!context) {
resolve(undefined);
return;
}
context.drawImage(img, 0, 0, width, height);
canvas.toBlob((thumbnail) => {
resolve(thumbnail ?? undefined);
}, thumbnailMimeType ?? 'image/jpeg');
});