Merge pull request #2997 from matrix-org/bwindels/pill-avatars
Message editing: render avatars for pills in the editor
This commit is contained in:
commit
2d4d608ed6
10 changed files with 198 additions and 97 deletions
|
@ -21,6 +21,7 @@ limitations under the License.
|
||||||
// padding around and in the editor.
|
// padding around and in the editor.
|
||||||
// Actual values from fiddling around in inspector
|
// Actual values from fiddling around in inspector
|
||||||
margin: -7px -10px -5px -10px;
|
margin: -7px -10px -5px -10px;
|
||||||
|
overflow: visible !important; // override mx_EventTile_content
|
||||||
|
|
||||||
.mx_MessageEditor_editor {
|
.mx_MessageEditor_editor {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -33,20 +34,28 @@ limitations under the License.
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
||||||
span {
|
span.mx_UserPill, span.mx_RoomPill {
|
||||||
display: inline-block;
|
padding-left: 21px;
|
||||||
padding: 0 5px;
|
position: relative;
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.user-pill, span.room-pill {
|
// avatar psuedo element
|
||||||
border-radius: 16px;
|
&::before {
|
||||||
display: inline-block;
|
position: absolute;
|
||||||
color: $primary-fg-color;
|
left: 2px;
|
||||||
background-color: $other-user-pill-bg-color;
|
top: 2px;
|
||||||
padding-left: 5px;
|
content: var(--avatar-letter);
|
||||||
padding-right: 5px;
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: var(--avatar-background), $avatar-bg-color;
|
||||||
|
color: $avatar-initial-color;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 16px;
|
||||||
|
font-size: 10.4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +70,7 @@ limitations under the License.
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin: 0 -110px 0 0;
|
margin: 0 -110px 0 0;
|
||||||
padding-right: 104px;
|
padding-right: 147px;
|
||||||
|
|
||||||
.mx_AccessibleButton {
|
.mx_AccessibleButton {
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
|
|
|
@ -40,7 +40,12 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_continuation {
|
.mx_EventTile_continuation {
|
||||||
padding-top: 0px ! important;
|
padding-top: 0px !important;
|
||||||
|
|
||||||
|
&.mx_EventTile_isEditing {
|
||||||
|
padding-top: 5px !important;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_EventTile_isEditing {
|
.mx_EventTile_isEditing {
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
import {ContentRepo} from 'matrix-js-sdk';
|
import {ContentRepo} from 'matrix-js-sdk';
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
import MatrixClientPeg from './MatrixClientPeg';
|
||||||
|
import DMRoomMap from './utils/DMRoomMap';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
avatarUrlForMember: function(member, width, height, resizeMethod) {
|
||||||
|
@ -58,4 +59,71 @@ module.exports = {
|
||||||
}
|
}
|
||||||
return require('../res/img/' + images[total % images.length] + '.png');
|
return require('../res/img/' + images[total % images.length] + '.png');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the first (non-sigil) character of 'name',
|
||||||
|
* converted to uppercase
|
||||||
|
* @param {string} name
|
||||||
|
* @return {string} the first letter
|
||||||
|
*/
|
||||||
|
getInitialLetter(name) {
|
||||||
|
if (name.length < 1) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idx = 0;
|
||||||
|
const initial = name[0];
|
||||||
|
if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// string.codePointAt(0) would do this, but that isn't supported by
|
||||||
|
// some browsers (notably PhantomJS).
|
||||||
|
let chars = 1;
|
||||||
|
const first = name.charCodeAt(idx);
|
||||||
|
|
||||||
|
// check if it’s the start of a surrogate pair
|
||||||
|
if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
|
||||||
|
const second = name.charCodeAt(idx+1);
|
||||||
|
if (second >= 0xDC00 && second <= 0xDFFF) {
|
||||||
|
chars++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstChar = name.substring(idx, idx+chars);
|
||||||
|
return firstChar.toUpperCase();
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarUrlForRoom(room, width, height, resizeMethod) {
|
||||||
|
const explicitRoomAvatar = room.getAvatarUrl(
|
||||||
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
resizeMethod,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if (explicitRoomAvatar) {
|
||||||
|
return explicitRoomAvatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
let otherMember = null;
|
||||||
|
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
||||||
|
if (otherUserId) {
|
||||||
|
otherMember = room.getMember(otherUserId);
|
||||||
|
} else {
|
||||||
|
// if the room is not marked as a 1:1, but only has max 2 members
|
||||||
|
// then still try to show any avatar (pref. other member)
|
||||||
|
otherMember = room.getAvatarFallbackMember();
|
||||||
|
}
|
||||||
|
if (otherMember) {
|
||||||
|
return otherMember.getAvatarUrl(
|
||||||
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
resizeMethod,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -133,38 +133,6 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* returns the first (non-sigil) character of 'name',
|
|
||||||
* converted to uppercase
|
|
||||||
*/
|
|
||||||
_getInitialLetter: function(name) {
|
|
||||||
if (name.length < 1) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idx = 0;
|
|
||||||
const initial = name[0];
|
|
||||||
if ((initial === '@' || initial === '#' || initial === '+') && name[1]) {
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// string.codePointAt(0) would do this, but that isn't supported by
|
|
||||||
// some browsers (notably PhantomJS).
|
|
||||||
let chars = 1;
|
|
||||||
const first = name.charCodeAt(idx);
|
|
||||||
|
|
||||||
// check if it’s the start of a surrogate pair
|
|
||||||
if (first >= 0xD800 && first <= 0xDBFF && name[idx+1]) {
|
|
||||||
const second = name.charCodeAt(idx+1);
|
|
||||||
if (second >= 0xDC00 && second <= 0xDFFF) {
|
|
||||||
chars++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstChar = name.substring(idx, idx+chars);
|
|
||||||
return firstChar.toUpperCase();
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const imageUrl = this.state.imageUrls[this.state.urlsIndex];
|
const imageUrl = this.state.imageUrls[this.state.urlsIndex];
|
||||||
|
|
||||||
|
@ -175,7 +143,7 @@ module.exports = React.createClass({
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (imageUrl === this.state.defaultImageUrl) {
|
if (imageUrl === this.state.defaultImageUrl) {
|
||||||
const initialLetter = this._getInitialLetter(name);
|
const initialLetter = AvatarLogic.getInitialLetter(name);
|
||||||
const textNode = (
|
const textNode = (
|
||||||
<span className="mx_BaseAvatar_initial" aria-hidden="true"
|
<span className="mx_BaseAvatar_initial" aria-hidden="true"
|
||||||
style={{ fontSize: (width * 0.65) + "px",
|
style={{ fontSize: (width * 0.65) + "px",
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {ContentRepo} from "matrix-js-sdk";
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import sdk from "../../../index";
|
import sdk from "../../../index";
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import Avatar from '../../../Avatar';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomAvatar',
|
displayName: 'RoomAvatar',
|
||||||
|
@ -89,7 +89,6 @@ module.exports = React.createClass({
|
||||||
props.resizeMethod,
|
props.resizeMethod,
|
||||||
), // highest priority
|
), // highest priority
|
||||||
this.getRoomAvatarUrl(props),
|
this.getRoomAvatarUrl(props),
|
||||||
this.getOneToOneAvatar(props), // lowest priority
|
|
||||||
].filter(function(url) {
|
].filter(function(url) {
|
||||||
return (url != null && url != "");
|
return (url != null && url != "");
|
||||||
});
|
});
|
||||||
|
@ -98,41 +97,14 @@ module.exports = React.createClass({
|
||||||
getRoomAvatarUrl: function(props) {
|
getRoomAvatarUrl: function(props) {
|
||||||
if (!props.room) return null;
|
if (!props.room) return null;
|
||||||
|
|
||||||
return props.room.getAvatarUrl(
|
return Avatar.avatarUrlForRoom(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
props.room,
|
||||||
Math.floor(props.width * window.devicePixelRatio),
|
Math.floor(props.width * window.devicePixelRatio),
|
||||||
Math.floor(props.height * window.devicePixelRatio),
|
Math.floor(props.height * window.devicePixelRatio),
|
||||||
props.resizeMethod,
|
props.resizeMethod,
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getOneToOneAvatar: function(props) {
|
|
||||||
const room = props.room;
|
|
||||||
if (!room) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
let otherMember = null;
|
|
||||||
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
|
|
||||||
if (otherUserId) {
|
|
||||||
otherMember = room.getMember(otherUserId);
|
|
||||||
} else {
|
|
||||||
// if the room is not marked as a 1:1, but only has max 2 members
|
|
||||||
// then still try to show any avatar (pref. other member)
|
|
||||||
otherMember = room.getAvatarFallbackMember();
|
|
||||||
}
|
|
||||||
if (otherMember) {
|
|
||||||
return otherMember.getAvatarUrl(
|
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
|
||||||
Math.floor(props.width * window.devicePixelRatio),
|
|
||||||
Math.floor(props.height * window.devicePixelRatio),
|
|
||||||
props.resizeMethod,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
onRoomAvatarClick: function() {
|
onRoomAvatarClick: function() {
|
||||||
const avatarUrl = this.props.room.getAvatarUrl(
|
const avatarUrl = this.props.room.getAvatarUrl(
|
||||||
MatrixClientPeg.get().getHomeserverUrl(),
|
MatrixClientPeg.get().getHomeserverUrl(),
|
||||||
|
|
|
@ -27,6 +27,7 @@ import Autocomplete from '../rooms/Autocomplete';
|
||||||
import {PartCreator} from '../../../editor/parts';
|
import {PartCreator} from '../../../editor/parts';
|
||||||
import {renderModel} from '../../../editor/render';
|
import {renderModel} from '../../../editor/render';
|
||||||
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class MessageEditor extends React.Component {
|
export default class MessageEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -40,16 +41,17 @@ export default class MessageEditor extends React.Component {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
const room = this.context.matrixClient.getRoom(this.props.event.getRoomId());
|
||||||
const partCreator = new PartCreator(
|
const partCreator = new PartCreator(
|
||||||
() => this._autocompleteRef,
|
() => this._autocompleteRef,
|
||||||
query => this.setState({query}),
|
query => this.setState({query}),
|
||||||
|
room,
|
||||||
);
|
);
|
||||||
this.model = new EditorModel(
|
this.model = new EditorModel(
|
||||||
parseEvent(this.props.event),
|
parseEvent(this.props.event, room),
|
||||||
partCreator,
|
partCreator,
|
||||||
this._updateEditorState,
|
this._updateEditorState,
|
||||||
);
|
);
|
||||||
const room = this.context.matrixClient.getRoom(this.props.event.getRoomId());
|
|
||||||
this.state = {
|
this.state = {
|
||||||
autoComplete: null,
|
autoComplete: null,
|
||||||
room,
|
room,
|
||||||
|
@ -176,7 +178,7 @@ export default class MessageEditor extends React.Component {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
return <div className="mx_MessageEditor">
|
return <div className={classNames("mx_MessageEditor", this.props.className)}>
|
||||||
{ autoComplete }
|
{ autoComplete }
|
||||||
<div
|
<div
|
||||||
className="mx_MessageEditor_editor"
|
className="mx_MessageEditor_editor"
|
||||||
|
|
|
@ -471,7 +471,7 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
if (this.props.isEditing) {
|
if (this.props.isEditing) {
|
||||||
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
const MessageEditor = sdk.getComponent('elements.MessageEditor');
|
||||||
return <MessageEditor event={this.props.mxEvent} />;
|
return <MessageEditor event={this.props.mxEvent} className="mx_EventTile_content" />;
|
||||||
}
|
}
|
||||||
const mxEvent = this.props.mxEvent;
|
const mxEvent = this.props.mxEvent;
|
||||||
const content = mxEvent.getContent();
|
const content = mxEvent.getContent();
|
||||||
|
|
|
@ -17,11 +17,12 @@ limitations under the License.
|
||||||
import {UserPillPart, RoomPillPart, PlainPart} from "./parts";
|
import {UserPillPart, RoomPillPart, PlainPart} from "./parts";
|
||||||
|
|
||||||
export default class AutocompleteWrapperModel {
|
export default class AutocompleteWrapperModel {
|
||||||
constructor(updateCallback, getAutocompleterComponent, updateQuery) {
|
constructor(updateCallback, getAutocompleterComponent, updateQuery, room) {
|
||||||
this._updateCallback = updateCallback;
|
this._updateCallback = updateCallback;
|
||||||
this._getAutocompleterComponent = getAutocompleterComponent;
|
this._getAutocompleterComponent = getAutocompleterComponent;
|
||||||
this._updateQuery = updateQuery;
|
this._updateQuery = updateQuery;
|
||||||
this._query = null;
|
this._query = null;
|
||||||
|
this._room = room;
|
||||||
}
|
}
|
||||||
|
|
||||||
onEscape(e) {
|
onEscape(e) {
|
||||||
|
@ -83,7 +84,8 @@ export default class AutocompleteWrapperModel {
|
||||||
case "@": {
|
case "@": {
|
||||||
const displayName = completion.completion;
|
const displayName = completion.completion;
|
||||||
const userId = completion.completionId;
|
const userId = completion.completionId;
|
||||||
return new UserPillPart(userId, displayName);
|
const member = this._room.getMember(userId);
|
||||||
|
return new UserPillPart(userId, displayName, member);
|
||||||
}
|
}
|
||||||
case "#": {
|
case "#": {
|
||||||
const displayAlias = completion.completionId;
|
const displayAlias = completion.completionId;
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import { MATRIXTO_URL_PATTERN } from '../linkify-matrix';
|
import { MATRIXTO_URL_PATTERN } from '../linkify-matrix';
|
||||||
import { PlainPart, UserPillPart, RoomPillPart, NewlinePart } from "./parts";
|
import { PlainPart, UserPillPart, RoomPillPart, NewlinePart } from "./parts";
|
||||||
|
|
||||||
function parseHtmlMessage(html) {
|
function parseHtmlMessage(html, room) {
|
||||||
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
||||||
// no nodes from parsing here should be inserted in the document,
|
// no nodes from parsing here should be inserted in the document,
|
||||||
// as scripts in event handlers, etc would be executed then.
|
// as scripts in event handlers, etc would be executed then.
|
||||||
|
@ -37,8 +37,8 @@ function parseHtmlMessage(html) {
|
||||||
const resourceId = pillMatch[1]; // The room/user ID
|
const resourceId = pillMatch[1]; // The room/user ID
|
||||||
const prefix = pillMatch[2]; // The first character of prefix
|
const prefix = pillMatch[2]; // The first character of prefix
|
||||||
switch (prefix) {
|
switch (prefix) {
|
||||||
case "@": return new UserPillPart(resourceId, n.textContent);
|
case "@": return new UserPillPart(resourceId, n.textContent, room.getMember(resourceId));
|
||||||
case "#": return new RoomPillPart(resourceId, n.textContent);
|
case "#": return new RoomPillPart(resourceId);
|
||||||
default: return new PlainPart(n.textContent);
|
default: return new PlainPart(n.textContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,10 +54,10 @@ function parseHtmlMessage(html) {
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseEvent(event) {
|
export function parseEvent(event, room) {
|
||||||
const content = event.getContent();
|
const content = event.getContent();
|
||||||
if (content.format === "org.matrix.custom.html") {
|
if (content.format === "org.matrix.custom.html") {
|
||||||
return parseHtmlMessage(content.formatted_body || "");
|
return parseHtmlMessage(content.formatted_body || "", room);
|
||||||
} else {
|
} else {
|
||||||
const body = content.body || "";
|
const body = content.body || "";
|
||||||
const lines = body.split("\n");
|
const lines = body.split("\n");
|
||||||
|
|
|
@ -15,6 +15,8 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import AutocompleteWrapperModel from "./autocomplete";
|
import AutocompleteWrapperModel from "./autocomplete";
|
||||||
|
import Avatar from "../Avatar";
|
||||||
|
import MatrixClientPeg from "../MatrixClientPeg";
|
||||||
|
|
||||||
class BasePart {
|
class BasePart {
|
||||||
constructor(text = "") {
|
constructor(text = "") {
|
||||||
|
@ -150,21 +152,21 @@ class PillPart extends BasePart {
|
||||||
|
|
||||||
toDOMNode() {
|
toDOMNode() {
|
||||||
const container = document.createElement("span");
|
const container = document.createElement("span");
|
||||||
container.className = this.type;
|
container.className = this.className;
|
||||||
container.appendChild(document.createTextNode(this.text));
|
container.appendChild(document.createTextNode(this.text));
|
||||||
|
this.setAvatar(container);
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDOMNode(node) {
|
updateDOMNode(node) {
|
||||||
const textNode = node.childNodes[0];
|
const textNode = node.childNodes[0];
|
||||||
if (textNode.textContent !== this.text) {
|
if (textNode.textContent !== this.text) {
|
||||||
// console.log("changing pill text from", textNode.textContent, "to", this.text);
|
|
||||||
textNode.textContent = this.text;
|
textNode.textContent = this.text;
|
||||||
}
|
}
|
||||||
if (node.className !== this.type) {
|
if (node.className !== this.className) {
|
||||||
// console.log("turning", node.className, "into", this.type);
|
node.className = this.className;
|
||||||
node.className = this.type;
|
|
||||||
}
|
}
|
||||||
|
this.setAvatar(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
canUpdateDOMNode(node) {
|
canUpdateDOMNode(node) {
|
||||||
|
@ -174,6 +176,20 @@ class PillPart extends BasePart {
|
||||||
node.childNodes[0].nodeType === Node.TEXT_NODE;
|
node.childNodes[0].nodeType === Node.TEXT_NODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper method for subclasses
|
||||||
|
_setAvatarVars(node, avatarUrl, initialLetter) {
|
||||||
|
const avatarBackground = `url('${avatarUrl}')`;
|
||||||
|
const avatarLetter = `'${initialLetter}'`;
|
||||||
|
// check if the value is changing,
|
||||||
|
// otherwise the avatars flicker on every keystroke while updating.
|
||||||
|
if (node.style.getPropertyValue("--avatar-background") !== avatarBackground) {
|
||||||
|
node.style.setProperty("--avatar-background", avatarBackground);
|
||||||
|
}
|
||||||
|
if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
|
||||||
|
node.style.setProperty("--avatar-letter", avatarLetter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get canEdit() {
|
get canEdit() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -218,17 +234,71 @@ export class NewlinePart extends BasePart {
|
||||||
export class RoomPillPart extends PillPart {
|
export class RoomPillPart extends PillPart {
|
||||||
constructor(displayAlias) {
|
constructor(displayAlias) {
|
||||||
super(displayAlias, displayAlias);
|
super(displayAlias, displayAlias);
|
||||||
|
this._room = this._findRoomByAlias(displayAlias);
|
||||||
|
}
|
||||||
|
|
||||||
|
_findRoomByAlias(alias) {
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
if (alias[0] === '#') {
|
||||||
|
return client.getRooms().find((r) => {
|
||||||
|
return r.getAliases().includes(alias);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return client.getRoom(alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setAvatar(node) {
|
||||||
|
let initialLetter = "";
|
||||||
|
let avatarUrl = Avatar.avatarUrlForRoom(this._room, 16 * window.devicePixelRatio, 16 * window.devicePixelRatio);
|
||||||
|
if (!avatarUrl) {
|
||||||
|
initialLetter = Avatar.getInitialLetter(this._room.name);
|
||||||
|
avatarUrl = `../../${Avatar.defaultAvatarUrlForString(this._room.roomId)}`;
|
||||||
|
}
|
||||||
|
this._setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
}
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
return "room-pill";
|
return "room-pill";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get className() {
|
||||||
|
return "mx_RoomPill mx_Pill";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserPillPart extends PillPart {
|
export class UserPillPart extends PillPart {
|
||||||
|
constructor(userId, displayName, member) {
|
||||||
|
super(userId, displayName);
|
||||||
|
this._member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAvatar(node) {
|
||||||
|
const name = this._member.name || this._member.userId;
|
||||||
|
const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this._member.userId);
|
||||||
|
let avatarUrl = Avatar.avatarUrlForMember(
|
||||||
|
this._member,
|
||||||
|
16 * window.devicePixelRatio,
|
||||||
|
16 * window.devicePixelRatio);
|
||||||
|
let initialLetter = "";
|
||||||
|
if (avatarUrl === defaultAvatarUrl) {
|
||||||
|
// the url from defaultAvatarUrlForString is meant to go in an img element,
|
||||||
|
// which has the base of the document. we're using it in css,
|
||||||
|
// which has the base of the theme css file, two levels deeper than the document,
|
||||||
|
// so go up to the level of the document.
|
||||||
|
avatarUrl = `../../${avatarUrl}`;
|
||||||
|
initialLetter = Avatar.getInitialLetter(name);
|
||||||
|
}
|
||||||
|
this._setAvatarVars(node, avatarUrl, initialLetter);
|
||||||
|
}
|
||||||
|
|
||||||
get type() {
|
get type() {
|
||||||
return "user-pill";
|
return "user-pill";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get className() {
|
||||||
|
return "mx_UserPill mx_Pill";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,9 +326,14 @@ export class PillCandidatePart extends PlainPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PartCreator {
|
export class PartCreator {
|
||||||
constructor(getAutocompleterComponent, updateQuery) {
|
constructor(getAutocompleterComponent, updateQuery, room) {
|
||||||
this._autoCompleteCreator = (updateCallback) => {
|
this._autoCompleteCreator = (updateCallback) => {
|
||||||
return new AutocompleteWrapperModel(updateCallback, getAutocompleterComponent, updateQuery);
|
return new AutocompleteWrapperModel(
|
||||||
|
updateCallback,
|
||||||
|
getAutocompleterComponent,
|
||||||
|
updateQuery,
|
||||||
|
room,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue