Compare commits

...

9 Commits

Author SHA1 Message Date
Ajay Bura
1487dcbadc Fix login with CAS #165
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-14 13:35:01 +05:30
Krishan
a4b27fdeab Fixed pull request preview deploys (#166)
* Update and rename pull-request.yml to build-pull-request.yml

* Create deploy-pull-request.yml
2021-11-14 12:54:17 +05:30
Ajay Bura
1137c11c59 Bug fixed
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-14 11:31:22 +05:30
Samuel Dionne-Riel
14cd84dab7 Add basic support for displaying emotes (#161) 2021-11-14 10:32:32 +05:30
Ajay Bura
b5c5cd9586 Fix add initial_device_display_name on register
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-11 16:56:36 +05:30
Ajay Bura
85cc4cb8f7 Fix cropped loading and login screen
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-11 16:47:08 +05:30
Ajay Bura
cf6732fb29 Fix crash on profile opening
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-11 16:45:55 +05:30
Ajay Bura
1207f5abad Fixed error on register, zoom on safari and removed webpack copying env vars to bundle
Signed-off-by: Ajay Bura <ajbura@gmail.com>
2021-11-11 14:09:06 +05:30
Samuel Dionne-Riel
6e9394ec7a Use Unicode aware character-wise slicing (#159) 2021-11-10 13:30:25 +05:30
28 changed files with 199 additions and 93 deletions

View File

@@ -0,0 +1,32 @@
name: 'Build PR'
on:
pull_request:
types: ['opened', 'synchronize']
jobs:
build:
runs-on: ubuntu-latest
env:
PR_NUMBER: ${{github.event.number}}
steps:
- uses: actions/checkout@v2
- name: Build
run: npm install && npm run build
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: previewbuild
path: dist
retention-days: 1
- uses: actions/github-script@v3.1.0
with:
script: |
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json', JSON.stringify(context.payload.pull_request));
- name: Upload PR Info
uses: actions/upload-artifact@v2
with:
name: pr.json
path: pr.json
retention-days: 1

View File

@@ -0,0 +1,78 @@
name: Upload Preview Build to Netlify
on:
workflow_run:
workflows: ["Build PR"]
types:
- completed
jobs:
build:
runs-on: ubuntu-latest
if: >
${{ github.event.workflow_run.conclusion == 'success' }}
steps:
# There's a 'download artifact' action but it hasn't been updated for the
# workflow_run action (https://github.com/actions/download-artifact/issues/60)
# so instead we get this mess:
- name: 'Download artifact'
uses: actions/github-script@v3.1.0
with:
script: |
var artifacts = await github.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: ${{github.event.workflow_run.id }},
});
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "previewbuild"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/previewbuild.zip', Buffer.from(download.data));
var prInfoArtifact = artifacts.data.artifacts.filter((artifact) => {
return artifact.name == "pr.json"
})[0];
var download = await github.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: prInfoArtifact.id,
archive_format: 'zip',
});
var fs = require('fs');
fs.writeFileSync('${{github.workspace}}/pr.json.zip', Buffer.from(download.data));
- name: Extract Artifacts
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@v3.1.0
with:
script: |
var fs = require('fs');
var pr = JSON.parse(fs.readFileSync('${{github.workspace}}/pr.json'));
console.log(`::set-output name=prnumber::${pr.number}`);
- name: Deploy to Netlify
id: netlify
uses: nwtgck/actions-netlify@v1.2
with:
publish-dir: dist
deploy-message: "Deploy from GitHub Actions"
# These don't work because we're in workflow_run
enable-pull-request-comment: false
enable-commit-comment: false
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
timeout-minutes: 1
- name: Edit PR Description
uses: velas/pr-description@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
pull-request-number: ${{ steps.readctx.outputs.prnumber }}
description-message: |
Preview: ${{ steps.netlify.outputs.deploy-url }}
⚠️ Do you trust the author of this PR? Maybe this build will steal your keys or give you malware. Exercise caution. Use test accounts.

View File

@@ -1,30 +0,0 @@
name: 'Netlify Preview Deploy'
on:
pull_request_target:
types: ['opened', 'synchronize']
jobs:
deploy:
name: "Deploy to Netlify"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: jsmrcaga/action-netlify-deploy@master
with:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}
BUILD_DIRECTORY: "dist"
- name: Post on PR
uses: nwtgck/actions-netlify@v1.1
with:
publish-dir: "dist"
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
enable-pull-request-comment: true
enable-commit-comment: false
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE3_ID }}

View File

@@ -57,7 +57,7 @@ To set default Homeserver on login and register page, place a customized [`confi
## License
Copyright (c) 2021 Ajay Bura (ajbura) and other contributors
Copyright (c) 2021 Ajay Bura (ajbura) and contributors
Code licensed under the MIT License: <http://opensource.org/licenses/MIT>

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "cinny",
"version": "1.5.0",
"version": "1.5.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "cinny",
"version": "1.5.0",
"version": "1.5.1",
"license": "MIT",
"dependencies": {
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.4.tgz",

View File

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

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no">
<link href="https://api.fontshare.com/css?f[]=supreme@300,301,400,401,500,501,700,701&display=swap" rel="stylesheet">
<title>Cinny</title>
<meta name="name" content="Cinny">

View File

@@ -29,7 +29,7 @@ function Avatar({
{
iconSrc !== null
? <RawIcon size={size} src={iconSrc} />
: text !== null && <Text variant={textSize}>{text}</Text>
: text !== null && <Text variant={textSize}>{[...text][0]}</Text>
}
</span>
)

View File

@@ -48,7 +48,7 @@ function ImageUpload({
>
<Avatar
imageSrc={imageSrc}
text={text.slice(0, 1)}
text={text}
bgColor={bgColor}
size="large"
/>

View File

@@ -106,10 +106,17 @@ MessageReply.propTypes = {
content: PropTypes.string.isRequired,
};
function MessageContent({ content, isMarkdown, isEdited }) {
function MessageContent({
senderName,
content,
isMarkdown,
isEdited,
msgType,
}) {
return (
<div className="message__content">
<div className="text text-b1">
{ msgType === 'm.emote' && `* ${senderName} ` }
{ isMarkdown ? genMarkdown(content) : linkifyContent(content) }
</div>
{ isEdited && <Text className="message__content-edited" variant="b3">(edited)</Text>}
@@ -121,9 +128,11 @@ MessageContent.defaultProps = {
isEdited: false,
};
MessageContent.propTypes = {
senderName: PropTypes.string.isRequired,
content: PropTypes.node.isRequired,
isMarkdown: PropTypes.bool,
isEdited: PropTypes.bool,
msgType: PropTypes.string.isRequired,
};
function MessageEdit({ content, onSave, onCancel }) {
@@ -228,10 +237,27 @@ MessageOptions.propTypes = {
function Message({
avatar, header, reply, content, editContent, reactions, options,
msgType,
}) {
const msgClass = header === null ? ' message--content-only' : ' message--full';
const className = [
'message',
header === null ? ' message--content-only' : ' message--full',
];
switch (msgType) {
case 'm.text':
className.push('message--type-text');
break;
case 'm.emote':
className.push('message--type-emote');
break;
case 'm.notice':
className.push('message--type-notice');
break;
default:
}
return (
<div className={`message${msgClass}`}>
<div className={className.join(' ')}>
<div className="message__avatar-container">
{avatar !== null && avatar}
</div>
@@ -254,6 +280,7 @@ Message.defaultProps = {
editContent: null,
reactions: null,
options: null,
msgType: 'm.text',
};
Message.propTypes = {
avatar: PropTypes.node,
@@ -263,6 +290,7 @@ Message.propTypes = {
editContent: PropTypes.node,
reactions: PropTypes.node,
options: PropTypes.node,
msgType: PropTypes.string,
};
export {

View File

@@ -410,3 +410,14 @@
}
}
}
.message.message--type-emote {
.message__content {
font-style: italic;
// Remove blockness of first `<p>` so that markdown emotes stay on one line.
p:first-of-type {
display: inline;
}
}
}

View File

@@ -18,7 +18,7 @@ function PeopleSelector({
onClick={onClick}
type="button"
>
<Avatar imageSrc={avatarSrc} text={name.slice(0, 1)} bgColor={color} size="extra-small" />
<Avatar imageSrc={avatarSrc} text={name} bgColor={color} size="extra-small" />
<Text className="people-selector__name" variant="b1">{name}</Text>
{peopleRole !== null && <Text className="people-selector__role" variant="b3">{peopleRole}</Text>}
</button>

View File

@@ -17,7 +17,7 @@ function RoomIntro({
}) {
return (
<div className="room-intro">
<Avatar imageSrc={avatarSrc} text={name.slice(0, 1)} bgColor={colorMXID(roomId)} size="large" />
<Avatar imageSrc={avatarSrc} text={name} bgColor={colorMXID(roomId)} size="large" />
<div className="room-intro__content">
<Text className="room-intro__name" variant="h1">{heading}</Text>
<Text className="room-intro__desc" variant="b1">{linkifyContent(desc)}</Text>

View File

@@ -51,7 +51,7 @@ function RoomSelector({
content={(
<>
<Avatar
text={name.slice(0, 1)}
text={name}
bgColor={colorMXID(roomId)}
imageSrc={imageSrc}
iconSrc={iconSrc}

View File

@@ -22,7 +22,7 @@ function RoomTile({
<Avatar
imageSrc={avatarSrc}
bgColor={colorMXID(id)}
text={name.slice(0, 1)}
text={name}
/>
</div>
<div className="room-tile__content">

View File

@@ -70,7 +70,7 @@ function ProfileAvatarMenu() {
tooltip={profile.displayName}
imageSrc={profile.avatarUrl !== null ? mx.mxcUrlToHttp(profile.avatarUrl, 42, 42, 'crop') : null}
bgColor={colorMXID(mx.getUserId())}
text={profile.displayName.slice(0, 1)}
text={profile.displayName}
/>
)}
/>
@@ -190,7 +190,7 @@ function SideBar() {
tooltip={room.name}
bgColor={colorMXID(room.roomId)}
imageSrc={room.getAvatarUrl(initMatrix.matrixClient.baseUrl, 42, 42, 'crop') || null}
text={room.name.slice(0, 1)}
text={room.name}
isUnread={notifications.hasNoti(sRoomId)}
notificationCount={abbreviateNumber(notifications.getTotalNoti(sRoomId))}
isAlert={notifications.getHighlightNoti(sRoomId) !== 0}

View File

@@ -249,15 +249,15 @@ function ProfileViewer() {
}, [isOpen]);
function renderProfile() {
const member = room.getMember(userId) || mx.getUser(userId);
const avatarMxc = member.getMxcAvatarUrl() || member.avatarUrl;
const member = room.getMember(userId) || mx.getUser(userId) || {};
const avatarMxc = member.getMxcAvatarUrl?.() || member.avatarUrl;
return (
<div className="profile-viewer">
<div className="profile-viewer__user">
<Avatar
imageSrc={!avatarMxc ? null : mx.mxcUrlToHttp(avatarMxc, 80, 80, 'crop')}
text={username.slice(0, 1)}
text={username}
bgColor={colorMXID(userId)}
size="large"
/>

View File

@@ -8,7 +8,7 @@ import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import { redactEvent, sendReaction } from '../../../client/action/roomTimeline';
import { getUsername, getUsernameOfRoomMember, doesRoomHaveUnread } from '../../../util/matrixUtil';
import { getUsername, getUsernameOfRoomMember } from '../../../util/matrixUtil';
import colorMXID from '../../../util/colorMXID';
import { diffMinutes, isNotInSameDay, getEventCords } from '../../../util/common';
import { openEmojiBoard, openProfileViewer, openReadReceipts } from '../../../client/action/navigation';
@@ -191,6 +191,7 @@ function RoomViewContent({
const [onPagination, setOnPagination] = useState(null);
const [editEvent, setEditEvent] = useState(null);
const mx = initMatrix.matrixClient;
const noti = initMatrix.notifications;
function autoLoadTimeline() {
if (timelineScroll.isScrollable() === true) return;
@@ -199,7 +200,7 @@ function RoomViewContent({
function trySendingReadReceipt() {
const { room, timeline } = roomTimeline;
if (
(doesRoomHaveUnread(room) || initMatrix.notifications.hasNoti(roomId))
(noti.doesRoomHaveUnread(room) || noti.hasNoti(roomId))
&& timeline.length !== 0) {
mx.sendReadReceipt(timeline[timeline.length - 1]);
}
@@ -282,6 +283,7 @@ function RoomViewContent({
let content = mEvent.getContent().body;
if (typeof content === 'undefined') return null;
const msgType = mEvent.getContent()?.msgtype;
let reply = null;
let reactions = null;
let isMarkdown = mEvent.getContent().format === 'org.matrix.custom.html';
@@ -356,7 +358,7 @@ function RoomViewContent({
<button type="button" onClick={() => openProfileViewer(mEvent.sender.userId, roomId)}>
<Avatar
imageSrc={mEvent.sender.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop')}
text={getUsernameOfRoomMember(mEvent.sender).slice(0, 1)}
text={getUsernameOfRoomMember(mEvent.sender)}
bgColor={senderMXIDColor}
size="small"
/>
@@ -379,8 +381,10 @@ function RoomViewContent({
);
const userContent = (
<MessageContent
senderName={getUsernameOfRoomMember(mEvent.sender)}
isMarkdown={isMarkdown}
content={isMedia(mEvent) ? genMediaContent(mEvent) : content}
msgType={msgType}
isEdited={isEdited}
/>
);
@@ -496,6 +500,7 @@ function RoomViewContent({
header={userHeader}
reply={userReply}
content={editEvent !== null && isEditingEvent ? null : userContent}
msgType={msgType}
editContent={editEvent !== null && isEditingEvent ? (
<MessageEdit
content={content}

View File

@@ -24,7 +24,7 @@ function RoomViewHeader({ roomId }) {
return (
<Header>
<Avatar imageSrc={avatarSrc} text={roomName.slice(0, 1)} bgColor={colorMXID(roomId)} size="small" />
<Avatar imageSrc={avatarSrc} text={roomName} bgColor={colorMXID(roomId)} size="small" />
<TitleWrapper>
<Text variant="h2">{roomName}</Text>
{ typeof roomTopic !== 'undefined' && <p title={roomTopic} className="text text-b3">{roomTopic}</p>}

View File

@@ -156,7 +156,7 @@ function Login({ loginFlow, baseUrl }) {
const [typeIndex, setTypeIndex] = useState(0);
const loginTypes = ['Username', 'Email'];
const isPassword = loginFlow?.filter((flow) => flow.type === 'm.login.password')[0];
const ssoProviders = loginFlow?.filter((flow) => flow.type.match(/^m.login.(sso|cas)$/))[0];
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
const initialValues = {
username: '', password: '', email: '', other: '',
@@ -248,7 +248,7 @@ function Login({ loginFlow, baseUrl }) {
{ssoProviders && isPassword && <Text className="sso__divider">OR</Text>}
{ssoProviders && (
<SSOButtons
type={ssoProviders.type.match(/^m.login.(sso|cas)$/)[1]}
type="sso"
identityProviders={ssoProviders.identity_providers}
baseUrl={baseUrl}
/>
@@ -269,7 +269,7 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
const [process, setProcess] = useState({});
const formRef = useRef();
const ssoProviders = loginFlow?.filter((flow) => flow.type.match(/^m.login.(sso|cas)$/))[0];
const ssoProviders = loginFlow?.filter((flow) => flow.type === 'm.login.sso')[0];
const isDisabled = registerInfo.errcode !== undefined;
const { flows, params, session } = registerInfo;
@@ -332,8 +332,8 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
actions.setSubmitting(false);
}).catch((err) => {
const msg = err.message || err.error;
if (['M_USER_IN_USE', 'M_INVALID_USERNAME', 'M_EXCLUSIVE'].indexOf(err.errcode) > 0) {
actions.setErrors({ username: err.errCode === 'M_USER_IN_USE' ? 'Username is already taken' : msg });
if (['M_USER_IN_USE', 'M_INVALID_USERNAME', 'M_EXCLUSIVE'].indexOf(err.errcode) > -1) {
actions.setErrors({ username: err.errcode === 'M_USER_IN_USE' ? 'Username is already taken' : msg });
} else if (msg) actions.setErrors({ other: msg });
actions.setSubmitting(false);
@@ -452,7 +452,7 @@ function Register({ registerInfo, loginFlow, baseUrl }) {
)}
{isDisabled && ssoProviders && (
<SSOButtons
type={ssoProviders.type.match(/^m.login.(sso|cas)$/)[1]}
type="sso"
identityProviders={ssoProviders.identity_providers}
baseUrl={baseUrl}
/>

View File

@@ -1,6 +1,6 @@
.auth__base {
--pattern-size: 48px;
min-height: 100vh;
min-height: 100%;
background-color: var(--bg-surface-low);
background-image: radial-gradient(rgba(0, 0, 0, 6%) 2px, rgba(0, 0, 0, 0%) 2px);

View File

@@ -18,7 +18,7 @@
top: 0;
left: 0;
width: 100vw;
height: 100vh;
height: 100%;
display: flex;
flex-direction: column;

View File

@@ -75,7 +75,10 @@ async function completeRegisterStage(
try {
const result = await tempClient.registerRequest({
username, password, auth,
username,
password,
auth,
initial_device_display_name: cons.DEVICE_DISPLAY_NAME,
});
const data = { completed: result.completed || [] };
if (result.access_token) {

View File

@@ -31,6 +31,7 @@ class InitMatrix extends EventEmitter {
sessionStore: new sdk.WebStorageSessionStore(global.localStorage),
cryptoStore: new sdk.IndexedDBCryptoStore(global.indexedDB, 'crypto-store'),
deviceId: secret.deviceId,
timelineSupport: true,
});
await this.matrixClient.initCrypto();

View File

@@ -1,5 +1,5 @@
const cons = {
version: '1.5.0',
version: '1.5.1',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',

View File

@@ -290,7 +290,13 @@ button {
overflow: visible;
-webkit-appearance: button;
}
textarea, input[type="text"] {
textarea,
input,
input[type],
input[type=text],
input[type=username],
input[type=password],
input[type=email] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;

View File

@@ -48,30 +48,6 @@ async function isRoomAliasAvailable(alias) {
}
}
function doesRoomHaveUnread(room) {
const userId = initMatrix.matrixClient.getUserId();
const readUpToId = room.getEventReadUpTo(userId);
const supportEvents = ['m.room.message', 'm.room.encrypted', 'm.sticker'];
if (room.timeline.length
&& room.timeline[room.timeline.length - 1].sender
&& room.timeline[room.timeline.length - 1].sender.userId === userId
&& room.timeline[room.timeline.length - 1].getType() !== 'm.room.member') {
return false;
}
for (let i = room.timeline.length - 1; i >= 0; i -= 1) {
const event = room.timeline[i];
if (event.getId() === readUpToId) return false;
if (supportEvents.includes(event.getType())) {
return true;
}
}
return true;
}
function getPowerLabel(powerLevel) {
if (powerLevel > 9000) return 'Goku';
if (powerLevel > 100) return 'Founder';
@@ -82,5 +58,5 @@ function getPowerLabel(powerLevel) {
export {
getBaseUrl, getUsername, getUsernameOfRoomMember,
isRoomAliasAvailable, doesRoomHaveUnread, getPowerLabel,
isRoomAliasAvailable, getPowerLabel,
};

View File

@@ -1,6 +1,5 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const FaviconsWebpackPlugin = require('favicons-webpack-plugin');
const webpack = require('webpack');
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
@@ -66,9 +65,6 @@ module.exports = {
}
}
}),
new webpack.DefinePlugin({
'process.env': JSON.stringify(process.env),
}),
new CopyPlugin({
patterns: [
{ from: 'olm.wasm' },