Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92727b60f4 | ||
|
|
e23ae8fec1 | ||
|
|
93117be8cf | ||
|
|
947c46ad83 | ||
|
|
b3bff6b43f |
No files matched your search
+6
-1
@@ -36,4 +36,9 @@ 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).**
|
||||
**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)
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Ajay Bura (ajbura)
|
||||
Copyright (c) 2021-present 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,9 +1,5 @@
|
||||
<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">
|
||||
# Cinny
|
||||
<p>
|
||||
<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">
|
||||
@@ -16,21 +12,30 @@
|
||||
<img alt="Sponsor Cinny" src="https://img.shields.io/opencollective/all/cinny?logo=opencollective&style=social"></a>
|
||||
</p>
|
||||
|
||||
**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)
|
||||
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)
|
||||
- [Contributing](./CONTRIBUTING.md)
|
||||
|
||||
## Building and Running
|
||||
## 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.
|
||||
|
||||
### Running pre-compiled
|
||||
You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
|
||||
|
||||
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.
|
||||
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
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>PGP Public Key to verify pre-compiled tarball</summary>
|
||||
<summary>PGP Public Key to verify tarball</summary>
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
@@ -77,48 +82,39 @@ UeGsouhyuITLwEhScounZDqop+Dx
|
||||
```
|
||||
</details>
|
||||
|
||||
### Building from source
|
||||
## Local development
|
||||
> 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 compile the app from its source code:
|
||||
|
||||
Execute the following commands to start a development server:
|
||||
```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 Ajay Bura (ajbura)
|
||||
Copyright (c) 2021-present Ajay Bura (ajbura)
|
||||
|
||||
Code licensed under the MIT License: <http://opensource.org/licenses/MIT>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Graphics (c) by Ajay Bura (ajbura)
|
||||
Copyright (c) 2021-present Ajay Bura (ajbura)
|
||||
|
||||
Graphic content is licensed under a
|
||||
Creative Commons Attribution 4.0 International License.
|
||||
|
||||
@@ -714,9 +714,9 @@ function getEditedBody(editedMEvent) {
|
||||
}
|
||||
|
||||
function Message({
|
||||
mEvent, isBodyOnly, roomTimeline, focus, fullTime,
|
||||
mEvent, isBodyOnly, roomTimeline,
|
||||
focus, fullTime, isEdit, setEdit, cancelEdit,
|
||||
}) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const roomId = mEvent.getRoomId();
|
||||
const { editedTimeline, reactionTimeline } = roomTimeline ?? {};
|
||||
|
||||
@@ -731,7 +731,7 @@ function Message({
|
||||
const avatarSrc = mEvent.sender?.getAvatarUrl(initMatrix.matrixClient.baseUrl, 36, 36, 'crop') ?? null;
|
||||
|
||||
const edit = useCallback(() => {
|
||||
setIsEditing(true);
|
||||
setEdit(eventId);
|
||||
}, []);
|
||||
const reply = useCallback(() => {
|
||||
replyTo(senderId, mEvent.getId(), body);
|
||||
@@ -788,7 +788,7 @@ function Message({
|
||||
eventId={mEvent.replyEventId}
|
||||
/>
|
||||
)}
|
||||
{!isEditing && (
|
||||
{!isEdit && (
|
||||
<MessageBody
|
||||
senderName={username}
|
||||
isCustomHTML={isCustomHTML}
|
||||
@@ -797,22 +797,22 @@ function Message({
|
||||
isEdited={isEdited}
|
||||
/>
|
||||
)}
|
||||
{isEditing && (
|
||||
{isEdit && (
|
||||
<MessageEdit
|
||||
body={body}
|
||||
onSave={(newBody) => {
|
||||
if (newBody !== body) {
|
||||
initMatrix.roomsInput.sendEditedMessage(roomId, mEvent, newBody);
|
||||
}
|
||||
setIsEditing(false);
|
||||
cancelEdit();
|
||||
}}
|
||||
onCancel={() => setIsEditing(false)}
|
||||
onCancel={cancelEdit}
|
||||
/>
|
||||
)}
|
||||
{haveReactions && (
|
||||
<MessageReactionGroup roomTimeline={roomTimeline} mEvent={mEvent} />
|
||||
)}
|
||||
{roomTimeline && !isEditing && (
|
||||
{roomTimeline && !isEdit && (
|
||||
<MessageOptions
|
||||
roomTimeline={roomTimeline}
|
||||
mEvent={mEvent}
|
||||
@@ -829,6 +829,9 @@ Message.defaultProps = {
|
||||
focus: false,
|
||||
roomTimeline: null,
|
||||
fullTime: false,
|
||||
isEdit: false,
|
||||
setEdit: null,
|
||||
cancelEdit: null,
|
||||
};
|
||||
Message.propTypes = {
|
||||
mEvent: PropTypes.shape({}).isRequired,
|
||||
@@ -836,6 +839,9 @@ Message.propTypes = {
|
||||
roomTimeline: PropTypes.shape({}),
|
||||
focus: PropTypes.bool,
|
||||
fullTime: PropTypes.bool,
|
||||
isEdit: PropTypes.bool,
|
||||
setEdit: PropTypes.func,
|
||||
cancelEdit: PropTypes.func,
|
||||
};
|
||||
|
||||
export { Message, MessageReply, PlaceholderMessage };
|
||||
@@ -229,6 +229,8 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,15 @@ function handleOnClickCapture(e) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
|
||||
function renderEvent(
|
||||
roomTimeline,
|
||||
mEvent,
|
||||
prevMEvent,
|
||||
isFocus,
|
||||
isEdit,
|
||||
setEdit,
|
||||
cancelEdit,
|
||||
) {
|
||||
const isBodyOnly = (prevMEvent !== null
|
||||
&& prevMEvent.getSender() === mEvent.getSender()
|
||||
&& prevMEvent.getType() !== 'm.room.member'
|
||||
@@ -147,6 +155,9 @@ function renderEvent(roomTimeline, mEvent, prevMEvent, isFocus = false) {
|
||||
roomTimeline={roomTimeline}
|
||||
focus={isFocus}
|
||||
fullTime={false}
|
||||
isEdit={isEdit}
|
||||
setEdit={setEdit}
|
||||
cancelEdit={cancelEdit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -386,6 +397,8 @@ 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();
|
||||
@@ -470,6 +483,42 @@ 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;
|
||||
@@ -535,7 +584,15 @@ function RoomViewContent({ eventId, roomTimeline }) {
|
||||
const isFocus = focusId === mEvent.getId();
|
||||
if (isFocus) jumpToItemIndex = itemCountIndex;
|
||||
|
||||
tl.push(renderEvent(roomTimeline, mEvent, isNewEvent ? null : prevMEvent, isFocus));
|
||||
tl.push(renderEvent(
|
||||
roomTimeline,
|
||||
mEvent,
|
||||
isNewEvent ? null : prevMEvent,
|
||||
isFocus,
|
||||
editEventId === mEvent.getId(),
|
||||
setEditEventId,
|
||||
cancelEdit,
|
||||
));
|
||||
itemCountIndex += 1;
|
||||
}
|
||||
if (roomTimeline.canPaginateForward() || limit.length < timeline.length) {
|
||||
|
||||
Reference in new issue
Block a user