Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Improve handling of pills in the composer #6353

Merged
merged 18 commits into from
Aug 11, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/css/views/rooms/_BasicMessageComposer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ limitations under the License.
&.mx_BasicMessageComposer_input_shouldShowPillAvatar {
span.mx_UserPill, span.mx_RoomPill {
position: relative;
cursor: pointer;
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved

// avatar psuedo element
&::before {
Expand Down
3 changes: 2 additions & 1 deletion src/ActiveRoomObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import { EventSubscription } from 'fbemitter';
import RoomViewStore from './stores/RoomViewStore';

type Listener = (isActive: boolean) => void;
Expand All @@ -30,7 +31,7 @@ type Listener = (isActive: boolean) => void;
export class ActiveRoomObserver {
private listeners: {[key: string]: Listener[]} = {};
private _activeRoomId = RoomViewStore.getRoomId();
private readonly roomStoreToken: string;
private readonly roomStoreToken: EventSubscription;

constructor() {
// TODO: We could self-destruct when the last listener goes away, or at least stop listening.
Expand Down
24 changes: 24 additions & 0 deletions src/components/views/rooms/BasicMessageComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
handled = true;
} else if (event.key === Key.BACKSPACE || event.key === Key.DELETE) {
this.formatBarRef.current.hide();
handled = this.fakeDeletion(event.key === Key.BACKSPACE ? "deleteContentBackward" : "deleteContentForward");
}

if (handled) {
Expand All @@ -515,6 +516,29 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
}
};

/**
* Because pills have contentEditable="false" there is no event emitted when
* the user tries to delete them. Therefore we need to fake what would
* normally happen
* @param direction in which to delete
* @returns handled
*/
private fakeDeletion(direction: "deleteContentForward" | "deleteContentBackward" ): boolean {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
const selection = document.getSelection();
// Use the default handling for ranges
if (selection.type === "Range") return false;

this.modifiedFlag = true;
const { caret, text } = getCaretOffsetAndText(this.editorRef.current, selection);

// Do the deletion itself
if (direction === "deleteContentBackward") caret.offset--;
const newText = text.slice(0, caret.offset) + text.slice(caret.offset + 1);

this.props.model.update(newText, direction, caret);
return true;
}

private async tabCompleteName(): Promise<void> {
try {
await new Promise<void>(resolve => this.setState({ showVisualBell: false }, resolve));
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/voip/CallPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export default class CallPreview extends React.Component<IProps, IState> {
this.scheduledUpdate.mark();
};

private onRoomViewStoreUpdate = (payload) => {
private onRoomViewStoreUpdate = () => {
if (RoomViewStore.getRoomId() === this.state.roomId) return;

const roomId = RoomViewStore.getRoomId();
Expand Down
19 changes: 19 additions & 0 deletions src/editor/parts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import AutocompleteWrapperModel, {
UpdateQuery,
} from "./autocomplete";
import * as Avatar from "../Avatar";
import defaultDispatcher from "../dispatcher/dispatcher";
import { Action } from "../dispatcher/actions";
import RoomViewStore from "../stores/RoomViewStore";
import { MatrixClientPeg } from "../MatrixClientPeg";

interface ISerializedPart {
type: Type.Plain | Type.Newline | Type.Command | Type.PillCandidate;
Expand Down Expand Up @@ -74,6 +78,7 @@ interface IPillCandidatePart extends Omit<IBasePart, "type" | "createAutoComplet
interface IPillPart extends Omit<IBasePart, "type" | "resourceId"> {
type: Type.AtRoomPill | Type.RoomPill | Type.UserPill;
resourceId: string;
onClick?(): void;
}

export type Part = IBasePart | IPillCandidatePart | IPillPart;
Expand Down Expand Up @@ -249,6 +254,8 @@ abstract class PillPart extends BasePart implements IPillPart {
toDOMNode() {
const container = document.createElement("span");
container.setAttribute("spellcheck", "false");
container.setAttribute("contentEditable", "false");
container.onclick = this.onClick;
container.className = this.className;
container.appendChild(document.createTextNode(this.text));
this.setAvatar(container);
Expand Down Expand Up @@ -303,6 +310,8 @@ abstract class PillPart extends BasePart implements IPillPart {

abstract get className(): string;

abstract onClick?(): void;
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved

abstract setAvatar(node: HTMLElement): void;
}

Expand Down Expand Up @@ -364,6 +373,9 @@ class RoomPillPart extends PillPart {
get className() {
return "mx_RoomPill mx_Pill";
}

// FIXME: We do this to shut up the linter, is there a way to do this properly
onClick = undefined;
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
}

class AtRoomPillPart extends RoomPillPart {
Expand Down Expand Up @@ -402,6 +414,13 @@ class UserPillPart extends PillPart {
this._setAvatarVars(node, avatarUrl, initialLetter);
}

onClick = () => {
SimonBrandner marked this conversation as resolved.
Show resolved Hide resolved
defaultDispatcher.dispatch({
action: Action.ViewUser,
member: MatrixClientPeg.get().getRoom(RoomViewStore.getRoomId()).getMember(this.resourceId),
});
};

get type(): IPillPart["type"] {
return Type.UserPill;
}
Expand Down
2 changes: 1 addition & 1 deletion src/stores/RoomViewStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ class RoomViewStore extends Store<ActionPayload> {
}
}

let singletonRoomViewStore = null;
let singletonRoomViewStore: RoomViewStore = null;
if (!singletonRoomViewStore) {
singletonRoomViewStore = new RoomViewStore();
}
Expand Down