diff --git a/src/app/app.component.html b/src/app/app.component.html index 250f09f..5a759d2 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -10,7 +10,7 @@ - + @@ -23,6 +23,9 @@ + + + diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts deleted file mode 100644 index dbbfed5..0000000 --- a/src/app/app.component.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { TestBed, async } from '@angular/core/testing'; - -import { Platform } from '@ionic/angular'; -import { SplashScreen } from '@ionic-native/splash-screen/ngx'; -import { StatusBar } from '@ionic-native/status-bar/ngx'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { AppComponent } from './app.component'; - -describe('AppComponent', () => { - - let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy; - - beforeEach(async(() => { - statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']); - splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']); - platformReadySpy = Promise.resolve(); - platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy }); - - TestBed.configureTestingModule({ - declarations: [AppComponent], - schemas: [CUSTOM_ELEMENTS_SCHEMA], - providers: [ - { provide: StatusBar, useValue: statusBarSpy }, - { provide: SplashScreen, useValue: splashScreenSpy }, - { provide: Platform, useValue: platformSpy }, - ], - imports: [ RouterTestingModule.withRoutes([])], - }).compileComponents(); - })); - - it('should create the app', async () => { - const fixture = TestBed.createComponent(AppComponent); - const app = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - }); - - it('should initialize the app', async () => { - TestBed.createComponent(AppComponent); - expect(platformSpy.ready).toHaveBeenCalled(); - await platformReadySpy; - expect(statusBarSpy.styleDefault).toHaveBeenCalled(); - expect(splashScreenSpy.hide).toHaveBeenCalled(); - }); - - it('should have menu labels', async () => { - const fixture = await TestBed.createComponent(AppComponent); - await fixture.detectChanges(); - const app = fixture.nativeElement; - const menuItems = app.querySelectorAll('ion-label'); - expect(menuItems.length).toEqual(2); - expect(menuItems[0].textContent).toContain('Home'); - expect(menuItems[1].textContent).toContain('List'); - }); - - it('should have urls', async () => { - const fixture = await TestBed.createComponent(AppComponent); - await fixture.detectChanges(); - const app = fixture.nativeElement; - const menuItems = app.querySelectorAll('ion-item'); - expect(menuItems.length).toEqual(2); - expect(menuItems[0].getAttribute('ng-reflect-router-link')).toEqual('/home'); - expect(menuItems[1].getAttribute('ng-reflect-router-link')).toEqual('/list'); - }); - -}); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7990df4..4492c90 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,6 +1,6 @@ import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core'; -import {AlertController, Platform, PopoverController} from '@ionic/angular'; +import {AlertController, ModalController, Platform, PopoverController} from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; import { ApiService } from './service/api.service'; @@ -8,6 +8,8 @@ import { Router } from '@angular/router'; import {TREE_ACTIONS, TreeComponent} from 'angular-tree-component'; import { Observable } from 'rxjs'; import {OptionPickerComponent} from './components/option-picker/option-picker.component'; +import {OptionMenuComponent} from './components/option-menu/option-menu.component'; +import {SelectorComponent} from './components/sharing/selector/selector.component'; @Component({ selector: 'app-root', @@ -18,6 +20,7 @@ export class AppComponent implements OnInit { @ViewChild('menuTree', {static: false}) menuTree: TreeComponent; public addChildTarget: any = false; public deleteTarget: any = false; + public menuTarget: any = false; public refreshingMenu = false; public lastClickEvent: Array = []; public nodes = []; @@ -37,13 +40,20 @@ export class AppComponent implements OnInit { click: (tree, node, $event) => { TREE_ACTIONS.FOCUS(tree, node, $event); TREE_ACTIONS.EXPAND(tree, node, $event); - if ( !node.data.noChildren ) { + + this.addChildTarget = false; + this.deleteTarget = false; + this.menuTarget = false; + + if ( !node.data.noChildren && (!node.data.level || node.data.level === 'manage') ) { this.addChildTarget = node; } - if ( !node.data.noDelete ) { + if ( !node.data.noDelete && (!node.data.level || node.data.level === 'manage') ) { this.deleteTarget = node; } + + this.menuTarget = node; this.lastClickEvent = [tree, node, $event]; } } @@ -60,6 +70,7 @@ export class AppComponent implements OnInit { protected router: Router, protected alerts: AlertController, protected popover: PopoverController, + protected modal: ModalController, ) { this.initializeApp(); } @@ -86,6 +97,33 @@ export class AppComponent implements OnInit { this.menuTree.treeModel.filterNodes(value, true); } + async onNodeMenuClick($event) { + const popover = await this.popover.create({ + component: OptionMenuComponent, + componentProps: { + menuItems: [ + ...(this.menuTarget.data.level === 'view' ? [] : [{name: 'Share Sub-Tree', icon: 'person-add', value: 'share'}]), + ], + }, + event: $event, + }); + + popover.onDidDismiss().then((result) => { + if ( result.data === 'share' ) { + this.modal.create({ + component: SelectorComponent, + componentProps: { + node: this.menuTarget.data, + } + }).then(modal => { + modal.present(); + }); + } + }); + + await popover.present(); + } + async onTopLevelCreate() { const alert = await this.alerts.create({ header: 'Create Page', @@ -185,6 +223,7 @@ export class AppComponent implements OnInit { this.reloadMenuItems().subscribe(); this.deleteTarget = false; this.addChildTarget = false; + this.menuTarget = false; }); } } diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index b0ddaf1..f77d0a2 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -12,6 +12,8 @@ import {MonacoEditorModule} from 'ngx-monaco-editor'; import {FilesComponent} from './editor/files/files.component'; import {OptionPickerComponent} from './option-picker/option-picker.component'; import {HostOptionsComponent} from './editor/host-options/host-options.component'; +import {OptionMenuComponent} from './option-menu/option-menu.component'; +import {SelectorComponent} from './sharing/selector/selector.component'; @NgModule({ declarations: [ @@ -22,7 +24,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component CodeComponent, FilesComponent, OptionPickerComponent, - HostOptionsComponent + HostOptionsComponent, + OptionMenuComponent, + SelectorComponent, ], imports: [ CommonModule, @@ -39,7 +43,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component CodeComponent, FilesComponent, OptionPickerComponent, - HostOptionsComponent + HostOptionsComponent, + OptionMenuComponent, + SelectorComponent, ], exports: [ HostComponent, @@ -49,7 +55,9 @@ import {HostOptionsComponent} from './editor/host-options/host-options.component CodeComponent, FilesComponent, OptionPickerComponent, - HostOptionsComponent + HostOptionsComponent, + OptionMenuComponent, + SelectorComponent, ] }) export class ComponentsModule {} diff --git a/src/app/components/editor/code/code.component.html b/src/app/components/editor/code/code.component.html index f9d5a43..b6bea5f 100644 --- a/src/app/components/editor/code/code.component.html +++ b/src/app/components/editor/code/code.component.html @@ -2,7 +2,7 @@ Language - + {{lang}} @@ -11,11 +11,11 @@ - +  Drop Editor  Save Changes diff --git a/src/app/components/editor/code/code.component.ts b/src/app/components/editor/code/code.component.ts index cda9b7b..beb4891 100644 --- a/src/app/components/editor/code/code.component.ts +++ b/src/app/components/editor/code/code.component.ts @@ -11,6 +11,7 @@ import {AlertController, LoadingController} from '@ionic/angular'; styleUrls: ['./code.component.scss'], }) export class CodeComponent implements OnInit { + @Input() readonly = false; @Input() hostRecord: HostRecord; @Output() hostRecordChange = new EventEmitter(); @Output() requestParentSave = new EventEmitter(); @@ -90,6 +91,7 @@ export class CodeComponent implements OnInit { public editorOptions = { language: 'javascript', uri: v4(), + readOnly: this.readonly, }; public editorValue = ''; @@ -104,6 +106,7 @@ export class CodeComponent implements OnInit { loader.present().then(() => { this.getInitObservable().subscribe(() => { this.editorOptions.language = this.dbRecord.language; + this.editorOptions.readOnly = this.readonly; this.onSelectChange(false); loader.dismiss(); }); @@ -118,7 +121,7 @@ export class CodeComponent implements OnInit { this.hostRecord.Value = {}; } - if ( !this.hostRecord.Value.Value ) { + if ( !this.hostRecord.Value.Value && !this.readonly ) { this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => { this.dbRecord = res.data; this.hostRecord.Value.Mode = 'code'; @@ -146,6 +149,10 @@ export class CodeComponent implements OnInit { } onSaveClick() { + if ( this.readonly ) { + return; + } + this.dbRecord.code = this.editorValue; this.dbRecord.language = this.editorOptions.language; this.api.post(`/code/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}`, this.dbRecord) @@ -158,6 +165,10 @@ export class CodeComponent implements OnInit { } async onDropClick() { + if ( this.readonly ) { + return; + } + const alert = await this.alerts.create({ header: 'Are you sure?', message: `You are about to delete this code. This action cannot be undone.`, diff --git a/src/app/components/editor/database/database.component.html b/src/app/components/editor/database/database.component.html index b8864f6..b91edfe 100644 --- a/src/app/components/editor/database/database.component.html +++ b/src/app/components/editor/database/database.component.html @@ -1,5 +1,5 @@
- +  Manage Columns  Insert Row @@ -14,6 +14,7 @@ class="ag-theme-balham" [rowData]="rowData" [columnDefs]="columnDefs" + suppressMovableColumns="true" (rowClicked)="onRowClicked($event)" (cellValueChanged)="onCellValueChanged()" #agGridElement diff --git a/src/app/components/editor/database/database.component.ts b/src/app/components/editor/database/database.component.ts index 6b84653..a90a795 100644 --- a/src/app/components/editor/database/database.component.ts +++ b/src/app/components/editor/database/database.component.ts @@ -13,6 +13,7 @@ import {AgGridAngular} from 'ag-grid-angular'; }) export class DatabaseComponent implements OnInit { @Input() hostRecord: HostRecord; + @Input() readonly = false; @Output() hostRecordChange = new EventEmitter(); @Output() requestParentSave = new EventEmitter(); @Output() requestParentDelete = new EventEmitter(); @@ -55,6 +56,10 @@ export class DatabaseComponent implements OnInit { } async onManageColumns() { + if ( this.readonly ) { + return; + } + const modal = await this.modals.create({ component: ColumnsComponent, componentProps: {columnSets: this.columnDefs}, @@ -91,12 +96,20 @@ export class DatabaseComponent implements OnInit { } onInsertRow() { + if ( this.readonly ) { + return; + } + this.rowData.push({}); this.agGridElement.api.setRowData(this.rowData); this.dirty = true; } async onRemoveRow() { + if ( this.readonly ) { + return; + } + const alert = await this.alerts.create({ header: 'Are you sure?', message: `You are about to delete row ${this.lastClickRow + 1}. This cannot be undone.`, @@ -125,6 +138,10 @@ export class DatabaseComponent implements OnInit { } async onDropDatabase() { + if ( this.readonly ) { + return; + } + const alert = await this.alerts.create({ header: 'Are you sure?', message: `You are about to delete this database and all its entries. This action cannot be undone.`, @@ -162,6 +179,10 @@ export class DatabaseComponent implements OnInit { } onSyncRecords() { + if ( this.readonly ) { + return; + } + this.loader.create({message: 'Syncing the database...'}).then(loader => { loader.present().then(() => { this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/set/${this.hostRecord.Value.Value}/data`, this.rowData) @@ -179,7 +200,7 @@ export class DatabaseComponent implements OnInit { return new Observable(sub => { this.api.get(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/get/${this.hostRecord.Value.Value}/columns`).subscribe(res => { this.columnDefs = res.data.map(x => { - x.editable = true; + x.editable = !this.readonly; if ( x.Type === 'text' ) { x.editor = 'agTextCellEditor'; } else if ( x.Type === 'number' ) { @@ -208,7 +229,7 @@ export class DatabaseComponent implements OnInit { if ( !this.hostRecord.Value ) { this.hostRecord.Value = {}; } - if ( !this.hostRecord.Value.Value ) { + if ( !this.hostRecord.Value.Value && !this.readonly ) { this.api.post(`/db/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => { this.dbRecord = res.data; this.hostRecord.Value.Mode = 'database'; diff --git a/src/app/components/editor/files/files.component.html b/src/app/components/editor/files/files.component.html index df587fd..867ecb7 100644 --- a/src/app/components/editor/files/files.component.html +++ b/src/app/components/editor/files/files.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/src/app/components/editor/files/files.component.ts b/src/app/components/editor/files/files.component.ts index 6173fe5..304d401 100644 --- a/src/app/components/editor/files/files.component.ts +++ b/src/app/components/editor/files/files.component.ts @@ -11,6 +11,7 @@ import { APP_BASE_HREF } from '@angular/common'; styleUrls: ['./files.component.scss'], }) export class FilesComponent implements OnInit { + @Input() readonly = false; @Input() hostRecord: HostRecord; @Output() hostRecordChange = new EventEmitter(); @Output() requestParentSave = new EventEmitter(); @@ -38,6 +39,10 @@ export class FilesComponent implements OnInit { } onSubmitClick() { + if ( this.readonly ) { + return; + } + this.uploadForm.nativeElement.submit(); } @@ -51,6 +56,10 @@ export class FilesComponent implements OnInit { } async onDestroyClick() { + if ( this.readonly ) { + return; + } + const alert = await this.alerts.create({ header: 'Are you sure?', message: 'You are about to delete these files. This action cannot be undone.', @@ -81,7 +90,7 @@ export class FilesComponent implements OnInit { this.hostRecord.Value = {}; } - if ( !this.hostRecord.Value.Value ) { + if ( !this.hostRecord.Value.Value && !this.readonly ) { this.api.post(`/files/${this.hostRecord.PageId}/${this.hostRecord.UUID}/create`).subscribe(res => { this.dbRecord = res.data; this.fileRecords = res.data.files; diff --git a/src/app/components/editor/host/host.component.html b/src/app/components/editor/host/host.component.html index fd27e72..0ae8f5e 100644 --- a/src/app/components/editor/host/host.component.html +++ b/src/app/components/editor/host/host.component.html @@ -1,6 +1,12 @@
+
+
    +
  • +

@@ -31,6 +61,7 @@ class="db-wrapper" >
(); @Output() newHostRequested = new EventEmitter(); diff --git a/src/app/components/option-menu/option-menu.component.html b/src/app/components/option-menu/option-menu.component.html new file mode 100644 index 0000000..506db30 --- /dev/null +++ b/src/app/components/option-menu/option-menu.component.html @@ -0,0 +1,6 @@ + + + + {{ menuItems[i].name }} + + diff --git a/src/app/components/option-menu/option-menu.component.scss b/src/app/components/option-menu/option-menu.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/option-menu/option-menu.component.ts b/src/app/components/option-menu/option-menu.component.ts new file mode 100644 index 0000000..2a30469 --- /dev/null +++ b/src/app/components/option-menu/option-menu.component.ts @@ -0,0 +1,22 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {PopoverController} from '@ionic/angular'; + +@Component({ + selector: 'common-option-menu', + templateUrl: './option-menu.component.html', + styleUrls: ['./option-menu.component.scss'], +}) +export class OptionMenuComponent implements OnInit { + @Input() menuItems: Array<{name: string, icon: string, value: string, type?: string}> = []; + + constructor( + protected popover: PopoverController, + ) { } + + ngOnInit() {} + + async onSelect(value) { + await this.popover.dismiss(value); + } + +} diff --git a/src/app/components/sharing/selector/selector.component.html b/src/app/components/sharing/selector/selector.component.html new file mode 100644 index 0000000..5bb3bfc --- /dev/null +++ b/src/app/components/sharing/selector/selector.component.html @@ -0,0 +1,98 @@ + + + {{ title }} + + + + + + + + + + + + +

Create Sharing Link

+

You can share this sub-tree of your notes by generating a sharing link. You can choose to allow the recipient to have view, edit, or manage access to this sub-tree. Sharing links are one time use only and require a Noded account.

+ + +   View + + +   Edit + + +   Manage + + +
+
+ + + + Share this link to give access: + + + + + + +

Shared With

+ + + + + {{ sharingInfo.view[i].username }}   + + + + + + + + + + + + + + + + + {{ sharingInfo.update[i].username }}     + + + + + + + + + + + + + + + + + {{ sharingInfo.manage[i].username }}       + + + + + + + + + + + + + + +
+
+
+
diff --git a/src/app/components/sharing/selector/selector.component.scss b/src/app/components/sharing/selector/selector.component.scss new file mode 100644 index 0000000..e669173 --- /dev/null +++ b/src/app/components/sharing/selector/selector.component.scss @@ -0,0 +1,8 @@ +.share-token { + background-color: #dedede; + color: #555; + padding: 5px; + font-size: 10pt; + font-style: italic; + font-family: monospace; +} diff --git a/src/app/components/sharing/selector/selector.component.ts b/src/app/components/sharing/selector/selector.component.ts new file mode 100644 index 0000000..4cda107 --- /dev/null +++ b/src/app/components/sharing/selector/selector.component.ts @@ -0,0 +1,77 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {ModalController} from '@ionic/angular'; +import {ApiService} from '../../../service/api.service'; +import {Observable} from 'rxjs'; + +@Component({ + selector: 'app-selector', + templateUrl: './selector.component.html', + styleUrls: ['./selector.component.scss'], +}) +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: [], update: [], manage: []}; + public generatedLink = ''; + + constructor( + protected modals: ModalController, + protected api: ApiService, + ) {} + + public get title() { + return this.node ? `Share ${this.node.name}` : 'Manage Sharing'; + } + + public get isShared() { + return (this.sharingInfo.view.length > 0) || (this.sharingInfo.update.length > 0) || (this.sharingInfo.manage.length > 0); + } + + ngOnInit() { + this.loadShareInfo().subscribe(data => { + this.sharingInfo = data; + }); + } + + dismissModal(success: boolean) { + this.modals.dismiss(); + } + + loadShareInfo(): Observable<{ + 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'}>, + }> { + return new Observable(sub => { + this.api.get(`/share/page/${this.node.id}/info`).subscribe(result => { + sub.next(result.data); + sub.complete(); + }); + }); + } + + setShareLevel(group, level) { + this.api.post(`/share/page/${this.node.id}/share`, { user_id: group.id, level }).subscribe(result => { + this.loadShareInfo().subscribe(data => { + this.sharingInfo = data; + }); + }); + } + + unsharePage(group) { + this.api.post(`/share/page/${this.node.id}/revoke`, { user_id: group.id }).subscribe(result => { + this.loadShareInfo().subscribe(data => { + this.sharingInfo = data; + }); + }); + } + + getShareLink(level) { + this.api.get(`/share/page/${this.node.id}/link/${level}`).subscribe(result => { + this.generatedLink = result.data.link; + }); + } +} diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html index 91a3563..69b5222 100644 --- a/src/app/pages/editor/editor.page.html +++ b/src/app/pages/editor/editor.page.html @@ -23,7 +23,7 @@ (mouseenter)="makeVisible(i)" (mouseleave)="makeInvisible(i)" > - +
-
+
Add Node Save
diff --git a/src/app/pages/editor/editor.page.ts b/src/app/pages/editor/editor.page.ts index 28ffbf9..354c6a6 100644 --- a/src/app/pages/editor/editor.page.ts +++ b/src/app/pages/editor/editor.page.ts @@ -55,7 +55,9 @@ export class EditorPage implements OnInit { this.pageRecord = pageRecord; this.pages.get_nodes(pageRecord).subscribe((hosts: Array) => { this.hostRecords = hosts; - this.onSaveClick(); + if ( !pageRecord.isViewOnly() ) { + this.onSaveClick(); + } }); }); } else { @@ -64,10 +66,16 @@ export class EditorPage implements OnInit { } onHostRecordChange($event, i) { - this.hostRecords[i] = $event; + if ( !this.pageRecord.isViewOnly() ) { + this.hostRecords[i] = $event; + } } async onAddClick($event) { + if ( this.pageRecord.isViewOnly() ) { + return; + } + const popover = await this.popover.create({ component: NodePickerComponent, event: $event, @@ -119,6 +127,10 @@ export class EditorPage implements OnInit { } onNewHostRequested($event) { + if ( this.pageRecord.isViewOnly() ) { + return; + } + const insertAfter = this.getIndexFromRecord($event.record); const record = new HostRecord(''); const newHosts = [] @@ -137,6 +149,10 @@ export class EditorPage implements OnInit { } onDestroyHostRequested($event) { + if ( this.pageRecord.isViewOnly() ) { + return; + } + let removedIndex = 0; const newHostRecords = this.editorHosts.filter((host, i) => { if ( $event.record === host.record ) { @@ -158,8 +174,6 @@ export class EditorPage implements OnInit { focusIndex = removedIndex - 1; } - console.log({removedIndex, focusIndex, edHArr: this.editorHosts.toArray()}); - if ( focusIndex >= 0 ) { this.editorHosts.toArray()[focusIndex].takeFocus(false); } @@ -177,6 +191,10 @@ export class EditorPage implements OnInit { } onSaveClick() { + if ( this.pageRecord.isViewOnly() ) { + return; + } + this.loader.create({message: 'Saving changes...'}).then(loader => { loader.present().then(() => { this.pageRecord.Name = this.titleBar.el.innerText.trim(); @@ -196,6 +214,10 @@ export class EditorPage implements OnInit { } async onOptionsClick($event, i) { + if ( this.pageRecord.isViewOnly() ) { + return; + } + const popover = await this.popover.create({ component: HostOptionsComponent, event: $event, @@ -208,7 +230,6 @@ export class EditorPage implements OnInit { }); popover.onDidDismiss().then((result) => { - console.log({result}); if ( result.data === 'delete_node' ) { $event.record = this.hostRecords[i]; this.onDestroyHostRequested($event); diff --git a/src/app/structures/PageRecord.ts b/src/app/structures/PageRecord.ts index 8350912..f42bc06 100644 --- a/src/app/structures/PageRecord.ts +++ b/src/app/structures/PageRecord.ts @@ -11,8 +11,9 @@ export default class PageRecord { public CreatedUserId: string; public UpdateUserId: string; public ChildPageIds: Array; + public level: 'view'|'manage'|'update'|false; - constructor(data: any = {Name: 'Click to edit title...'}) { + constructor(data: any = {Name: 'Click to edit...'}) { [ 'UUID', 'Name', @@ -25,7 +26,8 @@ export default class PageRecord { 'UpdatedAt', 'CreatedUserId', 'UpdateUserId', - 'ChildPageIds' + 'ChildPageIds', + 'level' ].forEach(field => { if ( field in data ) { this[field] = data[field]; @@ -55,4 +57,8 @@ export default class PageRecord { }); return data; } + + isViewOnly() { + return this.level === 'view'; + } }