Merge pull request #131 from ajbura/dev

v1.3.2
This commit is contained in:
Krishan 2021-10-06 13:22:18 +05:30 committed by GitHub
commit bdb94d7145
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 121 additions and 29 deletions

View file

@ -14,6 +14,11 @@ Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, ele
## Building and Running ## Building and Running
### Running pre-compiled
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.
### Building from source ### Building from source
Execute the following commands to compile the app from its source code: Execute the following commands to compile the app from its source code:
@ -44,10 +49,12 @@ 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 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`. 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`.
## License ## License
Copyright (c) 2021 Ajay Bura (ajbura) and other contributors Copyright (c) 2021 Ajay Bura (ajbura) and other contributors
Code licensed under the MIT License: <http://opensource.org/licenses/MIT> Code licensed under the MIT License: <http://opensource.org/licenses/MIT>
Graphics licensed under CC-BY 4.0: <https://creativecommons.org/licenses/by/4.0/> Graphics licensed under CC-BY 4.0: <https://creativecommons.org/licenses/by/4.0/>

2
package-lock.json generated
View file

@ -1,6 +1,6 @@
{ {
"name": "cinny", "name": "cinny",
"version": "1.3.1", "version": "1.3.2",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "cinny", "name": "cinny",
"version": "1.3.1", "version": "1.3.2",
"description": "Yet another matrix client", "description": "Yet another matrix client",
"main": "index.js", "main": "index.js",
"engines": { "engines": {

View file

@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Chip.scss';
import Text from '../text/Text';
import RawIcon from '../system-icons/RawIcon';
function Chip({
iconSrc, iconColor, text, children,
}) {
return (
<div className="chip">
{iconSrc != null && <RawIcon src={iconSrc} color={iconColor} size="small" />}
{(text != null && text !== '') && <Text variant="b2">{text}</Text>}
{children}
</div>
);
}
Chip.propTypes = {
iconSrc: PropTypes.string,
iconColor: PropTypes.string,
text: PropTypes.string,
children: PropTypes.element,
};
Chip.defaultProps = {
iconSrc: null,
iconColor: null,
text: null,
children: null,
};
export default Chip;

View file

@ -0,0 +1,19 @@
.chip {
padding: var(--sp-ultra-tight) var(--sp-extra-tight);
display: inline-flex;
flex-direction: row;
align-items: center;
background: var(--bg-surface-low);
border-radius: var(--bo-radius);
border: 1px solid var(--bg-surface-border);
& > .ic-raw {
margin-right: var(--sp-extra-tight);
[dir=rtl] & {
margin-right: 0;
margin-left: var(--sp-extra-tight);
}
}
}

View file

@ -31,6 +31,7 @@ function ContextMenu({
interactive interactive
arrow={false} arrow={false}
maxWidth={maxWidth} maxWidth={maxWidth}
duration={200}
> >
{render(isVisible ? hideMenu : showMenu)} {render(isVisible ? hideMenu : showMenu)}
</Tippy> </Tippy>

View file

@ -139,14 +139,20 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) {
updateIsViewMore(false); updateIsViewMore(false);
if (totalRooms.length === 0) { if (totalRooms.length === 0) {
updateSearchQuery({ updateSearchQuery({
error: `No result found for "${inputRoomName}" on ${inputHs}`, error: inputRoomName === ''
? `No public rooms on ${inputHs}`
: `No result found for "${inputRoomName}" on ${inputHs}`,
alias: isInputAlias ? inputRoomName : null, alias: isInputAlias ? inputRoomName : null,
}); });
} }
} catch (e) { } catch (e) {
updatePublicRooms([]); updatePublicRooms([]);
let err = 'Something went wrong!';
if (e?.httpStatus >= 400 && e?.httpStatus < 500) {
err = e.message;
}
updateSearchQuery({ updateSearchQuery({
error: 'Something went wrong!', error: err,
alias: isInputAlias ? inputRoomName : null, alias: isInputAlias ? inputRoomName : null,
}); });
updateIsSearching(false); updateIsSearching(false);

View file

@ -327,7 +327,15 @@ function RoomViewInput({
if (file !== null) roomsInput.setAttachment(roomId, file); if (file !== null) roomsInput.setAttachment(roomId, file);
} }
const myPowerlevel = roomTimeline.room.getMember(mx.getUserId()).powerLevel;
const canISend = roomTimeline.room.currentState.hasSufficientPowerLevelFor('events_default', myPowerlevel);
function renderInputs() { function renderInputs() {
if (!canISend) {
return (
<Text className="room-input__disallowed">You do not have permission to post to this room</Text>
);
}
return ( return (
<> <>
<div className={`room-input__option-container${attachment === null ? '' : ' room-attachment__option'}`}> <div className={`room-input__option-container${attachment === null ? '' : ' room-attachment__option'}`}>

View file

@ -3,6 +3,11 @@
display: flex; display: flex;
min-height: 48px; min-height: 48px;
&__disallowed {
flex: 1;
text-align: center;
}
&__space { &__space {
min-width: 0; min-width: 0;
align-self: center; align-self: center;

View file

@ -104,7 +104,7 @@ function AboutSection() {
<div> <div>
<Text variant="h2"> <Text variant="h2">
Cinny Cinny
<span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>v1.3.1</span> <span className="text text-b3" style={{ margin: '0 var(--sp-extra-tight)' }}>v1.3.2</span>
</Text> </Text>
<Text>Yet another matrix client</Text> <Text>Yet another matrix client</Text>

View file

@ -3,7 +3,7 @@ import {
BrowserRouter, Switch, Route, Redirect, BrowserRouter, Switch, Route, Redirect,
} from 'react-router-dom'; } from 'react-router-dom';
import { isAuthanticated } from '../../client/state/auth'; import { isAuthenticated } from '../../client/state/auth';
import Auth from '../templates/auth/Auth'; import Auth from '../templates/auth/Auth';
import Client from '../templates/client/Client'; import Client from '../templates/client/Client';
@ -13,13 +13,13 @@ function App() {
<BrowserRouter> <BrowserRouter>
<Switch> <Switch>
<Route exact path="/"> <Route exact path="/">
{ isAuthanticated() ? <Client /> : <Redirect to="/login" />} { isAuthenticated() ? <Client /> : <Redirect to="/login" />}
</Route> </Route>
<Route path="/login"> <Route path="/login">
{ isAuthanticated() ? <Redirect to="/" /> : <Auth type="login" />} { isAuthenticated() ? <Redirect to="/" /> : <Auth type="login" />}
</Route> </Route>
<Route path="/register"> <Route path="/register">
{ isAuthanticated() ? <Redirect to="/" /> : <Auth type="register" />} { isAuthenticated() ? <Redirect to="/" /> : <Auth type="register" />}
</Route> </Route>
</Switch> </Switch>
</BrowserRouter> </BrowserRouter>

View file

@ -32,6 +32,7 @@ const EMAIL_REGEX = /([a-z0-9]+[_a-z0-9.-][a-z0-9]+)@([a-z0-9-]+(?:.[a-z0-9-]+).
const BAD_EMAIL_ERROR = 'Invalid email address'; const BAD_EMAIL_ERROR = 'Invalid email address';
function isValidInput(value, regex) { function isValidInput(value, regex) {
if (typeof regex === 'string') return regex === value;
return regex.test(value); return regex.test(value);
} }
function renderErrorMessage(error) { function renderErrorMessage(error) {
@ -39,24 +40,25 @@ function renderErrorMessage(error) {
$error.textContent = error; $error.textContent = error;
$error.style.display = 'block'; $error.style.display = 'block';
} }
function showBadInputError($input, error) { function showBadInputError($input, error, stopAutoFocus) {
renderErrorMessage(error); renderErrorMessage(error);
$input.focus(); if (!stopAutoFocus) $input.focus();
const myInput = $input; const myInput = $input;
myInput.style.border = '1px solid var(--bg-danger)'; myInput.style.border = '1px solid var(--bg-danger)';
myInput.style.boxShadow = 'none'; myInput.style.boxShadow = 'none';
document.getElementById('auth_submit-btn').disabled = true; document.getElementById('auth_submit-btn').disabled = true;
} }
function validateOnChange(e, regex, error) { function validateOnChange(targetInput, regex, error, stopAutoFocus) {
if (!isValidInput(e.target.value, regex) && e.target.value) { if (!isValidInput(targetInput.value, regex) && targetInput.value) {
showBadInputError(e.target, error); showBadInputError(targetInput, error, stopAutoFocus);
return; return false;
} }
document.getElementById('auth_error').style.display = 'none'; document.getElementById('auth_error').style.display = 'none';
e.target.style.removeProperty('border'); targetInput.style.removeProperty('border');
e.target.style.removeProperty('box-shadow'); targetInput.style.removeProperty('box-shadow');
document.getElementById('auth_submit-btn').disabled = false; document.getElementById('auth_submit-btn').disabled = false;
return true;
} }
/** /**
@ -195,8 +197,8 @@ function Auth({ type }) {
<Input <Input
forwardRef={usernameRef} forwardRef={usernameRef}
onChange={(e) => (type === 'login' onChange={(e) => (type === 'login'
? validateOnChange(e, LOCALPART_LOGIN_REGEX, BAD_LOCALPART_ERROR) ? validateOnChange(e.target, LOCALPART_LOGIN_REGEX, BAD_LOCALPART_ERROR)
: validateOnChange(e, LOCALPART_SIGNUP_REGEX, BAD_LOCALPART_ERROR))} : validateOnChange(e.target, LOCALPART_SIGNUP_REGEX, BAD_LOCALPART_ERROR))}
id="auth_username" id="auth_username"
label="Username" label="Username"
required required
@ -212,7 +214,15 @@ function Auth({ type }) {
<div className="password__wrapper"> <div className="password__wrapper">
<Input <Input
forwardRef={passwordRef} forwardRef={passwordRef}
onChange={(e) => validateOnChange(e, ((type === 'login') ? PASSWORD_REGEX : PASSWORD_STRENGHT_REGEX), BAD_PASSWORD_ERROR)} onChange={(e) => {
const isValidPass = validateOnChange(e.target, ((type === 'login') ? PASSWORD_REGEX : PASSWORD_STRENGHT_REGEX), BAD_PASSWORD_ERROR);
if (type === 'register' && isValidPass) {
validateOnChange(
confirmPasswordRef.current, passwordRef.current.value,
CONFIRM_PASSWORD_ERROR, true,
);
}
}}
id="auth_password" id="auth_password"
type="password" type="password"
label="Password" label="Password"
@ -233,7 +243,9 @@ function Auth({ type }) {
<div className="password__wrapper"> <div className="password__wrapper">
<Input <Input
forwardRef={confirmPasswordRef} forwardRef={confirmPasswordRef}
onChange={(e) => validateOnChange(e, new RegExp(`^(${passwordRef.current.value})$`), CONFIRM_PASSWORD_ERROR)} onChange={(e) => {
validateOnChange(e.target, passwordRef.current.value, CONFIRM_PASSWORD_ERROR);
}}
id="auth_confirmPassword" id="auth_confirmPassword"
type="password" type="password"
label="Confirm password" label="Confirm password"
@ -251,7 +263,7 @@ function Auth({ type }) {
</div> </div>
<Input <Input
forwardRef={emailRef} forwardRef={emailRef}
onChange={(e) => validateOnChange(e, EMAIL_REGEX, BAD_EMAIL_ERROR)} onChange={(e) => validateOnChange(e.target, EMAIL_REGEX, BAD_EMAIL_ERROR)}
id="auth_email" id="auth_email"
type="email" type="email"
label="Email" label="Email"

View file

@ -4,7 +4,7 @@ function getSecret(key) {
return localStorage.getItem(key); return localStorage.getItem(key);
} }
const isAuthanticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null; const isAuthenticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null;
const secret = { const secret = {
accessToken: getSecret(cons.secretKey.ACCESS_TOKEN), accessToken: getSecret(cons.secretKey.ACCESS_TOKEN),
@ -14,6 +14,6 @@ const secret = {
}; };
export { export {
isAuthanticated, isAuthenticated,
secret, secret,
}; };

View file

@ -168,10 +168,10 @@
.dark-theme, .dark-theme,
.butter-theme { .butter-theme {
/* background color | --bg-[background type]: value */ /* background color | --bg-[background type]: value */
--bg-surface: hsl(208, 8%, 16%); --bg-surface: hsl(208, 8%, 20%);
--bg-surface-transparent: hsla(208, 8%, 16%, 0); --bg-surface-transparent: hsla(208, 8%, 20%, 0);
--bg-surface-low: hsl(208, 8%, 12%); --bg-surface-low: hsl(208, 8%, 16%);
--bg-surface-low-transparent: hsla(208, 8%, 12%, 0); --bg-surface-low-transparent: hsla(208, 8%, 16%, 0);
--bg-surface-hover: rgba(255, 255, 255, 3%); --bg-surface-hover: rgba(255, 255, 255, 3%);
--bg-surface-active: rgba(255, 255, 255, 5%); --bg-surface-active: rgba(255, 255, 255, 5%);
--bg-surface-border: rgba(0, 0, 0, 20%); --bg-surface-border: rgba(0, 0, 0, 20%);