diff --git a/README.md b/README.md index e4995d9a..6679eaf3 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,11 @@ Cinny is a [Matrix](https://matrix.org) client focusing primarily on simple, ele ## 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 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 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 Copyright (c) 2021 Ajay Bura (ajbura) and other contributors Code licensed under the MIT License: -Graphics licensed under CC-BY 4.0: \ No newline at end of file +Graphics licensed under CC-BY 4.0: diff --git a/package-lock.json b/package-lock.json index 43b60803..b4e48653 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b62fc796..a5f99451 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cinny", - "version": "1.3.1", + "version": "1.3.2", "description": "Yet another matrix client", "main": "index.js", "engines": { diff --git a/src/app/atoms/chip/Chip.jsx b/src/app/atoms/chip/Chip.jsx new file mode 100644 index 00000000..3cededff --- /dev/null +++ b/src/app/atoms/chip/Chip.jsx @@ -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 ( +
+ {iconSrc != null && } + {(text != null && text !== '') && {text}} + {children} +
+ ); +} + +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; diff --git a/src/app/atoms/chip/Chip.scss b/src/app/atoms/chip/Chip.scss new file mode 100644 index 00000000..c53f5ba7 --- /dev/null +++ b/src/app/atoms/chip/Chip.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/src/app/atoms/context-menu/ContextMenu.jsx b/src/app/atoms/context-menu/ContextMenu.jsx index 023ee38b..69734518 100644 --- a/src/app/atoms/context-menu/ContextMenu.jsx +++ b/src/app/atoms/context-menu/ContextMenu.jsx @@ -31,6 +31,7 @@ function ContextMenu({ interactive arrow={false} maxWidth={maxWidth} + duration={200} > {render(isVisible ? hideMenu : showMenu)} diff --git a/src/app/organisms/public-rooms/PublicRooms.jsx b/src/app/organisms/public-rooms/PublicRooms.jsx index b8f92449..23401298 100644 --- a/src/app/organisms/public-rooms/PublicRooms.jsx +++ b/src/app/organisms/public-rooms/PublicRooms.jsx @@ -139,14 +139,20 @@ function PublicRooms({ isOpen, searchTerm, onRequestClose }) { updateIsViewMore(false); if (totalRooms.length === 0) { 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, }); } } catch (e) { updatePublicRooms([]); + let err = 'Something went wrong!'; + if (e?.httpStatus >= 400 && e?.httpStatus < 500) { + err = e.message; + } updateSearchQuery({ - error: 'Something went wrong!', + error: err, alias: isInputAlias ? inputRoomName : null, }); updateIsSearching(false); diff --git a/src/app/organisms/room/RoomViewInput.jsx b/src/app/organisms/room/RoomViewInput.jsx index edad9c99..6c354eda 100644 --- a/src/app/organisms/room/RoomViewInput.jsx +++ b/src/app/organisms/room/RoomViewInput.jsx @@ -327,7 +327,15 @@ function RoomViewInput({ 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() { + if (!canISend) { + return ( + You do not have permission to post to this room + ); + } return ( <>
diff --git a/src/app/organisms/room/RoomViewInput.scss b/src/app/organisms/room/RoomViewInput.scss index 112a4c4a..9e0f1a91 100644 --- a/src/app/organisms/room/RoomViewInput.scss +++ b/src/app/organisms/room/RoomViewInput.scss @@ -3,6 +3,11 @@ display: flex; min-height: 48px; + &__disallowed { + flex: 1; + text-align: center; + } + &__space { min-width: 0; align-self: center; diff --git a/src/app/organisms/settings/Settings.jsx b/src/app/organisms/settings/Settings.jsx index cda2f3ac..1917bd2f 100644 --- a/src/app/organisms/settings/Settings.jsx +++ b/src/app/organisms/settings/Settings.jsx @@ -104,7 +104,7 @@ function AboutSection() {
Cinny - v1.3.1 + v1.3.2 Yet another matrix client diff --git a/src/app/pages/App.jsx b/src/app/pages/App.jsx index 0df840db..bb265056 100644 --- a/src/app/pages/App.jsx +++ b/src/app/pages/App.jsx @@ -3,7 +3,7 @@ import { BrowserRouter, Switch, Route, Redirect, } from 'react-router-dom'; -import { isAuthanticated } from '../../client/state/auth'; +import { isAuthenticated } from '../../client/state/auth'; import Auth from '../templates/auth/Auth'; import Client from '../templates/client/Client'; @@ -13,13 +13,13 @@ function App() { - { isAuthanticated() ? : } + { isAuthenticated() ? : } - { isAuthanticated() ? : } + { isAuthenticated() ? : } - { isAuthanticated() ? : } + { isAuthenticated() ? : } diff --git a/src/app/templates/auth/Auth.jsx b/src/app/templates/auth/Auth.jsx index cf4b51d7..3d97ca51 100644 --- a/src/app/templates/auth/Auth.jsx +++ b/src/app/templates/auth/Auth.jsx @@ -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'; function isValidInput(value, regex) { + if (typeof regex === 'string') return regex === value; return regex.test(value); } function renderErrorMessage(error) { @@ -39,24 +40,25 @@ function renderErrorMessage(error) { $error.textContent = error; $error.style.display = 'block'; } -function showBadInputError($input, error) { +function showBadInputError($input, error, stopAutoFocus) { renderErrorMessage(error); - $input.focus(); + if (!stopAutoFocus) $input.focus(); const myInput = $input; myInput.style.border = '1px solid var(--bg-danger)'; myInput.style.boxShadow = 'none'; document.getElementById('auth_submit-btn').disabled = true; } -function validateOnChange(e, regex, error) { - if (!isValidInput(e.target.value, regex) && e.target.value) { - showBadInputError(e.target, error); - return; +function validateOnChange(targetInput, regex, error, stopAutoFocus) { + if (!isValidInput(targetInput.value, regex) && targetInput.value) { + showBadInputError(targetInput, error, stopAutoFocus); + return false; } document.getElementById('auth_error').style.display = 'none'; - e.target.style.removeProperty('border'); - e.target.style.removeProperty('box-shadow'); + targetInput.style.removeProperty('border'); + targetInput.style.removeProperty('box-shadow'); document.getElementById('auth_submit-btn').disabled = false; + return true; } /** @@ -195,8 +197,8 @@ function Auth({ type }) { (type === 'login' - ? validateOnChange(e, LOCALPART_LOGIN_REGEX, BAD_LOCALPART_ERROR) - : validateOnChange(e, LOCALPART_SIGNUP_REGEX, BAD_LOCALPART_ERROR))} + ? validateOnChange(e.target, LOCALPART_LOGIN_REGEX, BAD_LOCALPART_ERROR) + : validateOnChange(e.target, LOCALPART_SIGNUP_REGEX, BAD_LOCALPART_ERROR))} id="auth_username" label="Username" required @@ -212,7 +214,15 @@ function Auth({ type }) {
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" type="password" label="Password" @@ -233,7 +243,9 @@ function Auth({ type }) {
validateOnChange(e, new RegExp(`^(${passwordRef.current.value})$`), CONFIRM_PASSWORD_ERROR)} + onChange={(e) => { + validateOnChange(e.target, passwordRef.current.value, CONFIRM_PASSWORD_ERROR); + }} id="auth_confirmPassword" type="password" label="Confirm password" @@ -251,7 +263,7 @@ function Auth({ type }) {
validateOnChange(e, EMAIL_REGEX, BAD_EMAIL_ERROR)} + onChange={(e) => validateOnChange(e.target, EMAIL_REGEX, BAD_EMAIL_ERROR)} id="auth_email" type="email" label="Email" diff --git a/src/client/state/auth.js b/src/client/state/auth.js index c919f640..fbc23f6f 100644 --- a/src/client/state/auth.js +++ b/src/client/state/auth.js @@ -4,7 +4,7 @@ function getSecret(key) { return localStorage.getItem(key); } -const isAuthanticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null; +const isAuthenticated = () => getSecret(cons.secretKey.ACCESS_TOKEN) !== null; const secret = { accessToken: getSecret(cons.secretKey.ACCESS_TOKEN), @@ -14,6 +14,6 @@ const secret = { }; export { - isAuthanticated, + isAuthenticated, secret, }; diff --git a/src/index.scss b/src/index.scss index f565170d..2b56022b 100644 --- a/src/index.scss +++ b/src/index.scss @@ -168,10 +168,10 @@ .dark-theme, .butter-theme { /* background color | --bg-[background type]: value */ - --bg-surface: hsl(208, 8%, 16%); - --bg-surface-transparent: hsla(208, 8%, 16%, 0); - --bg-surface-low: hsl(208, 8%, 12%); - --bg-surface-low-transparent: hsla(208, 8%, 12%, 0); + --bg-surface: hsl(208, 8%, 20%); + --bg-surface-transparent: hsla(208, 8%, 20%, 0); + --bg-surface-low: hsl(208, 8%, 16%); + --bg-surface-low-transparent: hsla(208, 8%, 16%, 0); --bg-surface-hover: rgba(255, 255, 255, 3%); --bg-surface-active: rgba(255, 255, 255, 5%); --bg-surface-border: rgba(0, 0, 0, 20%);