Merge branch 'dev' into dev

This commit is contained in:
Dylan 2022-07-16 23:49:34 +09:30 committed by GitHub
commit 05fc88a9fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1397 additions and 1309 deletions

View file

@ -1,4 +1,4 @@
<!-- Please read https://github.com/ajbura/cinny/CONTRIBUTING.md before submitting your pull request --> <!-- Please read https://github.com/ajbura/cinny/blob/dev/CONTRIBUTING.md before submitting your pull request -->
### Description ### Description
<!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. --> <!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

View file

@ -12,6 +12,10 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build app - name: Build app
run: npm ci && npm run build run: npm ci && npm run build
- name: Upload artifact - name: Upload artifact

View file

@ -14,8 +14,12 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build and deploy to Netlify - name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
with: with:
install_command: "npm ci" install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View file

@ -11,6 +11,10 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build - name: Build
run: | run: |
npm ci npm ci
@ -45,8 +49,12 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3.0.2 uses: actions/checkout@v3.0.2
- name: Setup node
uses: actions/setup-node@v3.4.1
with:
node-version: 17.9.0
- name: Build and deploy to Netlify - name: Build and deploy to Netlify
uses: jsmrcaga/action-netlify-deploy@fb6a5f936a4b06a8f7793e69fc5a022ffe39807a uses: jsmrcaga/action-netlify-deploy@53de32e559b0b3833615b9788c7a090cd2fddb03
with: with:
install_command: "npm ci" install_command: "npm ci"
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View file

@ -10,7 +10,7 @@ RUN npm run build
## App ## App
FROM nginx:1.21.6-alpine FROM nginx:1.23.0-alpine
COPY --from=builder /src/dist /app COPY --from=builder /src/dist /app

View file

@ -1,12 +1,11 @@
{ {
"defaultHomeserver": 4, "defaultHomeserver": 3,
"homeserverList": [ "homeserverList": [
"converser.eu",
"envs.net", "envs.net",
"halogen.city", "halogen.city",
"kde.org", "kde.org",
"matrix.org", "matrix.org",
"chat.mozilla.org" "mozilla.org"
], ],
"allowCustomHomeservers": true "allowCustomHomeservers": true
} }

2414
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -30,9 +30,11 @@
"i18next": "^21.8.9", "i18next": "^21.8.9",
"i18next-browser-languagedetector": "^6.1.4", "i18next-browser-languagedetector": "^6.1.4",
"i18next-http-backend": "^1.4.1", "i18next-http-backend": "^1.4.1",
"html-react-parser": "^2.0.0",
"katex": "^0.15.6", "katex": "^0.15.6",
"linkifyjs": "^2.1.9", "linkify-html": "^4.0.0-beta.5",
"matrix-js-sdk": "^18.0.0", "linkifyjs": "^4.0.0-beta.5",
"matrix-js-sdk": "^18.1.0",
"micromark": "^3.0.10", "micromark": "^3.0.10",
"micromark-extension-gfm": "^2.0.1", "micromark-extension-gfm": "^2.0.1",
"micromark-extension-math": "^2.0.2", "micromark-extension-math": "^2.0.2",
@ -53,9 +55,9 @@
"twemoji": "^14.0.2" "twemoji": "^14.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.2", "@babel/core": "^7.18.6",
"@babel/preset-env": "^7.18.2", "@babel/preset-env": "^7.18.6",
"@babel/preset-react": "^7.17.12", "@babel/preset-react": "^7.18.6",
"assert": "^2.0.0", "assert": "^2.0.0",
"babel-loader": "^8.2.5", "babel-loader": "^8.2.5",
"browserify-fs": "^1.0.0", "browserify-fs": "^1.0.0",
@ -65,27 +67,27 @@
"crypto-browserify": "^3.12.0", "crypto-browserify": "^3.12.0",
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0", "css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.17.0", "eslint": "^8.19.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-react": "^7.30.0", "eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.5.0", "eslint-plugin-react-hooks": "^4.6.0",
"favicons": "^6.2.2", "favicons": "^6.2.2",
"favicons-webpack-plugin": "^5.0.2", "favicons-webpack-plugin": "^5.0.2",
"html-loader": "^3.1.0", "html-loader": "^3.1.2",
"html-webpack-plugin": "^5.3.1", "html-webpack-plugin": "^5.3.1",
"mini-css-extract-plugin": "^2.6.0", "mini-css-extract-plugin": "^2.6.1",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"sass": "^1.52.2", "sass": "^1.53.0",
"sass-loader": "^13.0.0", "sass-loader": "^13.0.2",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"url": "^0.11.0", "url": "^0.11.0",
"util": "^0.12.4", "util": "^0.12.4",
"webpack": "^5.73.0", "webpack": "^5.73.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.2", "webpack-dev-server": "^4.9.3",
"webpack-merge": "^5.7.3" "webpack-merge": "^5.7.3"
} }
} }

View file

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92896 3.51471L3.51474 4.92892L5.97515 7.38933C4.46742 8.5776 3.32116 9.93994 2.7 10.8C2.1 11.5 2.1 12.5 2.7 13.2C4 15 7.6 19 12 19C13.5709 19 15.0398 18.4902 16.3384 17.7526L19.0711 20.4853L20.4853 19.0711L4.92896 3.51471ZM4.2 12C4.68291 11.3561 5.85678 9.9637 7.39721 8.81139L9.29238 10.7066C9.10496 11.0982 9 11.5368 9 12C9 13.6569 10.3431 15 12 15C12.4632 15 12.9018 14.895 13.2934 14.7076L14.8573 16.2715C13.9566 16.7128 12.9896 17 12 17C8.4 17 5.1 13.2 4.2 12Z" fill="black"/>
<path d="M9.6226 5.37995L11.2906 7.04797C11.5254 7.01661 11.762 7 12 7C15.6 7 18.9 10.8 19.8 12C19.493 12.4094 18.9066 13.1213 18.1244 13.8817L19.5194 15.2768C20.2973 14.4974 20.9049 13.7471 21.3 13.2C21.9 12.5 21.9 11.5 21.3 10.8C20 9 16.4 5 12 5C11.1762 5 10.3805 5.14021 9.6226 5.37995Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 943 B

View file

@ -1,13 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <path d="M12 19C7.6 19 4 15 2.7 13.2C2.1 12.5 2.1 11.5 2.7 10.8C4 9 7.6 5 12 5C16.4 5 20 9 21.3 10.8C21.9 11.5 21.9 12.5 21.3 13.2C20 15 16.4 19 12 19ZM12 7C8.4 7 5.1 10.8 4.2 12C5.1 13.2 8.4 17 12 17C15.6 17 18.9 13.2 19.8 12C18.9 10.8 15.6 7 12 7Z" fill="black"/>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z" fill="black"/>
<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>
<g>
<path d="M12,19c-4.4,0-8-4-9.3-5.8c-0.6-0.7-0.6-1.7,0-2.4C4,9,7.6,5,12,5s8,4,9.3,5.8c0.6,0.7,0.6,1.7,0,2.4C20,15,16.4,19,12,19
z M12,7c-3.6,0-6.9,3.8-7.8,5c0.9,1.2,4.2,5,7.8,5s6.9-3.8,7.8-5C18.9,10.8,15.6,7,12,7z"/>
</g>
<circle cx="12" cy="12" r="3"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 718 B

After

Width:  |  Height:  |  Size: 508 B

View file

@ -125,7 +125,7 @@ function RoomAliases({ roomId }) {
const loadLocalAliases = async () => { const loadLocalAliases = async () => {
let local = []; let local = [];
try { try {
const result = await mx.unstableGetLocalAliases(roomId); const result = await mx.getLocalAliases(roomId);
local = result.aliases.filter((alias) => !aliases.published.includes(alias)); local = result.aliases.filter((alias) => !aliases.published.includes(alias));
} catch { } catch {
local = []; local = [];

View file

@ -242,12 +242,12 @@ function RoomPermissions({ roomId }) {
? permissions[permInfo.parent]?.[permKey] ? permissions[permInfo.parent]?.[permKey]
: permissions[permKey]; : permissions[permKey];
if (!permValue) permValue = permInfo.default; if (permValue === undefined) permValue = permInfo.default;
if (typeof permValue === 'number') { if (typeof permValue === 'number') {
powerLevel = permValue; powerLevel = permValue;
} else if (permKey === 'notifications') { } else if (permKey === 'notifications') {
powerLevel = permValue.room || 50; powerLevel = permValue.room ?? 50;
} }
return ( return (
<SettingTile <SettingTile

View file

@ -1,5 +1,6 @@
import emojisData from 'emojibase-data/en/compact.json'; import emojisData from 'emojibase-data/en/compact.json';
import shortcodes from 'emojibase-data/en/shortcodes/joypixels.json'; import joypixels from 'emojibase-data/en/shortcodes/joypixels.json';
import emojibase from 'emojibase-data/en/shortcodes/emojibase.json';
const emojiGroups = [{ const emojiGroups = [{
name: 'Smileys & people', name: 'Smileys & people',
@ -52,7 +53,7 @@ function addToGroup(emoji) {
const emojis = []; const emojis = [];
emojisData.forEach((emoji) => { emojisData.forEach((emoji) => {
const myShortCodes = shortcodes[emoji.hexcode]; const myShortCodes = joypixels[emoji.hexcode] || emojibase[emoji.hexcode];
if (!myShortCodes) return; if (!myShortCodes) return;
const em = { const em = {
...emoji, ...emoji,

View file

@ -7,7 +7,7 @@ import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons'; import cons from '../../../client/state/cons';
import * as roomActions from '../../../client/action/room'; import * as roomActions from '../../../client/action/room';
import { selectRoom } from '../../../client/action/navigation'; import { selectRoom } from '../../../client/action/navigation';
import { hasDMWith } from '../../../util/matrixUtil'; import { hasDMWith, hasDevices } from '../../../util/matrixUtil';
import Text from '../../atoms/text/Text'; import Text from '../../atoms/text/Text';
import Button from '../../atoms/button/Button'; import Button from '../../atoms/button/Button';

View file

@ -12,7 +12,7 @@ import { selectRoom, openReusableContextMenu } from '../../../client/action/navi
import * as roomActions from '../../../client/action/room'; import * as roomActions from '../../../client/action/room';
import { import {
getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, getUsername, getUsernameOfRoomMember, getPowerLabel, hasDMWith, hasDevices
} from '../../../util/matrixUtil'; } from '../../../util/matrixUtil';
import { getEventCords } from '../../../util/common'; import { getEventCords } from '../../../util/common';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
@ -209,7 +209,7 @@ function ProfileFooter({ roomId, userId, onRequestClose }) {
// Create new DM // Create new DM
try { try {
setIsCreatingDM(true); setIsCreatingDM(true);
await roomActions.createDM(userId); await roomActions.createDM(userId, await hasDevices(userId));
} catch { } catch {
if (isMountedRef.current === false) return; if (isMountedRef.current === false) return;
setIsCreatingDM(false); setIsCreatingDM(false);

View file

@ -62,23 +62,25 @@ function AppearanceSection() {
)} )}
content={<Text variant="b3">{t('Organisms.Settings.theme.follow_system.description')}</Text>} content={<Text variant="b3">{t('Organisms.Settings.theme.follow_system.description')}</Text>}
/> />
{!settings.useSystemTheme && (
<SettingTile <SettingTile
title={t('Organisms.Settings.theme.title')} title={t('Organisms.Settings.theme.title')}
content={( content={(
<SegmentedControls <SegmentedControls
selected={settings.getThemeIndex()} selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()}
segments={[ segments={[
{ text: t('Organisms.Settings.theme.theme_light') }, { text: t('Organisms.Settings.theme.theme_light') },
{ text: t('Organisms.Settings.theme.theme_silver') }, { text: t('Organisms.Settings.theme.theme_silver') },
{ text: t('Organisms.Settings.theme.theme_dark') }, { text: t('Organisms.Settings.theme.theme_dark') },
{ text: t('Organisms.Settings.theme.theme_butter') }, { text: t('Organisms.Settings.theme.theme_butter') },
]} ]}
onSelect={(index) => settings.setTheme(index)} onSelect={(index) => {
if (settings.useSystemTheme) toggleSystemTheme();
settings.setTheme(index);
updateState({});
}}
/> />
)} )}
/> />
)}
</div> </div>
<div className="settings-appearance__card"> <div className="settings-appearance__card">
<MenuHeader>Room messages</MenuHeader> <MenuHeader>Room messages</MenuHeader>

View file

@ -21,6 +21,8 @@ import Avatar from '../../atoms/avatar/Avatar';
import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu'; import ContextMenu, { MenuItem, MenuHeader } from '../../atoms/context-menu/ContextMenu';
import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg';
import EyeIC from '../../../../public/res/ic/outlined/eye.svg';
import EyeBlindIC from '../../../../public/res/ic/outlined/eye-blind.svg';
import CinnySvg from '../../../../public/res/svg/cinny.svg'; import CinnySvg from '../../../../public/res/svg/cinny.svg';
import SSOButtons from '../../molecules/sso-buttons/SSOButtons'; import SSOButtons from '../../molecules/sso-buttons/SSOButtons';
@ -54,11 +56,8 @@ function Homeserver({ onChange }) {
const setupHsConfig = async (servername) => { const setupHsConfig = async (servername) => {
setProcess({ isLoading: true, message: 'Looking for homeserver...' }); setProcess({ isLoading: true, message: 'Looking for homeserver...' });
let baseUrl = null; let baseUrl = null;
try {
baseUrl = await getBaseUrl(servername); baseUrl = await getBaseUrl(servername);
} catch (e) {
baseUrl = e.message;
}
if (searchingHs !== servername) return; if (searchingHs !== servername) return;
setProcess({ isLoading: true, message: `Connecting to ${baseUrl}...` }); setProcess({ isLoading: true, message: `Connecting to ${baseUrl}...` });
const tempClient = auth.createTemporaryClient(baseUrl); const tempClient = auth.createTemporaryClient(baseUrl);
@ -97,7 +96,7 @@ function Homeserver({ onChange }) {
if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) { if (!hsList?.length > 0 || selectedHs < 0 || selectedHs >= hsList?.length) {
throw new Error(); throw new Error();
} }
setHs({ selected: hsList[selectedHs], list: hsList, allowCustom: allowCustom }); setHs({ selected: hsList[selectedHs], list: hsList, allowCustom });
} catch { } catch {
setHs({ selected: 'matrix.org', list: ['matrix.org'], allowCustom: true }); setHs({ selected: 'matrix.org', list: ['matrix.org'], allowCustom: true });
} }
@ -114,8 +113,14 @@ function Homeserver({ onChange }) {
return ( return (
<> <>
<div className="homeserver-form"> <div className="homeserver-form">
<Input name="homeserver" onChange={handleHsInput} value={hs?.selected} forwardRef={hsRef} label="Homeserver" <Input
disabled={hs === null || !hs.allowCustom} /> name="homeserver"
onChange={handleHsInput}
value={hs?.selected}
forwardRef={hsRef}
label="Homeserver"
disabled={hs === null || !hs.allowCustom}
/>
<ContextMenu <ContextMenu
placement="right" placement="right"
content={(hideMenu) => ( content={(hideMenu) => (
@ -156,6 +161,7 @@ Homeserver.propTypes = {
function Login({ loginFlow, baseUrl }) { function Login({ loginFlow, baseUrl }) {
const [typeIndex, setTypeIndex] = useState(0); const [typeIndex, setTypeIndex] = useState(0);
const [passVisible, setPassVisible] = useState(false);
const loginTypes = ['Username', 'Email']; const loginTypes = ['Username', 'Email'];
const isPassword = loginFlow?.filter((flow) => flow.type === 'm.login.password')[0]; const isPassword = loginFlow?.filter((flow) => flow.type === 'm.login.password')[0];
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0]; const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
@ -166,17 +172,23 @@ function Login({ loginFlow, baseUrl }) {
const validator = (values) => { const validator = (values) => {
const errors = {}; const errors = {};
if (typeIndex === 0 && values.username.length > 0 && values.username.indexOf(':') > -1) {
errors.username = 'Username must contain local-part only';
}
if (typeIndex === 1 && values.email.length > 0 && !isValidInput(values.email, EMAIL_REGEX)) { if (typeIndex === 1 && values.email.length > 0 && !isValidInput(values.email, EMAIL_REGEX)) {
errors.email = BAD_EMAIL_ERROR; errors.email = BAD_EMAIL_ERROR;
} }
return errors; return errors;
}; };
const submitter = (values, actions) => auth.login( const submitter = async (values, actions) => {
baseUrl, let userBaseUrl = baseUrl;
typeIndex === 0 ? normalizeUsername(values.username) : undefined, let { username } = values;
const mxIdMatch = username.match(/^@(.+):(.+\..+)$/);
if (typeIndex === 0 && mxIdMatch) {
[, username, userBaseUrl] = mxIdMatch;
userBaseUrl = await getBaseUrl(userBaseUrl);
}
return auth.login(
userBaseUrl,
typeIndex === 0 ? normalizeUsername(username) : undefined,
typeIndex === 1 ? values.email : undefined, typeIndex === 1 ? values.email : undefined,
values.password, values.password,
).then(() => { ).then(() => {
@ -191,6 +203,7 @@ function Login({ loginFlow, baseUrl }) {
}); });
actions.setSubmitting(false); actions.setSubmitting(false);
}); });
};
return ( return (
<> <>
@ -236,7 +249,10 @@ function Login({ loginFlow, baseUrl }) {
{errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>} {errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>}
{typeIndex === 1 && <Input values={values.email} name="email" onChange={handleChange} label="Email" type="email" required />} {typeIndex === 1 && <Input values={values.email} name="email" onChange={handleChange} label="Email" type="email" required />}
{errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>} {errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>}
<Input values={values.password} name="password" onChange={handleChange} label="Password" type="password" required /> <div className="auth-form__pass-eye-wrapper">
<Input values={values.password} name="password" onChange={handleChange} label="Password" type={passVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setPassVisible(!passVisible)} src={passVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>} {errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>}
{errors.other && <Text className="auth-form__error" variant="b3">{errors.other}</Text>} {errors.other && <Text className="auth-form__error" variant="b3">{errors.other}</Text>}
<div className="auth-form__btns"> <div className="auth-form__btns">
@ -269,6 +285,8 @@ let sid;
let clientSecret; let clientSecret;
function Register({ registerInfo, loginFlow, baseUrl }) { function Register({ registerInfo, loginFlow, baseUrl }) {
const [process, setProcess] = useState({}); const [process, setProcess] = useState({});
const [passVisible, setPassVisible] = useState(false);
const [cPassVisible, setCPassVisible] = useState(false);
const formRef = useRef(); const formRef = useRef();
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0]; const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
@ -319,6 +337,7 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
if (!isAvail) { if (!isAvail) {
actions.setErrors({ username: 'Username is already taken' }); actions.setErrors({ username: 'Username is already taken' });
actions.setSubmitting(false); actions.setSubmitting(false);
return;
} }
if (isEmail && values.email.length > 0) { if (isEmail && values.email.length > 0) {
const result = await auth.verifyEmail(baseUrl, values.email, clientSecret, 1); const result = await auth.verifyEmail(baseUrl, values.email, clientSecret, 1);
@ -437,9 +456,15 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
<form className="auth-form" ref={formRef} onSubmit={handleSubmit}> <form className="auth-form" ref={formRef} onSubmit={handleSubmit}>
<Input values={values.username} name="username" onChange={handleChange} label="Username" type="username" required /> <Input values={values.username} name="username" onChange={handleChange} label="Username" type="username" required />
{errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>} {errors.username && <Text className="auth-form__error" variant="b3">{errors.username}</Text>}
<Input values={values.password} name="password" onChange={handleChange} label="Password" type="password" required /> <div className="auth-form__pass-eye-wrapper">
<Input values={values.password} name="password" onChange={handleChange} label="Password" type={passVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setPassVisible(!passVisible)} src={passVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>} {errors.password && <Text className="auth-form__error" variant="b3">{errors.password}</Text>}
<Input values={values.confirmPassword} name="confirmPassword" onChange={handleChange} label="Confirm password" type="password" required /> <div className="auth-form__pass-eye-wrapper">
<Input values={values.confirmPassword} name="confirmPassword" onChange={handleChange} label="Confirm password" type={cPassVisible ? 'text' : 'password'} required />
<IconButton onClick={() => setCPassVisible(!cPassVisible)} src={cPassVisible ? EyeIC : EyeBlindIC} size="extra-small" />
</div>
{errors.confirmPassword && <Text className="auth-form__error" variant="b3">{errors.confirmPassword}</Text>} {errors.confirmPassword && <Text className="auth-form__error" variant="b3">{errors.confirmPassword}</Text>}
{isEmail && <Input values={values.email} name="email" onChange={handleChange} label={`Email${isEmailRequired ? '' : ' (optional)'}`} type="email" required={isEmailRequired} />} {isEmail && <Input values={values.email} name="email" onChange={handleChange} label={`Email${isEmailRequired ? '' : ' (optional)'}`} type="email" required={isEmailRequired} />}
{errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>} {errors.email && <Text className="auth-form__error" variant="b3">{errors.email}</Text>}

View file

@ -97,7 +97,8 @@
} }
.auth-form { .auth-form {
& > .input-container { & > .input-container,
&__pass-eye-wrapper {
margin: var(--sp-tight) 0 var(--sp-ultra-tight); margin: var(--sp-tight) 0 var(--sp-ultra-tight);
} }
@ -107,6 +108,20 @@
margin-top: calc(var(--sp-extra-loose) + var(--sp-tight)); margin-top: calc(var(--sp-extra-loose) + var(--sp-tight));
} }
&__pass-eye-wrapper {
position: relative;
& .ic-btn {
position: absolute;
@include dir.prop(right, 6px, unset);
@include dir.prop(left, unset, 6px );
bottom: 6px;
border-radius: 4px;
}
& input {
@include dir.side(padding, var(--sp-normal), 46px);
}
}
&__btns { &__btns {
padding-top: var(--sp-loose); padding-top: var(--sp-loose);
margin-bottom: var(--sp-extra-loose); margin-bottom: var(--sp-extra-loose);

View file

@ -48,31 +48,43 @@ class Settings extends EventEmitter {
return this.themes[this.themeIndex]; return this.themes[this.themeIndex];
} }
setTheme(themeIndex) { _clearTheme() {
const appBody = document.getElementById('appBody'); document.body.classList.remove('system-theme');
appBody.classList.remove('system-theme');
this.themes.forEach((themeName) => { this.themes.forEach((themeName) => {
if (themeName === '') return; if (themeName === '') return;
appBody.classList.remove(themeName); document.body.classList.remove(themeName);
}); });
// If use system theme is enabled
// we will override current theme choice with system theme
if (this.useSystemTheme) {
appBody.classList.add('system-theme');
} else if (this.themes[themeIndex] !== '') {
appBody.classList.add(this.themes[themeIndex]);
} }
setSettings('themeIndex', themeIndex);
applyTheme() {
this._clearTheme();
if (this.useSystemTheme) {
document.body.classList.add('system-theme');
} else if (this.themes[this.themeIndex]) {
document.body.classList.add(this.themes[this.themeIndex]);
}
}
setTheme(themeIndex) {
this.themeIndex = themeIndex; this.themeIndex = themeIndex;
setSettings('themeIndex', this.themeIndex);
this.applyTheme();
}
toggleUseSystemTheme() {
this.useSystemTheme = !this.useSystemTheme;
setSettings('useSystemTheme', this.useSystemTheme);
this.applyTheme();
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
} }
getUseSystemTheme() { getUseSystemTheme() {
if (typeof this.useSystemTheme === 'boolean') return this.useSystemTheme; if (typeof this.useSystemTheme === 'boolean') return this.useSystemTheme;
const settings = getSettings(); const settings = getSettings();
if (settings === null) return false; if (settings === null) return true;
if (typeof settings.useSystemTheme === 'undefined') return false; if (typeof settings.useSystemTheme === 'undefined') return true;
return settings.useSystemTheme; return settings.useSystemTheme;
} }
@ -138,12 +150,7 @@ class Settings extends EventEmitter {
setter(action) { setter(action) {
const actions = { const actions = {
[cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => { [cons.actions.settings.TOGGLE_SYSTEM_THEME]: () => {
this.useSystemTheme = !this.useSystemTheme; this.toggleUseSystemTheme();
setSettings('useSystemTheme', this.useSystemTheme);
this.setTheme(this.themeIndex);
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
}, },
[cons.actions.settings.TOGGLE_MARKDOWN]: () => { [cons.actions.settings.TOGGLE_MARKDOWN]: () => {
this.isMarkdown = !this.isMarkdown; this.isMarkdown = !this.isMarkdown;

View file

@ -7,7 +7,7 @@ import settings from './client/state/settings';
import App from './app/pages/App'; import App from './app/pages/App';
settings.setTheme(settings.getThemeIndex()); settings.applyTheme();
ReactDom.render( ReactDom.render(
<App />, <App />,

View file

@ -20,7 +20,7 @@ export async function getBaseUrl(servername) {
if (baseUrl === undefined) throw new Error(); if (baseUrl === undefined) throw new Error();
return baseUrl; return baseUrl;
} catch (e) { } catch (e) {
throw new Error(`${protocol}${servername}`); return `${protocol}${servername}`;
} }
} }
@ -204,3 +204,15 @@ export function getSSKeyInfo(key) {
return undefined; return undefined;
} }
} }
export async function hasDevices(userId) {
const mx = initMatrix.matrixClient;
try {
const usersDeviceMap = await mx.downloadKeys([userId, mx.getUserId()]);
return Object.values(usersDeviceMap)
.every((userDevices) => (Object.keys(userDevices).length > 0));
} catch (e) {
console.error("Error determining if it's possible to encrypt to all users: ", e);
return false;
}
}

View file

@ -44,7 +44,7 @@ function transformSpanTag(tagName, attribs) {
} }
function transformATag(tagName, attribs) { function transformATag(tagName, attribs) {
const userLink = attribs.href.match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/); const userLink = decodeURIComponent(attribs.href).match(/^https?:\/\/matrix.to\/#\/(@.+:.+)/);
if (userLink !== null) { if (userLink !== null) {
// convert user link to pill // convert user link to pill
const userId = userLink[1]; const userId = userLink[1];

View file

@ -1,7 +1,7 @@
/* eslint-disable import/prefer-default-export */ /* eslint-disable import/prefer-default-export */
import React, { lazy, Suspense } from 'react'; import React, { lazy, Suspense } from 'react';
import linkifyHtml from 'linkifyjs/html'; import linkifyHtml from 'linkify-html';
import parse from 'html-react-parser'; import parse from 'html-react-parser';
import twemoji from 'twemoji'; import twemoji from 'twemoji';
import { sanitizeText } from './sanitize'; import { sanitizeText } from './sanitize';