first step
This commit is contained in:
parent
44318d1ecd
commit
4b7f07ef6d
14 changed files with 404 additions and 79 deletions
|
@ -96,6 +96,6 @@
|
|||
<audio id="inviteSound">
|
||||
<source src="./public/sound/invite.ogg" type="audio/ogg" />
|
||||
</audio>
|
||||
<script type="module" src="./src/index.jsx"></script>
|
||||
<script type="module" src="./src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
28
package-lock.json
generated
28
package-lock.json
generated
|
@ -46,6 +46,7 @@
|
|||
"@types/node": "18.11.18",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/sanitize-html": "2.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"@vitejs/plugin-react": "3.0.0",
|
||||
|
@ -1165,6 +1166,15 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||
},
|
||||
"node_modules/@types/sanitize-html": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.8.0.tgz",
|
||||
"integrity": "sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"htmlparser2": "^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
|
||||
|
@ -3668,9 +3678,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
|
@ -4974,9 +4984,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
|
@ -5059,9 +5069,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "0.7.32",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.32.tgz",
|
||||
"integrity": "sha512-f9BESNVhzlhEFf2CHMSj40NWOjYPl1YKYbrvIr/hFTDEmLq7SRbWvm7FcdcpCYT95zrOhC7gZSxjdnnTpBcwVw==",
|
||||
"version": "0.7.33",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz",
|
||||
"integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
"@types/node": "18.11.18",
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/sanitize-html": "2.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.46.1",
|
||||
"@typescript-eslint/parser": "5.46.1",
|
||||
"@vitejs/plugin-react": "3.0.0",
|
||||
|
|
4
src/custom.d.ts
vendored
Normal file
4
src/custom.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module '*.svg' {
|
||||
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
|
||||
export default content;
|
||||
}
|
|
@ -1,6 +1,18 @@
|
|||
import EventEmitter from 'events';
|
||||
|
||||
class AsyncSearch extends EventEmitter {
|
||||
RESULT_SENT: string;
|
||||
dataList: (string | object)[];
|
||||
term: any;
|
||||
searchKeys: any;
|
||||
isContain: boolean;
|
||||
isCaseSensitive: boolean;
|
||||
normalizeUnicode: boolean;
|
||||
ignoreWhitespace: boolean;
|
||||
limit: number;
|
||||
findingList: any[];
|
||||
searchUptoIndex: number;
|
||||
sessionStartTimestamp: number;
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
@ -44,7 +56,17 @@ class AsyncSearch extends EventEmitter {
|
|||
* @param {boolean} [opts.ignoreWhitespace=true]
|
||||
* @param {number} [opts.limit=null] - Stop search after limit
|
||||
*/
|
||||
setup(dataList, opts) {
|
||||
setup(
|
||||
dataList: (string | object)[],
|
||||
opts: {
|
||||
keys?: string | string[];
|
||||
isContain?: boolean;
|
||||
isCaseSensitive?: boolean;
|
||||
normalizeUnicode?: boolean;
|
||||
ignoreWhitespace?: boolean;
|
||||
limit: number;
|
||||
}
|
||||
) {
|
||||
this._reset();
|
||||
this.dataList = dataList;
|
||||
this.searchKeys = opts?.keys || null;
|
||||
|
@ -55,7 +77,7 @@ class AsyncSearch extends EventEmitter {
|
|||
this.limit = opts?.limit || null;
|
||||
}
|
||||
|
||||
search(term) {
|
||||
search(term: any) {
|
||||
this._softReset();
|
||||
|
||||
this.term = this._normalize(term);
|
||||
|
@ -67,7 +89,7 @@ class AsyncSearch extends EventEmitter {
|
|||
this._find(this.sessionStartTimestamp, 0);
|
||||
}
|
||||
|
||||
_find(sessionTimestamp, lastFindingCount) {
|
||||
_find(sessionTimestamp: number, lastFindingCount: number) {
|
||||
if (sessionTimestamp !== this.sessionStartTimestamp) return;
|
||||
this.sessionStartTimestamp = window.performance.now();
|
||||
|
||||
|
@ -93,12 +115,12 @@ class AsyncSearch extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
if (lastFindingCount !== this.findingList.length
|
||||
|| lastFindingCount === 0) this._sendFindings();
|
||||
if (lastFindingCount !== this.findingList.length || lastFindingCount === 0)
|
||||
this._sendFindings();
|
||||
this._softReset();
|
||||
}
|
||||
|
||||
_match(item) {
|
||||
_match(item: string | object) {
|
||||
if (typeof item === 'string') {
|
||||
return this._compare(item);
|
||||
}
|
||||
|
@ -113,14 +135,14 @@ class AsyncSearch extends EventEmitter {
|
|||
return false;
|
||||
}
|
||||
|
||||
_compare(item) {
|
||||
_compare(item: string) {
|
||||
if (typeof item !== 'string') return false;
|
||||
const myItem = this._normalize(item);
|
||||
if (this.isContain) return myItem.indexOf(this.term) !== -1;
|
||||
return myItem.startsWith(this.term);
|
||||
}
|
||||
|
||||
_normalize(item) {
|
||||
_normalize(item: string) {
|
||||
let myItem = item.normalize(this.normalizeUnicode ? 'NFKC' : 'NFC');
|
||||
if (!this.isCaseSensitive) myItem = myItem.toLocaleLowerCase();
|
||||
if (this.ignoreWhitespace) myItem = myItem.replace(/\s/g, '');
|
|
@ -1,9 +1,10 @@
|
|||
class Postie {
|
||||
_topics: Map<string, Map<string, Set<Function>>>;
|
||||
constructor() {
|
||||
this._topics = new Map();
|
||||
}
|
||||
|
||||
_getSubscribers(topic) {
|
||||
_getSubscribers(topic: string) {
|
||||
const subscribers = this._topics.get(topic);
|
||||
if (subscribers === undefined) {
|
||||
throw new Error(`Topic:"${topic}" doesn't exist.`);
|
||||
|
@ -11,7 +12,7 @@ class Postie {
|
|||
return subscribers;
|
||||
}
|
||||
|
||||
_getInboxes(topic, address) {
|
||||
_getInboxes(topic: string, address: string) {
|
||||
const subscribers = this._getSubscribers(topic);
|
||||
const inboxes = subscribers.get(address);
|
||||
if (inboxes === undefined) {
|
||||
|
@ -20,19 +21,17 @@ class Postie {
|
|||
return inboxes;
|
||||
}
|
||||
|
||||
hasTopic(topic) {
|
||||
hasTopic(topic: string) {
|
||||
return this._topics.get(topic) !== undefined;
|
||||
}
|
||||
|
||||
hasSubscriber(topic, address) {
|
||||
hasSubscriber(topic: string, address: string) {
|
||||
const subscribers = this._getSubscribers(topic);
|
||||
return subscribers.get(address) !== undefined;
|
||||
}
|
||||
|
||||
hasTopicAndSubscriber(topic, address) {
|
||||
return (this.hasTopic(topic))
|
||||
? this.hasSubscriber(topic, address)
|
||||
: false;
|
||||
hasTopicAndSubscriber(topic: string, address: string) {
|
||||
return this.hasTopic(topic) ? this.hasSubscriber(topic, address) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,12 +39,12 @@ class Postie {
|
|||
* @param {string} address - Address of subscriber
|
||||
* @param {function} inbox - The inbox function to receive post data
|
||||
*/
|
||||
subscribe(topic, address, inbox) {
|
||||
subscribe(topic: string, address: string, inbox: Set<Function>) {
|
||||
if (typeof inbox !== 'function') {
|
||||
throw new TypeError('Inbox must be a function.');
|
||||
}
|
||||
|
||||
if (this._topics.has(topic) === false) {
|
||||
if (!this._topics.has(topic)) {
|
||||
this._topics.set(topic, new Map());
|
||||
}
|
||||
const subscribers = this._topics.get(topic);
|
||||
|
@ -57,14 +56,17 @@ class Postie {
|
|||
return () => this.unsubscribe(topic, address, inbox);
|
||||
}
|
||||
|
||||
unsubscribe(topic, address, inbox) {
|
||||
unsubscribe(topic: string, address: string, inbox: Function) {
|
||||
const subscribers = this._getSubscribers(topic);
|
||||
if (!subscribers) throw new Error(`Unable to unsubscribe. Topic: "${topic}" doesn't exist.`);
|
||||
|
||||
const inboxes = subscribers.get(address);
|
||||
if (!inboxes) throw new Error(`Unable to unsubscribe. Subscriber on topic:"${topic}" at address:"${address}" doesn't exist`);
|
||||
if (!inboxes)
|
||||
throw new Error(
|
||||
`Unable to unsubscribe. Subscriber on topic:"${topic}" at address:"${address}" doesn't exist`
|
||||
);
|
||||
|
||||
if (!inboxes.delete(inbox)) throw new Error('Unable to unsubscribe. Inbox doesn\'t exist');
|
||||
if (!inboxes.delete(inbox)) throw new Error("Unable to unsubscribe. Inbox doesn't exist");
|
||||
|
||||
if (inboxes.size === 0) subscribers.delete(address);
|
||||
if (subscribers.size === 0) this._topics.delete(topic);
|
||||
|
@ -75,10 +77,12 @@ class Postie {
|
|||
* @param {string|string[]} address - Address of subscriber
|
||||
* @param {*} data - Data to deliver to subscriber
|
||||
*/
|
||||
post(topic, address, data) {
|
||||
const sendPost = (inboxes, addr) => {
|
||||
post(topic: string, address: string | string[], data: any) {
|
||||
const sendPost = (inboxes: Set<Function>, addr: string) => {
|
||||
if (inboxes === undefined) {
|
||||
throw new Error(`Unable to post on topic:"${topic}" at address:"${addr}". Subscriber doesn't exist.`);
|
||||
throw new Error(
|
||||
`Unable to post on topic:"${topic}" at address:"${addr}". Subscriber doesn't exist.`
|
||||
);
|
||||
}
|
||||
inboxes.forEach((inbox) => inbox(data));
|
||||
};
|
||||
|
@ -88,7 +92,7 @@ class Postie {
|
|||
return;
|
||||
}
|
||||
const subscribers = this._getSubscribers(topic);
|
||||
address.forEach((addr) => {
|
||||
address.forEach((addr: string) => {
|
||||
sendPost(subscribers.get(addr), addr);
|
||||
});
|
||||
}
|
|
@ -1,27 +1,27 @@
|
|||
// https://github.com/cloudrac3r/cadencegq/blob/master/pug/mxid.pug
|
||||
|
||||
export function hashCode(str) {
|
||||
export function hashCode(str: string) {
|
||||
let hash = 0;
|
||||
let i;
|
||||
let chr;
|
||||
let i: number;
|
||||
let chr: number;
|
||||
if (str.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
for (i = 0; i < str.length; i += 1) {
|
||||
chr = str.charCodeAt(i);
|
||||
// eslint-disable-next-line no-bitwise
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash = (hash << 5) - hash + chr;
|
||||
// eslint-disable-next-line no-bitwise
|
||||
hash |= 0;
|
||||
}
|
||||
return Math.abs(hash);
|
||||
}
|
||||
|
||||
export function cssColorMXID(userId) {
|
||||
export function cssColorMXID(userId: string) {
|
||||
const colorNumber = hashCode(userId) % 8;
|
||||
return `--mx-uc-${colorNumber + 1}`;
|
||||
}
|
||||
|
||||
export default function colorMXID(userId) {
|
||||
export default function colorMXID(userId: string) {
|
||||
return `var(${cssColorMXID(userId)})`;
|
||||
}
|
233
src/util/common.ts
Normal file
233
src/util/common.ts
Normal file
|
@ -0,0 +1,233 @@
|
|||
/* eslint-disable max-classes-per-file */
|
||||
export function bytesToSize(bytes: number) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes === 0) return 'n/a';
|
||||
const i = Math.floor(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||
if (i === 0) return `${bytes} ${sizes[i]}`;
|
||||
return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
export function diffMinutes(dt2: { getTime: () => number }, dt1: { getTime: () => number }) {
|
||||
let diff = (dt2.getTime() - dt1.getTime()) / 1000;
|
||||
diff /= 60;
|
||||
return Math.abs(Math.round(diff));
|
||||
}
|
||||
|
||||
export function isInSameDay(dt2: Date, dt1: Date) {
|
||||
return (
|
||||
dt2.getFullYear() === dt1.getFullYear() &&
|
||||
dt2.getMonth() === dt1.getMonth() &&
|
||||
dt2.getDate() === dt1.getDate()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
* @param {string} [targetSelector] element selector for Element.matches([selector])
|
||||
*/
|
||||
export function getEventCords(ev: Event, targetSelector: string) {
|
||||
let boxInfo: DOMRect;
|
||||
|
||||
const path = ev.composedPath();
|
||||
const target = targetSelector
|
||||
? path.find((element) => (element as HTMLElement).matches?.(targetSelector))
|
||||
: null;
|
||||
if (target) {
|
||||
boxInfo = (target as HTMLElement).getBoundingClientRect();
|
||||
} else {
|
||||
boxInfo = (ev.target as HTMLElement).getBoundingClientRect();
|
||||
}
|
||||
|
||||
return {
|
||||
x: boxInfo.x,
|
||||
y: boxInfo.y,
|
||||
width: boxInfo.width,
|
||||
height: boxInfo.height,
|
||||
detail: (ev as MouseEvent).detail,
|
||||
};
|
||||
}
|
||||
|
||||
export function abbreviateNumber(number: number) {
|
||||
if (number > 99) return '99+';
|
||||
return number;
|
||||
}
|
||||
|
||||
export class Debounce {
|
||||
timeoutId: any;
|
||||
constructor() {
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} func - callback function
|
||||
* @param {number} wait - wait in milliseconds to call func
|
||||
* @returns {func} debounceCallback - to pass arguments to func callback
|
||||
*/
|
||||
_(func: Function, wait: number) {
|
||||
const debounceCallback = (...args) => {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = setTimeout(() => {
|
||||
func(args);
|
||||
this.timeoutId = null;
|
||||
}, wait);
|
||||
};
|
||||
return debounceCallback;
|
||||
}
|
||||
}
|
||||
|
||||
export class Throttle {
|
||||
timeoutId: any;
|
||||
constructor() {
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {function} func - callback function
|
||||
* @param {number} wait - wait in milliseconds to call func
|
||||
* @returns {function} throttleCallback - to pass arguments to func callback
|
||||
*/
|
||||
_(func: Function, wait: number) {
|
||||
const throttleCallback = (...args) => {
|
||||
if (this.timeoutId !== null) return;
|
||||
this.timeoutId = setTimeout(() => {
|
||||
func(args);
|
||||
this.timeoutId = null;
|
||||
}, wait);
|
||||
};
|
||||
return throttleCallback;
|
||||
}
|
||||
}
|
||||
|
||||
export function getUrlPrams(paramName: string) {
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
return urlParams.get(paramName);
|
||||
}
|
||||
|
||||
export function getScrollInfo(target: HTMLElement) {
|
||||
const scroll = {
|
||||
top: Math.round(target.scrollTop),
|
||||
height: Math.round(target.scrollHeight),
|
||||
viewHeight: Math.round(target.offsetHeight),
|
||||
isScrollable: this.height > this.viewHeight,
|
||||
};
|
||||
return scroll;
|
||||
}
|
||||
|
||||
export function avatarInitials(text) {
|
||||
return [...text][0];
|
||||
}
|
||||
|
||||
export function cssVar(name: string) {
|
||||
return getComputedStyle(document.body).getPropertyValue(name);
|
||||
}
|
||||
|
||||
export function setFavicon(url: string) {
|
||||
const favicon = document.querySelector('#favicon');
|
||||
if (!favicon) return;
|
||||
favicon.setAttribute('href', url);
|
||||
}
|
||||
|
||||
export function copyToClipboard(text: string) {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const host = document.body;
|
||||
const copyInput = document.createElement('input');
|
||||
copyInput.style.position = 'fixed';
|
||||
copyInput.style.opacity = '0';
|
||||
copyInput.value = text;
|
||||
host.append(copyInput);
|
||||
|
||||
copyInput.select();
|
||||
copyInput.setSelectionRange(0, 99999);
|
||||
document.execCommand('Copy');
|
||||
copyInput.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export function suffixRename(name: string | number, validator: Function) {
|
||||
let suffix = 2;
|
||||
let newName = name;
|
||||
do {
|
||||
newName = `name${suffix}`;
|
||||
suffix += 1;
|
||||
} while (validator(newName));
|
||||
|
||||
return newName;
|
||||
}
|
||||
|
||||
export function getImageDimension(file: Blob | MediaSource) {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = async () => {
|
||||
resolve({
|
||||
w: img.width,
|
||||
h: img.height,
|
||||
});
|
||||
URL.revokeObjectURL(img.src);
|
||||
};
|
||||
img.src = URL.createObjectURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
export function scaleDownImage(imageFile: Blob, width: number, height: number) {
|
||||
return new Promise((resolve) => {
|
||||
const imgURL = URL.createObjectURL(imageFile);
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
let newWidth = img.width;
|
||||
let newHeight = img.height;
|
||||
if (newHeight <= height && newWidth <= width) {
|
||||
resolve(imageFile);
|
||||
}
|
||||
|
||||
if (newHeight > height) {
|
||||
newWidth = Math.floor(newWidth * (height / newHeight));
|
||||
newHeight = height;
|
||||
}
|
||||
if (newWidth > width) {
|
||||
newHeight = Math.floor(newHeight * (width / newWidth));
|
||||
newWidth = width;
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = newWidth;
|
||||
canvas.height = newHeight;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, newWidth, newHeight);
|
||||
|
||||
canvas.toBlob((thumbnail) => {
|
||||
URL.revokeObjectURL(imgURL);
|
||||
resolve(thumbnail);
|
||||
}, imageFile.type);
|
||||
};
|
||||
|
||||
img.src = imgURL;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {sigil} string sigil to search for (for example '@', '#' or '$')
|
||||
* @param {flags} string regex flags
|
||||
* @param {prefix} string prefix appended at the beginning of the regex
|
||||
* @returns {RegExp}
|
||||
*/
|
||||
export function idRegex(sigil: string, flags: string, prefix: string): RegExp {
|
||||
const servername = '(?:[a-zA-Z0-9-.]*[a-zA-Z0-9]+|\\[\\S+?\\])(?::\\d+)?';
|
||||
return new RegExp(`${prefix}(${sigil}\\S+:${servername})`, flags);
|
||||
}
|
||||
|
||||
const matrixToRegex = /^https?:\/\/matrix.to\/#\/(\S+:\S+)/;
|
||||
/**
|
||||
* Parses a matrix.to URL into an matrix id.
|
||||
* This function can later be extended to support matrix: URIs
|
||||
* @param {string} uri The URI to parse
|
||||
* @returns {string|null} The id or null if the URI does not match
|
||||
*/
|
||||
export function parseIdUri(uri: string): string | null {
|
||||
const res = decodeURIComponent(uri).match(matrixToRegex);
|
||||
if (!res) return null;
|
||||
return res[1];
|
||||
}
|
|
@ -6,10 +6,12 @@ import HashLockIC from '../../public/res/ic/outlined/hash-lock.svg';
|
|||
import SpaceIC from '../../public/res/ic/outlined/space.svg';
|
||||
import SpaceGlobeIC from '../../public/res/ic/outlined/space-globe.svg';
|
||||
import SpaceLockIC from '../../public/res/ic/outlined/space-lock.svg';
|
||||
import { RoomMember } from 'matrix-js-sdk';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
|
||||
const WELL_KNOWN_URI = '/.well-known/matrix/client';
|
||||
|
||||
export async function getBaseUrl(servername) {
|
||||
export async function getBaseUrl(servername: string) {
|
||||
let protocol = 'https://';
|
||||
if (servername.match(/^https?:\/\//) !== null) protocol = '';
|
||||
const serverDiscoveryUrl = `${protocol}${servername}${WELL_KNOWN_URI}`;
|
||||
|
@ -24,7 +26,7 @@ export async function getBaseUrl(servername) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getUsername(userId) {
|
||||
export function getUsername(userId: string) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const user = mx.getUser(userId);
|
||||
if (user === null) return userId;
|
||||
|
@ -35,11 +37,11 @@ export function getUsername(userId) {
|
|||
return username;
|
||||
}
|
||||
|
||||
export function getUsernameOfRoomMember(roomMember) {
|
||||
export function getUsernameOfRoomMember(roomMember: RoomMember) {
|
||||
return roomMember.name || roomMember.userId;
|
||||
}
|
||||
|
||||
export async function isRoomAliasAvailable(alias) {
|
||||
export async function isRoomAliasAvailable(alias: string) {
|
||||
try {
|
||||
const result = await initMatrix.matrixClient.resolveRoomAlias(alias);
|
||||
if (result.room_id) return false;
|
||||
|
@ -50,7 +52,7 @@ export async function isRoomAliasAvailable(alias) {
|
|||
}
|
||||
}
|
||||
|
||||
export function getPowerLabel(powerLevel) {
|
||||
export function getPowerLabel(powerLevel: number) {
|
||||
if (powerLevel > 9000) return 'Goku';
|
||||
if (powerLevel > 100) return 'Founder';
|
||||
if (powerLevel === 100) return 'Admin';
|
||||
|
@ -58,7 +60,7 @@ export function getPowerLabel(powerLevel) {
|
|||
return null;
|
||||
}
|
||||
|
||||
export function parseReply(rawBody) {
|
||||
export function parseReply(rawBody: string) {
|
||||
if (rawBody?.indexOf('>') !== 0) return null;
|
||||
let body = rawBody.slice(rawBody.indexOf('<') + 1);
|
||||
const user = body.slice(0, body.indexOf('>'));
|
||||
|
@ -79,7 +81,7 @@ export function parseReply(rawBody) {
|
|||
};
|
||||
}
|
||||
|
||||
export function trimHTMLReply(html) {
|
||||
export function trimHTMLReply(html: string | string[]) {
|
||||
if (!html) return html;
|
||||
const suffix = '</mx-reply>';
|
||||
const i = html.indexOf(suffix);
|
||||
|
@ -89,7 +91,7 @@ export function trimHTMLReply(html) {
|
|||
return html.slice(i + suffix.length);
|
||||
}
|
||||
|
||||
export function hasDMWith(userId) {
|
||||
export function hasDMWith(userId: string) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const directIds = [...initMatrix.roomList.directs];
|
||||
|
||||
|
@ -103,18 +105,22 @@ export function hasDMWith(userId) {
|
|||
});
|
||||
}
|
||||
|
||||
export function joinRuleToIconSrc(joinRule, isSpace) {
|
||||
return ({
|
||||
export function joinRuleToIconSrc(joinRule: string | number, isSpace: boolean) {
|
||||
return (
|
||||
{
|
||||
restricted: () => (isSpace ? SpaceIC : HashIC),
|
||||
knock: () => (isSpace ? SpaceLockIC : HashLockIC),
|
||||
invite: () => (isSpace ? SpaceLockIC : HashLockIC),
|
||||
public: () => (isSpace ? SpaceGlobeIC : HashGlobeIC),
|
||||
}[joinRule]?.() || null);
|
||||
}[joinRule]?.() || null
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE: it gives userId with minimum power level 50;
|
||||
function getHighestPowerUserId(room) {
|
||||
const userIdToPower = room.currentState.getStateEvents('m.room.power_levels', '')?.getContent().users;
|
||||
function getHighestPowerUserId(room: Room) {
|
||||
const userIdToPower = room.currentState
|
||||
.getStateEvents('m.room.power_levels', '')
|
||||
?.getContent().users;
|
||||
let powerUserId = null;
|
||||
if (!userIdToPower) return powerUserId;
|
||||
|
||||
|
@ -131,12 +137,12 @@ function getHighestPowerUserId(room) {
|
|||
return powerUserId;
|
||||
}
|
||||
|
||||
export function getIdServer(userId) {
|
||||
export function getIdServer(userId: string) {
|
||||
const idParts = userId.split(':');
|
||||
return idParts[1];
|
||||
}
|
||||
|
||||
export function getServerToPopulation(room) {
|
||||
export function getServerToPopulation(room: Room) {
|
||||
const members = room.getMembers();
|
||||
const serverToPop = {};
|
||||
|
||||
|
@ -154,7 +160,7 @@ export function getServerToPopulation(room) {
|
|||
return serverToPop;
|
||||
}
|
||||
|
||||
export function genRoomVia(room) {
|
||||
export function genRoomVia(room: Room) {
|
||||
const via = [];
|
||||
const userId = getHighestPowerUserId(room);
|
||||
if (userId) {
|
||||
|
@ -163,7 +169,7 @@ export function genRoomVia(room) {
|
|||
}
|
||||
const serverToPop = getServerToPopulation(room);
|
||||
const sortedServers = Object.keys(serverToPop).sort(
|
||||
(svrA, svrB) => serverToPop[svrB] - serverToPop[svrA],
|
||||
(svrA, svrB) => serverToPop[svrB] - serverToPop[svrA]
|
||||
);
|
||||
const mostPop3 = sortedServers.slice(0, 3);
|
||||
if (via.length === 0) return mostPop3;
|
||||
|
@ -173,7 +179,7 @@ export function genRoomVia(room) {
|
|||
return via.concat(mostPop3.slice(0, 2));
|
||||
}
|
||||
|
||||
export function isCrossVerified(deviceId) {
|
||||
export function isCrossVerified(deviceId: string) {
|
||||
try {
|
||||
const mx = initMatrix.matrixClient;
|
||||
const crossSignInfo = mx.getStoredCrossSigningForUser(mx.getUserId());
|
||||
|
@ -201,7 +207,7 @@ export function getDefaultSSKey() {
|
|||
}
|
||||
}
|
||||
|
||||
export function getSSKeyInfo(key) {
|
||||
export function getSSKeyInfo(key: string) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
try {
|
||||
return mx.getAccountData(`m.secret_storage.key.${key}`).getContent();
|
||||
|
@ -210,12 +216,13 @@ export function getSSKeyInfo(key) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function hasDevices(userId) {
|
||||
export async function hasDevices(userId: string) {
|
||||
const mx = initMatrix.matrixClient;
|
||||
try {
|
||||
const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]);
|
||||
return Object.values(usersDeviceMap)
|
||||
.every((userDevices) => (Object.keys(userDevices).length > 0));
|
||||
return Object.values(usersDeviceMap).every(
|
||||
(userDevices) => Object.keys(userDevices).length > 0
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Error determining if it's possible to encrypt to all users: ", e);
|
||||
return false;
|
|
@ -25,7 +25,7 @@ export const ALLOWED_BLOB_MIMETYPES = [
|
|||
'audio/x-flac',
|
||||
];
|
||||
|
||||
export function getBlobSafeMimeType(mimetype) {
|
||||
export function getBlobSafeMimeType(mimetype: string) {
|
||||
if (typeof mimetype !== 'string') return 'application/octet-stream';
|
||||
const [type] = mimetype.split(';');
|
||||
if (!ALLOWED_BLOB_MIMETYPES.includes(type)) {
|
|
@ -1,21 +1,64 @@
|
|||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { HTMLAttributes } from 'react';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
|
||||
const MAX_TAG_NESTING = 100;
|
||||
let mx = null;
|
||||
|
||||
const permittedHtmlTags = [
|
||||
'font', 'del', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
|
||||
'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code',
|
||||
'hr', 'br', 'div', 'table', 'thead', 'tbody', 'tr', 'th',
|
||||
'td', 'caption', 'pre', 'span', 'img', 'details', 'summary',
|
||||
'font',
|
||||
'del',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'blockquote',
|
||||
'p',
|
||||
'a',
|
||||
'ul',
|
||||
'ol',
|
||||
'sup',
|
||||
'sub',
|
||||
'li',
|
||||
'b',
|
||||
'i',
|
||||
'u',
|
||||
'strong',
|
||||
'em',
|
||||
'strike',
|
||||
'code',
|
||||
'hr',
|
||||
'br',
|
||||
'div',
|
||||
'table',
|
||||
'thead',
|
||||
'tbody',
|
||||
'tr',
|
||||
'th',
|
||||
'td',
|
||||
'caption',
|
||||
'pre',
|
||||
'span',
|
||||
'img',
|
||||
'details',
|
||||
'summary',
|
||||
];
|
||||
|
||||
const urlSchemes = ['https', 'http', 'ftp', 'mailto', 'magnet'];
|
||||
|
||||
const permittedTagToAttributes = {
|
||||
font: ['style', 'data-mx-bg-color', 'data-mx-color', 'color'],
|
||||
span: ['style', 'data-mx-bg-color', 'data-mx-color', 'data-mx-spoiler', 'data-mx-maths', 'data-mx-pill', 'data-mx-ping'],
|
||||
span: [
|
||||
'style',
|
||||
'data-mx-bg-color',
|
||||
'data-mx-color',
|
||||
'data-mx-spoiler',
|
||||
'data-mx-maths',
|
||||
'data-mx-pill',
|
||||
'data-mx-ping',
|
||||
],
|
||||
div: ['data-mx-maths'],
|
||||
a: ['name', 'target', 'href', 'rel'],
|
||||
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
||||
|
@ -60,7 +103,8 @@ function transformATag(tagName, attribs) {
|
|||
return pill;
|
||||
}
|
||||
|
||||
const rex = /[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/ug;
|
||||
const rex =
|
||||
/[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/gu;
|
||||
const newHref = attribs.href.replace(rex, (match) => `[e-${match.codePointAt(0).toString(16)}]`);
|
||||
|
||||
return {
|
||||
|
@ -96,7 +140,7 @@ function transformImgTag(tagName, attribs) {
|
|||
};
|
||||
}
|
||||
|
||||
export function sanitizeCustomHtml(matrixClient, body) {
|
||||
export function sanitizeCustomHtml(matrixClient: MatrixClient, body: string) {
|
||||
mx = matrixClient;
|
||||
return sanitizeHtml(body, {
|
||||
allowedTags: permittedHtmlTags,
|
||||
|
@ -128,7 +172,7 @@ export function sanitizeCustomHtml(matrixClient, body) {
|
|||
});
|
||||
}
|
||||
|
||||
export function sanitizeText(body) {
|
||||
export function sanitizeText(body: string) {
|
||||
const tagsToReplace = {
|
||||
'&': '&',
|
||||
'<': '<',
|
|
@ -1,13 +1,13 @@
|
|||
import initMatrix from '../client/initMatrix';
|
||||
|
||||
export function roomIdByActivity(id1, id2) {
|
||||
export function roomIdByActivity(id1: string, id2: string) {
|
||||
const room1 = initMatrix.matrixClient.getRoom(id1);
|
||||
const room2 = initMatrix.matrixClient.getRoom(id2);
|
||||
|
||||
return room2.getLastActiveTimestamp() - room1.getLastActiveTimestamp();
|
||||
}
|
||||
|
||||
export function roomIdByAtoZ(aId, bId) {
|
||||
export function roomIdByAtoZ(aId: string, bId: string) {
|
||||
let aName = initMatrix.matrixClient.getRoom(aId).name;
|
||||
let bName = initMatrix.matrixClient.getRoom(bId).name;
|
||||
|
Loading…
Reference in a new issue