implement RoomAliasField component
adding a postfix to Field to show the domain namepull/21833/head
parent
c5f9ef87ba
commit
8a1c1bbec4
|
@ -99,6 +99,7 @@
|
||||||
@import "./views/elements/_ResizeHandle.scss";
|
@import "./views/elements/_ResizeHandle.scss";
|
||||||
@import "./views/elements/_RichText.scss";
|
@import "./views/elements/_RichText.scss";
|
||||||
@import "./views/elements/_RoleButton.scss";
|
@import "./views/elements/_RoleButton.scss";
|
||||||
|
@import "./views/elements/_RoomAliasField.scss";
|
||||||
@import "./views/elements/_Spinner.scss";
|
@import "./views/elements/_Spinner.scss";
|
||||||
@import "./views/elements/_SyntaxHighlight.scss";
|
@import "./views/elements/_SyntaxHighlight.scss";
|
||||||
@import "./views/elements/_TextWithTooltip.scss";
|
@import "./views/elements/_TextWithTooltip.scss";
|
||||||
|
|
|
@ -31,6 +31,10 @@ limitations under the License.
|
||||||
border-right: 1px solid $input-border-color;
|
border-right: 1px solid $input-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_Field_postfix {
|
||||||
|
border-left: 1px solid $input-border-color;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_Field input,
|
.mx_Field input,
|
||||||
.mx_Field select,
|
.mx_Field select,
|
||||||
.mx_Field textarea {
|
.mx_Field textarea {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_RoomAliasField {
|
||||||
|
// if parent is a flex container, this allows the
|
||||||
|
// width to be as wide as needed, and not 100%
|
||||||
|
flex: 0 1 auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 150px;
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: $greyed-fg-color;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field_prefix, .mx_Field_postfix {
|
||||||
|
color: $greyed-fg-color;
|
||||||
|
border-left: none;
|
||||||
|
border-right: none;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 9px 10px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_Field_postfix {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
// this allows the domain name to show
|
||||||
|
// as long as it doesn't make the input shrink
|
||||||
|
// if it's too big, it shows an ellipsis
|
||||||
|
// 180: 28 for prefix, 152 for input
|
||||||
|
max-width: calc(100% - 180px);
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,8 @@ export default class Field extends React.PureComponent {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
// Optional component to include inside the field before the input.
|
// Optional component to include inside the field before the input.
|
||||||
prefix: PropTypes.node,
|
prefix: PropTypes.node,
|
||||||
|
// Optional component to include inside the field after the input.
|
||||||
|
postfix: PropTypes.node,
|
||||||
// The callback called whenever the contents of the field
|
// The callback called whenever the contents of the field
|
||||||
// changes. Returns an object with `valid` boolean field
|
// changes. Returns an object with `valid` boolean field
|
||||||
// and a `feedback` react component field to provide feedback
|
// and a `feedback` react component field to provide feedback
|
||||||
|
@ -54,6 +56,8 @@ export default class Field extends React.PureComponent {
|
||||||
// If specified alongside tooltipContent, the class name to apply to the
|
// If specified alongside tooltipContent, the class name to apply to the
|
||||||
// tooltip itself.
|
// tooltip itself.
|
||||||
tooltipClassName: PropTypes.string,
|
tooltipClassName: PropTypes.string,
|
||||||
|
// If specified, an additional class name to apply to the field container
|
||||||
|
className: PropTypes.string,
|
||||||
// All other props pass through to the <input>.
|
// All other props pass through to the <input>.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -143,8 +147,8 @@ export default class Field extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
element, prefix, onValidate, children, tooltipContent, flagInvalid,
|
element, prefix, postfix, className, onValidate, children,
|
||||||
tooltipClassName, ...inputProps} = this.props;
|
tooltipContent, flagInvalid, tooltipClassName, ...inputProps} = this.props;
|
||||||
|
|
||||||
const inputElement = element || "input";
|
const inputElement = element || "input";
|
||||||
|
|
||||||
|
@ -163,9 +167,13 @@ export default class Field extends React.PureComponent {
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
|
prefixContainer = <span className="mx_Field_prefix">{prefix}</span>;
|
||||||
}
|
}
|
||||||
|
let postfixContainer = null;
|
||||||
|
if (postfix) {
|
||||||
|
postfixContainer = <span className="mx_Field_postfix">{postfix}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
|
const hasValidationFlag = flagInvalid !== null && flagInvalid !== undefined;
|
||||||
const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, {
|
const fieldClasses = classNames("mx_Field", `mx_Field_${inputElement}`, className, {
|
||||||
// If we have a prefix element, leave the label always at the top left and
|
// If we have a prefix element, leave the label always at the top left and
|
||||||
// don't animate it, as it looks a bit clunky and would add complexity to do
|
// don't animate it, as it looks a bit clunky and would add complexity to do
|
||||||
// properly.
|
// properly.
|
||||||
|
@ -192,6 +200,7 @@ export default class Field extends React.PureComponent {
|
||||||
{prefixContainer}
|
{prefixContainer}
|
||||||
{fieldInput}
|
{fieldInput}
|
||||||
<label htmlFor={this.props.id}>{this.props.label}</label>
|
<label htmlFor={this.props.id}>{this.props.label}</label>
|
||||||
|
{postfixContainer}
|
||||||
{fieldTooltip}
|
{fieldTooltip}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import withValidation from './Validation';
|
||||||
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
|
|
||||||
|
export default class RoomAliasField extends React.PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {isValid: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
_asFullAlias(localpart) {
|
||||||
|
return `#${localpart}:${this.props.domain}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const Field = sdk.getComponent('views.elements.Field');
|
||||||
|
const poundSign = (<span>#</span>);
|
||||||
|
const aliasPostfix = ":" + this.props.domain;
|
||||||
|
const domain = (<span title={aliasPostfix}>{aliasPostfix}</span>);
|
||||||
|
const maxlength = 255 - this.props.domain.length - 2; // 2 for # and :
|
||||||
|
return (
|
||||||
|
<Field
|
||||||
|
label={_t("Room alias")}
|
||||||
|
className="mx_RoomAliasField"
|
||||||
|
prefix={poundSign}
|
||||||
|
postfix={domain}
|
||||||
|
id={this.props.id}
|
||||||
|
ref={ref => this._fieldRef = ref}
|
||||||
|
onValidate={this._onValidate}
|
||||||
|
placeholder={_t("e.g. my-room")}
|
||||||
|
onChange={this._onChange}
|
||||||
|
maxLength={maxlength} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onChange = (ev) => {
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(this._asFullAlias(ev.target.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_onValidate = async (fieldState) => {
|
||||||
|
const result = await this._validationRules(fieldState);
|
||||||
|
this.setState({isValid: result.valid});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
_validationRules = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "safeLocalpart",
|
||||||
|
test: async ({ value }) => {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const fullAlias = this._asFullAlias(value);
|
||||||
|
// XXX: FIXME https://github.com/matrix-org/matrix-doc/issues/668
|
||||||
|
return !value.includes("#") && !value.includes(":") && !value.includes(",") &&
|
||||||
|
encodeURI(fullAlias) === fullAlias;
|
||||||
|
},
|
||||||
|
invalid: () => _t("Some characters not allowed"),
|
||||||
|
}, {
|
||||||
|
key: "required",
|
||||||
|
test: async ({ value, allowEmpty }) => allowEmpty || !!value,
|
||||||
|
invalid: () => _t("Please provide a room alias"),
|
||||||
|
}, {
|
||||||
|
key: "taken",
|
||||||
|
test: async ({value}) => {
|
||||||
|
if (!value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const client = MatrixClientPeg.get();
|
||||||
|
try {
|
||||||
|
await client.getRoomIdForAlias(this._asFullAlias(value));
|
||||||
|
// we got a room id, so the alias is taken
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
// any server error code will do,
|
||||||
|
// either it M_NOT_FOUND or the alias is invalid somehow,
|
||||||
|
// in which case we don't want to show the invalid message
|
||||||
|
return !!err.errcode;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valid: () => _t("This alias is available to use"),
|
||||||
|
invalid: () => _t("This alias is already in use"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
get isValid() {
|
||||||
|
return this.state.isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(options) {
|
||||||
|
return this._fieldRef.validate(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this._fieldRef.focus();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue