#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 {LoginPage} from './pages/login/login.page';
|
||||||
import {AuthService} from './service/auth.service';
|
import {AuthService} from './service/auth.service';
|
||||||
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
|
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
|
||||||
|
import {EditorGuard} from './service/guard/Editor.guard';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -17,7 +18,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'editor',
|
path: 'editor',
|
||||||
canActivate: [AuthService],
|
canActivate: [EditorGuard],
|
||||||
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,9 @@
|
|||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
</ion-col>
|
</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-row>
|
||||||
</ion-grid>
|
</ion-grid>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,11 +2,11 @@ div.files-wrapper {
|
|||||||
border: 2px solid #8c8c8c;
|
border: 2px solid #8c8c8c;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
&.not-available {
|
.not-available {
|
||||||
height: 150px;
|
height: 150px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
color: #494949;
|
color: #494949;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ export class FilesComponent extends EditorNodeContract implements OnInit {
|
|||||||
this.node.Value = {};
|
this.node.Value = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('files load', this)
|
||||||
|
|
||||||
if ( !this.node.Value.Value && !this.readonly ) {
|
if ( !this.node.Value.Value && !this.readonly ) {
|
||||||
this.dbRecord = await this.api.createFileGroup(this.page.UUID, this.node.UUID);
|
this.dbRecord = await this.api.createFileGroup(this.page.UUID, this.node.UUID);
|
||||||
this.fileRecords = this.dbRecord.files;
|
this.fileRecords = this.dbRecord.files;
|
||||||
|
@ -22,9 +22,9 @@
|
|||||||
(click)="dismiss()"
|
(click)="dismiss()"
|
||||||
><i class="fa fa-times"></i></button>
|
><i class="fa fa-times"></i></button>
|
||||||
</div>
|
</div>
|
||||||
<ion-buttons *ngIf="!readonly" style="flex-wrap: wrap;">
|
<ion-buttons style="flex-wrap: wrap;">
|
||||||
<ion-button [disabled]="isLoading" (click)="onActionClick('folder-add')"><i class="fa fa-folder-plus"></i> Folder</ion-button>
|
<ion-button *ngIf="!readonly" [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-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>
|
<ion-button [disabled]="isLoading" (click)="openFileBox()" *ngIf="!fullPage"><i class="fa fa-external-link-alt"></i> Open</ion-button>
|
||||||
|
|
||||||
<div class="buffer"></div>
|
<div class="buffer"></div>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
></ion-input>
|
></ion-input>
|
||||||
</div>
|
</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-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
<div class="content-wrapper" (contextmenu)="onSurfaceContextMenu($event)">
|
<div class="content-wrapper" (contextmenu)="onSurfaceContextMenu($event)">
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 600px;
|
min-height: 200px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
.editor-container, .display {
|
.editor-container, .display {
|
||||||
|
@ -22,9 +22,13 @@
|
|||||||
<ion-button fill="outline" color="medium" (click)="getShareLink('update')">
|
<ion-button fill="outline" color="medium" (click)="getShareLink('update')">
|
||||||
<ion-icon name="link" color="dark"></ion-icon> Edit
|
<ion-icon name="link" color="dark"></ion-icon> Edit
|
||||||
</ion-button>
|
</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-icon name="link" color="dark"></ion-icon> Manage
|
||||||
</ion-button>
|
</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-buttons>
|
||||||
</ion-col>
|
</ion-col>
|
||||||
</ion-row>
|
</ion-row>
|
||||||
@ -49,7 +53,7 @@
|
|||||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'update')">
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'update')">
|
||||||
<ion-icon name="create" color="medium"></ion-icon>
|
<ion-icon name="create" color="medium"></ion-icon>
|
||||||
</ion-button>
|
</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-icon name="build" color="medium"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.view[i])">
|
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.view[i])">
|
||||||
@ -66,7 +70,7 @@
|
|||||||
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'view')">
|
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'view')">
|
||||||
<ion-icon name="eye" color="medium"></ion-icon>
|
<ion-icon name="eye" color="medium"></ion-icon>
|
||||||
</ion-button>
|
</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-icon name="build" color="medium"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.update[i])">
|
<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 {ModalController} from '@ionic/angular';
|
||||||
import {ApiService} from '../../../service/api.service';
|
import {ApiService} from '../../../service/api.service';
|
||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
|
import {environment} from '../../../../environments/environment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-selector',
|
selector: 'app-selector',
|
||||||
@ -11,11 +12,12 @@ import {Observable} from 'rxjs';
|
|||||||
export class SelectorComponent implements OnInit {
|
export class SelectorComponent implements OnInit {
|
||||||
@Input() node: any;
|
@Input() node: any;
|
||||||
public sharingInfo: {
|
public sharingInfo: {
|
||||||
view: 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, id: string, level: 'view'|'update'|'manage'}>,
|
update: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
manage: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
|
manage: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
|
||||||
} = {view: [], update: [], manage: []};
|
} = {view: [], update: [], manage: []};
|
||||||
public generatedLink = '';
|
public generatedLink = '';
|
||||||
|
public publicLink = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected modals: ModalController,
|
protected modals: ModalController,
|
||||||
@ -54,7 +56,7 @@ export class SelectorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setShareLevel(group, level) {
|
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.loadShareInfo().subscribe(data => {
|
||||||
this.sharingInfo = data;
|
this.sharingInfo = data;
|
||||||
});
|
});
|
||||||
@ -62,7 +64,7 @@ export class SelectorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsharePage(group) {
|
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.loadShareInfo().subscribe(data => {
|
||||||
this.sharingInfo = data;
|
this.sharingInfo = data;
|
||||||
});
|
});
|
||||||
@ -70,8 +72,13 @@ export class SelectorComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getShareLink(level) {
|
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;
|
this.generatedLink = result.data.link;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {ApiService} from '../service/api.service';
|
import {ApiService} from '../service/api.service';
|
||||||
import {isDebug, debug} from '../utility';
|
import {isDebug, debug} from '../utility';
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@ -10,6 +11,7 @@ import {isDebug, debug} from '../utility';
|
|||||||
export class HomePage implements OnInit {
|
export class HomePage implements OnInit {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly api: ApiService,
|
public readonly api: ApiService,
|
||||||
|
public readonly router: Router,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -17,10 +19,10 @@ export class HomePage implements OnInit {
|
|||||||
if ( isDebug() ) {
|
if ( isDebug() ) {
|
||||||
debug('Forcing authentication...');
|
debug('Forcing authentication...');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.api.forceRestart();
|
this.router.navigate(['/login']);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} 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 }> {
|
public attemptLogin(uid: string, password: string): Promise<{ success: boolean, message?: string }> {
|
||||||
return new Promise(async (res, rej) => {
|
return new Promise(async (res, rej) => {
|
||||||
if ( this.isOffline ) {
|
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',
|
versionUrl: '/i/version.html?ngsw-bypass',
|
||||||
logoUrl: '/i/assets/icon/logo_lines.svg',
|
logoUrl: '/i/assets/icon/logo_lines.svg',
|
||||||
starshipUrl: '/auth/starship_oauth/login',
|
starshipUrl: '/auth/starship_oauth/login',
|
||||||
|
appBase: '/i/',
|
||||||
outputDebug: false,
|
outputDebug: false,
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ export const environment = {
|
|||||||
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
|
||||||
logoUrl: '/assets/icon/logo_lines.svg',
|
logoUrl: '/assets/icon/logo_lines.svg',
|
||||||
starshipUrl: '/link_api/auth/starship_oauth/login',
|
starshipUrl: '/link_api/auth/starship_oauth/login',
|
||||||
|
appBase: '/',
|
||||||
outputDebug: true,
|
outputDebug: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user