diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/config.json b/config.json index 484c7cd..762c3a3 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,26 @@ ], "allowCustomHomeservers": true, + "featuredCommunities": { + "openAsDefault": false, + "spaces": [ + "#cinny-space:matrix.org", + "#community:matrix.org", + "#space:envs.net", + "#science-space:matrix.org", + "#libregaming-games:tchncs.de", + "#mathematics-on:matrix.org" + ], + "rooms": [ + "#cinny:matrix.org", + "#foundation-office:matrix.org", + "#thisweekinmatrix:matrix.org", + "#matrix-dev:matrix.org", + "#matrix:matrix.org" + ], + "servers": ["envs.net", "matrix.org", "monero.social", "mozilla.org"] + }, + "hashRouter": { "enabled": false, "basename": "/" diff --git a/netlify.toml b/netlify.toml index e7d948e..d79aa91 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,9 +9,10 @@ status = 200 [[redirects]] - from = "/olm.wasm" + from = "*/olm.wasm" to = "/olm.wasm" status = 200 + force = true [[redirects]] from = "/pdf.worker.min.js" @@ -31,4 +32,5 @@ [[redirects]] from = "/*" to = "/index.html" - status = 200 \ No newline at end of file + status = 200 + force = true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2eec291..17e4dd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,15 @@ "version": "3.2.0", "license": "AGPL-3.0-only", "dependencies": { + "@atlaskit/pragmatic-drag-and-drop": "1.1.6", + "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0", + "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3", "@fontsource/inter": "4.5.14", "@khanacademy/simple-markdown": "0.8.6", "@matrix-org/olm": "3.2.14", - "@tanstack/react-virtual": "3.0.0-beta.54", + "@tanstack/react-query": "5.24.1", + "@tanstack/react-query-devtools": "5.24.1", + "@tanstack/react-virtual": "3.2.0", "@tippyjs/react": "4.2.6", "@vanilla-extract/css": "1.9.3", "@vanilla-extract/recipes": "0.3.0", @@ -29,7 +34,7 @@ "file-saver": "2.0.5", "flux": "4.0.3", "focus-trap-react": "10.0.2", - "folds": "1.5.1", + "folds": "2.0.0", "formik": "2.2.9", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", @@ -111,6 +116,34 @@ "node": ">=6.0.0" } }, + "node_modules/@atlaskit/pragmatic-drag-and-drop": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@atlaskit/pragmatic-drag-and-drop/-/pragmatic-drag-and-drop-1.1.6.tgz", + "integrity": "sha512-+jGspaRMyHWB6g9w+N1KImS5I+xt0ML89pwUyCueEhf2KGsl6zyH9ZxjTVKfrbY89FyZvuuXT9oFRHTUKGBi/w==", + "dependencies": { + "@babel/runtime": "^7.0.0", + "bind-event-listener": "^3.0.0", + "raf-schd": "^4.0.3" + } + }, + "node_modules/@atlaskit/pragmatic-drag-and-drop-auto-scroll": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@atlaskit/pragmatic-drag-and-drop-auto-scroll/-/pragmatic-drag-and-drop-auto-scroll-1.3.0.tgz", + "integrity": "sha512-8wjKAI5qSrLojt8ZJ2WhoS5P75oBu5g0yMpAnTDgfqFyQnkt5Uc1txCRWpG26SS1mv19nm8ak9XHF2DOugVfpw==", + "dependencies": { + "@atlaskit/pragmatic-drag-and-drop": "^1.1.0", + "@babel/runtime": "^7.0.0" + } + }, + "node_modules/@atlaskit/pragmatic-drag-and-drop-hitbox": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@atlaskit/pragmatic-drag-and-drop-hitbox/-/pragmatic-drag-and-drop-hitbox-1.0.3.tgz", + "integrity": "sha512-/Sbu/HqN2VGLYBhnsG7SbRNg98XKkbF6L7XDdBi+izRybfaK1FeMfodPpm/xnBHPJzwYMdkE0qtLyv6afhgMUA==", + "dependencies": { + "@atlaskit/pragmatic-drag-and-drop": "^1.1.0", + "@babel/runtime": "^7.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.4.tgz", @@ -3093,25 +3126,75 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@tanstack/react-virtual": { - "version": "3.0.0-beta.54", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.0.0-beta.54.tgz", - "integrity": "sha512-D1mDMf4UPbrtHRZZriCly5bXTBMhylslm4dhcHqTtDJ6brQcgGmk8YD9JdWBGWfGSWPKoh2x1H3e7eh+hgPXtQ==", + "node_modules/@tanstack/query-core": { + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.24.1.tgz", + "integrity": "sha512-DZ6Nx9p7BhjkG50ayJ+MKPgff+lMeol7QYXkvuU5jr2ryW/4ok5eanaS9W5eooA4xN0A/GPHdLGOZGzArgf5Cg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.24.0.tgz", + "integrity": "sha512-pThim455t69zrZaQKa7IRkEIK8UBTS+gHVAdNfhO72Xh4rWpMc63ovRje5/n6iw63+d6QiJzVadsJVdPoodSeQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.24.1.tgz", + "integrity": "sha512-4+09JEdO4d6+Gc8Y/g2M/MuxDK5IY0QV8+2wL2304wPKJgJ54cBbULd3nciJ5uvh/as8rrxx6s0mtIwpRuGd1g==", "dependencies": { - "@tanstack/virtual-core": "3.0.0-beta.54" + "@tanstack/query-core": "5.24.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.24.1.tgz", + "integrity": "sha512-qa4SEugN+EF8JJXcpsM9Lu05HfUv5cvHvLuB0uw/81eJZyNHFdtHFBi5RLCgpBrOyVMDfH8UQ3VBMqXzFKV68A==", + "dependencies": { + "@tanstack/query-devtools": "5.24.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.24.1", + "react": "^18.0.0" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.2.0.tgz", + "integrity": "sha512-OEdMByf2hEfDa6XDbGlZN8qO6bTjlNKqjM3im9JG+u3mCL8jALy0T/67oDI001raUUPh1Bdmfn4ZvPOV5knpcg==", + "dependencies": { + "@tanstack/virtual-core": "3.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@tanstack/virtual-core": { - "version": "3.0.0-beta.54", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.0.0-beta.54.tgz", - "integrity": "sha512-jtkwqdP2rY2iCCDVAFuaNBH3fiEi29aTn2RhtIoky8DTTiCdc48plpHHreLwmv1PICJ4AJUUESaq3xa8fZH8+g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.2.0.tgz", + "integrity": "sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -4065,6 +4148,11 @@ "node": ">=8" } }, + "node_modules/bind-event-listener": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bind-event-listener/-/bind-event-listener-3.0.0.tgz", + "integrity": "sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==" + }, "node_modules/blurhash": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.4.tgz", @@ -5665,9 +5753,9 @@ } }, "node_modules/folds": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/folds/-/folds-1.5.1.tgz", - "integrity": "sha512-2QxyA+FRKjPKXDTMDoD7NmOUiReWrKYO0Msg44QqlzTkTrRVEzJgyPIfC/Ia4/u0ByQpk6dbq8UQxomKmneJ/g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/folds/-/folds-2.0.0.tgz", + "integrity": "sha512-lKv31vij4GEpEzGKWk5c3ar78fMZ9Di5n1XFR14Z2wnnpqhiiM5JTIzr127Gk5dOfy4mJkjnv/ZfMZvM2k+OQg==", "peerDependencies": { "@vanilla-extract/css": "^1.9.2", "@vanilla-extract/recipes": "^0.3.0", @@ -7580,6 +7668,11 @@ } ] }, + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", diff --git a/package.json b/package.json index 44c28d3..e626e83 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,15 @@ "author": "Ajay Bura", "license": "AGPL-3.0-only", "dependencies": { + "@atlaskit/pragmatic-drag-and-drop": "1.1.6", + "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0", + "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3", "@fontsource/inter": "4.5.14", "@khanacademy/simple-markdown": "0.8.6", "@matrix-org/olm": "3.2.14", - "@tanstack/react-virtual": "3.0.0-beta.54", + "@tanstack/react-query": "5.24.1", + "@tanstack/react-query-devtools": "5.24.1", + "@tanstack/react-virtual": "3.2.0", "@tippyjs/react": "4.2.6", "@vanilla-extract/css": "1.9.3", "@vanilla-extract/recipes": "0.3.0", @@ -40,7 +45,7 @@ "file-saver": "2.0.5", "flux": "4.0.3", "focus-trap-react": "10.0.2", - "folds": "1.5.1", + "folds": "2.0.0", "formik": "2.2.9", "html-dom-parser": "4.0.0", "html-react-parser": "4.2.0", diff --git a/src/app/components/CapabilitiesAndMediaConfigLoader.tsx b/src/app/components/CapabilitiesAndMediaConfigLoader.tsx new file mode 100644 index 0000000..338e528 --- /dev/null +++ b/src/app/components/CapabilitiesAndMediaConfigLoader.tsx @@ -0,0 +1,36 @@ +import { ReactNode, useCallback, useEffect } from 'react'; +import { Capabilities } from 'matrix-js-sdk'; +import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; +import { useMatrixClient } from '../hooks/useMatrixClient'; +import { MediaConfig } from '../hooks/useMediaConfig'; +import { promiseFulfilledResult } from '../utils/common'; + +type CapabilitiesAndMediaConfigLoaderProps = { + children: (capabilities?: Capabilities, mediaConfig?: MediaConfig) => ReactNode; +}; +export function CapabilitiesAndMediaConfigLoader({ + children, +}: CapabilitiesAndMediaConfigLoaderProps) { + const mx = useMatrixClient(); + + const [state, load] = useAsyncCallback< + [Capabilities | undefined, MediaConfig | undefined], + unknown, + [] + >( + useCallback(async () => { + const result = await Promise.allSettled([mx.getCapabilities(true), mx.getMediaConfig()]); + const capabilities = promiseFulfilledResult(result[0]); + const mediaConfig = promiseFulfilledResult(result[1]); + return [capabilities, mediaConfig]; + }, [mx]) + ); + + useEffect(() => { + load(); + }, [load]); + + const [capabilities, mediaConfig] = + state.status === AsyncStatus.Success ? state.data : [undefined, undefined]; + return children(capabilities, mediaConfig); +} diff --git a/src/app/components/CapabilitiesLoader.tsx b/src/app/components/CapabilitiesLoader.tsx new file mode 100644 index 0000000..dad59ec --- /dev/null +++ b/src/app/components/CapabilitiesLoader.tsx @@ -0,0 +1,19 @@ +import { ReactNode, useCallback, useEffect } from 'react'; +import { Capabilities } from 'matrix-js-sdk'; +import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; +import { useMatrixClient } from '../hooks/useMatrixClient'; + +type CapabilitiesLoaderProps = { + children: (capabilities: Capabilities | undefined) => ReactNode; +}; +export function CapabilitiesLoader({ children }: CapabilitiesLoaderProps) { + const mx = useMatrixClient(); + + const [state, load] = useAsyncCallback(useCallback(() => mx.getCapabilities(true), [mx])); + + useEffect(() => { + load(); + }, [load]); + + return children(state.status === AsyncStatus.Success ? state.data : undefined); +} diff --git a/src/app/components/MediaConfigLoader.tsx b/src/app/components/MediaConfigLoader.tsx new file mode 100644 index 0000000..9fd9933 --- /dev/null +++ b/src/app/components/MediaConfigLoader.tsx @@ -0,0 +1,19 @@ +import { ReactNode, useCallback, useEffect } from 'react'; +import { AsyncStatus, useAsyncCallback } from '../hooks/useAsyncCallback'; +import { useMatrixClient } from '../hooks/useMatrixClient'; +import { MediaConfig } from '../hooks/useMediaConfig'; + +type MediaConfigLoaderProps = { + children: (mediaConfig: MediaConfig | undefined) => ReactNode; +}; +export function MediaConfigLoader({ children }: MediaConfigLoaderProps) { + const mx = useMatrixClient(); + + const [state, load] = useAsyncCallback(useCallback(() => mx.getMediaConfig(), [mx])); + + useEffect(() => { + load(); + }, [load]); + + return children(state.status === AsyncStatus.Success ? state.data : undefined); +} diff --git a/src/app/components/Pdf-viewer/PdfViewer.tsx b/src/app/components/Pdf-viewer/PdfViewer.tsx index c440cce..a78c13f 100644 --- a/src/app/components/Pdf-viewer/PdfViewer.tsx +++ b/src/app/components/Pdf-viewer/PdfViewer.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-param-reassign */ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions */ -import React, { FormEventHandler, useEffect, useRef, useState } from 'react'; +import React, { FormEventHandler, MouseEventHandler, useEffect, useRef, useState } from 'react'; import classNames from 'classnames'; import { Box, @@ -13,6 +13,7 @@ import { Input, Menu, PopOut, + RectCords, Scroll, Spinner, Text, @@ -48,7 +49,7 @@ export const PdfViewer = as<'div', PdfViewerProps>( const isError = pdfJSState.status === AsyncStatus.Error || docState.status === AsyncStatus.Error; const [pageNo, setPageNo] = useState(1); - const [openJump, setOpenJump] = useState(false); + const [jumpAnchor, setJumpAnchor] = useState(); useEffect(() => { loadPdfJS(); @@ -86,7 +87,7 @@ export const PdfViewer = as<'div', PdfViewerProps>( if (!jumpInput) return; const jumpTo = parseInt(jumpInput.value, 10); setPageNo(Math.max(1, Math.min(docState.data.numPages, jumpTo))); - setOpenJump(false); + setJumpAnchor(undefined); }; const handlePrevPage = () => { @@ -98,6 +99,10 @@ export const PdfViewer = as<'div', PdfViewerProps>( setPageNo((n) => Math.min(n + 1, docState.data.numPages)); }; + const handleOpenJump: MouseEventHandler = (evt) => { + setJumpAnchor(evt.currentTarget.getBoundingClientRect()); + }; + return (
@@ -187,14 +192,14 @@ export const PdfViewer = as<'div', PdfViewerProps>( setOpenJump(false), + onDeactivate: () => setJumpAnchor(undefined), clickOutsideDeactivates: true, }} > @@ -227,17 +232,14 @@ export const PdfViewer = as<'div', PdfViewerProps>( } > - {(anchorRef) => ( - setOpenJump(!openJump)} - ref={anchorRef} - variant="SurfaceVariant" - radii="300" - aria-pressed={openJump} - > - {`${pageNo}/${docState.data.numPages}`} - - )} + + {`${pageNo}/${docState.data.numPages}`} + () => T; + mediaAutoLoad?: boolean; + urlPreview?: boolean; + highlightRegex?: RegExp; + htmlReactParserOptions: HTMLReactParserOptions; + outlineAttachment?: boolean; +}; +export function RenderMessageContent({ + displayName, + msgType, + ts, + edited, + getContent, + mediaAutoLoad, + urlPreview, + highlightRegex, + htmlReactParserOptions, + outlineAttachment, +}: RenderMessageContentProps) { + const renderFile = () => ( + ( + ( + } + /> + )} + renderAsTextFile={() => ( + } + /> + )} + > + + + )} + outlined={outlineAttachment} + /> + ); + + if (msgType === MsgType.Text) { + return ( + ( + + )} + renderUrlsPreview={ + urlPreview + ? (urls) => ( + + {urls.map((url) => ( + + ))} + + ) + : undefined + } + /> + ); + } + + if (msgType === MsgType.Emote) { + return ( + ( + + )} + renderUrlsPreview={ + urlPreview + ? (urls) => ( + + {urls.map((url) => ( + + ))} + + ) + : undefined + } + /> + ); + } + + if (msgType === MsgType.Notice) { + return ( + ( + + )} + renderUrlsPreview={ + urlPreview + ? (urls) => ( + + {urls.map((url) => ( + + ))} + + ) + : undefined + } + /> + ); + } + + if (msgType === MsgType.Image) { + return ( + ( + } + renderViewer={(p) => } + /> + )} + outlined={outlineAttachment} + /> + ); + } + + if (msgType === MsgType.Video) { + return ( + ( + ( + ( + {body} + )} + /> + ) + : undefined + } + renderVideo={(p) =>