UX: Maximize the preview space in composer (#15188)

A follow-up to #15117 and #15141. Applies the previous changes to PM-specific fields, makes the preview area take the all the available height of the composer, and unifies more spacing between composer elements.
This commit is contained in:
Jarek Radosz 2021-12-24 12:38:33 +01:00 committed by GitHub
parent a263743268
commit 0b34d5ac6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 259 additions and 235 deletions

View File

@ -1,4 +1,4 @@
{{d-editor
{{#d-editor
value=composer.reply
placeholder=replyPlaceholder
previewUpdated=(action "previewUpdated")
@ -19,6 +19,8 @@
disabled=disableTextarea
outletArgs=(hash composer=composer editorType="composer")
}}
{{yield}}
{{/d-editor}}
{{#if allowUpload}}
{{#if acceptsAllFormats}}

View File

@ -1,58 +1,62 @@
<div class="d-editor-container">
<div class="d-editor-textarea-wrapper {{if disabled "disabled"}} {{if isEditorFocused "in-focus"}}">
<div class="d-editor-button-bar" role="toolbar">
{{#each toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{#if b.popupMenu}}
{{toolbar-popup-menu-options
content=popupMenuOptions
onChange=onPopupMenuAction
onOpen=(action b.action b)
class=b.className
tabindex=-1
onKeydown=rovingButtonBar
options=(hash
<div class="d-editor-textarea-column">
{{yield}}
<div class="d-editor-textarea-wrapper {{if disabled "disabled"}} {{if isEditorFocused "in-focus"}}">
<div class="d-editor-button-bar" role="toolbar">
{{#each toolbar.groups as |group|}}
{{#each group.buttons as |b|}}
{{#if b.popupMenu}}
{{toolbar-popup-menu-options
content=popupMenuOptions
onChange=onPopupMenuAction
onOpen=(action b.action b)
class=b.className
tabindex=-1
onKeydown=rovingButtonBar
options=(hash
icon=b.icon
focusAfterOnChange=false
)
}}
{{else}}
{{d-button
action=b.action
type="button"
actionParam=b
translatedTitle=b.title
label=b.label
icon=b.icon
focusAfterOnChange=false
)
}}
{{else}}
{{d-button
action=b.action
type="button"
actionParam=b
translatedTitle=b.title
label=b.label
icon=b.icon
class=b.className
preventFocus=b.preventFocus
tabindex=b.tabindex
onKeyDown=rovingButtonBar
}}
{{/if}}
class=b.className
preventFocus=b.preventFocus
tabindex=b.tabindex
onKeyDown=rovingButtonBar
}}
{{/if}}
{{/each}}
{{#unless group.lastGroup}}
<div class="d-editor-spacer"></div>
{{/unless}}
{{/each}}
</div>
{{#unless group.lastGroup}}
<div class="d-editor-spacer"></div>
{{/unless}}
{{/each}}
{{conditional-loading-spinner condition=loading}}
{{d-textarea
autocomplete="discourse"
tabindex=tabindex
value=value
class="d-editor-input"
placeholder=placeholderTranslated
aria-label=placeholderTranslated
disabled=disabled
input=change
focusIn=(action "focusIn")
focusOut=(action "focusOut")
}}
{{popup-input-tip validation=validation}}
{{plugin-outlet name="after-d-editor" tagName="" args=outletArgs}}
</div>
{{conditional-loading-spinner condition=loading}}
{{d-textarea
autocomplete="discourse"
tabindex=tabindex
value=value
class="d-editor-input"
placeholder=placeholderTranslated
aria-label=placeholderTranslated
disabled=disabled
input=change
focusIn=(action "focusIn")
focusOut=(action "focusOut")
}}
{{popup-input-tip validation=validation}}
{{plugin-outlet name="after-d-editor" tagName="" args=outletArgs}}
</div>
<div class="d-editor-preview-wrapper {{if forcePreview "force-preview"}}">

View File

@ -1,120 +1,68 @@
{{#composer-body composer=model
showPreview=showPreview
openIfDraft=(action "openIfDraft")
typed=(action "typed")
cancelled=(action "cancelled")
save=(action "save")}}
{{#composer-body
composer=model
showPreview=showPreview
openIfDraft=(action "openIfDraft")
typed=(action "typed")
cancelled=(action "cancelled")
save=(action "save")
}}
<div class="grippie"></div>
{{#if visible}}
{{composer-messages
composer=model
messageCount=messageCount
addLinkLookup=(action "addLinkLookup")
}}
{{#if model.viewOpenOrFullscreen}}
<div role="form" aria-label={{I18n saveLabel}} class="reply-area {{if canEditTags "with-tags" "without-tags"}}">
<div class="composer-fields">
{{plugin-outlet name="composer-open" args=(hash model=model)}}
<div class="reply-to">
{{#unless model.viewFullscreen}}
<div class="reply-details">
{{composer-action-title
model=model
openComposer=(action "openComposer")
closeComposer=(action "closeComposer")
canWhisper=canWhisper
}}
{{plugin-outlet name="composer-action-after" noTags=true args=(hash model=model)}}
{{plugin-outlet name="composer-open" args=(hash model=model)}}
{{#unless site.mobileView}}
{{#if model.unlistTopic}}
<span class="unlist">({{i18n "composer.unlist"}})</span>
{{/if}}
{{#if isWhispering}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
{{/if}}
{{/unless}}
{{#if canEdit}}
{{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason icon="info-circle" class="display-edit-reason"}}
{{text-field value=editReason id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{/link-to-input}}
{{/if}}
</div>
{{/unless}}
{{plugin-outlet name="before-composer-controls" args=(hash model=model) tagName="" connectorTagName=""}}
{{composer-toggles
composeState=model.composeState
showToolbar=showToolbar
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")
toggleFullscreen=(action "fullscreenComposer")
disableTextarea=disableTextarea
}}
</div>
<div class="reply-to">
{{#unless model.viewFullscreen}}
{{#if model.canEditTitle}}
{{#if model.creatingPrivateMessage}}
<div class="user-selector">
{{composer-user-selector
topicId=topicModel.id
recipients=model.targetRecipients
hasGroups=model.hasTargetGroups
focusTarget=focusTarget
class=(concat "users-input" (if showWarning " can-warn"))
}}
{{#if showWarning}}
<label class="add-warning">
{{input type="checkbox" checked=model.isWarning}}
<span>{{i18n "composer.add_warning"}}</span>
</label>
<div class="reply-details">
{{composer-action-title
model=model
openComposer=(action "openComposer")
closeComposer=(action "closeComposer")
canWhisper=canWhisper
}}
{{plugin-outlet name="composer-action-after" noTags=true args=(hash model=model)}}
{{#unless site.mobileView}}
{{#if model.unlistTopic}}
<span class="unlist">({{i18n "composer.unlist"}})</span>
{{/if}}
{{#if isWhispering}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
</div>
{{/if}}
{{/unless}}
{{#if canEdit}}
{{#link-to-input onClick=(action "displayEditReason") showInput=showEditReason icon="info-circle" class="display-edit-reason"}}
{{text-field value=editReason id="edit-reason" maxlength="255" placeholderKey="composer.edit_reason_placeholder"}}
{{/link-to-input}}
{{/if}}
<div class="title-and-category {{if showPreview "with-preview"}}">
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser
value=model.categoryId
onChange=(action (mut model.categoryId))
isDisabled=disableCategoryChooser
options=(hash
scopedCategoryId=scopedCategoryId
prioritizedCategoryId=prioritizedCategoryId
)
}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{/if}}
{{#if canEditTags}}
{{mini-tag-chooser
value=model.tags
isDisabled=disableTagsChooser
onChange=(action (mut model.tags))
options=(hash
categoryId=model.categoryId
minimum=model.minimumRequiredTags
)
}}
{{popup-input-tip validation=tagValidation}}
{{/if}}
</div>
{{/if}}
{{plugin-outlet name="composer-fields" args=(hash model=model showPreview=showPreview)}}
</div>
{{/unless}}
{{plugin-outlet name="before-composer-controls" args=(hash model=model) tagName="" connectorTagName=""}}
{{composer-toggles
composeState=model.composeState
showToolbar=showToolbar
toggleComposer=(action "toggle")
toggleToolbar=(action "toggleToolbar")
toggleFullscreen=(action "fullscreenComposer")
disableTextarea=disableTextarea
}}
</div>
{{composer-editor
{{#composer-editor
topic=topic
composer=model
lastValidatedAt=lastValidatedAt
@ -141,8 +89,67 @@
focusTarget=focusTarget
disableTextarea=disableTextarea
}}
<div class="composer-fields">
{{#unless model.viewFullscreen}}
{{#if model.canEditTitle}}
{{#if model.creatingPrivateMessage}}
<div class="user-selector">
{{composer-user-selector
topicId=topicModel.id
recipients=model.targetRecipients
hasGroups=model.hasTargetGroups
focusTarget=focusTarget
class=(concat "users-input" (if showWarning " can-warn"))
}}
{{#if showWarning}}
<label class="add-warning">
{{input type="checkbox" checked=model.isWarning}}
<span>{{i18n "composer.add_warning"}}</span>
</label>
{{/if}}
</div>
{{/if}}
<div class="title-and-category {{if showPreview "with-preview"}}">
{{composer-title composer=model lastValidatedAt=lastValidatedAt focusTarget=focusTarget}}
{{#if model.showCategoryChooser}}
<div class="category-input">
{{category-chooser
value=model.categoryId
onChange=(action (mut model.categoryId))
isDisabled=disableCategoryChooser
options=(hash
scopedCategoryId=scopedCategoryId
prioritizedCategoryId=prioritizedCategoryId
)
}}
{{popup-input-tip validation=categoryValidation}}
</div>
{{/if}}
{{#if canEditTags}}
{{mini-tag-chooser
value=model.tags
isDisabled=disableTagsChooser
onChange=(action (mut model.tags))
options=(hash
categoryId=model.categoryId
minimum=model.minimumRequiredTags
)
}}
{{popup-input-tip validation=tagValidation}}
{{/if}}
</div>
{{/if}}
{{plugin-outlet name="composer-fields" args=(hash model=model showPreview=showPreview)}}
{{/unless}}
</div>
{{/composer-editor}}
{{plugin-outlet name="composer-after-composer-editor" connectorTagName="" args=(hash model=model)}}
<div class="submit-panel">
{{plugin-outlet name="composer-fields-below" args=(hash model=model)}}
@ -175,6 +182,7 @@
{{d-icon "far-eye-slash"}}
</span>
{{/if}}
{{#if model.noBump}}
<span class="no-bump">{{d-icon "anchor"}}</span>
{{/if}}
@ -187,11 +195,13 @@
{{else}}
{{loading-spinner size="small"}}<span>{{i18n "upload_selector.uploading"}} {{uploadProgress}}%</span>
{{/if}}
{{#if isCancellable}}
<a href id="cancel-file-upload" {{action "cancelUpload"}}>{{d-icon "times"}}</a>
{{/if}}
</div>
{{/if}}
<div class={{if isUploading "hidden"}} id="draft-status">
{{#if model.draftStatus}}
<span title={{model.draftStatus}}>
@ -206,6 +216,7 @@
</span>
{{/if}}
</div>
{{plugin-outlet name="composer-after-save-or-cancel" connectorTagName="" args=(hash model=model)}}
</div>
@ -233,10 +244,8 @@
class=(concat "btn-flat btn-mini-toggle toggle-preview " (unless showPreview "active"))
}}
{{/if}}
</div>
</div>
{{else}}
<div class="saving-text">
{{#if model.createdPost}}
@ -261,7 +270,5 @@
toggleToolbar=(action "toggleToolbar")
}}
{{/if}}
{{/if}}
{{/composer-body}}

View File

@ -360,7 +360,7 @@ acceptance("Composer Actions", function (needs) {
"whisper icon is not visible"
);
assert.ok(
!exists(".composer-fields .whisper .d-icon-anchor"),
!exists(".reply-details .whisper .d-icon-anchor"),
"no-bump icon is not visible"
);
assert.strictEqual(
@ -380,7 +380,7 @@ acceptance("Composer Actions", function (needs) {
"whisper icon is visible"
);
assert.strictEqual(
count(".composer-fields .no-bump .d-icon-anchor"),
count(".reply-details .no-bump .d-icon-anchor"),
1,
"no-bump icon is visible"
);

View File

@ -563,7 +563,7 @@ acceptance("Composer", function (needs) {
await click("#create-topic");
assert.ok(
!exists(".composer-fields .whisper .d-icon-far-eye-slash"),
!exists(".reply-details .whisper .d-icon-far-eye-slash"),
"it should reset the state of the composer's model"
);
@ -573,9 +573,9 @@ acceptance("Composer", function (needs) {
);
assert.ok(
queryAll(".composer-fields .unlist")
.text()
.indexOf(I18n.t("composer.unlist")) > 0,
query(".reply-details .unlist").innerText.includes(
I18n.t("composer.unlist")
),
"it sets the topic to unlisted"
);
@ -583,7 +583,7 @@ acceptance("Composer", function (needs) {
await click(".topic-post:nth-of-type(1) button.reply");
assert.ok(
!exists(".composer-fields .whisper"),
!exists(".reply-details .whisper"),
"it should reset the state of the composer's model"
);
});

View File

@ -30,7 +30,8 @@
.saving-text,
.draft-text {
display: none;
padding-left: 10px;
padding-left: 8px;
.spinner {
margin-left: 8px;
border-color: var(--secondary);
@ -59,14 +60,17 @@
background: var(--tertiary);
color: var(--secondary);
flex-direction: row;
width: 100%;
justify-content: space-between;
.composer-controls {
margin-left: auto;
display: flex;
gap: 8px;
padding-right: 8px;
.toggle-toolbar {
display: none;
}
.d-icon {
color: var(--secondary);
}
@ -100,11 +104,11 @@
justify-content: flex-end;
.reply-details {
flex: 1 1 auto;
flex: 1;
display: flex;
align-items: center;
min-width: 0;
white-space: nowrap;
overflow: auto;
.d-icon {
color: var(--primary-medium);
@ -151,6 +155,7 @@
.composer-controls {
display: flex;
gap: 8px;
margin-left: 8px;
.d-icon {
@ -196,11 +201,11 @@
display: flex;
width: 100%;
align-items: center;
margin-bottom: 8px;
position: relative;
&.with-preview {
width: 50%;
}
}
.user-selector {
margin-bottom: 8px;
}
.title-input {
@ -231,19 +236,24 @@
flex: 1 0 40%;
max-width: 40%;
margin: 0 0 8px 8px;
@media screen and (max-width: 955px) {
flex: 1 0 100%;
margin-left: 0;
}
.category-chooser {
display: flex;
flex: 1 0 auto;
max-width: 100%;
width: auto;
.select-kit-header {
color: var(--primary-high);
white-space: nowrap;
text-overflow: ellipsis;
}
// below needed for text-overflow: ellipsis;
.selected-name {
max-width: 100%;
@ -269,42 +279,56 @@
.title-and-category {
flex-wrap: wrap;
}
.category-input {
margin-left: 0;
margin-bottom: 0px;
margin-bottom: 8px;
min-width: 0; // allows flex to shrink
flex-wrap: wrap;
max-width: 50%;
max-width: calc(50% - 4px);
@media screen and (max-width: 920px) {
flex-basis: 100%;
margin-right: 0;
margin-bottom: 6px;
max-width: calc(50% - 3px);
}
}
}
.add-warning {
margin-left: 0.75em;
color: var(--primary-high);
padding-left: 8px;
margin-bottom: 0;
display: flex;
input {
margin-right: 8px;
}
}
#reply-title {
margin: 0 0 8px 0;
margin-bottom: 8px;
flex-basis: 50%;
width: unset;
&:focus {
box-shadow: none;
}
}
.mini-tag-chooser {
width: 49%;
margin: 0 0 0 auto;
background: var(--secondary);
flex-grow: 1;
max-width: calc(50% - 4px);
margin: 0 0 8px 8px;
z-index: z("composer", "dropdown");
@media screen and (max-width: 920px) {
max-width: calc(50% - 3px);
}
.select-kit-header {
color: var(--primary-high);
}
}
.wmd-controls {
@ -316,30 +340,36 @@
}
.submit-panel {
align-items: center;
display: flex;
flex-shrink: 0;
margin-top: 8px;
}
.save-or-cancel {
display: flex;
align-items: center;
display: flex;
flex: 1 1 auto;
.btn-primary {
flex: 0 0 auto;
}
.cancel {
align-items: center;
display: flex;
margin-left: 1.25em;
line-height: normal;
color: var(--primary-high);
transition: color 250ms;
&:hover,
&:focus {
outline: none;
color: var(--danger);
}
}
#draft-status,
#file-uploading {
margin-left: 25px;
@ -539,6 +569,7 @@ body:not(.ios-safari-composer-hacks) {
}
.toggle-preview {
margin-left: 8px;
transition: all 0.33s ease-out;
&.active {

View File

@ -10,17 +10,19 @@
min-height: 0;
}
.d-editor-textarea-wrapper,
.d-editor-preview-wrapper {
.d-editor-textarea-column {
display: flex;
flex: 1;
flex-direction: column;
}
.d-editor-textarea-wrapper {
display: flex;
flex: 1;
flex-direction: column;
background-color: var(--secondary);
position: relative;
border: 1px solid var(--primary-low);
border: 1px solid var(--primary-medium);
textarea {
background: transparent;
@ -44,13 +46,10 @@
}
.d-editor-preview-wrapper {
max-width: 49%;
margin-left: 1%;
display: flex;
flex: 1;
flex-direction: column;
}
.d-editor-preview-wrapper {
margin-left: 16px;
overflow: auto;
cursor: default;
-webkit-overflow-scrolling: touch;
@ -297,8 +296,6 @@
border-bottom: 1px solid var(--primary-low);
width: 100%;
box-sizing: border-box;
padding-left: 2px;
padding-right: 2px;
.d-editor-spacer {
height: 1em;

View File

@ -7,7 +7,7 @@
outline: none;
padding: 0;
margin-right: 8px;
border: 1px solid var(--primary-low);
border: 1px solid var(--primary-medium);
min-height: unset;
.d-icon {

View File

@ -8,7 +8,6 @@
.submit-panel {
flex-wrap: wrap;
align-items: center;
}
}
@ -23,7 +22,7 @@
.mini-tag-chooser {
margin-bottom: 8px; // match title input margin
flex: 0 0 auto;
margin-left: 1%; // matches margin between category and tag input
margin-left: 8px; // matches margin between category and tag input
width: 39%;
}
}
@ -47,12 +46,6 @@
@include ellipsis;
}
}
&.show-preview {
.user-selector {
width: 50%;
}
}
}
}
@ -88,22 +81,16 @@
}
}
.composer-popup-container {
max-width: 1500px;
margin-left: auto;
margin-right: auto;
}
.composer-popup {
box-sizing: border-box;
position: absolute;
width: calc(49% - 8px);
width: calc(50% - 8px);
top: 8px; // .reply-to margin-top + .reply-area padding-top
bottom: 8px;
left: 51%;
right: 8px;
overflow-y: auto;
z-index: z("composer", "popover");
padding: 8px 8px 32px 8px;
padding: 1.5em;
box-shadow: shadow("dropdown");
background: var(--highlight-medium);
@ -136,10 +123,6 @@
margin-bottom: 10px;
}
p {
margin-bottom: 10px;
}
a.close {
display: flex;
align-items: center;
@ -239,7 +222,6 @@
}
.composer-popup:nth-of-type(2) {
margin-left: 10px;
width: calc(50% - 65px);
}
@ -282,10 +264,7 @@ a.toggle-preview {
.reply-to {
border-bottom: 1px solid var(--primary-low);
margin-bottom: 0;
.composer-controls {
margin-right: 0;
}
padding-bottom: 8px;
}
.d-editor-textarea-wrapper {

View File

@ -29,7 +29,7 @@
.keyboard-visible body.ios-safari-composer-hacks &.open {
height: calc(var(--composer-vh, 1vh) * 100);
.reply-area {
padding-bottom: 0px;
padding-bottom: 6px;
}
}
@ -55,6 +55,7 @@
.composer-controls {
align-self: flex-start;
gap: 6px;
}
}
}
@ -71,7 +72,7 @@
.toggle-toolbar,
.toggle-minimize {
top: 8px;
top: 6px;
}
.draft-text {
width: calc(100% - 40px);
@ -81,6 +82,7 @@
#reply-title {
width: calc(100% - 20px);
margin-bottom: 6px;
}
.category-input {
@ -89,10 +91,10 @@
.submit-panel {
margin-top: 6px;
align-items: baseline;
.save-or-cancel {
flex: 1 1 auto;
#draft-status,
#file-uploading {
margin-left: 6px;
@ -121,10 +123,6 @@
}
}
.d-editor-textarea-wrapper {
width: 100%;
}
&.show-preview {
.submit-panel {
padding-top: 10px;
@ -179,7 +177,7 @@
.user-selector {
margin: 0;
.users-input {
margin-bottom: 5px;
margin-bottom: 6px;
}
}
@ -187,7 +185,10 @@
.mini-tag-chooser,
.category-chooser {
z-index: z("base");
margin-bottom: 5px;
}
.mini-tag-chooser {
margin: 0 0 6px 6px;
}
.selected-name {
@ -206,7 +207,7 @@
.without-tags {
.category-input {
margin-left: 5px;
margin-left: 6px;
}
}
@ -219,11 +220,15 @@
width: 100%;
}
.users-input .select-kit.multi-select {
width: 100%;
}
.add-warning {
margin: 0 0 5px 5px;
margin-bottom: 6px;
}
.whisper {
margin-right: 5px;
margin-right: 6px;
}
}

View File

@ -9,10 +9,10 @@
display: flex;
span.presence-text {
margin-left: 5px;
margin-right: 2px;
align-items: center;
display: flex;
flex: 0 0 auto;
padding-top: 3px;
margin-left: 8px;
}
.presence-avatars {
@ -66,8 +66,7 @@
// TMP: RTL overrides
.rtl {
span.presence-text {
margin-left: 2px;
margin-right: 5px;
margin-right: 8px;
}
.composer-fields .presence-users {

View File

@ -193,7 +193,7 @@ acceptance("Discourse Presence Plugin", function (needs) {
await click("#topic-footer-buttons .btn.create");
assert.ok(exists(".d-editor-input"), "the composer input is visible");
const avatarSelector = ".composer-fields .presence-avatars .avatar";
const avatarSelector = ".reply-to .presence-avatars .avatar";
assert.strictEqual(count(avatarSelector), 0, "no avatars displayed");
await joinChannel("/discourse-presence/reply/280", {