switch timeline messages to css grid, and add menu button
This commit is contained in:
parent
13ac41b264
commit
bbf9832d6a
9 changed files with 233 additions and 185 deletions
|
@ -16,6 +16,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@import url('inter.css');
|
@import url('inter.css');
|
||||||
|
@import url('timeline.css');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
|
@ -508,168 +509,6 @@ a {
|
||||||
background-color: #E3E8F0;
|
background-color: #E3E8F0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.Timeline > li:not(.continuation) {
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.Timeline > li.continuation .profile {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.Timeline > li.continuation time {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.Timeline > li.messageStatus .message-container > p {
|
|
||||||
font-style: italic;
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container {
|
|
||||||
padding: 1px 10px 0px 10px;
|
|
||||||
margin: 5px 10px 0 10px;
|
|
||||||
/* so the .media can grow horizontally and its spacer can grow vertically */
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .profile {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.TextMessageView {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.TextMessageView.continuation .message-container {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .sender {
|
|
||||||
margin: 6px 0;
|
|
||||||
margin-left: 6px;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hydrogen .sender.usercolor1 { color: var(--usercolor1); }
|
|
||||||
.hydrogen .sender.usercolor2 { color: var(--usercolor2); }
|
|
||||||
.hydrogen .sender.usercolor3 { color: var(--usercolor3); }
|
|
||||||
.hydrogen .sender.usercolor4 { color: var(--usercolor4); }
|
|
||||||
.hydrogen .sender.usercolor5 { color: var(--usercolor5); }
|
|
||||||
.hydrogen .sender.usercolor6 { color: var(--usercolor6); }
|
|
||||||
.hydrogen .sender.usercolor7 { color: var(--usercolor7); }
|
|
||||||
.hydrogen .sender.usercolor8 { color: var(--usercolor8); }
|
|
||||||
|
|
||||||
.message-container time {
|
|
||||||
padding: 2px 0 0px 10px;
|
|
||||||
font-size: 0.8em;
|
|
||||||
line-height: normal;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.message-container .media {
|
|
||||||
display: grid;
|
|
||||||
margin-top: 4px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.message-container .media > a {
|
|
||||||
text-decoration: none;
|
|
||||||
width: 100%;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .spacer grows with an inline padding-top to the size of the image,
|
|
||||||
so the timeline doesn't jump when the image loads */
|
|
||||||
.message-container .media > * {
|
|
||||||
grid-row: 1;
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .media img, .message-container .media video {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
/* for IE11 to still scale even though the spacer is too tall */
|
|
||||||
align-self: start;
|
|
||||||
border-radius: 4px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
/* stretch the image (to the spacer) on platforms
|
|
||||||
where we can trust the spacer to always have the correct height,
|
|
||||||
otherwise the image starts with height 0 and with loading=lazy
|
|
||||||
only loads when the top comes into view*/
|
|
||||||
.hydrogen:not(.legacy) .message-container .media img,
|
|
||||||
.hydrogen:not(.legacy) .message-container .media video {
|
|
||||||
align-self: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .media > .sendStatus {
|
|
||||||
align-self: end;
|
|
||||||
justify-self: start;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .media > progress {
|
|
||||||
align-self: center;
|
|
||||||
justify-self: center;
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .media > time {
|
|
||||||
align-self: end;
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container .media > time,
|
|
||||||
.message-container .media > .sendStatus {
|
|
||||||
color: #2e2f32;
|
|
||||||
display: block;
|
|
||||||
padding: 2px;
|
|
||||||
margin: 4px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.75);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.message-container .media > .spacer {
|
|
||||||
/* TODO: can we implement this with a pseudo element? or perhaps they are not grid items? */
|
|
||||||
width: 100%;
|
|
||||||
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
|
|
||||||
align-self: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.TextMessageView.unsent .message-container {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.TextMessageView.unverified .message-container {
|
|
||||||
color: #ff4b55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-container p {
|
|
||||||
margin: 3px 0;
|
|
||||||
line-height: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AnnouncementView {
|
|
||||||
margin: 5px 0;
|
|
||||||
padding: 5px 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AnnouncementView > div {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: rgba(245, 245, 245, 0.90);
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.GapView > :not(:first-child) {
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.SettingsBody {
|
.SettingsBody {
|
||||||
padding: 0px 16px;
|
padding: 0px 16px;
|
||||||
}
|
}
|
||||||
|
|
218
src/platform/web/ui/css/themes/element/timeline.css
Normal file
218
src/platform/web/ui/css/themes/element/timeline.css
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
ul.Timeline > li.messageStatus .Timeline_messageBody > p {
|
||||||
|
font-style: italic;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_message {
|
||||||
|
display: grid;
|
||||||
|
grid-template:
|
||||||
|
"avatar sender" auto
|
||||||
|
"avatar body" auto
|
||||||
|
"time body" 1fr /
|
||||||
|
30px 1fr;
|
||||||
|
column-gap: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
margin: 0 12px;
|
||||||
|
/* TODO: check whether this is needed for .media to maintain aspect ratio (on IE11) like the 100% above */
|
||||||
|
/* width: 100%; */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_message:not(.continuation) {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.Timeline_message {
|
||||||
|
grid-template:
|
||||||
|
"avatar sender" auto
|
||||||
|
"body body" 1fr
|
||||||
|
"time time" auto /
|
||||||
|
30px 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageSender {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_message:hover, .Timeline_message.selected {
|
||||||
|
background-color: rgba(141, 151, 165, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_message:hover > .Timeline_messageOptions {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageAvatar {
|
||||||
|
grid-area: avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageSender {
|
||||||
|
grid-area: sender;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageSender, .Timeline_messageBody {
|
||||||
|
/* reset body margin */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_message:not(.continuation) .Timeline_messageSender,
|
||||||
|
.Timeline_message:not(.continuation) .Timeline_messageBody {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageOptions {
|
||||||
|
display: none;
|
||||||
|
grid-area: body;
|
||||||
|
align-self: start;
|
||||||
|
justify-self: right;
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageTime {
|
||||||
|
grid-area: time;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody time {
|
||||||
|
padding: 2px 0 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody time, .Timeline_messageTime {
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: normal;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody {
|
||||||
|
grid-area: body;
|
||||||
|
line-height: 2.2rem;
|
||||||
|
/* so the .media can grow horizontally and its spacer can grow vertically */
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor1 { color: var(--usercolor1); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor2 { color: var(--usercolor2); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor3 { color: var(--usercolor3); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor4 { color: var(--usercolor4); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor5 { color: var(--usercolor5); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor6 { color: var(--usercolor6); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor7 { color: var(--usercolor7); }
|
||||||
|
.hydrogen .Timeline_messageSender.usercolor8 { color: var(--usercolor8); }
|
||||||
|
|
||||||
|
|
||||||
|
.Timeline_messageBody .media {
|
||||||
|
display: grid;
|
||||||
|
margin-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media > a {
|
||||||
|
text-decoration: none;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .spacer grows with an inline padding-top to the size of the image,
|
||||||
|
so the timeline doesn't jump when the image loads */
|
||||||
|
.Timeline_messageBody .media > * {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media img, .Timeline_messageBody .media video {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
/* for IE11 to still scale even though the spacer is too tall */
|
||||||
|
align-self: start;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* stretch the image (to the spacer) on platforms
|
||||||
|
where we can trust the spacer to always have the correct height,
|
||||||
|
otherwise the image starts with height 0 and with loading=lazy
|
||||||
|
only loads when the top comes into view*/
|
||||||
|
.hydrogen:not(.legacy) .Timeline_messageBody .media img,
|
||||||
|
.hydrogen:not(.legacy) .Timeline_messageBody .media video {
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media > .sendStatus {
|
||||||
|
align-self: end;
|
||||||
|
justify-self: start;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media > progress {
|
||||||
|
align-self: center;
|
||||||
|
justify-self: center;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media > time {
|
||||||
|
align-self: end;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody .media > time,
|
||||||
|
.Timeline_messageBody .media > .sendStatus {
|
||||||
|
color: #2e2f32;
|
||||||
|
display: block;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 4px;
|
||||||
|
background-color: rgba(255, 255, 255, 0.75);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.Timeline_messageBody .media > .spacer {
|
||||||
|
/* TODO: can we implement this with a pseudo element? or perhaps they are not grid items? */
|
||||||
|
width: 100%;
|
||||||
|
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody.unsent .Timeline_messageBody {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Timeline_messageBody.unverified .Timeline_messageBody {
|
||||||
|
color: #ff4b55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AnnouncementView {
|
||||||
|
margin: 5px 0;
|
||||||
|
padding: 5px 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.AnnouncementView > div {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: rgba(245, 245, 245, 0.90);
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.GapView > :not(:first-child) {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
|
@ -43,11 +43,6 @@ limitations under the License.
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.TextMessageView {
|
|
||||||
display: flex;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.AnnouncementView {
|
.AnnouncementView {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -52,9 +52,9 @@ export class BaseMediaView extends TemplateView {
|
||||||
});
|
});
|
||||||
children.push(sendStatus, progress);
|
children.push(sendStatus, progress);
|
||||||
}
|
}
|
||||||
return renderMessage(t, vm, [
|
return renderMessage(t, vm, t.div({className: "Timeline_messageBody"}, [
|
||||||
t.div({className: "media", style: `max-width: ${vm.width}px`}, children),
|
t.div({className: "media", style: `max-width: ${vm.width}px`}, children),
|
||||||
t.if(vm => vm.error, t => t.p({className: "error"}, vm.error))
|
t.if(vm => vm.error, t => t.p({className: "error"}, vm.error))
|
||||||
]);
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ export class FileView extends TemplateView {
|
||||||
t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Cancel`),
|
t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Cancel`),
|
||||||
]));
|
]));
|
||||||
} else {
|
} else {
|
||||||
return renderMessage(t, vm, t.p([
|
return renderMessage(t, vm, t.p({className: "Timeline_messageBody"}, [
|
||||||
t.button({className: "link", onClick: () => vm.download()}, vm => vm.label),
|
t.button({className: "link", onClick: () => vm.download()}, vm => vm.label),
|
||||||
t.time(vm.date + " " + vm.time)
|
t.time(vm.date + " " + vm.time)
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -20,6 +20,6 @@ import {renderMessage} from "./common.js";
|
||||||
export class MissingAttachmentView extends TemplateView {
|
export class MissingAttachmentView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
const remove = t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Remove`);
|
const remove = t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Remove`);
|
||||||
return renderMessage(t, vm, t.p([vm.label, " ", remove]));
|
return renderMessage(t, vm, t.p({className: "Timeline_messageBody"}, [vm.label, " ", remove]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class RedactedView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
const cancelButton = t.if(vm => vm.isRedacting, t => t.button({onClick: () => vm.abortPendingRedaction()}, "Cancel"));
|
const cancelButton = t.if(vm => vm.isRedacting, t => t.button({onClick: () => vm.abortPendingRedaction()}, "Cancel"));
|
||||||
return renderMessage(t, vm,
|
return renderMessage(t, vm,
|
||||||
[t.p([vm => vm.description, " ", cancelButton, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])]
|
t.p({className: "Timeline_messageBody"}, [vm => vm.description, " ", cancelButton])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,9 +22,8 @@ import {renderMessage} from "./common.js";
|
||||||
export class TextMessageView extends TemplateView {
|
export class TextMessageView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
const bodyView = t.mapView(vm => vm.body, body => new BodyView(body));
|
const bodyView = t.mapView(vm => vm.body, body => new BodyView(body));
|
||||||
const redactButton = t.button({onClick: () => vm.redact()}, "Redact");
|
|
||||||
return renderMessage(t, vm,
|
return renderMessage(t, vm,
|
||||||
[t.p([bodyView, redactButton, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])]
|
t.p({className: "Timeline_messageBody"}, [bodyView, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,23 +17,20 @@ limitations under the License.
|
||||||
|
|
||||||
import {renderStaticAvatar} from "../../../avatar.js";
|
import {renderStaticAvatar} from "../../../avatar.js";
|
||||||
|
|
||||||
export function renderMessage(t, vm, children) {
|
export function renderMessage(t, vm, body) {
|
||||||
const classes = {
|
const classes = {
|
||||||
"TextMessageView": true,
|
"Timeline_message": true,
|
||||||
own: vm.isOwn,
|
own: vm.isOwn,
|
||||||
unsent: vm.isUnsent,
|
unsent: vm.isUnsent,
|
||||||
unverified: vm.isUnverified,
|
unverified: vm.isUnverified,
|
||||||
continuation: vm => vm.isContinuation,
|
continuation: vm => vm.isContinuation,
|
||||||
messageStatus: vm => vm.shape === "message-status" || vm.shape === "missing-attachment" || vm.shape === "file" || vm.shape === "redacted",
|
messageStatus: vm => vm.shape === "message-status" || vm.shape === "missing-attachment" || vm.shape === "file" || vm.shape === "redacted",
|
||||||
};
|
};
|
||||||
|
return t.li({className: classes}, [
|
||||||
const profile = t.div({className: "profile"}, [
|
t.if(vm => !vm.isContinuation, t => renderStaticAvatar(vm, 30, "Timeline_messageAvatar")),
|
||||||
renderStaticAvatar(vm, 30),
|
t.if(vm => !vm.isContinuation, t => t.div({className: `Timeline_messageSender usercolor${vm.avatarColorNumber}`}, vm.displayName)),
|
||||||
t.div({className: `sender usercolor${vm.avatarColorNumber}`}, vm.displayName)
|
body,
|
||||||
|
// should be after body as it is overlayed on top
|
||||||
|
t.button({className: "Timeline_messageOptions"}, "⋮"),
|
||||||
]);
|
]);
|
||||||
children = [profile].concat(children);
|
|
||||||
return t.li(
|
|
||||||
{className: classes},
|
|
||||||
t.div({className: "message-container"}, children)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue