added keyboard selection

This commit is contained in:
Nicolas Constant 2019-07-24 18:09:50 -04:00
parent 0ced6445b5
commit fdcdf9d813
No known key found for this signature in database
GPG Key ID: 1E9F677FB01A5688
5 changed files with 133 additions and 36 deletions

View File

@ -1,15 +1,17 @@
<div class="autosuggest" *ngIf="accounts.length > 0 || hashtags.length > 0">
<a href *ngFor="let a of accounts"
title="@{{a.acct}}"
title="@{{a.account.acct}}"
(click)="accountSelected(a)"
class="autosuggest__entry autosuggest__account">
<img class="autosuggest__account--avatar" src="{{ a.avatar }}" /> {{ a.username }} <span class="autosuggest__account--acct">@{{ a.acct }}</span>
class="autosuggest__entry autosuggest__account"
[class.autosuggest__entry--selected]="a.selected">
<img class="autosuggest__account--avatar" src="{{ a.account.avatar }}" /> {{ a.account.username }} <span class="autosuggest__account--acct">@{{ a.account.acct }}</span>
</a>
<a href *ngFor="let h of hashtags"
title="#{{h}}"
title="#{{h.hashtag}}"
(click)="hashtagSelected(h)"
class="autosuggest__entry">
#{{ h }}
class="autosuggest__entry"
[class.autosuggest__entry--selected]="h.selected">
#{{ h.hashtag }}
</a>
</div>

View File

@ -16,11 +16,13 @@
overflow: hidden;
width: calc(100%);
&:hover {
&:hover, &--selected {
color: $font-color-primary;
text-decoration: none;
background-color: $column-color;
}
}
&__account {
@ -39,7 +41,7 @@
}
}
&__entry:hover &__account--acct {
&__entry:hover &__account--acct, &__entry--selected &__account--acct {
color: $font-link-primary-hover;
}
}

View File

@ -1,18 +1,23 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Component, OnInit, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
import { ToolsService } from '../../../services/tools.service';
import { MastodonService } from '../../../services/mastodon.service';
import { NotificationService } from '../../../services/notification.service';
import { Results, Account } from '../../../services/models/mastodon.interfaces';
import { Actions } from '@ngxs/store';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-autosuggest',
templateUrl: './autosuggest.component.html',
styleUrls: ['./autosuggest.component.scss']
})
export class AutosuggestComponent implements OnInit {
accounts: Account[] = [];
hashtags: string[] = [];
export class AutosuggestComponent implements OnInit, OnDestroy {
private lastPatternUsed: string;
private lastPatternUsedWtType: string;
accounts: SelectableAccount[] = [];
hashtags: SelectableHashtag[] = [];
@Output() suggestionSelectedEvent = new EventEmitter<AutosuggestSelection>();
@Output() hasSuggestionsEvent = new EventEmitter<boolean>();
@ -32,16 +37,26 @@ export class AutosuggestComponent implements OnInit {
return this._pattern;
}
@Input() autoSuggestUserActionsStream: EventEmitter<AutosuggestUserActionEnum>;
private autoSuggestUserActionsSub: Subscription;
constructor(
private readonly notificationService: NotificationService,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonService) { }
ngOnInit() {
if (this.autoSuggestUserActionsStream) {
this.autoSuggestUserActionsSub = this.autoSuggestUserActionsStream.subscribe((action: AutosuggestUserActionEnum) => {
this.processUserInput(action);
});
}
}
ngOnDestroy(): void {
if (this.autoSuggestUserActionsSub) this.autoSuggestUserActionsSub.unsubscribe();
}
private lastPatternUsed: string;
private lastPatternUsedWtType: string;
private analysePattern(value: string) {
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
const isAccount = value[0] === '@';
@ -58,21 +73,23 @@ export class AutosuggestComponent implements OnInit {
if (isAccount) {
for (let account of results.accounts) {
this.accounts.push(account);
if(this.accounts.length > 7) return;
this.accounts.push(new SelectableAccount(account));
this.accounts[0].selected = true;
if (this.accounts.length > 7) return;
}
}
else {
for (let hashtag of results.hashtags) {
if (hashtag.includes(this.lastPatternUsed) || hashtag === this.lastPatternUsed) {
this.hashtags.push(hashtag);
if(this.hashtags.length > 7) return;
this.hashtags.push(new SelectableHashtag(hashtag));
this.hashtags[0].selected = true;
if (this.hashtags.length > 7) return;
}
}
}
})
.then(() => {
if(this.hashtags.length > 0 || this.accounts.length > 0){
if (this.hashtags.length > 0 || this.accounts.length > 0) {
this.hasSuggestionsEvent.next(true);
} else {
this.hasSuggestionsEvent.next(false);
@ -83,19 +100,81 @@ export class AutosuggestComponent implements OnInit {
});
}
accountSelected(account: Account): boolean {
const fullHandle = this.toolsService.getAccountFullHandle(account);
private processUserInput(action: AutosuggestUserActionEnum) {
const isAutosuggestingHashtag = this.hashtags.length > 0;
switch (action) {
case AutosuggestUserActionEnum.Validate:
if (isAutosuggestingHashtag) {
let selection = this.hashtags.find(x => x.selected);
this.hashtagSelected(selection);
} else {
let selection = this.accounts.find(x => x.selected);
this.accountSelected(selection);
}
break;
case AutosuggestUserActionEnum.MoveDown:
if (isAutosuggestingHashtag) {
let selectionIndex = this.hashtags.findIndex(x => x.selected);
if (selectionIndex < (this.hashtags.length - 1)) {
this.hashtags[selectionIndex].selected = false;
this.hashtags[selectionIndex + 1].selected = true;
}
} else {
let selectionIndex = this.accounts.findIndex(x => x.selected);
if (selectionIndex < (this.accounts.length - 1)) {
this.accounts[selectionIndex].selected = false;
this.accounts[selectionIndex + 1].selected = true;
}
}
break;
case AutosuggestUserActionEnum.MoveUp:
if (isAutosuggestingHashtag) {
let selectionIndex = this.hashtags.findIndex(x => x.selected);
if (selectionIndex > 0) {
this.hashtags[selectionIndex].selected = false;
this.hashtags[selectionIndex - 1].selected = true;
}
} else {
let selectionIndex = this.accounts.findIndex(x => x.selected);
if (selectionIndex > 0) {
this.accounts[selectionIndex].selected = false;
this.accounts[selectionIndex - 1].selected = true;
}
}
break;
}
}
accountSelected(selAccount: SelectableAccount): boolean {
const fullHandle = this.toolsService.getAccountFullHandle(selAccount.account);
this.suggestionSelectedEvent.next(new AutosuggestSelection(this.lastPatternUsedWtType, fullHandle));
return false;
}
hashtagSelected(hashtag: string): boolean {
this.suggestionSelectedEvent.next(new AutosuggestSelection(this.lastPatternUsedWtType, `#${hashtag}`));
hashtagSelected(selHashtag: SelectableHashtag): boolean {
this.suggestionSelectedEvent.next(new AutosuggestSelection(this.lastPatternUsedWtType, `#${selHashtag.hashtag}`));
return false;
}
}
export class AutosuggestSelection{
constructor(public pattern: string, public autosuggest: string) {
class SelectableAccount {
constructor(public account: Account, public selected: boolean = false) {
}
}
class SelectableHashtag {
constructor(public hashtag: string, public selected: boolean = false) {
}
}
export class AutosuggestSelection {
constructor(public pattern: string, public autosuggest: string) {
}
}
export enum AutosuggestUserActionEnum {
MoveDown,
MoveUp,
Validate
}

View File

@ -14,6 +14,7 @@
<app-autosuggest class="status-form__autosuggest" *ngIf="autosuggestData"
[pattern]="autosuggestData"
[autoSuggestUserActionsStream]="autoSuggestUserActionsStream"
(suggestionSelectedEvent)="suggestionSelected($event)"
(hasSuggestionsEvent)="suggestionsChanged($event)"></app-autosuggest>

View File

@ -12,7 +12,7 @@ import { StatusWrapper } from '../../models/common.model';
import { AccountInfo } from '../../states/accounts.state';
import { InstancesInfoService } from '../../services/instances-info.service';
import { MediaService } from '../../services/media.service';
import { AutosuggestSelection } from './autosuggest/autosuggest.component';
import { AutosuggestSelection, AutosuggestUserActionEnum } from './autosuggest/autosuggest.component';
@Component({
@ -21,6 +21,8 @@ import { AutosuggestSelection } from './autosuggest/autosuggest.component';
styleUrls: ['./create-status.component.scss']
})
export class CreateStatusComponent implements OnInit, OnDestroy {
autoSuggestUserActionsStream = new EventEmitter<AutosuggestUserActionEnum>();
private _title: string;
set title(value: string) {
this._title = value;
@ -51,7 +53,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
let parser = new DOMParser();
var dom = parser.parseFromString(value.status.content, 'text/html')
this.status = dom.body.textContent;
this.setVisibilityFromStatus(value.status);
this.title = value.status.spoiler_text;
@ -64,7 +66,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
const mentions = this.getMentions(this.statusReplyingToWrapper.status, this.statusReplyingToWrapper.provider);
for (const mention of mentions) {
const name = `@${mention.split('@')[0]}`;
if(this.status.includes(name)){
if (this.status.includes(name)) {
this.status = this.status.replace(name, `@${mention}`);
} else {
this.status = `@${mention} ` + this.status;
@ -170,9 +172,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
private detectAutosuggestion(status: string) {
const parsedStatus = status.split(' ');
if(parsedStatus && parsedStatus.length > 0){
if (parsedStatus && parsedStatus.length > 0) {
const lastElement = parsedStatus[parsedStatus.length - 1];
if(lastElement.length > 2 && (lastElement.startsWith('@') || lastElement.startsWith('#'))){
if (lastElement.length > 2 && (lastElement.startsWith('@') || lastElement.startsWith('#'))) {
//this.autosuggestData = lastElement.substring(1);
this.autosuggestData = lastElement;
return;
@ -467,9 +469,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
}
suggestionSelected(selection: AutosuggestSelection){
suggestionSelected(selection: AutosuggestSelection) {
const parsedStatus = this.status.split(' ');
if(parsedStatus[parsedStatus.length - 1] === selection.pattern){
if (parsedStatus[parsedStatus.length - 1] === selection.pattern) {
this.status = `${this.status.replace(new RegExp(`${selection.pattern}$`), selection.autosuggest)} `;
this.focus();
}
@ -481,13 +483,24 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
}
handleKeyDown(event: KeyboardEvent): boolean {
if(this.hasSuggestions){
if (this.hasSuggestions) {
if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW || event.keyCode === ENTER) {
event.stopImmediatePropagation();
event.preventDefault();
event.stopPropagation();
console.warn(event.keyCode);
event.stopPropagation();
switch (event.keyCode) {
case DOWN_ARROW:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.MoveDown);
break;
case UP_ARROW:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.MoveUp);
break;
case ENTER:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.Validate);
break;
}
return false;
}
}