Compare commits
2
Commits
doc-update
..
v2.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17fabcaf02 | ||
|
|
44875d0de0 |
No files matched your search
@@ -25,7 +25,7 @@ jobs:
|
||||
path: dist
|
||||
retention-days: 1
|
||||
- name: Get PR info
|
||||
uses: actions/github-script@v6.1.1
|
||||
uses: actions/github-script@v6.1.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
|
||||
# so instead we get this mess:
|
||||
- name: Download artifact
|
||||
uses: actions/github-script@v6.1.1
|
||||
uses: actions/github-script@v6.1.0
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: unzip -d dist previewbuild.zip && rm previewbuild.zip && unzip pr.json.zip && rm pr.json.zip
|
||||
- name: Read PR Info
|
||||
id: readctx
|
||||
uses: actions/github-script@v6.1.1
|
||||
uses: actions/github-script@v6.1.0
|
||||
with:
|
||||
script: |
|
||||
var fs = require('fs');
|
||||
|
||||
@@ -64,11 +64,10 @@ jobs:
|
||||
NETLIFY_DEPLOY_TO_PROD: true
|
||||
|
||||
push-to-dockerhub:
|
||||
name: Push Docker image to Docker Hub, ghcr
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.0.2
|
||||
@@ -81,19 +80,11 @@ jobs:
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Login to the Container registry
|
||||
uses: docker/login-action@v2.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.0.1
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_USERNAME }}/cinny
|
||||
ghcr.io/${{ github.repository }}
|
||||
images: ajbura/cinny
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v3.1.1
|
||||
with:
|
||||
|
||||
+1
-6
@@ -36,9 +36,4 @@ It is not always possible to phrase every change in such a manner, but it is des
|
||||
|
||||
Also, we use [ESLint](https://eslint.org/) for clean and stylistically consistent code syntax, so make sure your pull request follow it.
|
||||
|
||||
**For any query or design discussion, join our [Matrix room](https://matrix.to/#/#cinny:matrix.org).**
|
||||
|
||||
## Helpful links
|
||||
- [BEM methodology](http://getbem.com/introduction/)
|
||||
- [Atomic design](https://bradfrost.com/blog/post/atomic-web-design/)
|
||||
- [Matrix JavaScript SDK documentation](https://matrix-org.github.io/matrix-js-sdk/19.2.0/index.html)
|
||||
**For any query or design discussion, join our [Matrix room](https://matrix.to/#/#cinny:matrix.org).**
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-present Ajay Bura (ajbura)
|
||||
Copyright (c) 2021 Ajay Bura (ajbura)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Cinny
|
||||
<p>
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/ajbura/cinny/dev/public/res/svg/cinny.svg?sanitize=true"
|
||||
height="16">
|
||||
<span><b>Cinny</b></span>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/ajbura/cinny/releases">
|
||||
<img alt="GitHub release downloads" src="https://img.shields.io/github/downloads/ajbura/cinny/total?logo=github&style=social"></a>
|
||||
<a href="https://hub.docker.com/r/ajbura/cinny">
|
||||
@@ -12,30 +16,21 @@
|
||||
<img alt="Sponsor Cinny" src="https://img.shields.io/opencollective/all/cinny?logo=opencollective&style=social"></a>
|
||||
</p>
|
||||
|
||||
A Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have an instant messaging application that is easy on people and has a modern touch.
|
||||
- [Roadmap](https://github.com/ajbura/cinny/projects/11)
|
||||
**Cinny** is a Matrix client focusing primarily on simple, elegant and secure interface. The main goal is to have a client that is easy on end user
|
||||
and feels a modern chat application.
|
||||
|
||||
- [Contributing](./CONTRIBUTING.md)
|
||||
- [Roadmap](https://github.com/ajbura/cinny/projects/11)
|
||||
|
||||
## Getting started
|
||||
Web app is available at https://app.cinny.in and gets updated on each new release. The `dev` branch is continuously deployed at https://dev.cinny.in but keep in mind that it could have things broken.
|
||||
## Building and Running
|
||||
|
||||
You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
|
||||
### Running pre-compiled
|
||||
|
||||
To host Cinny on your own, download tarball of the app from [GitHub release](https://github.com/cinnyapp/cinny/releases/latest).
|
||||
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
|
||||
To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
|
||||
|
||||
Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by:
|
||||
```
|
||||
docker pull ajbura/cinny
|
||||
```
|
||||
or [ghcr image](https://github.com/cinnyapp/cinny/pkgs/container/cinny) by:
|
||||
```
|
||||
docker pull ghcr.io/cinnyapp/cinny:latest
|
||||
```
|
||||
A tarball of pre-compiled version of the app is provided with each [release](https://github.com/ajbura/cinny/releases).
|
||||
You can serve the application with a webserver of your choosing by simply copying `dist/` directory to the webroot.
|
||||
|
||||
<details>
|
||||
<summary>PGP Public Key to verify tarball</summary>
|
||||
<summary>PGP Public Key to verify pre-compiled tarball</summary>
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
@@ -82,39 +77,48 @@ UeGsouhyuITLwEhScounZDqop+Dx
|
||||
```
|
||||
</details>
|
||||
|
||||
## Local development
|
||||
### Building from source
|
||||
> We recommend using a version manager as versions change very quickly. You will likely need to switch
|
||||
between multiple Node.js versions based on the needs of different projects you're working on. [NVM on windows](https://github.com/coreybutler/nvm-windows#installation--upgrades) on Windows and [nvm](https://github.com/nvm-sh/nvm) on Linux/macOS are pretty good choices. Also recommended nodejs version is 16.15.0 LTS.
|
||||
|
||||
Execute the following commands to start a development server:
|
||||
Execute the following commands to compile the app from its source code:
|
||||
|
||||
```sh
|
||||
npm ci # Installs all dependencies
|
||||
npm start # Serve a development version
|
||||
```
|
||||
|
||||
To build the app:
|
||||
```sh
|
||||
npm run build # Compiles the app into the dist/ directory
|
||||
```
|
||||
|
||||
You can then copy the files to a webserver's webroot of your choice.
|
||||
|
||||
To serve a development version of the app locally for testing, you need to use the command `npm start`.
|
||||
|
||||
### Running with Docker
|
||||
|
||||
This repository includes a Dockerfile, which builds the application from source and serves it with Nginx on port 80. To
|
||||
use this locally, you can build the container like so:
|
||||
|
||||
```
|
||||
docker build -t cinny:latest .
|
||||
```
|
||||
|
||||
You can then run the container you've built with a command similar to this:
|
||||
|
||||
```
|
||||
docker run -p 8080:80 cinny:latest
|
||||
```
|
||||
|
||||
This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by navigating to `http://localhost:8080`.
|
||||
This will forward your `localhost` port 8080 to the container's port 80. You can visit the app in your browser by
|
||||
navigating to `http://localhost:8080`.
|
||||
|
||||
Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by `docker pull ajbura/cinny`.
|
||||
|
||||
### Configuring default Homeserver
|
||||
|
||||
To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2021-present Ajay Bura (ajbura)
|
||||
Copyright (c) 2021 Ajay Bura (ajbura)
|
||||
|
||||
Code licensed under the MIT License: <http://opensource.org/licenses/MIT>
|
||||
|
||||
|
||||
Generated
+1212
-119
File diff suppressed because it is too large.
Load diff
+11
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cinny",
|
||||
"version": "2.1.2",
|
||||
"version": "2.1.3",
|
||||
"description": "Yet another matrix client",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
@@ -17,7 +17,6 @@
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^4.5.12",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@khanacademy/simple-markdown": "^0.8.3",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.12.tgz",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
@@ -32,7 +31,13 @@
|
||||
"katex": "^0.16.0",
|
||||
"linkify-html": "^4.0.0-beta.5",
|
||||
"linkifyjs": "^4.0.0-beta.5",
|
||||
"matrix-js-sdk": "^19.2.0",
|
||||
"matrix-js-sdk": "^19.4.0",
|
||||
"micromark": "^3.0.10",
|
||||
"micromark-extension-gfm": "^2.0.1",
|
||||
"micromark-extension-math": "^2.0.2",
|
||||
"micromark-util-chunked": "^1.0.0",
|
||||
"micromark-util-resolve-all": "^1.0.0",
|
||||
"micromark-util-symbol": "^1.0.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^17.0.2",
|
||||
"react-autosize-textarea": "^7.1.0",
|
||||
@@ -59,7 +64,7 @@
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^4.0.0",
|
||||
"eslint": "^8.22.0",
|
||||
"eslint": "^8.21.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||
@@ -71,7 +76,7 @@
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"mini-css-extract-plugin": "^2.6.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
"sass": "^1.54.4",
|
||||
"sass": "^1.54.3",
|
||||
"sass-loader": "^13.0.2",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
@@ -79,7 +84,7 @@
|
||||
"util": "^0.12.4",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.10.0",
|
||||
"webpack-dev-server": "^4.9.3",
|
||||
"webpack-merge": "^5.7.3"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2021-present Ajay Bura (ajbura)
|
||||
Graphics (c) by Ajay Bura (ajbura)
|
||||
|
||||
Graphic content is licensed under a
|
||||
Creative Commons Attribution 4.0 International License.
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 3V5H17.8L12.9 9.9L14.3 11.3L19 6.6V10.2H21V3H14Z" fill="black"/>
|
||||
<path d="M5 5H10V3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V14.2H19V19H5V5Z" fill="black"/>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve">
|
||||
<g>
|
||||
<polygon points="14,3 14,5 17.8,5 12.9,9.9 14.3,11.3 19,6.6 19,10.2 21,10.2 21,3 "/>
|
||||
<path d="M3,10.2h2V5h5V3H5C3.9,3,3,3.9,3,5V10.2z"/>
|
||||
<path d="M5,14.2H3V19c0,1.1,0.9,2,2,2h5v-2H5V14.2z"/>
|
||||
<path d="M19,19h-5v2h5c1.1,0,2-0.9,2-2v-4.8h-2V19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 703 B |
@@ -42,6 +42,7 @@ function RawModal({
|
||||
shouldCloseOnEsc={closeFromOutside}
|
||||
shouldCloseOnOverlayClick={closeFromOutside}
|
||||
shouldReturnFocusAfterClose={false}
|
||||
closeTimeoutMS={300}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
|
||||
@@ -1,9 +1,33 @@
|
||||
.ReactModal__Overlay {
|
||||
opacity: 0;
|
||||
transition: opacity 200ms var(--fluid-slide-up);
|
||||
}
|
||||
.ReactModal__Overlay--after-open{
|
||||
opacity: 1;
|
||||
}
|
||||
.ReactModal__Overlay--before-close{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
transform: translateY(100%);
|
||||
transition: transform 200ms var(--fluid-slide-up);
|
||||
}
|
||||
|
||||
.ReactModal__Content--after-open{
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.ReactModal__Content--before-close{
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.raw-modal {
|
||||
--small-modal-width: 525px;
|
||||
--medium-modal-width: 712px;
|
||||
--large-modal-width: 1024px;
|
||||
|
||||
position: relative;
|
||||
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: var(--bo-radius);
|
||||
@@ -36,31 +60,4 @@
|
||||
height: 100%;
|
||||
background-color: var(--bg-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
.ReactModal__Overlay {
|
||||
animation: raw-modal--overlay 150ms;
|
||||
}
|
||||
|
||||
.ReactModal__Content {
|
||||
animation: raw-modal--content 150ms;
|
||||
}
|
||||
|
||||
@keyframes raw-modal--content {
|
||||
0% {
|
||||
transform: translateY(100px);
|
||||
opacity: .5;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes raw-modal--overlay {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './ImageLightbox.scss';
|
||||
import FileSaver from 'file-saver';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import RawModal from '../../atoms/modal/RawModal';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
|
||||
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
||||
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
||||
|
||||
function ImageLightbox({
|
||||
url, alt, isOpen, onRequestClose,
|
||||
}) {
|
||||
const handleDownload = () => {
|
||||
FileSaver.saveAs(url, alt);
|
||||
};
|
||||
|
||||
return (
|
||||
<RawModal
|
||||
className="image-lightbox__modal"
|
||||
overlayClassName="image-lightbox__overlay"
|
||||
isOpen={isOpen}
|
||||
onRequestClose={onRequestClose}
|
||||
size="large"
|
||||
>
|
||||
<div className="image-lightbox__header">
|
||||
<Text variant="b2" weight="medium">{alt}</Text>
|
||||
<IconButton onClick={() => window.open(url)} size="small" src={ExternalSVG} />
|
||||
<IconButton onClick={handleDownload} size="small" src={DownloadSVG} />
|
||||
</div>
|
||||
<div className="image-lightbox__content">
|
||||
<img src={url} alt={alt} />
|
||||
</div>
|
||||
</RawModal>
|
||||
);
|
||||
}
|
||||
|
||||
ImageLightbox.propTypes = {
|
||||
url: PropTypes.string.isRequired,
|
||||
alt: PropTypes.string.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onRequestClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ImageLightbox;
|
||||
@@ -1,50 +0,0 @@
|
||||
@use '../../partials/flex';
|
||||
@use '../../partials/text';
|
||||
|
||||
.image-lightbox__modal {
|
||||
box-shadow: none;
|
||||
width: unset;
|
||||
gap: var(--sp-normal);
|
||||
|
||||
border-radius: 0;
|
||||
pointer-events: none;
|
||||
|
||||
& .text {
|
||||
color: white;
|
||||
}
|
||||
& .ic-raw {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.image-lightbox__overlay {
|
||||
background-color: var(--bg-overlay-low);
|
||||
}
|
||||
|
||||
|
||||
.image-lightbox__header > *,
|
||||
.image-lightbox__content > * {
|
||||
pointer-events: all;
|
||||
}
|
||||
.image-lightbox__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > .text {
|
||||
@extend .cp-fx__item-one;
|
||||
@extend .cp-txt__ellipsis;
|
||||
}
|
||||
}
|
||||
.image-lightbox__content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-height: 80vh;
|
||||
|
||||
& img {
|
||||
background-color: var(--bg-surface-low);
|
||||
object-fit: contain;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: var(--bo-radius);
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,47 @@ import { BlurhashCanvas } from 'react-blurhash';
|
||||
import Text from '../../atoms/text/Text';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
import Spinner from '../../atoms/spinner/Spinner';
|
||||
import ImageLightbox from '../image-lightbox/ImageLightbox';
|
||||
|
||||
import DownloadSVG from '../../../../public/res/ic/outlined/download.svg';
|
||||
import ExternalSVG from '../../../../public/res/ic/outlined/external.svg';
|
||||
import PlaySVG from '../../../../public/res/ic/outlined/play.svg';
|
||||
|
||||
import { getBlobSafeMimeType } from '../../../util/mimetypes';
|
||||
// https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts#L73
|
||||
const ALLOWED_BLOB_MIMETYPES = [
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/apng',
|
||||
'image/webp',
|
||||
'image/avif',
|
||||
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
'video/ogg',
|
||||
'video/quicktime',
|
||||
|
||||
'audio/mp4',
|
||||
'audio/webm',
|
||||
'audio/aac',
|
||||
'audio/mpeg',
|
||||
'audio/ogg',
|
||||
'audio/wave',
|
||||
'audio/wav',
|
||||
'audio/x-wav',
|
||||
'audio/x-pn-wav',
|
||||
'audio/flac',
|
||||
'audio/x-flac',
|
||||
];
|
||||
function getBlobSafeMimeType(mimetype) {
|
||||
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
// Required for Chromium browsers
|
||||
if (mimetype === 'video/quicktime') {
|
||||
return 'video/mp4';
|
||||
}
|
||||
return mimetype;
|
||||
}
|
||||
|
||||
async function getDecryptedBlob(response, type, decryptData) {
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
@@ -125,7 +159,6 @@ function Image({
|
||||
}) {
|
||||
const [url, setUrl] = useState(null);
|
||||
const [blur, setBlur] = useState(true);
|
||||
const [lightbox, setLightbox] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let unmounted = false;
|
||||
@@ -140,42 +173,14 @@ function Image({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const toggleLightbox = () => {
|
||||
if (!url) return;
|
||||
setLightbox(!lightbox);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="file-container">
|
||||
<div
|
||||
style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }}
|
||||
className="image-container"
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
onClick={toggleLightbox}
|
||||
onKeyDown={toggleLightbox}
|
||||
>
|
||||
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
|
||||
{ url !== null && (
|
||||
<img
|
||||
style={{ display: blur ? 'none' : 'unset' }}
|
||||
onLoad={() => setBlur(false)}
|
||||
src={url || link}
|
||||
alt={name}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="file-container">
|
||||
<FileHeader name={name} link={url || link} type={type} external />
|
||||
<div style={{ height: width !== null ? getNativeHeight(width, height) : 'unset' }} className="image-container">
|
||||
{ blurhash && blur && <BlurhashCanvas hash={blurhash} punch={1} />}
|
||||
{ url !== null && <img style={{ display: blur ? 'none' : 'unset' }} onLoad={() => setBlur(false)} src={url || link} alt={name} />}
|
||||
</div>
|
||||
{url && (
|
||||
<ImageLightbox
|
||||
url={url}
|
||||
alt={name}
|
||||
isOpen={lightbox}
|
||||
onRequestClose={toggleLightbox}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Image.defaultProps = {
|
||||
|
||||
@@ -62,13 +62,6 @@
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
.image-container {
|
||||
max-height: 460px;
|
||||
img {
|
||||
cursor: pointer;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
|
||||
@@ -37,7 +37,6 @@ import CmdIC from '../../../../public/res/ic/outlined/cmd.svg';
|
||||
import BinIC from '../../../../public/res/ic/outlined/bin.svg';
|
||||
|
||||
import { confirmDialog } from '../confirm-dialog/ConfirmDialog';
|
||||
import { getBlobSafeMimeType } from '../../../util/mimetypes';
|
||||
|
||||
function PlaceholderMessage() {
|
||||
return (
|
||||
@@ -290,11 +289,6 @@ function MessageEdit({ body, onSave, onCancel }) {
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
onCancel();
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && e.shiftKey === false) {
|
||||
e.preventDefault();
|
||||
onSave(editInputRef.current.value);
|
||||
@@ -627,12 +621,7 @@ function genMediaContent(mE) {
|
||||
if (typeof mediaMXC === 'undefined' || mediaMXC === '') return <span style={{ color: 'var(--bg-danger)' }}>Malformed event</span>;
|
||||
|
||||
let msgType = mE.getContent()?.msgtype;
|
||||
const safeMimetype = getBlobSafeMimeType(mContent.info?.mimetype);
|
||||
if (mE.getType() === 'm.sticker') {
|
||||
msgType = 'm.sticker';
|
||||
} else if (safeMimetype === 'application/octet-stream') {
|
||||
msgType = 'm.file';
|
||||
}
|
||||
if (mE.getType() === 'm.sticker') msgType = 'm.sticker';
|
||||
|
||||
const blurhash = mContent?.info?.['xyz.amorgan.blurhash'];
|
||||
|
||||
@@ -714,9 +703,9 @@ function getEditedBody(editedMEvent) {
|
||||
}
|
||||
|
||||
function Message({
|
||||
mEvent, isBodyOnly, roomTimeline,
|
||||
focus, fullTime, isEdit, setEdit, cancelEdit,
|
||||
mEvent, isBodyOnly, roomTimeline, focus, fullTime,
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const roomId = mEvent.getRoomId();
|
||||
const { editedTimeline, reactionTimeline } = roomTimeline ?? {};
|
||||
|
||||
@@ -731,7 +720,7 @@ function Message({
|
||||
const avatarSrc = mEvent.sender?.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop') ?? null;
|
||||
|
||||
const edit = useCallback(() => {
|
||||
setEdit(eventId);
|
||||
setIsEditing(true);
|
||||
}, []);
|
||||
const reply = useCallback(() => {
|
||||
replyTo(senderId, mEvent.getId(), body);
|
||||
@@ -788,7 +777,7 @@ function Message({
|
||||
eventId={mEvent.replyEventId}
|
||||
/>
|
||||
)}
|
||||
{!isEdit && (
|
||||
{!isEditing && (
|
||||
<MessageBody
|
||||
senderName={username}
|
||||
isCustomHTML={isCustomHTML}
|
||||
@@ -797,22 +786,22 @@ function Message({
|
||||
isEdited={isEdited}
|
||||
/>
|
||||
)}
|
||||
{isEdit && (
|
||||
{isEditing && (
|
||||
<MessageEdit
|
||||
body={body}
|
||||
onSave={(newBody) => {
|
||||
if (newBody !== body) {
|
||||
initMatrix.roomsInput.sendEditedMessage(roomId, mEvent, newBody);
|
||||
}
|
||||
cancelEdit();
|
||||
setIsEditing(false);
|
||||
}}
|
||||
onCancel={cancelEdit}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
/>
|
||||
)}
|
||||
{haveReactions && (
|
||||
<MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />
|
||||
)}
|
||||
{roomTimeline && !isEdit && (
|
||||
{roomTimeline && !isEditing && (
|
||||
<MessageOptions
|
||||
roomTimeline={roomTimeline}
|
||||
mEvent={mEvent}
|
||||
@@ -829,9 +818,6 @@ Message.defaultProps = {
|
||||
focus: false,
|
||||
roomTimeline: null,
|
||||
fullTime: false,
|
||||
isEdit: false,
|
||||
setEdit: null,
|
||||
cancelEdit: null,
|
||||
};
|
||||
Message.propTypes = {
|
||||
mEvent: PropTypes.shape({}).isRequired,
|
||||
@@ -839,9 +825,6 @@ Message.propTypes = {
|
||||
roomTimeline: PropTypes.shape({}),
|
||||
focus: PropTypes.bool,
|
||||
fullTime: PropTypes.bool,
|
||||
isEdit: PropTypes.bool,
|
||||
setEdit: PropTypes.func,
|
||||
cancelEdit: PropTypes.func,
|
||||
};
|
||||
|
||||
export { Message, MessageReply, PlaceholderMessage };
|
||||
@@ -15,7 +15,7 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__avatar-container {
|
||||
padding-top: 6px;
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
@@ -101,6 +101,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.message__header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -114,16 +115,10 @@
|
||||
@extend .cp-txt__ellipsis;
|
||||
color: inherit;
|
||||
}
|
||||
& > span:last-child {
|
||||
display: none;
|
||||
}
|
||||
& > span:last-child { display: none; }
|
||||
&:hover {
|
||||
& > span:first-child {
|
||||
display: none;
|
||||
}
|
||||
& > span:last-child {
|
||||
display: block;
|
||||
}
|
||||
& > span:first-child { display: none; }
|
||||
& > span:last-child { display: block; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +168,7 @@
|
||||
& > .text > a {
|
||||
white-space: initial !important;
|
||||
}
|
||||
|
||||
& p:not(:last-child) {
|
||||
margin-bottom: var(--sp-normal);
|
||||
}
|
||||
|
||||
|
||||
& span[data-mx-pill] {
|
||||
background-color: hsla(0, 0%, 64%, 0.15);
|
||||
padding: 0 2px;
|
||||
@@ -200,7 +191,7 @@
|
||||
& span[data-mx-spoiler] {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(124, 124, 124, 0.5);
|
||||
color: transparent;
|
||||
color:transparent;
|
||||
cursor: pointer;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
@@ -229,8 +220,6 @@
|
||||
padding: var(--sp-extra-tight) 0;
|
||||
&-btns button {
|
||||
margin: var(--sp-tight) 0 0 0;
|
||||
padding: var(--sp-ultra-tight) var(--sp-tight);
|
||||
min-width: 0;
|
||||
@include dir.side(margin, 0, var(--sp-tight));
|
||||
}
|
||||
}
|
||||
@@ -266,9 +255,9 @@
|
||||
}
|
||||
&-count {
|
||||
margin: 0 var(--sp-ultra-tight);
|
||||
color: var(--tc-surface-normal);
|
||||
color: var(--tc-surface-normal)
|
||||
}
|
||||
&-tooltip .react-emoji {
|
||||
&-tooltip .react-emoji {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 var(--sp-ultra-tight);
|
||||
@@ -281,19 +270,19 @@
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--bg-surface-active);
|
||||
background-color: var(--bg-surface-active)
|
||||
}
|
||||
|
||||
&--active {
|
||||
background-color: var(--bg-caution-active);
|
||||
|
||||
|
||||
@media (hover: hover) {
|
||||
&:hover {
|
||||
background-color: var(--bg-caution-hover);
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--bg-caution-active);
|
||||
background-color: var(--bg-caution-active)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,12 +303,7 @@
|
||||
|
||||
// markdown formating
|
||||
.message__body {
|
||||
& h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
& h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
margin-bottom: var(--sp-ultra-tight);
|
||||
font-weight: var(--fw-medium);
|
||||
@@ -440,8 +424,7 @@
|
||||
@include scrollbar.scroll__h;
|
||||
@include scrollbar.scroll--auto-hide;
|
||||
|
||||
& td,
|
||||
& th {
|
||||
& td, & th {
|
||||
padding: var(--sp-extra-tight);
|
||||
border: 1px solid var(--bg-surface-border);
|
||||
border-width: 0 1px 1px 0;
|
||||
@@ -449,11 +432,11 @@
|
||||
&:last-child {
|
||||
border-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
[dir='rtl'] & {
|
||||
[dir=rtl] & {
|
||||
border-width: 0 1px 1px 0;
|
||||
}
|
||||
}
|
||||
[dir='rtl'] &:first-child {
|
||||
[dir=rtl] &:first-child {
|
||||
border-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ function SpaceOptions({ roomId, afterOptionSelect }) {
|
||||
|
||||
const handleMarkAsRead = () => {
|
||||
const spaceChildren = roomList.getCategorizedSpaces([roomId]);
|
||||
spaceChildren?.forEach((childIds) => {
|
||||
spaceChildren?.forEach((childIds, spaceId) => {
|
||||
childIds?.forEach((childId) => {
|
||||
markAsRead(childId);
|
||||
});
|
||||
})
|
||||
});
|
||||
afterOptionSelect();
|
||||
};
|
||||
|
||||
@@ -177,7 +177,7 @@ function getUserImagePack(mx) {
|
||||
}
|
||||
|
||||
const userImagePack = ImagePack.parsePack(mx.getUserId(), accountDataEmoji.event.content);
|
||||
if (userImagePack) userImagePack.displayName ??= 'Personal Emoji';
|
||||
userImagePack.displayName ??= 'Personal Emoji';
|
||||
return userImagePack;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { join } from '../../../client/action/room';
|
||||
import { selectRoom, selectTab } from '../../../client/action/navigation';
|
||||
import { selectRoom, selectSpace } from '../../../client/action/navigation';
|
||||
|
||||
import Text from '../../atoms/text/Text';
|
||||
import IconButton from '../../atoms/button/IconButton';
|
||||
@@ -32,7 +32,7 @@ function JoinAliasContent({ term, requestClose }) {
|
||||
const openRoom = (roomId) => {
|
||||
const room = mx.getRoom(roomId);
|
||||
if (!room) return;
|
||||
if (room.isSpaceRoom()) selectTab(roomId);
|
||||
if (room.isSpaceRoom()) selectSpace(roomId);
|
||||
else selectRoom(roomId);
|
||||
requestClose();
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { twemojify } from '../../../util/twemojify';
|
||||
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import cons from '../../../client/state/cons';
|
||||
import { selectTab, selectSpace } from '../../../client/action/navigation';
|
||||
import { selectSpace } from '../../../client/action/navigation';
|
||||
import navigation from '../../../client/state/navigation';
|
||||
import { abbreviateNumber } from '../../../util/common';
|
||||
|
||||
@@ -107,10 +107,7 @@ function DrawerBreadcrumb({ spaceId }) {
|
||||
{ index !== 0 && <RawIcon size="extra-small" src={ChevronRightIC} />}
|
||||
<Button
|
||||
className={index === spacePath.length - 1 ? 'drawer-breadcrumb__btn--selected' : ''}
|
||||
onClick={() => {
|
||||
if (id === cons.tabs.HOME) selectTab(id);
|
||||
else selectSpace(id);
|
||||
}}
|
||||
onClick={() => selectSpace(id)}
|
||||
>
|
||||
<Text variant="b2">{id === cons.tabs.HOME ? 'Home' : twemojify(mx.getRoom(id).name)}</Text>
|
||||
{ noti !== null && (
|
||||
|
||||
@@ -30,7 +30,7 @@ function Home({ spaceId }) {
|
||||
roomIds = spaceChildIds.filter((roomId) => rooms.has(roomId));
|
||||
directIds = spaceChildIds.filter((roomId) => directs.has(roomId));
|
||||
} else {
|
||||
spaceIds = roomList.getOrphanSpaces().filter((id) => !accountData.spaceShortcut.has(id));
|
||||
spaceIds = roomList.getOrphanSpaces();
|
||||
roomIds = roomList.getOrphanRooms();
|
||||
}
|
||||
|
||||
@@ -80,10 +80,10 @@ function Home({ spaceId }) {
|
||||
<RoomsCategory name="People" roomIds={directIds.sort(roomIdByActivity)} drawerPostie={drawerPostie} />
|
||||
)}
|
||||
|
||||
{ isCategorized && [...categories.keys()].sort(roomIdByAtoZ).map((catId) => {
|
||||
{ isCategorized && [...categories].map(([catId, childIds]) => {
|
||||
const rms = [];
|
||||
const dms = [];
|
||||
categories.get(catId).forEach((id) => {
|
||||
childIds.forEach((id) => {
|
||||
if (directs.has(id)) dms.push(id);
|
||||
else rms.push(id);
|
||||
});
|
||||
|
||||
@@ -118,15 +118,7 @@ function handleOnClickCapture(e) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderEvent(
|
||||
roomTimeline,
|
||||
mEvent,
|
||||
prevMEvent,
|
||||
isFocus,
|
||||
isEdit,
|
||||
setEdit,
|
||||
cancelEdit,
|
||||
) {
|
||||
function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
|
||||
const isBodyOnly = (prevMEvent !== null
|
||||
&& prevMEvent.getSender() === mEvent.getSender()
|
||||
&& prevMEvent.getType() !== 'm.room.member'
|
||||
@@ -155,9 +147,6 @@ function renderEvent(
|
||||
roomTimeline={roomTimeline}
|
||||
focus={isFocus}
|
||||
fullTime={false}
|
||||
isEdit={isEdit}
|
||||
setEdit={setEdit}
|
||||
cancelEdit={cancelEdit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -397,8 +386,6 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
const timelineSVRef = useRef(null);
|
||||
const timelineScrollRef = useRef(null);
|
||||
const eventLimitRef = useRef(null);
|
||||
const [editEventId, setEditEventId] = useState(null);
|
||||
const cancelEdit = () => setEditEventId(null);
|
||||
|
||||
const readUptoEvtStore = useStore(roomTimeline);
|
||||
const [onLimitUpdate, forceUpdateLimit] = useForceUpdate();
|
||||
@@ -483,42 +470,6 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
}
|
||||
}, [newEvent]);
|
||||
|
||||
const listenKeyboard = useCallback((event) => {
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) return;
|
||||
if (event.key !== 'ArrowUp') return;
|
||||
if (navigation.isRawModalVisible) return;
|
||||
|
||||
if (document.activeElement.id !== 'message-textarea') return;
|
||||
if (document.activeElement.value !== '') return;
|
||||
|
||||
const {
|
||||
timeline: tl, activeTimeline, liveTimeline, matrixClient: mx,
|
||||
} = roomTimeline;
|
||||
const limit = eventLimitRef.current;
|
||||
if (activeTimeline !== liveTimeline) return;
|
||||
if (tl.length > limit.length) return;
|
||||
|
||||
const mTypes = ['m.text'];
|
||||
for (let i = tl.length - 1; i >= 0; i -= 1) {
|
||||
const mE = tl[i];
|
||||
if (
|
||||
mE.getSender() === mx.getUserId()
|
||||
&& mE.getType() === 'm.room.message'
|
||||
&& mTypes.includes(mE.getContent()?.msgtype)
|
||||
) {
|
||||
setEditEventId(mE.getId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [roomTimeline]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.addEventListener('keydown', listenKeyboard);
|
||||
return () => {
|
||||
document.body.removeEventListener('keydown', listenKeyboard);
|
||||
};
|
||||
}, [listenKeyboard]);
|
||||
|
||||
const handleTimelineScroll = (event) => {
|
||||
const timelineScroll = timelineScrollRef.current;
|
||||
if (!event.target) return;
|
||||
@@ -584,15 +535,7 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
const isFocus = focusId === mEvent.getId();
|
||||
if (isFocus) jumpToItemIndex = itemCountIndex;
|
||||
|
||||
tl.push(renderEvent(
|
||||
roomTimeline,
|
||||
mEvent,
|
||||
isNewEvent ? null : prevMEvent,
|
||||
isFocus,
|
||||
editEventId === mEvent.getId(),
|
||||
setEditEventId,
|
||||
cancelEdit,
|
||||
));
|
||||
tl.push(renderEvent(roomTimeline, mEvent, isNewEvent ? null : prevMEvent, isFocus));
|
||||
itemCountIndex += 1;
|
||||
}
|
||||
if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
&__preview > img {
|
||||
max-height: 40px;
|
||||
border-radius: var(--bo-radius);
|
||||
max-width: 150px;
|
||||
}
|
||||
&__icon {
|
||||
padding: var(--sp-extra-tight);
|
||||
|
||||
@@ -164,7 +164,7 @@ function getUsersActionJsx(roomId, userIds, actionStr) {
|
||||
|
||||
const othersCount = userIds.length - MAX_VISIBLE_COUNT;
|
||||
// eslint-disable-next-line react/jsx-one-expression-per-line
|
||||
return <>{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} others are {actionStr}</>;
|
||||
return <>{u1Jsx}, {u2Jsx}, {u3Jsx} and {othersCount} other are {actionStr}</>;
|
||||
}
|
||||
|
||||
function parseTimelineChange(mEvent) {
|
||||
|
||||
@@ -4,17 +4,6 @@ import { selectTab, selectSpace, selectRoom } from '../action/navigation';
|
||||
|
||||
function initRoomListListener(roomList) {
|
||||
const listenRoomLeave = (roomId) => {
|
||||
const parents = roomList.roomIdToParents.get(roomId);
|
||||
|
||||
if (parents) {
|
||||
[...parents].forEach((pId) => {
|
||||
const data = navigation.spaceToRoom.get(pId);
|
||||
if (data?.roomId === roomId) {
|
||||
navigation.spaceToRoom.delete(pId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (navigation.selectedRoomId === roomId) {
|
||||
selectRoom(null);
|
||||
}
|
||||
@@ -24,8 +13,6 @@ function initRoomListListener(roomList) {
|
||||
if (idIndex === 0) selectTab(cons.tabs.HOME);
|
||||
else selectSpace(navigation.selectedSpacePath[idIndex - 1]);
|
||||
}
|
||||
|
||||
navigation.removeRecentRoom(roomId);
|
||||
};
|
||||
|
||||
roomList.on(cons.events.roomList.ROOM_LEAVED, listenRoomLeave);
|
||||
|
||||
@@ -8,19 +8,12 @@ import AccountData from './state/AccountData';
|
||||
import RoomsInput from './state/RoomsInput';
|
||||
import Notifications from './state/Notifications';
|
||||
import { cryptoCallbacks } from './state/secretStorageKeys';
|
||||
import navigation from './state/navigation';
|
||||
|
||||
global.Olm = require('@matrix-org/olm');
|
||||
|
||||
// logger.disableAll();
|
||||
|
||||
class InitMatrix extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
navigation.initMatrix = this;
|
||||
}
|
||||
|
||||
async init() {
|
||||
await this.startClient();
|
||||
this.setupSync();
|
||||
|
||||
@@ -89,7 +89,7 @@ class RoomList extends EventEmitter {
|
||||
|
||||
child.forEach((childId) => {
|
||||
const room = this.matrixClient.getRoom(childId);
|
||||
if (room === null || room.getMyMembership() !== 'join') return;
|
||||
if (room === null) return;
|
||||
if (room.isSpaceRoom()) categorizeSpace(childId);
|
||||
else mappedChild.add(childId);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import EventEmitter from 'events';
|
||||
import { micromark } from 'micromark';
|
||||
import { gfm, gfmHtml } from 'micromark-extension-gfm';
|
||||
import encrypt from 'browser-encrypt-attachment';
|
||||
import { math } from 'micromark-extension-math';
|
||||
import { encode } from 'blurhash';
|
||||
import { getShortcodeToEmoji } from '../../app/organisms/emoji-board/custom-emoji';
|
||||
import { getBlobSafeMimeType } from '../../util/mimetypes';
|
||||
import { mathExtensionHtml, spoilerExtension, spoilerExtensionHtml } from '../../util/markdown';
|
||||
import { sanitizeText } from '../../util/sanitize';
|
||||
import cons from './cons';
|
||||
import settings from './settings';
|
||||
import { htmlOutput, parser } from '../../util/markdown';
|
||||
|
||||
const blurhashField = 'xyz.amorgan.blurhash';
|
||||
const MXID_REGEX = /\B@\S+:\S+\.\S+[^.,:;?!\s]/g;
|
||||
@@ -101,11 +103,14 @@ function getVideoThumbnail(video, width, height, mimeType) {
|
||||
}
|
||||
|
||||
function getFormattedBody(markdown) {
|
||||
let content = parser(markdown);
|
||||
if (content.length === 1 && content[0].type === 'paragraph') {
|
||||
content = content[0].content;
|
||||
}
|
||||
return htmlOutput(content);
|
||||
const result = micromark(markdown, {
|
||||
extensions: [gfm(), spoilerExtension(), math()],
|
||||
htmlExtensions: [gfmHtml(), spoilerExtensionHtml, mathExtensionHtml],
|
||||
});
|
||||
const bodyParts = result.match(/^(<p>)(.*)(<\/p>)$/);
|
||||
if (bodyParts === null) return result;
|
||||
if (bodyParts[2].indexOf('</p>') >= 0) return result;
|
||||
return bodyParts[2];
|
||||
}
|
||||
|
||||
function getReplyFormattedBody(roomId, reply) {
|
||||
@@ -350,7 +355,7 @@ class RoomsInput extends EventEmitter {
|
||||
}
|
||||
|
||||
async sendFile(roomId, file) {
|
||||
const fileType = getBlobSafeMimeType(file.type).slice(0, file.type.indexOf('/'));
|
||||
const fileType = file.type.slice(0, file.type.indexOf('/'));
|
||||
const info = {
|
||||
mimetype: file.type,
|
||||
size: file.size,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const cons = {
|
||||
version: '2.1.2',
|
||||
version: '2.1.3',
|
||||
secretKey: {
|
||||
ACCESS_TOKEN: 'cinny_access_token',
|
||||
DEVICE_ID: 'cinny_device_id',
|
||||
|
||||
+32
-236
@@ -5,8 +5,6 @@ import cons from './cons';
|
||||
class Navigation extends EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
// this will attached by initMatrix
|
||||
this.initMatrix = {};
|
||||
|
||||
this.selectedTab = cons.tabs.HOME;
|
||||
this.selectedSpaceId = null;
|
||||
@@ -16,20 +14,14 @@ class Navigation extends EventEmitter {
|
||||
this.isRoomSettings = false;
|
||||
this.recentRooms = [];
|
||||
|
||||
this.spaceToRoom = new Map();
|
||||
|
||||
this.rawModelStack = [];
|
||||
}
|
||||
|
||||
_addToSpacePath(roomId, asRoot) {
|
||||
if (typeof roomId !== 'string') {
|
||||
_setSpacePath(roomId) {
|
||||
if (roomId === null || roomId === cons.tabs.HOME) {
|
||||
this.selectedSpacePath = [cons.tabs.HOME];
|
||||
return;
|
||||
}
|
||||
if (asRoot) {
|
||||
this.selectedSpacePath = [roomId];
|
||||
return;
|
||||
}
|
||||
if (this.selectedSpacePath.includes(roomId)) {
|
||||
const spIndex = this.selectedSpacePath.indexOf(roomId);
|
||||
this.selectedSpacePath = this.selectedSpacePath.slice(0, spIndex + 1);
|
||||
@@ -38,223 +30,6 @@ class Navigation extends EventEmitter {
|
||||
this.selectedSpacePath.push(roomId);
|
||||
}
|
||||
|
||||
_mapRoomToSpace(roomId) {
|
||||
const { roomList, accountData } = this.initMatrix;
|
||||
if (
|
||||
this.selectedTab === cons.tabs.HOME
|
||||
&& roomList.rooms.has(roomId)
|
||||
&& !roomList.roomIdToParents.has(roomId)
|
||||
) {
|
||||
this.spaceToRoom.set(cons.tabs.HOME, {
|
||||
roomId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.selectedTab === cons.tabs.DIRECTS && roomList.directs.has(roomId)) {
|
||||
this.spaceToRoom.set(cons.tabs.DIRECTS, {
|
||||
roomId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const parents = roomList.roomIdToParents.get(roomId);
|
||||
if (!parents) return;
|
||||
if (parents.has(this.selectedSpaceId)) {
|
||||
this.spaceToRoom.set(this.selectedSpaceId, {
|
||||
roomId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
} else if (accountData.categorizedSpaces.has(this.selectedSpaceId)) {
|
||||
const categories = roomList.getCategorizedSpaces([this.selectedSpaceId]);
|
||||
const parent = [...parents].find((pId) => categories.has(pId));
|
||||
if (parent) {
|
||||
this.spaceToRoom.set(parent, {
|
||||
roomId,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_selectRoom(roomId, eventId) {
|
||||
const prevSelectedRoomId = this.selectedRoomId;
|
||||
this.selectedRoomId = roomId;
|
||||
if (prevSelectedRoomId !== roomId) this._mapRoomToSpace(roomId);
|
||||
this.removeRecentRoom(prevSelectedRoomId);
|
||||
this.addRecentRoom(prevSelectedRoomId);
|
||||
this.removeRecentRoom(this.selectedRoomId);
|
||||
if (this.isRoomSettings && typeof this.selectedRoomId === 'string') {
|
||||
this.isRoomSettings = !this.isRoomSettings;
|
||||
this.emit(cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings);
|
||||
}
|
||||
this.emit(
|
||||
cons.events.navigation.ROOM_SELECTED,
|
||||
this.selectedRoomId,
|
||||
prevSelectedRoomId,
|
||||
eventId,
|
||||
);
|
||||
}
|
||||
|
||||
_selectTabWithRoom(roomId) {
|
||||
const { roomList, accountData } = this.initMatrix;
|
||||
const { categorizedSpaces } = accountData;
|
||||
|
||||
if (roomList.isOrphan(roomId)) {
|
||||
if (roomList.directs.has(roomId)) {
|
||||
this._selectSpace(null, true, false);
|
||||
this._selectTab(cons.tabs.DIRECTS, false);
|
||||
return;
|
||||
}
|
||||
this._selectSpace(null, true, false);
|
||||
this._selectTab(cons.tabs.HOME, false);
|
||||
return;
|
||||
}
|
||||
|
||||
const parents = roomList.roomIdToParents.get(roomId);
|
||||
|
||||
if (parents.has(this.selectedSpaceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (categorizedSpaces.has(this.selectedSpaceId)) {
|
||||
const categories = roomList.getCategorizedSpaces([this.selectedSpaceId]);
|
||||
if ([...parents].find((pId) => categories.has(pId))) {
|
||||
// No need to select tab
|
||||
// As one of parent is child of selected categorized space.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const spaceInPath = [...this.selectedSpacePath].reverse().find((sId) => parents.has(sId));
|
||||
if (spaceInPath) {
|
||||
this._selectSpace(spaceInPath, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomList.directs.has(roomId)) {
|
||||
this._selectSpace(null, true, false);
|
||||
this._selectTab(cons.tabs.DIRECTS, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parents.size > 0) {
|
||||
const sortedParents = [...parents].sort((p1, p2) => {
|
||||
const t1 = this.spaceToRoom.get(p1)?.timestamp ?? 0;
|
||||
const t2 = this.spaceToRoom.get(p2)?.timestamp ?? 0;
|
||||
return t2 - t1;
|
||||
});
|
||||
this._selectSpace(sortedParents[0], true, false);
|
||||
this._selectTab(sortedParents[0], false);
|
||||
}
|
||||
}
|
||||
|
||||
_getLatestActiveRoomId(roomIds) {
|
||||
const mx = this.initMatrix.matrixClient;
|
||||
|
||||
let ts = 0;
|
||||
let roomId = null;
|
||||
roomIds.forEach((childId) => {
|
||||
const room = mx.getRoom(childId);
|
||||
if (!room) return;
|
||||
const newTs = room.getLastActiveTimestamp();
|
||||
if (newTs > ts) {
|
||||
ts = newTs;
|
||||
roomId = childId;
|
||||
}
|
||||
});
|
||||
return roomId;
|
||||
}
|
||||
|
||||
_getLatestSelectedRoomId(spaceIds) {
|
||||
let ts = 0;
|
||||
let roomId = null;
|
||||
|
||||
spaceIds.forEach((sId) => {
|
||||
const data = this.spaceToRoom.get(sId);
|
||||
if (!data) return;
|
||||
const newTs = data.timestamp;
|
||||
if (newTs > ts) {
|
||||
ts = newTs;
|
||||
roomId = data.roomId;
|
||||
}
|
||||
});
|
||||
return roomId;
|
||||
}
|
||||
|
||||
_selectTab(tabId, selectRoom = true) {
|
||||
this.selectedTab = tabId;
|
||||
if (selectRoom) this._selectRoomWithTab(this.selectedTab);
|
||||
this.emit(cons.events.navigation.TAB_SELECTED, this.selectedTab);
|
||||
}
|
||||
|
||||
_selectSpace(roomId, asRoot, selectRoom = true) {
|
||||
this._addToSpacePath(roomId, asRoot);
|
||||
this.selectedSpaceId = roomId;
|
||||
if (!asRoot && selectRoom) this._selectRoomWithSpace(this.selectedSpaceId);
|
||||
this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
|
||||
}
|
||||
|
||||
_selectRoomWithSpace(spaceId) {
|
||||
if (!spaceId) return;
|
||||
const { roomList, accountData, matrixClient } = this.initMatrix;
|
||||
const { categorizedSpaces } = accountData;
|
||||
|
||||
const data = this.spaceToRoom.get(spaceId);
|
||||
if (data && !categorizedSpaces.has(spaceId)) {
|
||||
this._selectRoom(data.roomId);
|
||||
return;
|
||||
}
|
||||
|
||||
const children = [];
|
||||
|
||||
if (categorizedSpaces.has(spaceId)) {
|
||||
const categories = roomList.getCategorizedSpaces([spaceId]);
|
||||
|
||||
const latestSelectedRoom = this._getLatestSelectedRoomId([...categories.keys()]);
|
||||
|
||||
if (latestSelectedRoom) {
|
||||
this._selectRoom(latestSelectedRoom);
|
||||
return;
|
||||
}
|
||||
|
||||
categories?.forEach((categoryId) => {
|
||||
categoryId?.forEach((childId) => {
|
||||
children.push(childId);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
roomList.getSpaceChildren(spaceId).forEach((id) => {
|
||||
if (matrixClient.getRoom(id)?.isSpaceRoom() === false) {
|
||||
children.push(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!children) {
|
||||
this._selectRoom(null);
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectRoom(this._getLatestActiveRoomId(children));
|
||||
}
|
||||
|
||||
_selectRoomWithTab(tabId) {
|
||||
const { roomList } = this.initMatrix;
|
||||
if (tabId === cons.tabs.HOME || tabId === cons.tabs.DIRECTS) {
|
||||
const data = this.spaceToRoom.get(tabId);
|
||||
if (data) {
|
||||
this._selectRoom(data.roomId);
|
||||
return;
|
||||
}
|
||||
const children = tabId === cons.tabs.HOME ? roomList.getOrphanRooms() : [...roomList.directs];
|
||||
this._selectRoom(this._getLatestActiveRoomId(children));
|
||||
return;
|
||||
}
|
||||
this._selectRoomWithSpace(tabId);
|
||||
}
|
||||
|
||||
removeRecentRoom(roomId) {
|
||||
if (typeof roomId !== 'string') return;
|
||||
const roomIdIndex = this.recentRooms.indexOf(roomId);
|
||||
@@ -284,19 +59,40 @@ class Navigation extends EventEmitter {
|
||||
navigate(action) {
|
||||
const actions = {
|
||||
[cons.actions.navigation.SELECT_TAB]: () => {
|
||||
const roomId = (
|
||||
action.tabId !== cons.tabs.HOME && action.tabId !== cons.tabs.DIRECTS
|
||||
) ? action.tabId : null;
|
||||
|
||||
this._selectSpace(roomId, true);
|
||||
this._selectTab(action.tabId);
|
||||
this.selectedTab = action.tabId;
|
||||
if (this.selectedTab !== cons.tabs.DIRECTS) {
|
||||
if (this.selectedTab === cons.tabs.HOME) {
|
||||
this.selectedSpacePath = [cons.tabs.HOME];
|
||||
this.selectedSpaceId = null;
|
||||
} else {
|
||||
this.selectedSpacePath = [this.selectedTab];
|
||||
this.selectedSpaceId = this.selectedTab;
|
||||
}
|
||||
this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
|
||||
} else this.selectedSpaceId = null;
|
||||
this.emit(cons.events.navigation.TAB_SELECTED, this.selectedTab);
|
||||
},
|
||||
[cons.actions.navigation.SELECT_SPACE]: () => {
|
||||
this._selectSpace(action.roomId, false);
|
||||
this._setSpacePath(action.roomId);
|
||||
this.selectedSpaceId = action.roomId === cons.tabs.HOME ? null : action.roomId;
|
||||
this.emit(cons.events.navigation.SPACE_SELECTED, this.selectedSpaceId);
|
||||
},
|
||||
[cons.actions.navigation.SELECT_ROOM]: () => {
|
||||
if (action.roomId) this._selectTabWithRoom(action.roomId);
|
||||
this._selectRoom(action.roomId, action.eventId);
|
||||
const prevSelectedRoomId = this.selectedRoomId;
|
||||
this.selectedRoomId = action.roomId;
|
||||
this.removeRecentRoom(prevSelectedRoomId);
|
||||
this.addRecentRoom(prevSelectedRoomId);
|
||||
this.removeRecentRoom(this.selectedRoomId);
|
||||
if (this.isRoomSettings && typeof this.selectedRoomId === 'string') {
|
||||
this.isRoomSettings = !this.isRoomSettings;
|
||||
this.emit(cons.events.navigation.ROOM_SETTINGS_TOGGLED, this.isRoomSettings);
|
||||
}
|
||||
this.emit(
|
||||
cons.events.navigation.ROOM_SELECTED,
|
||||
this.selectedRoomId,
|
||||
prevSelectedRoomId,
|
||||
action.eventId,
|
||||
);
|
||||
},
|
||||
[cons.actions.navigation.OPEN_SPACE_SETTINGS]: () => {
|
||||
this.emit(cons.events.navigation.SPACE_SETTINGS_OPENED, action.roomId, action.tabText);
|
||||
|
||||
+2
-3
@@ -105,7 +105,7 @@
|
||||
|
||||
/* shadow and overlay */
|
||||
--bg-overlay: rgba(0, 0, 0, 20%);
|
||||
--bg-overlay-low: rgba(0, 0, 0, 50%);
|
||||
--bg-overlay-low: rgba(0, 0, 0, 85%);
|
||||
|
||||
--bs-popup: 0 0 16px rgba(0, 0, 0, 10%);
|
||||
|
||||
@@ -264,8 +264,7 @@
|
||||
}
|
||||
|
||||
/* shadow and overlay */
|
||||
--bg-overlay: rgba(0, 0, 0, 60%);
|
||||
--bg-overlay-low: rgba(0, 0, 0, 80%);
|
||||
--bg-overlay: rgba(0, 0, 0, 50%);
|
||||
|
||||
--bs-popup: 0 0 16px rgba(0, 0, 0, 25%);
|
||||
|
||||
|
||||
+187
-78
@@ -1,89 +1,198 @@
|
||||
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
||||
/* eslint-disable no-param-reassign */
|
||||
/* eslint-disable no-plusplus */
|
||||
/* eslint-disable no-continue */
|
||||
|
||||
const {
|
||||
defaultRules, parserFor, outputFor, anyScopeRegex, blockRegex, inlineRegex, htmlTag, sanitizeText,
|
||||
} = SimpleMarkdown;
|
||||
import { codes } from 'micromark-util-symbol/codes';
|
||||
import { types } from 'micromark-util-symbol/types';
|
||||
import { resolveAll } from 'micromark-util-resolve-all';
|
||||
import { splice } from 'micromark-util-chunked';
|
||||
|
||||
function mathHtml(wrap, node) {
|
||||
return htmlTag(wrap, htmlTag('code', sanitizeText(node.content)), { 'data-mx-maths': node.content });
|
||||
function inlineExtension(marker, len, key) {
|
||||
const keySeq = `${key}Sequence`;
|
||||
const keySeqTmp = `${keySeq}Temporary`;
|
||||
|
||||
return () => {
|
||||
function tokenize(effects, ok, nok) {
|
||||
const { previous, events } = this;
|
||||
|
||||
let size = 0;
|
||||
|
||||
function more(code) {
|
||||
// consume more markers if the maximum length hasn't been reached yet
|
||||
if (code === marker && size < len) {
|
||||
effects.consume(code);
|
||||
size += 1;
|
||||
return more;
|
||||
}
|
||||
|
||||
// check for minimum length
|
||||
if (size < len) return nok(code);
|
||||
|
||||
effects.exit(keySeqTmp);
|
||||
return ok(code);
|
||||
}
|
||||
|
||||
function start(code) {
|
||||
// ignore code if it's not a marker
|
||||
if (code !== marker) return nok(code);
|
||||
|
||||
if (previous === marker
|
||||
&& events[events.length - 1][1].type !== types.characterEscape) return nok(code);
|
||||
|
||||
effects.enter(keySeqTmp);
|
||||
return more(code);
|
||||
}
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
function resolve(events, context) {
|
||||
let i = -1;
|
||||
|
||||
while (++i < events.length) {
|
||||
if (events[i][0] !== 'enter' || events[i][1].type !== keySeqTmp) continue;
|
||||
|
||||
let open = i;
|
||||
while (open--) {
|
||||
if (events[open][0] !== 'exit' || events[open][1].type !== keySeqTmp) continue;
|
||||
|
||||
events[i][1].type = keySeq;
|
||||
events[open][1].type = keySeq;
|
||||
|
||||
const border = {
|
||||
type: key,
|
||||
start: { ...events[open][1].start },
|
||||
end: { ...events[i][1].end },
|
||||
};
|
||||
|
||||
const text = {
|
||||
type: `${key}Text`,
|
||||
start: { ...events[open][1].end },
|
||||
end: { ...events[i][1].start },
|
||||
};
|
||||
|
||||
const nextEvents = [
|
||||
['enter', border, context],
|
||||
['enter', events[open][1], context],
|
||||
['exit', events[open][1], context],
|
||||
['enter', text, context],
|
||||
];
|
||||
|
||||
splice(
|
||||
nextEvents,
|
||||
nextEvents.length,
|
||||
0,
|
||||
resolveAll(
|
||||
context.parser.constructs.insideSpan.null,
|
||||
events.slice(open + 1, i),
|
||||
context,
|
||||
),
|
||||
);
|
||||
|
||||
splice(nextEvents, nextEvents.length, 0, [
|
||||
['exit', text, context],
|
||||
['enter', events[i][1], context],
|
||||
['exit', events[i][1], context],
|
||||
['exit', border, context],
|
||||
]);
|
||||
|
||||
splice(events, open - 1, i - open + 3, nextEvents);
|
||||
|
||||
i = open + nextEvents.length - 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
events.forEach((event) => {
|
||||
if (event[1].type === keySeqTmp) {
|
||||
event[1].type = types.data;
|
||||
}
|
||||
});
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
const tokenizer = {
|
||||
tokenize,
|
||||
resolveAll: resolve,
|
||||
};
|
||||
|
||||
return {
|
||||
text: { [marker]: tokenizer },
|
||||
insideSpan: { null: [tokenizer] },
|
||||
attentionMarkers: { null: [marker] },
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const rules = {
|
||||
...defaultRules,
|
||||
Array: {
|
||||
...defaultRules.Array,
|
||||
plain: (arr, output, state) => arr.map((node) => output(node, state)).join(''),
|
||||
const spoilerExtension = inlineExtension(codes.verticalBar, 2, 'spoiler');
|
||||
|
||||
const spoilerExtensionHtml = {
|
||||
enter: {
|
||||
spoiler() {
|
||||
this.tag('<span data-mx-spoiler>');
|
||||
},
|
||||
},
|
||||
displayMath: {
|
||||
order: defaultRules.list.order + 0.5,
|
||||
match: blockRegex(/^\$\$\n*([\s\S]+?)\n*\$\$/),
|
||||
parse: (capture) => ({ content: capture[1] }),
|
||||
plain: (node) => `$$\n${node.content}\n$$`,
|
||||
html: (node) => mathHtml('div', node),
|
||||
},
|
||||
newline: {
|
||||
...defaultRules.newline,
|
||||
plain: () => '\n',
|
||||
},
|
||||
paragraph: {
|
||||
...defaultRules.paragraph,
|
||||
plain: (node, output, state) => `${output(node.content, state)}\n\n`,
|
||||
html: (node, output, state) => htmlTag('p', output(node.content, state)),
|
||||
},
|
||||
escape: {
|
||||
...defaultRules.escape,
|
||||
plain: (node, output, state) => `\\${output(node.content, state)}`,
|
||||
},
|
||||
em: {
|
||||
...defaultRules.em,
|
||||
plain: (node, output, state) => `_${output(node.content, state)}_`,
|
||||
},
|
||||
strong: {
|
||||
...defaultRules.strong,
|
||||
plain: (node, output, state) => `**${output(node.content, state)}**`,
|
||||
},
|
||||
u: {
|
||||
...defaultRules.u,
|
||||
plain: (node, output, state) => `__${output(node.content, state)}__`,
|
||||
},
|
||||
del: {
|
||||
...defaultRules.del,
|
||||
plain: (node, output, state) => `~~${output(node.content, state)}~~`,
|
||||
},
|
||||
spoiler: {
|
||||
order: defaultRules.em.order - 0.5,
|
||||
match: inlineRegex(/^\|\|([\s\S]+?)\|\|(?:\(([\s\S]+?)\))?/),
|
||||
parse: (capture, parse, state) => ({
|
||||
content: parse(capture[1], state),
|
||||
reason: capture[2],
|
||||
}),
|
||||
plain: (node) => `[spoiler${node.reason ? `: ${node.reason}` : ''}](mxc://somewhere)`,
|
||||
html: (node, output, state) => `<span data-mx-spoiler${node.reason ? `="${sanitizeText(node.reason)}"` : ''}>${output(node.content, state)}</span>`,
|
||||
},
|
||||
inlineMath: {
|
||||
order: defaultRules.del.order + 0.5,
|
||||
match: inlineRegex(/^\$(\S[\s\S]+?\S|\S)\$(?!\d)/),
|
||||
parse: (capture) => ({ content: capture[1] }),
|
||||
plain: (node) => `$${node.content}$`,
|
||||
html: (node) => mathHtml('span', node),
|
||||
},
|
||||
br: {
|
||||
...defaultRules.br,
|
||||
match: anyScopeRegex(/^ *\n/),
|
||||
plain: () => '\n',
|
||||
},
|
||||
text: {
|
||||
...defaultRules.text,
|
||||
match: anyScopeRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]| *\n|\w+:\S|$)/),
|
||||
plain: (node) => node.content,
|
||||
exit: {
|
||||
spoiler() {
|
||||
this.tag('</span>');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const parser = parserFor(rules);
|
||||
|
||||
const plainOutput = outputFor(rules, 'plain');
|
||||
const htmlOutput = outputFor(rules, 'html');
|
||||
const mathExtensionHtml = {
|
||||
enter: {
|
||||
mathFlow() {
|
||||
this.lineEndingIfNeeded();
|
||||
},
|
||||
mathFlowFenceMeta() {
|
||||
this.buffer();
|
||||
},
|
||||
mathText() {
|
||||
this.buffer();
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
mathFlow() {
|
||||
const value = this.encode(this.resume().replace(/(?:\r?\n|\r)$/, ''));
|
||||
this.tag('<div data-mx-maths="');
|
||||
this.tag(value);
|
||||
this.tag('"><code>');
|
||||
this.raw(value);
|
||||
this.tag('</code></div>');
|
||||
this.setData('mathFlowOpen');
|
||||
this.setData('slurpOneLineEnding');
|
||||
},
|
||||
mathFlowFence() {
|
||||
// After the first fence.
|
||||
if (!this.getData('mathFlowOpen')) {
|
||||
this.setData('mathFlowOpen', true);
|
||||
this.setData('slurpOneLineEnding', true);
|
||||
this.buffer();
|
||||
}
|
||||
},
|
||||
mathFlowFenceMeta() {
|
||||
this.resume();
|
||||
},
|
||||
mathFlowValue(token) {
|
||||
this.raw(this.sliceSerialize(token));
|
||||
},
|
||||
mathText() {
|
||||
const value = this.encode(this.resume());
|
||||
this.tag('<span data-mx-maths="');
|
||||
this.tag(value);
|
||||
this.tag('"><code>');
|
||||
this.raw(value);
|
||||
this.tag('</code></span>');
|
||||
},
|
||||
mathTextData(token) {
|
||||
this.raw(this.sliceSerialize(token));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
parser, plainOutput, htmlOutput,
|
||||
inlineExtension,
|
||||
spoilerExtension, spoilerExtensionHtml,
|
||||
mathExtensionHtml,
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
// https://github.com/matrix-org/matrix-react-sdk/blob/cd15e08fc285da42134817cce50de8011809cd53/src/utils/blobs.ts
|
||||
export const ALLOWED_BLOB_MIMETYPES = [
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/apng',
|
||||
'image/webp',
|
||||
'image/avif',
|
||||
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
'video/ogg',
|
||||
'video/quicktime',
|
||||
|
||||
'audio/mp4',
|
||||
'audio/webm',
|
||||
'audio/aac',
|
||||
'audio/mpeg',
|
||||
'audio/ogg',
|
||||
'audio/wave',
|
||||
'audio/wav',
|
||||
'audio/x-wav',
|
||||
'audio/x-pn-wav',
|
||||
'audio/flac',
|
||||
'audio/x-flac',
|
||||
];
|
||||
|
||||
export function getBlobSafeMimeType(mimetype) {
|
||||
if (!ALLOWED_BLOB_MIMETYPES.includes(mimetype)) {
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
// Required for Chromium browsers
|
||||
if (mimetype === 'video/quicktime') {
|
||||
return 'video/mp4';
|
||||
}
|
||||
return mimetype;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ const permittedTagToAttributes = {
|
||||
div: ['data-mx-maths'],
|
||||
a: ['name', 'target', 'href', 'rel'],
|
||||
img: ['width', 'height', 'alt', 'title', 'src', 'data-mx-emoticon'],
|
||||
ol: ['start'],
|
||||
o: ['start'],
|
||||
code: ['class'],
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user