Browse Source

several updates

- Added a new emoji picker and its presentation logic
- Added ability to delete a chat room (as owner)
- Removing a chat message now also removes all attachments on that
message from storage
- Fixed many UI bugs in chat view
-
develop
Rob Colbert 1 year ago
parent
commit
dbc47abac4
  1. 5
      README.md
  2. 13
      app/controllers/chat.js
  3. 11
      app/services/chat.js
  4. 10
      app/services/video.js
  5. 17
      app/views/chat/components/message.pug
  6. 8
      app/views/chat/room/settings.pug
  7. 24
      app/views/chat/room/view.pug
  8. 8
      app/views/components/emoji-picker.pug
  9. 7
      client/css/dtp-site.less
  10. 22
      client/css/site/emoji-picker.less
  11. 25
      client/css/site/stage.less
  12. 55
      client/js/chat-client.js
  13. 2
      client/js/index.js
  14. 1
      package.json
  15. 8
      yarn.lock

5
README.md

@ -1,2 +1,5 @@
# DTP Chat
This project is currently being used to develop an all-new harness for developing and deploying DTP web apps. Gulp is now gone, it's based on Webpack, Nodemon and BrowserSync.
This project is currently being used to develop an all-new harness for developing and deploying DTP web apps. Gulp is now gone, it's based on Webpack, Nodemon and BrowserSync.
## Emoji Picker
Chat currently uses [emoji-picker-element](https://www.npmjs.com/package/emoji-picker-element) as the renderer of an emoji picker, and then it manages the presentation of the picker itself.

13
app/controllers/chat.js

@ -219,14 +219,21 @@ export default class ChatController extends SiteController {
}
}
async deleteRoom (req, res, next) {
async deleteRoom (req, res) {
const { chat: chatService } = this.dtp.services;
try {
await chatService.destroyRoom(req.user, res.locals.room);
res.redirect('/');
const displayList = this.createDisplayList('chat-room-delete');
displayList.navigateto('/');
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to destroy chat room', { error });
return next(error);
res.status(error.statusCode || 500).json({
success: false,
message: error.message,
});
}
}
}

11
app/services/chat.js

@ -374,14 +374,23 @@ export default class ChatService extends SiteService {
}
async removeMessage (message) {
const { image: imageService } = this.dtp.services;
const { image: imageService, video: videoService } = this.dtp.services;
if (message.attachments) {
if (Array.isArray(message.attachments.images) && (message.attachments.images.length > 0)) {
for (const image of message.attachments.images) {
this.log.debug('removing message attachment', { imageId: image._id });
await imageService.deleteImage(image);
}
}
if (Array.isArray(message.attachments.videos) && (message.attachments.videos.length > 0)) {
for (const video of message.attachments.videos) {
this.log.debug('removing video attachment', { videoId: video._id });
await videoService.removeVideo(video);
}
}
}
this.log.debug('removing chat message', { messageId: message._id });
await ChatMessage.deleteOne({ _id: message._id });
}
}

10
app/services/video.js

@ -184,4 +184,14 @@ export default class VideoService extends SiteService {
.lean();
return video;
}
async removeVideo (video) {
const { minio: minioService } = this.dtp.services;
if (video.thumbnail) {
await this.removeVideoThumbnailImage(video);
}
await minioService.removeObject(video.media.bucket, video.media.key);
}
}

17
app/views/chat/components/message.pug

@ -7,11 +7,9 @@ mixin renderChatMessage (message)
+renderProfilePicture(message.author, { iconClass: 'member-profile-icon' })
.uk-width-expand
.message-attribution.uk-margin-small.no-select
.uk-flex.uk-flex-top
.uk-width-expand
if (message.author.displayName && (message.author.displayName.length > 0))
.author-display-name= message.author.displayName
.author-username @#{message.author.username}
div(uk-grid).uk-grid-small.uk-flex-middle
.uk-width-auto
.author-display-name= message.author.displayName || message.author.username
.uk-width-auto
.message-timestamp(
data-dtp-timestamp= message.created,
@ -43,4 +41,11 @@ mixin renderChatMessage (message)
if Array.isArray(message.links) && (message.links.length > 0)
each link in message.links
div(class="uk-width-large").uk-margin-small
+renderLinkPreview(link, { layout: 'responsive' })
+renderLinkPreview(link, { layout: 'responsive' })
.uk-width-auto
.uk-text-bold !
.message-menu
div(uk-grid).uk-grid-small
.uk-width-auto
div Emoji reacts & shit

8
app/views/chat/room/settings.pug

@ -18,4 +18,10 @@ block view-content
input(id="topic", name="topic", type="text", placeholder="Enter room topic or leave blank", value= room.topic).uk-input
.uk-card-footer.uk-flex.uk-flex-right
button(type="submit").uk-button.uk-button-default.uk-border-rounded Save Settings
button(
type="button",
data-room-id= room._id,
data-room-name= room.name,
onclick="dtp.app.confirmRoomDelete(event);",
).uk-button.uk-button-danger.uk-border-rounded.uk-margin-right Delete Room
button(type="submit").uk-button.uk-button-primary.uk-border-rounded Save Settings

24
app/views/chat/room/view.pug

@ -85,19 +85,31 @@ block view-content
.uk-flex
.uk-width-expand
div(uk-grid).uk-grid-small
.uk-width-auto
button(
type="button",
data-target="#chat-input-text",
uk-tooltip="Select emojis to add",
onclick="dtp.app.showEmojiPicker(event);",
).uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fa-regular.fa-face-smile
.uk-width-auto
.uk-form-custom
input(id="image-files", name="imageFiles", type="file")
input(id="image-files", name="imageFiles", type="file", uk-tooltip="Select an image to attach")
button(type="button").uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fa.fa-image
span
i.fa-regular.fa-image
.uk-width-auto
.uk-form-custom
input(id="video-file", name="videoFiles", type="file")
input(id="video-file", name="videoFiles", type="file", uk-tooltip="Select a video to attach")
button(type="button").uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fa.fa-video
span
i.fa-solid.fa-video
.uk-width-auto
button(id="chat-send-btn", type="submit", uk-tooltip={ title: 'Send message' }).uk-button.uk-button-primary.uk-button-small.uk-border-rounded
i.fas.fa-paper-plane
button(id="chat-send-btn", type="submit", uk-tooltip={ title: 'Send message' }).uk-button.uk-button-default.uk-button-small.uk-border-rounded
i.fa-regular.fa-paper-plane
include ../../components/emoji-picker
block viewjs
script.

8
app/views/components/emoji-picker.pug

@ -0,0 +1,8 @@
.emoji-picker-display
.emoji-picker-prompt.sr-only Select an emoji
emoji-picker(
class={
'dark': (user && (user.ui.theme === 'chat-dark')),
'light': (!user || (user.ui.theme === 'chat-light')),
}
)

7
client/css/dtp-site.less

@ -2,7 +2,8 @@
@import "site/main.less";
@import "site/button.less";
@import "site/navbar.less";
@import "site/stage.less";
@import "site/emoji-picker.less";
@import "site/image.less";
@import "site/link-preview.less";
@import "site/image.less";
@import "site/navbar.less";
@import "site/stage.less";

22
client/css/site/emoji-picker.less

@ -0,0 +1,22 @@
.emoji-picker-display {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0,0,0, 0.8);
color: #e8e8e8;
&.picker-active {
display: flex;
}
.emoji-picker-prompt {
margin-bottom: 10px;
font-size: 1.5em;
color: #e8e8e8;
}
}

25
client/css/site/stage.less

@ -40,7 +40,7 @@
width: 32px;
height: auto;
border-radius: 5px;
margin-right: 5px;
margin-right: 10px;
}
.member-list-item {
@ -140,6 +140,9 @@
overflow-y: scroll;
.chat-message {
box-sizing: border-box;
position: relative;
padding: 5px;
margin-bottom: 5px;
@ -148,6 +151,26 @@
background-color: @chat-message-bgcolor;
color: @chat-message-color;
&:hover {
.message-menu {
display: block;
}
}
.message-menu {
display: none;
box-sizing: border-box;
position: absolute;
top: 0; right: 10px;
padding: 5px 10px;
background-color: white;
color: #1a1a1a;
border-radius: 16px;
}
&.system-message {
font-size: 0.8em;

55
client/js/chat-client.js

@ -49,6 +49,15 @@ export class ChatApp extends DtpApp {
window.addEventListener('unload', this.onDtpUnload.bind(this));
this.updateTimestamps();
this.emojiPickerDisplay = document.querySelector('.emoji-picker-display');
if (this.emojiPickerDisplay) {
this.emojiPickerDisplay.addEventListener('click', this.onEmojiPickerClose.bind(this));
}
this.emojiPicker = document.querySelector('emoji-picker');
if (this.emojiPicker) {
this.emojiPicker.addEventListener('emoji-click', this.onEmojiPicked.bind(this));
}
}
async startAudio ( ) {
@ -373,6 +382,23 @@ export class ChatApp extends DtpApp {
return true;
}
async confirmRoomDelete (event) {
const target = event.currentTarget || event.target;
const roomId = target.getAttribute('data-room-id');
const roomName = target.getAttribute('data-room-name');
try {
await UIkit.modal.confirm(`Are you sure you want to delete "${roomName}"?`);
} catch (error) {
return;
}
try {
const response = await fetch(`/chat/room/${roomId}`, { method: 'DELETE' });
await this.processResponse(response);
} catch (error) {
UIkit.modal.alert(error.message);
}
}
async generateOtpQR (canvas, keyURI) {
QRCode.toCanvas(canvas, keyURI);
}
@ -683,4 +709,33 @@ export class ChatApp extends DtpApp {
this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight + 50000);
}
}
async showEmojiPicker (event) {
const target = event.currentTarget || event.target;
const emojiTargetSelector = target.getAttribute('data-target');
this.emojiPickerTarget = document.querySelector(emojiTargetSelector);
if (!this.emojiPickerTarget) {
UIkit.modal.alert('Invalid emoji picker target');
return;
}
this.emojiPickerDisplay.classList.add('picker-active');
}
async onEmojiPickerClose (event) {
if (!this.emojiPickerDisplay) {
return;
}
if (!event.target.classList.contains('emoji-picker-display')) {
return;
}
this.emojiPickerDisplay.classList.remove('picker-active');
}
async onEmojiPicked (event) {
event = event.detail;
this.log.info('onEmojiPicked', 'An emoji has been selected', { event });
this.emojiPickerTarget.value += event.unicode;
}
}

2
client/js/index.js

@ -11,6 +11,8 @@ import { ChatApp } from './chat-client.js';
import DtpWebLog from 'lib/dtp-log.js';
window.addEventListener('load', async ( ) => {
await import('emoji-picker-element');
dtp.log = new DtpWebLog(DTP_COMPONENT_NAME);
dtp.env = document.body.getAttribute('data-dtp-env');

1
package.json

@ -34,6 +34,7 @@
"dotenv": "^16.4.5",
"email-domain-check": "^1.1.4",
"email-validator": "^2.0.4",
"emoji-picker-element": "^1.21.3",
"express": "^4.19.2",
"express-limiter": "^1.6.1",
"express-session": "^1.18.0",

8
yarn.lock

@ -3709,6 +3709,7 @@ __metadata:
dotenv: "npm:^16.4.5"
email-domain-check: "npm:^1.1.4"
email-validator: "npm:^2.0.4"
emoji-picker-element: "npm:^1.21.3"
express: "npm:^4.19.2"
express-limiter: "npm:^1.6.1"
express-session: "npm:^1.18.0"
@ -3825,6 +3826,13 @@ __metadata:
languageName: node
linkType: hard
"emoji-picker-element@npm:^1.21.3":
version: 1.21.3
resolution: "emoji-picker-element@npm:1.21.3"
checksum: 10c0/d7c1f98c598a23f86e6d1e1d5ade8713b31e021e3b5388b175106476976e9a41751497cd6e448d85402811ed5dabed3b38177b0da3bcdadb2a6b263a9282c149
languageName: node
linkType: hard
"emoji-regex@npm:^8.0.0":
version: 8.0.0
resolution: "emoji-regex@npm:8.0.0"

Loading…
Cancel
Save