#4 - add support for sharing and viewing a page publicly without login
This commit is contained in:
parent
4f14a40994
commit
01c2fc18f2
@ -3,6 +3,7 @@ import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
|
||||
import {LoginPage} from './pages/login/login.page';
|
||||
import {AuthService} from './service/auth.service';
|
||||
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
|
||||
import {EditorGuard} from './service/guard/Editor.guard';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
@ -17,7 +18,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'editor',
|
||||
canActivate: [AuthService],
|
||||
canActivate: [EditorGuard],
|
||||
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
||||
},
|
||||
{
|
||||
|
@ -17,6 +17,9 @@
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</ion-col>
|
||||
<ion-col>
|
||||
<div class="not-available" *ngIf="fileRecords.length < 1 && readonly">There are no files in this group yet.</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</div>
|
||||
|
@ -2,11 +2,11 @@ div.files-wrapper {
|
||||
border: 2px solid #8c8c8c;
|
||||
border-radius: 3px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
&.not-available {
|
||||
.not-available {
|
||||
height: 150px;
|
||||
text-align: center;
|
||||
padding-top: 40px;
|
||||
color: #494949;
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ export class FilesComponent extends EditorNodeContract implements OnInit {
|
||||
this.node.Value = {};
|
||||
}
|
||||
|
||||
console.log('files load', this)
|
||||
|
||||
if ( !this.node.Value.Value && !this.readonly ) {
|
||||
this.dbRecord = await this.api.createFileGroup(this.page.UUID, this.node.UUID);
|
||||
this.fileRecords = this.dbRecord.files;
|
||||
|
@ -22,9 +22,9 @@
|
||||
(click)="dismiss()"
|
||||
><i class="fa fa-times"></i></button>
|
||||
</div>
|
||||
<ion-buttons *ngIf="!readonly" style="flex-wrap: wrap;">
|
||||
<ion-button [disabled]="isLoading" (click)="onActionClick('folder-add')"><i class="fa fa-folder-plus"></i> Folder</ion-button>
|
||||
<ion-button [disabled]="isLoading" (click)="onUploadFilesClick($event)"><i class="fa fa-upload"></i> Upload Files</ion-button>
|
||||
<ion-buttons style="flex-wrap: wrap;">
|
||||
<ion-button *ngIf="!readonly" [disabled]="isLoading" (click)="onActionClick('folder-add')"><i class="fa fa-folder-plus"></i> Folder</ion-button>
|
||||
<ion-button *ngIf="!readonly" [disabled]="isLoading" (click)="onUploadFilesClick($event)"><i class="fa fa-upload"></i> Upload Files</ion-button>
|
||||
<ion-button [disabled]="isLoading" (click)="openFileBox()" *ngIf="!fullPage"><i class="fa fa-external-link-alt"></i> Open</ion-button>
|
||||
|
||||
<div class="buffer"></div>
|
||||
@ -38,7 +38,7 @@
|
||||
></ion-input>
|
||||
</div>
|
||||
|
||||
<input type="file" name="fileInput" #fileInput multiple style="display: none;" (change)="onUploadFilesChange($event)">
|
||||
<input *ngIf="!readonly" type="file" name="fileInput" #fileInput multiple style="display: none;" (change)="onUploadFilesChange($event)">
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
<div class="content-wrapper" (contextmenu)="onSurfaceContextMenu($event)">
|
||||
|
@ -1,6 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
min-height: 600px;
|
||||
min-height: 200px;
|
||||
flex-direction: row;
|
||||
|
||||
.editor-container, .display {
|
||||
|
@ -22,9 +22,13 @@
|
||||
<ion-button fill="outline" color="medium" (click)="getShareLink('update')">
|
||||
<ion-icon name="link" color="dark"></ion-icon> Edit
|
||||
</ion-button>
|
||||
<ion-button fill="outline" color="medium" (click)="getShareLink('manage')">
|
||||
<ion-button fill="outline" color="medium" (click)="getShareLink('manage')" [disabled]="publicLink">
|
||||
<ion-icon name="link" color="dark"></ion-icon> Manage
|
||||
</ion-button>
|
||||
<ion-item title="If checked, the link will never expire, and can be accessed by anyone without logging in.">
|
||||
<ion-checkbox [(ngModel)]="publicLink"></ion-checkbox>
|
||||
<ion-label> Public link?</ion-label>
|
||||
</ion-item>
|
||||
</ion-buttons>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
@ -49,7 +53,7 @@
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'update')">
|
||||
<ion-icon name="create" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'manage')">
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'manage')" *ngIf="!group.public">
|
||||
<ion-icon name="build" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.view[i])">
|
||||
@ -66,7 +70,7 @@
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'view')">
|
||||
<ion-icon name="eye" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'manage')">
|
||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'manage')" *ngIf="!group.public">
|
||||
<ion-icon name="build" color="medium"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.update[i])">
|
||||
|
@ -2,6 +2,7 @@ import {Component, Input, OnInit} from '@angular/core';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {ApiService} from '../../../service/api.service';
|
||||
import {Observable} from 'rxjs';
|
||||
import {environment} from '../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-selector',
|
||||
@ -11,11 +12,12 @@ import {Observable} from 'rxjs';
|
||||
export class SelectorComponent implements OnInit {
|
||||
@Input() node: any;
|
||||
public sharingInfo: {
|
||||
view: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||
update: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||
manage: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
||||
view: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
|
||||
update: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
|
||||
manage: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
|
||||
} = {view: [], update: [], manage: []};
|
||||
public generatedLink = '';
|
||||
public publicLink = false;
|
||||
|
||||
constructor(
|
||||
protected modals: ModalController,
|
||||
@ -54,7 +56,7 @@ export class SelectorComponent implements OnInit {
|
||||
}
|
||||
|
||||
setShareLevel(group, level) {
|
||||
this.api.post(`/share/page/${this.node.id}/share`, { user_id: group.id, level }).subscribe(result => {
|
||||
this.api.post(`/share/page/${this.node.id}/share?public=${!!group.public}`, { user_id: group.id, level }).subscribe(result => {
|
||||
this.loadShareInfo().subscribe(data => {
|
||||
this.sharingInfo = data;
|
||||
});
|
||||
@ -62,7 +64,7 @@ export class SelectorComponent implements OnInit {
|
||||
}
|
||||
|
||||
unsharePage(group) {
|
||||
this.api.post(`/share/page/${this.node.id}/revoke`, { user_id: group.id }).subscribe(result => {
|
||||
this.api.post(`/share/page/${this.node.id}/revoke?public=${!!group.public}`, { user_id: group.id }).subscribe(result => {
|
||||
this.loadShareInfo().subscribe(data => {
|
||||
this.sharingInfo = data;
|
||||
});
|
||||
@ -70,8 +72,13 @@ export class SelectorComponent implements OnInit {
|
||||
}
|
||||
|
||||
getShareLink(level) {
|
||||
this.api.get(`/share/page/${this.node.id}/link/${level}`).subscribe(result => {
|
||||
this.api.get(`/share/page/${this.node.id}/link/${level}?public=${this.publicLink}`).subscribe(result => {
|
||||
if ( this.publicLink ) {
|
||||
this.generatedLink = `${window.location.origin}${environment.appBase}editor;id=${this.node.id}`;
|
||||
this.ngOnInit();
|
||||
} else {
|
||||
this.generatedLink = result.data.link;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../service/api.service';
|
||||
import {isDebug, debug} from '../utility';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
@ -10,6 +11,7 @@ import {isDebug, debug} from '../utility';
|
||||
export class HomePage implements OnInit {
|
||||
constructor(
|
||||
public readonly api: ApiService,
|
||||
public readonly router: Router,
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@ -17,10 +19,10 @@ export class HomePage implements OnInit {
|
||||
if ( isDebug() ) {
|
||||
debug('Forcing authentication...');
|
||||
setTimeout(() => {
|
||||
this.api.forceRestart();
|
||||
this.router.navigate(['/login']);
|
||||
}, 2000);
|
||||
} else {
|
||||
this.api.forceRestart();
|
||||
this.router.navigate(['/login']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1463,6 +1463,36 @@ export class ApiService {
|
||||
});
|
||||
}
|
||||
|
||||
public checkPermission(permission: string): Promise<boolean> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.post('/share/check', { permission }).subscribe({
|
||||
next: result => {
|
||||
return res(result.data.check);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public checkPagePermission(PageId: string, level: string = 'view'): Promise<boolean> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
return rej(new ResourceNotAvailableOfflineError());
|
||||
}
|
||||
|
||||
this.post(`/share/check-page/${PageId}/${level}`, {}).subscribe({
|
||||
next: result => {
|
||||
return res(result.data.check);
|
||||
},
|
||||
error: rej,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public attemptLogin(uid: string, password: string): Promise<{ success: boolean, message?: string }> {
|
||||
return new Promise(async (res, rej) => {
|
||||
if ( this.isOffline ) {
|
||||
|
43
src/app/service/guard/Editor.guard.ts
Normal file
43
src/app/service/guard/Editor.guard.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
|
||||
import {ApiService} from '../api.service';
|
||||
import {NavigationService} from '../navigation.service';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class EditorGuard implements CanActivate {
|
||||
constructor(
|
||||
protected readonly api: ApiService,
|
||||
protected readonly router: Router,
|
||||
protected readonly nav: NavigationService,
|
||||
) { }
|
||||
|
||||
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
|
||||
const checkCanActivate = async () => {
|
||||
const PageId = route.paramMap.get('id');
|
||||
|
||||
const canView = await this.api.checkPagePermission(String(PageId));
|
||||
|
||||
if ( !canView ) {
|
||||
await this.router.navigate(['/login']);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return new Promise(async res => {
|
||||
if ( !this.nav.initialized$.getValue() ) {
|
||||
const sub = this.nav.initialized$.subscribe(async initialized => {
|
||||
if ( initialized ) {
|
||||
sub.unsubscribe();
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res(await checkCanActivate());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -6,5 +6,6 @@ export const environment = {
|
||||
versionUrl: '/i/version.html?ngsw-bypass',
|
||||
logoUrl: '/i/assets/icon/logo_lines.svg',
|
||||
starshipUrl: '/auth/starship_oauth/login',
|
||||
appBase: '/i/',
|
||||
outputDebug: false,
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ export const environment = {
|
||||
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
||||
logoUrl: '/assets/icon/logo_lines.svg',
|
||||
starshipUrl: '/link_api/auth/starship_oauth/login',
|
||||
appBase: '/',
|
||||
outputDebug: true,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user