feat: adds list editor
feat: adds list editor
This commit is contained in:
@@ -10,6 +10,10 @@ trim_trailing_whitespace = true
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
max_line_length = 180
|
||||
|
||||
[*.html]
|
||||
max_line_length = 180
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
|
||||
@@ -75,4 +75,16 @@ export class MastodonApiListsService {
|
||||
return this.mastodonApiService
|
||||
.deleteAuthenticated(url, {account_ids: [accountId]}, accessToken);
|
||||
}
|
||||
|
||||
updateList(instanceName: string, accessToken: string, listId: string, newTitle: string) {
|
||||
const url = `https://${instanceName}/api/v1/lists/${listId}`;
|
||||
return this.mastodonApiService
|
||||
.putAuthenticated(url, {title: newTitle}, accessToken);
|
||||
}
|
||||
|
||||
createList(instanceName: string, accessToken: string, title: string) {
|
||||
const url = `https://${instanceName}/api/v1/lists`;
|
||||
return this.mastodonApiService
|
||||
.postAuthenticated(url, {title}, accessToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MastodonApiService {
|
||||
return this.httpClient.post<T>(url, parameters);
|
||||
}
|
||||
|
||||
postAuthenticated(url: string, body: { account_ids: string[] }, accessToken: string) {
|
||||
postAuthenticated(url: string, body: {}, accessToken: string) {
|
||||
const reqHeader = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + accessToken
|
||||
@@ -41,6 +41,14 @@ export class MastodonApiService {
|
||||
return this.httpClient.post(url, body, {headers: reqHeader});
|
||||
}
|
||||
|
||||
putAuthenticated(url: string, body: {}, accessToken: string) {
|
||||
const reqHeader = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + accessToken
|
||||
});
|
||||
return this.httpClient.put(url, body, {headers: reqHeader});
|
||||
}
|
||||
|
||||
deleteAuthenticated(url: string, body: { account_ids: string[] }, accessToken: string) {
|
||||
const reqHeader = new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -8,6 +8,18 @@ const routes: Routes = [
|
||||
redirectTo: 'home',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
redirectTo: 'followings/list',
|
||||
},
|
||||
{
|
||||
path: 'matrix',
|
||||
redirectTo: 'followings/matrix',
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
redirectTo: 'followings/table',
|
||||
},
|
||||
{
|
||||
path: 'home',
|
||||
loadChildren: () => import('./home/home.module').then(m => m.HomeModule),
|
||||
@@ -22,20 +34,16 @@ const routes: Routes = [
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'list',
|
||||
loadChildren: () => import('./followings-list/followings-list.module').then(m => m.FollowingsListModule),
|
||||
path: 'lists',
|
||||
loadChildren: () => import('./lists/lists.module').then(m => m.ListsModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'matrix',
|
||||
loadChildren: () => import('./followings-matrix/followings-matrix.module').then(m => m.FollowingsMatrixModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
loadChildren: () => import('./followings-table/followings-table.module').then(m => m.FollowingsTableModule),
|
||||
path: 'followings',
|
||||
loadChildren: () => import('./followings/followings.module').then(m => m.FollowingsModule),
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -14,9 +14,10 @@ export class AppComponent {
|
||||
navigationItems = [
|
||||
{title: 'Authorize', link: '/auth'},
|
||||
{title: 'Stats', link: '/sync'},
|
||||
{title: 'List view', link: '/list'},
|
||||
{title: 'Matrix View', link: '/matrix'},
|
||||
{title: 'Table View', link: '/table'},
|
||||
{title: 'Edit Lists', link: '/lists'},
|
||||
{title: 'List view', link: '/followings/list'},
|
||||
{title: 'Matrix View', link: '/followings/matrix'},
|
||||
{title: 'Table View', link: '/followings/table'},
|
||||
];
|
||||
|
||||
constructor(private store: Store, private persistentStore: PersistentStore) {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import {RouterModule, Routes} from "@angular/router";
|
||||
import {NgModule} from "@angular/core";
|
||||
import {ListComponent} from "./list/list.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ListComponent
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FollowingsListRoutingModule {
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ListComponent} from './list/list.component';
|
||||
import {SharedModule} from '../shared/shared.module';
|
||||
import {FollowingsListRoutingModule} from './followings-list-routing.module';
|
||||
import {NbListModule, NbToggleModule} from "@nebular/theme";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ListComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FollowingsListRoutingModule,
|
||||
// Nebula
|
||||
NbListModule,
|
||||
NbToggleModule,
|
||||
]
|
||||
})
|
||||
export class FollowingsListModule {
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import {RouterModule, Routes} from "@angular/router";
|
||||
import {NgModule} from "@angular/core";
|
||||
import {MatrixComponent} from "./matrix/matrix.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: MatrixComponent,
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FollowingsMatrixRoutingModule {
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MatrixComponent} from './matrix/matrix.component';
|
||||
import {FollowingsMatrixRoutingModule} from './followings-matrix-routing.module';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {NbBadgeModule, NbCheckboxModule, NbPopoverModule} from "@nebular/theme";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
MatrixComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FollowingsMatrixRoutingModule,
|
||||
// Nebular
|
||||
NbCheckboxModule,
|
||||
NbBadgeModule,
|
||||
NbPopoverModule,
|
||||
]
|
||||
|
||||
})
|
||||
export class FollowingsMatrixModule {
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {TableComponent} from './table/table.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {FollowingsTableRoutingModule} from "./followings-table-routing.module";
|
||||
import {NbUserModule} from "@nebular/theme";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TableComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FollowingsTableRoutingModule,
|
||||
// Nebular
|
||||
NbUserModule,
|
||||
]
|
||||
})
|
||||
export class FollowingsTableModule {
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import {RouterModule, Routes} from "@angular/router";
|
||||
import {NgModule} from "@angular/core";
|
||||
import {ListComponent} from "./list/list.component";
|
||||
import {MatrixComponent} from "./matrix/matrix.component";
|
||||
import {TableComponent} from "./table/table.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: 'list',
|
||||
component: ListComponent,
|
||||
},
|
||||
{
|
||||
path: 'matrix',
|
||||
component: MatrixComponent,
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: TableComponent,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FollowingsRoutingModule {
|
||||
}
|
||||
31
projects/mastolists/src/app/followings/followings.module.ts
Normal file
31
projects/mastolists/src/app/followings/followings.module.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {FollowingsRoutingModule} from "./followings-list-routing.module";
|
||||
import {NbBadgeModule, NbCheckboxModule, NbIconModule, NbListModule, NbPopoverModule, NbToggleModule, NbUserModule} from "@nebular/theme";
|
||||
import {ListComponent} from "./list/list.component";
|
||||
import {TableComponent} from "./table/table.component";
|
||||
import {MatrixComponent} from "./matrix/matrix.component";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ListComponent,
|
||||
TableComponent,
|
||||
MatrixComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FollowingsRoutingModule,
|
||||
// Nebula
|
||||
NbCheckboxModule,
|
||||
NbListModule,
|
||||
NbToggleModule,
|
||||
NbBadgeModule,
|
||||
NbPopoverModule,
|
||||
NbUserModule,
|
||||
]
|
||||
})
|
||||
export class FollowingsModule {
|
||||
}
|
||||
@@ -8,8 +8,8 @@
|
||||
<ul>
|
||||
<li><a [routerLink]="['/auth']">Authorize</a> with your instance</li>
|
||||
<li><a [routerLink]="['/sync']">Sync</a> your lists and followings to local storage</li>
|
||||
<li>Select the <a [routerLink]="['/list']">List view</a> to add and remove users from lists</li>
|
||||
<li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li>
|
||||
<li>Select the <a [routerLink]="['/followings/list']">List view</a> to add and remove users from lists</li>
|
||||
<li>... or use the experimental <a [routerLink]="['/followings/matrix']">Matrix view</a></li>
|
||||
</ul>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {RouterModule, Routes} from "@angular/router";
|
||||
import {NgModule} from "@angular/core";
|
||||
import {TableComponent} from "./table/table.component";
|
||||
import {ListsComponent} from "./lists/lists.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: TableComponent
|
||||
component: ListsComponent
|
||||
}
|
||||
];
|
||||
|
||||
@@ -13,5 +13,5 @@ const routes: Routes = [
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class FollowingsTableRoutingModule {
|
||||
export class ListsRoutingModule {
|
||||
}
|
||||
24
projects/mastolists/src/app/lists/lists.module.ts
Normal file
24
projects/mastolists/src/app/lists/lists.module.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {ListsComponent} from './lists/lists.component';
|
||||
import {SharedModule} from "../shared/shared.module";
|
||||
import {ListsRoutingModule} from "./lists-routing.module";
|
||||
import {NbSpinnerModule} from "@nebular/theme";
|
||||
import {ReactiveFormsModule} from "@angular/forms";
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
ListsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
ListsRoutingModule,
|
||||
ReactiveFormsModule,
|
||||
// Nebular
|
||||
NbSpinnerModule,
|
||||
]
|
||||
})
|
||||
export class ListsModule {
|
||||
}
|
||||
31
projects/mastolists/src/app/lists/lists/lists.component.html
Normal file
31
projects/mastolists/src/app/lists/lists/lists.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<nb-card>
|
||||
<nb-card-header>
|
||||
<h1>Lists</h1>
|
||||
<div class="divider"></div>
|
||||
<div [formGroup]="newListForm">
|
||||
<input nbInput id="title" type="text" [formControlName]="'title'" style="margin-right: 20px;">
|
||||
<button class="new-list-button" nbButton type="button"
|
||||
[nbSpinner]="creatingList"
|
||||
(click)="createList()">Create List
|
||||
</button>
|
||||
</div>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
<tr *ngFor="let list of lists$ | async">
|
||||
<td>
|
||||
<a [href]="'https://' + instanceName + '/lists/' + list.id" target="_blank" rel="noopener">
|
||||
{{list.id}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<input nbInput id="serverUrl" type="text" [value]="list.title" (change)="listNameChanged(list.id, $event)">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListsComponent } from './lists.component';
|
||||
|
||||
describe('ListsComponent', () => {
|
||||
let component: ListsComponent;
|
||||
let fixture: ComponentFixture<ListsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ListsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
43
projects/mastolists/src/app/lists/lists/lists.component.ts
Normal file
43
projects/mastolists/src/app/lists/lists/lists.component.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {List} from 'projects/mastodon-api/src/public-api';
|
||||
import {Observable} from "rxjs";
|
||||
import {select, Store} from "@ngrx/store";
|
||||
import {selectLists} from "../../shared/state/store/selectors";
|
||||
import {PersistentStore} from "../../shared/state/persistent/persistent-store.service";
|
||||
import {ListActions} from "../../shared/state/store/actions";
|
||||
import {FormControl, FormGroup} from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: 'app-lists',
|
||||
templateUrl: './lists.component.html',
|
||||
styleUrls: ['./lists.component.scss']
|
||||
})
|
||||
export class ListsComponent {
|
||||
|
||||
lists$: Observable<ReadonlyArray<List>>;
|
||||
instanceName: string;
|
||||
creatingList: boolean = false;
|
||||
|
||||
newListForm = new FormGroup({
|
||||
title: new FormControl(''),
|
||||
});
|
||||
|
||||
constructor(private store: Store, private persistentStore: PersistentStore) {
|
||||
this.instanceName = persistentStore.value.currentInstance.instanceName;
|
||||
this.lists$ = this.store.pipe(select(selectLists));
|
||||
}
|
||||
|
||||
listNameChanged(id: string, event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const newTitle = target.value;
|
||||
this.store.dispatch(ListActions.updateList({listId: id, newTitle}));
|
||||
}
|
||||
|
||||
createList() {
|
||||
let title = this.newListForm.value.title ?? '';
|
||||
if (title.length > 0) {
|
||||
this.store.dispatch(ListActions.createList({title}));
|
||||
this.newListForm.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,30 @@ export class ListService {
|
||||
.pipe(map(result => result.sort((a, b) => a.title.localeCompare(b.title))));
|
||||
}
|
||||
|
||||
|
||||
createList(title: string) {
|
||||
const applicationState = this.store.value;
|
||||
const instanceName = applicationState.currentInstance?.instanceName;
|
||||
const accessToken = applicationState.currentInstance?.accessToken;
|
||||
return this.mastodonApiListsService
|
||||
.createList(instanceName, accessToken!, title)
|
||||
.pipe(
|
||||
take(1),
|
||||
);
|
||||
}
|
||||
|
||||
updateList(listId: string, newName: string) {
|
||||
const applicationState = this.store.value;
|
||||
const instanceName = applicationState.currentInstance?.instanceName;
|
||||
const accessToken = applicationState.currentInstance?.accessToken;
|
||||
return this.mastodonApiListsService
|
||||
.updateList(instanceName, accessToken!, listId, newName)
|
||||
.pipe(
|
||||
take(1),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
loadAccountsIdsForList(listId: string): Observable<{ [id: string]: string[] }> {
|
||||
const applicationState = this.store.value;
|
||||
const instanceName = applicationState.currentInstance?.instanceName;
|
||||
@@ -74,6 +98,5 @@ export class ListService {
|
||||
take(1),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,12 @@ export const ListActions = createActionGroup({
|
||||
'Remove Account from List': props<{ accountId: string, listId: string }>(),
|
||||
'Remove Account from List Success': emptyProps(),
|
||||
'Remove Account from List Error': emptyProps(),
|
||||
'Create List': props<{ title: string }>(),
|
||||
'Create List Success': props<{ listId: string, title: string }>(),
|
||||
'Create List Error': emptyProps(),
|
||||
'Update List': props<{ listId: string, newTitle: string }>(),
|
||||
'Update List Success': props<{ listId: string, newTitle: string }>(),
|
||||
'Update List Error': emptyProps(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {act, Actions, createEffect, ofType} from "@ngrx/effects";
|
||||
import {ListActions, MastodonApiActions} from "./actions";
|
||||
import {catchError, concat, exhaustMap, forkJoin, map, merge, of, switchMap} from "rxjs";
|
||||
import {catchError, concat, exhaustMap, forkJoin, map, merge, of, switchMap, tap} from "rxjs";
|
||||
import {ListService} from "../../services/list.service";
|
||||
import {AccountService} from "../../services/account.service";
|
||||
import {NbToastrService} from "@nebular/theme";
|
||||
import {List} from "projects/mastodon-api/src/public-api";
|
||||
|
||||
@Injectable()
|
||||
export class ApplicationEffects {
|
||||
@@ -19,6 +20,59 @@ export class ApplicationEffects {
|
||||
)
|
||||
);
|
||||
|
||||
updateList$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.updateList),
|
||||
exhaustMap((action) => this.listService.updateList(action.listId, action.newTitle).pipe(
|
||||
map((resp) => {
|
||||
const list = resp as List;
|
||||
return ListActions.updateListSuccess({listId: list.id, newTitle: list.title});
|
||||
}),
|
||||
catchError(() => of(ListActions.updateListError)),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
updateListSuccess$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.updateListSuccess),
|
||||
map(() => this.toastService.success('List updated successfully', 'Success')),
|
||||
), {dispatch: false}
|
||||
);
|
||||
updateListError$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.updateListError),
|
||||
map(() => this.toastService.success('Could not update the list', 'Error')),
|
||||
), {dispatch: false}
|
||||
);
|
||||
|
||||
createList$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.createList),
|
||||
exhaustMap((action) => this.listService.createList(action.title).pipe(
|
||||
map((resp) => {
|
||||
const list = resp as List;
|
||||
return ListActions.createListSuccess({listId: list.id, title: list.title});
|
||||
}),
|
||||
catchError(() => of(ListActions.createListError())),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
createListSuccess$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.createListSuccess),
|
||||
map(() => this.toastService.success('List created successfully', 'Success')),
|
||||
), {dispatch: false}
|
||||
);
|
||||
createListError$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(ListActions.createListError),
|
||||
map(() => this.toastService.success('Could not create the list', 'Error')),
|
||||
), {dispatch: false}
|
||||
);
|
||||
|
||||
|
||||
listsLoaded$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(MastodonApiActions.listsLoaded),
|
||||
|
||||
@@ -89,5 +89,20 @@ export const applicationStateReducers = createReducer(
|
||||
on(FiltersActions.clearFilters, _state => {
|
||||
return {..._state, filters: initialState.filters};
|
||||
}),
|
||||
)
|
||||
;
|
||||
on(ListActions.createListSuccess, (_state, {listId, title}) => {
|
||||
const newListsArray = [..._state.lists, {id: listId, title: title, repliesPolicy: 'list', accounts: []}].sort((a, b) => a.title.localeCompare(b.title));
|
||||
return {
|
||||
..._state,
|
||||
listsAccounts: {..._state.listsAccounts, [listId]: []},
|
||||
lists: newListsArray,
|
||||
};
|
||||
}),
|
||||
on(ListActions.updateListSuccess, (_state, {listId, newTitle}) => {
|
||||
const listToUpdate = _state.lists.find(list => list.id === listId) as List;
|
||||
const newListsArray = [..._state.lists.filter(list => list.id !== listId), {...listToUpdate, title: newTitle}].sort((a, b) => a.title.localeCompare(b.title));
|
||||
return {
|
||||
..._state,
|
||||
lists: newListsArray,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -40,8 +40,9 @@
|
||||
<div *ngIf="(lists$ | async) && (followings$ | async)">
|
||||
<p>Now you can:</p>
|
||||
<ul>
|
||||
<li>Select the <a [routerLink]="['/list']">List view</a> to add and remove users from lists</li>
|
||||
<li>... or use the experimental <a [routerLink]="['/matrix']">Matrix view</a></li>
|
||||
<li>Edit the lists <a [routerLink]="['/lists']">Lists</a></li>
|
||||
<li>Select the <a [routerLink]="['/followings/list']">List view</a> to add and remove users from lists</li>
|
||||
<li>... or use the experimental <a [routerLink]="['/followings/matrix']">Matrix view</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nb-card-footer>
|
||||
|
||||
Reference in New Issue
Block a user