From d97d1eec19407ae1d05b70068ad6eef084d335a5 Mon Sep 17 00:00:00 2001 From: rob Date: Mon, 22 Apr 2024 18:02:50 -0400 Subject: [PATCH] many updates --- app/controllers/chat.js | 9 +- app/controllers/user.js | 21 +--- app/views/chat/components/message.pug | 3 + app/views/link/timeline.pug | 2 +- app/views/user/block-list.pug | 2 +- app/views/user/otp-disabled.pug | 2 +- app/views/user/otp-setup-complete.pug | 2 +- app/views/user/profile.pug | 105 ++++-------------- .../user/redeem/channel-pass-applied.pug | 2 +- app/views/user/redeem/channel-pass.pug | 2 +- client/css/site/image.less | 18 +++ client/js/chat-client.js | 34 ++++++ dtp-chat-cli.js | 68 ++++++++++-- 13 files changed, 150 insertions(+), 120 deletions(-) diff --git a/app/controllers/chat.js b/app/controllers/chat.js index 4f962f4..6d78637 100644 --- a/app/controllers/chat.js +++ b/app/controllers/chat.js @@ -92,8 +92,8 @@ export default class ChatController extends SiteController { router.get( '/room/:roomId/join', - // limiterService.create(limiterService.config.chat.getRoomView), - this.getRoomJoin.bind(this), + // limiterService.create(limiterService.config.chat.getRoomJoinView), + this.getRoomJoinView.bind(this), ); router.get( @@ -221,7 +221,7 @@ export default class ChatController extends SiteController { res.render('chat/room/create'); } - async getRoomJoin (req, res, next) { + async getRoomJoinView (req, res, next) { const { chat: chatService } = this.dtp.services; try { await chatService.joinRoom(res.locals.room, req.user); @@ -246,6 +246,8 @@ export default class ChatController extends SiteController { } async getRoomSettingsView (req, res) { + res.locals.currentView = 'chat-room'; + res.locals.pageTitle = `${res.locals.room.name} (Settings)`; res.render('chat/room/settings'); } @@ -253,6 +255,7 @@ export default class ChatController extends SiteController { const { chat: chatService } = this.dtp.services; try { res.locals.currentView = 'chat-room'; + res.locals.pageTitle = res.locals.room.name; res.locals.pagination = this.getPaginationParameters(req, ChatController.MESSAGES_PER_PAGE); res.locals.messages = await chatService.getRoomMessages(res.locals.room, res.locals.pagination); diff --git a/app/controllers/user.js b/app/controllers/user.js index 38b3e4d..cc00fa8 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -224,18 +224,8 @@ export default class UserController extends SiteController { try { const displayList = this.createDisplayList('profile-photo'); this.log.info('updating user profile photo', { user: req.user._id, file: req.file }); - const image = await userService.updatePhoto(req.user, req.file); - displayList.showNotification( - 'Profile photo updated successfully.', - 'success', - 'bottom-center', - 2000, - ); - displayList.setAttribute( - '#profile-picture-file', - 'src', - `/image/${image._id}`, - ); + await userService.updatePhoto(req.user, req.file); + displayList.reloadView(); res.status(200).json({ success: true, displayList }); } catch (error) { this.log.error('failed to update profile photo', { error }); @@ -441,12 +431,7 @@ export default class UserController extends SiteController { try { const displayList = this.createDisplayList('app-settings'); await userService.removePhoto(req.user); - displayList.showNotification( - 'Profile photo removed successfully.', - 'success', - 'bottom-center', - 2000, - ); + displayList.reloadView(); res.status(200).json({ success: true, displayList }); } catch (error) { this.log.error('failed to remove profile photo', { error }); diff --git a/app/views/chat/components/message.pug b/app/views/chat/components/message.pug index e5ff282..efa48a7 100644 --- a/app/views/chat/components/message.pug +++ b/app/views/chat/components/message.pug @@ -56,6 +56,9 @@ mixin renderChatMessage (message) +renderReactionBar(message) + if process.env.NODE_ENV !== 'production' + .uk-margin-small.uk-text-small.uk-text-muted id:#{message._id} + .message-menu .uk-flex.uk-flex-middle .uk-width-auto diff --git a/app/views/link/timeline.pug b/app/views/link/timeline.pug index 921cc60..ba350b6 100644 --- a/app/views/link/timeline.pug +++ b/app/views/link/timeline.pug @@ -1,4 +1,4 @@ -extends ../layouts/main +extends ../layout/main block vendorcss if user && (user.ui.theme === 'chat-light') link(rel='stylesheet', href=`/highlight.js/styles/qtcreator-light.min.css?v=${pkg.version}`) diff --git a/app/views/user/block-list.pug b/app/views/user/block-list.pug index b0f39f5..5fc96ee 100644 --- a/app/views/user/block-list.pug +++ b/app/views/user/block-list.pug @@ -1,4 +1,4 @@ -extends ../layouts/main +extends ../layout/main include components/profile-picture block content diff --git a/app/views/user/otp-disabled.pug b/app/views/user/otp-disabled.pug index 41d7989..103cb9a 100644 --- a/app/views/user/otp-disabled.pug +++ b/app/views/user/otp-disabled.pug @@ -1,4 +1,4 @@ -extends ../layouts/main +extends ../layout/main block content section.uk-section.uk-section-default diff --git a/app/views/user/otp-setup-complete.pug b/app/views/user/otp-setup-complete.pug index 294483c..9881927 100644 --- a/app/views/user/otp-setup-complete.pug +++ b/app/views/user/otp-setup-complete.pug @@ -1,4 +1,4 @@ -extends ../layouts/main +extends ../layout/main block content section.uk-section.uk-section-default diff --git a/app/views/user/profile.pug b/app/views/user/profile.pug index c890fb9..bb21ab2 100644 --- a/app/views/user/profile.pug +++ b/app/views/user/profile.pug @@ -1,95 +1,28 @@ -extends ../layouts/main -block content +extends ../layout/main +block view-content include components/profile-picture - include ../handcash/components/public-profile - section.uk-section.uk-section-default + section.uk-section.uk-section-default.uk-section-small .uk-container - .uk-margin-medium - div(uk-grid).uk-grid-small.uk-flex-middle.no-select + .uk-margin + div(uk-grid).no-select .uk-width-auto - +renderProfilePicture(user, { iconClass: 'sb-small' }) + +renderProfilePicture(user, { iconClass: 'picture-medium' }) .uk-width-expand h1.uk-margin-remove= user.displayName || user.username || user.email - if user.bio - .markdown-block.uk-text-truncate!= marked.parse(user.bio) - - .uk-margin-medium - h2.sr-only Account Services - div(class="uk-flex-around uk-flex-between@m", uk-grid) - .uk-width-auto - a(href=`/user/${userProfile._id}/settings`).uk-button.dtp-button-primary.uk-border-rounded - span - i.fas.fa-cog - span.uk-margin-small-left Settings - - .uk-width-auto - a(href='/sticker').uk-button.dtp-button-secondary.uk-border-rounded - span - i.far.fa-image - span.uk-margin-small-left Manage Stickers - - .uk-width-auto - a(href=`/user/${userProfile._id}/redeem/channel-pass`).uk-button.dtp-button-secondary.uk-border-rounded - span - i.fas.fa-bullhorn - span.uk-margin-small-left Redeem Pass + ul.uk-subnav + li + a(href=`/user/${userProfile._id}/settings`) + span + i.fas.fa-cog + span.uk-margin-small-left Settings + li + a(href=`/user/${userProfile._id}/block`) + span + i.fas.fa-ban + span.uk-margin-small-left Block List - .uk-width-auto - a(href=`/user/${userProfile._id}/block`).uk-button.dtp-button-secondary.uk-border-rounded - span - i.fas.fa-ban - span.uk-margin-small-left Block List - - - - var haveStripe = user.tokens && user.tokens.stripe; - var haveHandCash = handcash && handcash.profile; - - if haveStripe || haveHandCash - .uk-margin-medium - h2.sr-only Payment Services - div(uk-grid).uk-grid-match - if haveStripe - div(class="uk-width-1-1 uk-width-1-2@m") - .uk-card.uk-card-secondary.uk-card-small.uk-flex.uk-flex-column.uk-flex-stretch - .uk-card-header.uk-flex-none - h3.uk-card-title - div(uk-grid).uk-grid-small - .uk-width-auto - i.fab.fa-cc-stripe - .uk-width-expand Customer Portal - .uk-card-body.uk-flex-1 - p Access your Stripe Customer Portal to manage your subscription(s), payment methods, and customer profile. - p You may be required to log in or otherwise prove your identity when accessing the Stripe Customer Portal. - p Please email #[a(href="mailto:support@digitaltelepresence.com") support@digitaltelepresence.com] if you are having a problem here at #{site.name} that isn't related to Stripe or payments. - .uk-card-footer.uk-flex-none - a( - href=`/user/${userProfile._id}/stripe/customer-portal`, - uk-tooltip={ title: 'Manage your Stripe profile and details' }, - ).uk-button.dtp-button-default.uk-border-rounded - span Go To Customer Portal - - if haveHandCash - div(class="uk-width-1-1 uk-width-1-2@m") - .uk-card.uk-card-secondary.uk-card-small.uk-flex.uk-flex-column.uk-flex-stretch - .uk-card-header.uk-flex-none - h3.uk-card-title - div(uk-grid).uk-grid-small - .uk-width-auto - img(src="/img/payment/handcash-monogram.green.svg", style="height: 1em; width: auto;") - .uk-width-expand HandCash Web Wallet - .uk-card-body.uk-flex-1 - p You have connected your HandCash web wallet with #{site.name} for the easiest and fastest payments possible. - +renderHandcashPublicProfile(handcash.profile.publicProfile) - .uk-card-footer.uk-flex-none - .uk-margin - a( - href="https://market.handcash.io/my-account/balance", - target="_blank", - uk-tooltip={ title: 'Manage your HandCash web wallet' }, - ).uk-button.dtp-button-default.uk-border-rounded - span Go To Wallet - - .uk-margin-medium.uk-text-small.uk-text-muted You have used or connected with the payment services shown here. These convenience links help you manage those accounts, your subscriptions, payment methods, profile(s) and other details. \ No newline at end of file + if user.bio + .markdown-block.uk-text-truncate.uk-margin-remove!= marked.parse(user.bio) \ No newline at end of file diff --git a/app/views/user/redeem/channel-pass-applied.pug b/app/views/user/redeem/channel-pass-applied.pug index c7fe900..e5d4359 100644 --- a/app/views/user/redeem/channel-pass-applied.pug +++ b/app/views/user/redeem/channel-pass-applied.pug @@ -1,4 +1,4 @@ -extends ../../layouts/main +extends ../../layout/main block content section.uk-section.uk-section-default.uk-section-small diff --git a/app/views/user/redeem/channel-pass.pug b/app/views/user/redeem/channel-pass.pug index 4eaa23c..57a097b 100644 --- a/app/views/user/redeem/channel-pass.pug +++ b/app/views/user/redeem/channel-pass.pug @@ -1,4 +1,4 @@ -extends ../../layouts/main +extends ../../layout/main block content include ../../channel/components/list-item diff --git a/client/css/site/image.less b/client/css/site/image.less index 379cc1b..c0948e7 100644 --- a/client/css/site/image.less +++ b/client/css/site/image.less @@ -13,4 +13,22 @@ img.profile-picture { width: 64px; height: 64px; border-radius: 5px; + + &.picture-small { + width: 32px; + height: 32px; + border-radius: 5px; + } + + &.picture-medium { + width: 128px; + height: 128px; + border-radius: 5px; + } + + &.picture-large { + width: 512px; + height: 512px; + border-radius: 5px; + } } \ No newline at end of file diff --git a/client/js/chat-client.js b/client/js/chat-client.js index b9bbb82..a7f9fc8 100644 --- a/client/js/chat-client.js +++ b/client/js/chat-client.js @@ -758,6 +758,40 @@ export class ChatApp extends DtpApp { this.cropper = new Cropper(img, options); } + async removeImageFile (event) { + const target = event.target || event.currentTarget; + const imageType = target.getAttribute('data-image-type'); + const channelId = dtp.channel ? dtp.channel._id : dtp.channel; + + try { + this.log.info('removeImageFile', 'request to remove image', event); + + let imageUrl; + switch (imageType) { + case 'channel-thumbnail-file': + imageUrl = `/channel/${channelId}/thumbnail`; + break; + + case 'profile-picture-file': + imageUrl = `/user/${this.user._id}/profile-photo`; + break; + + default: + throw new Error(`Invalid image type: ${imageType}`); + } + + const response = await fetch(imageUrl, { method: 'DELETE' }); + if (!response.ok) { + throw new Error('Server error'); + } + + await this.processResponse(response); + } catch (error) { + this.log.error('removeImageFile', 'failed to remove image', { error }); + UIkit.modal.alert(`Failed to remove image: ${error.message}`); + } + } + scrollChatToBottom (isAtBottom = true) { if (this.chat && this.chat.messageList && isAtBottom) { this.chat.messageList.scrollTo(0, this.chat.messageList.scrollHeight); diff --git a/dtp-chat-cli.js b/dtp-chat-cli.js index 42da353..9437fb5 100644 --- a/dtp-chat-cli.js +++ b/dtp-chat-cli.js @@ -29,11 +29,35 @@ class SiteTerminalApp extends SiteRuntime { await super.start(); this.processors = { - 'grant': this.grant.bind(this), - 'revoke': this.revoke.bind(this), - 'probe': this.probeMediaFile.bind(this), - 'transcodeMov': this.transcodeMov.bind(this), - 'transcodeGif': this.transcodeGif.bind(this), + 'help': { + handler: this.help.bind(this), + help: 'help [command name]', + }, + + 'grant': { + handler: this.grant.bind(this), + help: 'grant [admin|moderator] username', + }, + 'revoke': { + handler: this.revoke.bind(this), + help: 'revoke [admin|moderator] username', + }, + 'probe': { + handler: this.probeMediaFile.bind(this), + help: 'probe filename', + }, + 'remove-message': { + handler: this.removeMessage.bind(this), + help: 'remove-message messageId', + }, + 'transcode-mov': { + handler: this.transcodeMov.bind(this), + help: 'transcode-mov filename', + }, + 'transcode-gif': { + handler: this.transcodeGif.bind(this), + help: 'transcode-gif filename', + }, }; } @@ -50,12 +74,32 @@ class SiteTerminalApp extends SiteRuntime { this.log.error('Unknown command', { command }); return; } - return processor(args); + return processor.handler(args); + } + + async help (args) { + const commandName = args.shift(); + if (commandName) { + const command = this.processors[commandName]; + if (!command) { + this.log.error('invalid/unknown command', { commandName }); + return; + } + console.log(`\n\nCommand: ${commandName}\n${command.help}\n`); + } + + console.log( + `\nDTP Chat Command Line Interface Help\nVersion: ${this.config.pkg.version}\n` + ); + for (const commandName in this.processors) { + const command = this.processors[commandName]; + console.log(`Command: ${commandName}\n${command.help}\n`); + } } async grant (args) { const User = mongoose.model('User'); - + const privilege = args.shift(); const username = args.shift(); @@ -105,6 +149,16 @@ class SiteTerminalApp extends SiteRuntime { this.log.info('FFPROBE result', { probe }); } + async removeMessage (args) { + const { chat: chatService } = this.services; + const messageId = args.shift(); + const message = await chatService.getMessageById(messageId); + if (!message) { + throw new Error('message not found'); + } + await chatService.removeMessage(message); + } + async transcodeMov (args) { const { video: videoService } = this.services;