UI: chat composer step 2 (#21641)

- few improved alignments
- displays emoji picker button inline on desktop
- keeps composer focused when focusing dropdown button
- align buttons to bottom when increasing height of textarea
- max-height of textarea is now linked to the height of the screen

Co-authored-by: chapoi <charlie@discourse.org>
This commit is contained in:
Joffrey JAFFEUX 2023-05-22 17:00:50 +02:00 committed by GitHub
parent 5cce829901
commit bdfd80bfe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 93 additions and 239 deletions

View File

@ -9,6 +9,7 @@
"chat-composer-dropdown__trigger-btn"
(if @hasActivePanel "has-active-panel")
}}
...attributes
/>
{{#if this.isExpanded}}

View File

@ -1,7 +0,0 @@
{{#each this.buttons as |button|}}
<DButton
@icon={{button.icon}}
@class={{concat "chat-composer-inline-button " button.id}}
@action={{action button.action}}
/>
{{/each}}

View File

@ -1,5 +0,0 @@
import Component from "@ember/component";
export default class ChatComposerInlineButtons extends Component {
tagName = "";
}

View File

@ -39,6 +39,8 @@
this.context
}}
@onCloseActivePanel={{this.chatEmojiPickerManager.close}}
{{on "focus" (fn this.computeIsFocused true)}}
{{on "blur" (fn this.computeIsFocused false)}}
/>
<div
@ -66,20 +68,36 @@
/>
</div>
{{#if this.inlineButtons.length}}
{{#each this.inlineButtons as |button|}}
<Chat::Composer::Button
@icon={{button.icon}}
class={{button.id}}
disabled={{button.disabled}}
tabindex={{if button.disabled -1 0}}
{{on
"click"
(fn this.handleInlineButonAction button.action)
bubbles=false
}}
{{on "focus" (fn this.computeIsFocused true)}}
{{on "blur" (fn this.computeIsFocused false)}}
/>
{{/each}}
<Chat::Composer::Separator />
{{/if}}
<Chat::Composer::Button
{{on "click" this.onSend}}
@icon="paper-plane"
class="chat-composer__send-btn"
title={{i18n "chat.composer.send"}}
disabled={{or this.disabled (not this.sendEnabled)}}
tabindex={{if this.sendEnabled 0 -1}}
{{on "click" this.onSend}}
{{on "focus" (fn this.computeIsFocused true)}}
{{on "blur" (fn this.computeIsFocused false)}}
/>
{{#unless this.disabled}}
<ChatComposerInlineButtons @buttons={{this.inlineButtons}} />
{{/unless}}
</div>
</div>
</div>

View File

@ -122,6 +122,13 @@ export default class ChatComposer extends Component {
cancel(this._persistHandler);
}
@action
handleInlineButonAction(buttonAction, event) {
event.stopPropagation();
buttonAction();
}
get currentMessage() {
return this.composer.message;
}

View File

@ -0,0 +1 @@
<div class="chat-composer-separator"></div>

View File

@ -0,0 +1,3 @@
import Component from "@glimmer/component";
export default class ChatComposerSeparator extends Component {}

View File

@ -18,6 +18,7 @@ export default {
initialize(container) {
this.chatService = container.lookup("service:chat");
this.site = container.lookup("service:site");
this.siteSettings = container.lookup("service:site-settings");
this.appEvents = container.lookup("service:app-events");
this.appEvents.on("discourse:focus-changed", this, "_handleFocusChanged");
@ -58,8 +59,8 @@ export default {
label: "chat.emoji",
id: "emoji",
class: "chat-emoji-btn",
icon: "discourse-emojis",
position: "dropdown",
icon: "far-smile",
position: this.site.desktopView ? "inline" : "dropdown",
context: "channel",
action() {
const chatEmojiPickerManager = container.lookup(

View File

@ -8,7 +8,7 @@
box-sizing: border-box;
width: 50px;
border: 0;
height: 100%;
height: 50px;
background: none;
}
}

View File

@ -1,9 +0,0 @@
.chat-composer-inline-button {
border-radius: 6px;
width: 32px;
height: 32px;
& + .chat-composer-inline-button {
margin-left: 0.25rem;
}
}

View File

@ -0,0 +1,8 @@
.chat-composer-separator {
width: 1px;
margin: 10px 0.25rem;
box-sizing: border-box;
background: var(--primary-low-mid);
height: 30px;
display: flex;
}

View File

@ -4,7 +4,7 @@
flex-direction: column;
z-index: 3;
background-color: var(--primary-very-low);
padding: 12px 10px env(safe-area-inset-bottom) 10px;
padding: 0.5rem 0 env(safe-area-inset-bottom) 0;
.keyboard-visible & {
padding-bottom: 0;
@ -22,7 +22,7 @@
.chat-composer-button,
.chat-composer-separator {
align-self: stretch;
align-self: flex-end;
}
&__outer-container {
@ -30,6 +30,19 @@
align-items: center;
box-sizing: border-box;
width: 100%;
padding-inline: 1rem;
}
&__inline-button {
.d-icon {
color: var(--primary-medium);
}
&:hover {
.d-icon {
color: var(--primary);
}
}
}
&__inner-container {
@ -85,6 +98,7 @@
.d-icon {
background: none !important;
color: var(--primary);
}
.chat-composer.is-send-enabled & {
@ -122,10 +136,6 @@
background: none !important;
}
}
.d-icon {
color: var(--primary);
}
}
&__input-container {
@ -136,6 +146,11 @@
align-self: stretch;
}
--100dvh: 100vh;
@supports (height: 100dvh) {
--100dvh: 100dvh;
}
&__input {
overflow-x: hidden;
width: 100%;
@ -143,13 +158,17 @@
outline: none;
border: 0;
resize: none;
max-height: 125px;
max-height: calc(
(
var(--100dvh) - var(--header-offset, 0px) -
var(--chat-header-offset, 0px)
) / 100 * 25
);
background: none;
margin: 0;
padding: 0;
margin: 5px 0;
text-overflow: ellipsis;
cursor: inherit;
@include chat-scrollbar();
&[disabled] {

View File

@ -10,7 +10,6 @@
@import "chat-channel-settings-saved-indicator";
@import "chat-channel-title";
@import "chat-composer-dropdown";
@import "chat-composer-inline-button";
@import "chat-composer-upload";
@import "chat-composer-uploads";
@import "chat-composer";
@ -52,3 +51,4 @@
@import "chat-thread-list-item";
@import "chat-threads-list";
@import "chat-thread-original-message";
@import "chat-composer-separator";

View File

@ -35,7 +35,7 @@
}
.chat-message:not(.user-info-hidden) {
padding: 0.65em 1em 0.15em;
padding: 0.65rem 1rem 0.15rem;
}
.chat-message-text {
@ -52,7 +52,7 @@
}
.chat-message.user-info-hidden {
padding: 0.15em 1em;
padding: 0.15rem 1rem;
.chat-time {
color: var(--secondary-medium);

View File

@ -192,8 +192,7 @@ RSpec.describe "Chat composer", type: :system, js: true do
SiteSetting.emoji_deny_list = "monkey|peach"
chat.visit_channel(channel_1)
channel.open_action_menu
channel.click_action_button("emoji")
channel.composer.open_emoji_picker
expect(page).to have_no_selector("[data-emoji='monkey']")
expect(page).to have_no_selector("[data-emoji='peach']")

View File

@ -158,6 +158,7 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile
expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "1")
expect(page).to have_css(
".chat-channel-row[data-chat-channel-id=\"#{dm_channel_1.id}\"] .chat-channel-unread-indicator",
wait: 25,
)
using_session(:user_1) do |session|
@ -165,7 +166,11 @@ RSpec.describe "Message notifications - mobile", type: :system, js: true, mobile
session.quit
end
expect(page).to have_css(".chat-header-icon .chat-channel-unread-indicator", text: "2")
expect(page).to have_css(
".chat-header-icon .chat-channel-unread-indicator",
text: "2",
wait: 25,
)
end
it "reorders channels" do

View File

@ -1,172 +0,0 @@
# frozen_string_literal: true
RSpec.describe "Navigating to message", type: :system, js: true do
fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:category_channel) }
fab!(:first_message) { Fabricate(:chat_message, chat_channel: channel_1) }
let(:chat_page) { PageObjects::Pages::Chat.new }
let(:chat_drawer_page) { PageObjects::Pages::ChatDrawer.new }
let(:link) { "My favorite message" }
before do
chat_system_bootstrap
channel_1.add(current_user)
75.times { Fabricate(:chat_message, chat_channel: channel_1) }
sign_in(current_user)
end
context "when in full page mode" do
before { chat_page.prefers_full_page }
context "when clicking a link containing a message id" do
fab!(:topic_1) { Fabricate(:topic) }
before do
Fabricate(
:post,
topic: topic_1,
raw: "<a href=\"/chat/c/-/#{channel_1.id}/#{first_message.id}\">#{link}</a>",
)
end
it "highlights the correct message" do
visit("/t/-/#{topic_1.id}")
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
context "when clicking a link to a message from the current channel" do
before do
Fabricate(
:chat_message,
chat_channel: channel_1,
message: "[#{link}](/chat/c/-/#{channel_1.id}/#{first_message.id})",
)
end
it "highlights the correct message" do
chat_page.visit_channel(channel_1)
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
it "highlights the correct message after using the bottom arrow" do
chat_page.visit_channel(channel_1)
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
click_button(class: "chat-scroll-to-bottom")
expect(page).to have_content(link)
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
context "when clicking a link to a message from another channel" do
fab!(:channel_2) { Fabricate(:category_channel) }
before do
Fabricate(
:chat_message,
chat_channel: channel_2,
message: "[#{link}](/chat/c/-/#{channel_1.id}/#{first_message.id})",
)
channel_2.add(current_user)
end
it "highlights the correct message" do
chat_page.visit_channel(channel_2)
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
context "when navigating directly to a message link" do
it "highglights the correct message" do
visit("/chat/c/-/#{channel_1.id}/#{first_message.id}")
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
end
context "when in drawer" do
context "when clicking a link containing a message id" do
fab!(:topic_1) { Fabricate(:topic) }
before do
Fabricate(
:post,
topic: topic_1,
raw: "<a href=\"/chat/c/-/#{channel_1.id}/#{first_message.id}\">#{link}</a>",
)
end
it "highlights correct message" do
visit("/t/-/#{topic_1.id}")
click_link(link)
expect(page).to have_css(
".chat-drawer.is-expanded .chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
context "when clicking a link to a message from the current channel" do
before do
Fabricate(
:chat_message,
chat_channel: channel_1,
message: "[#{link}](/chat/c/-/#{channel_1.id}/#{first_message.id})",
)
end
it "highlights the correct message" do
visit("/")
chat_page.open_from_header
chat_drawer_page.open_channel(channel_1)
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
it "highlights the correct message after using the bottom arrow" do
visit("/")
chat_page.open_from_header
chat_drawer_page.open_channel(channel_1)
click_link(link)
click_button(class: "chat-scroll-to-bottom")
click_link(link)
expect(page).to have_css(
".chat-message-container.highlighted[data-id='#{first_message.id}']",
)
end
end
end
end

View File

@ -3,6 +3,10 @@
module PageObjects
module Pages
class ChatChannel < PageObjects::Pages::Base
def composer
@composer ||= PageObjects::Components::Chat::Composer.new(".chat-channel")
end
def replying_to?(message)
find(".chat-channel .chat-reply", text: message.message)
end

View File

@ -27,6 +27,10 @@ module PageObjects
def edit_last_message_shortcut
input.send_keys(%i[arrow_up])
end
def open_emoji_picker
find(context).find(SELECTOR).find(".chat-composer-button__btn.emoji").click
end
end
end
end

View File

@ -1,23 +0,0 @@
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
module(
"Discourse Chat | Component | chat-composer-inline-buttons",
function (hooks) {
setupRenderingTest(hooks);
test("buttons", async function (assert) {
this.set("buttons", [{ id: "foo", icon: "times", action: () => {} }]);
await render(
hbs`<ChatComposerInlineButtons @buttons={{this.buttons}} />`
);
assert.true(exists(".chat-composer-inline-button.foo"));
assert.true(exists(".chat-composer-inline-button.foo .d-icon-times"));
});
}
);