Merge branch '004-server-history-support' into develop
This commit is contained in:
@@ -45,7 +45,7 @@ import {
|
|||||||
PrivilegesAuthModal,
|
PrivilegesAuthModal,
|
||||||
PrivilegesManagementModal,
|
PrivilegesManagementModal,
|
||||||
RevisionPreviewModal,
|
RevisionPreviewModal,
|
||||||
SessionHistoryMenu,
|
HistoryMenu,
|
||||||
SyncResolutionMenu
|
SyncResolutionMenu
|
||||||
} from './directives/views';
|
} from './directives/views';
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ function startApplication(platform: Platform) {
|
|||||||
.directive('privilegesAuthModal', () => new PrivilegesAuthModal())
|
.directive('privilegesAuthModal', () => new PrivilegesAuthModal())
|
||||||
.directive('privilegesManagementModal', () => new PrivilegesManagementModal())
|
.directive('privilegesManagementModal', () => new PrivilegesManagementModal())
|
||||||
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
|
.directive('revisionPreviewModal', () => new RevisionPreviewModal())
|
||||||
.directive('sessionHistoryMenu', () => new SessionHistoryMenu())
|
.directive('historyMenu', () => new HistoryMenu())
|
||||||
.directive('syncResolutionMenu', () => new SyncResolutionMenu());
|
.directive('syncResolutionMenu', () => new SyncResolutionMenu());
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
|
|||||||
191
app/assets/javascripts/directives/views/historyMenu.ts
Normal file
191
app/assets/javascripts/directives/views/historyMenu.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { WebDirective } from '../../types';
|
||||||
|
import { WebApplication } from '@/ui_models/application';
|
||||||
|
import template from '%/directives/history-menu.pug';
|
||||||
|
import { SNItem, ItemHistoryEntry } from '@node_modules/snjs/dist/@types';
|
||||||
|
import { PureViewCtrl } from '@/views';
|
||||||
|
import { ItemSessionHistory } from 'snjs/dist/@types/services/history/session/item_session_history';
|
||||||
|
import { RemoteHistoryList, RemoteHistoryListEntry } from 'snjs/dist/@types/services/history/history_manager';
|
||||||
|
import { confirmDialog } from '@/services/alertService';
|
||||||
|
|
||||||
|
type HistoryState = {
|
||||||
|
fetchingRemoteHistory: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HistoryScope {
|
||||||
|
application: WebApplication
|
||||||
|
item: SNItem
|
||||||
|
}
|
||||||
|
|
||||||
|
class HistoryMenuCtrl extends PureViewCtrl<{}, HistoryState> implements HistoryScope {
|
||||||
|
|
||||||
|
diskEnabled = false
|
||||||
|
autoOptimize = false
|
||||||
|
application!: WebApplication
|
||||||
|
item!: SNItem
|
||||||
|
sessionHistory?: ItemSessionHistory
|
||||||
|
remoteHistory?: RemoteHistoryList
|
||||||
|
|
||||||
|
/* @ngInject */
|
||||||
|
constructor(
|
||||||
|
$timeout: ng.ITimeoutService
|
||||||
|
) {
|
||||||
|
super($timeout);
|
||||||
|
this.state = {
|
||||||
|
fetchingRemoteHistory: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$onInit() {
|
||||||
|
super.$onInit();
|
||||||
|
this.reloadSessionHistory();
|
||||||
|
this.fetchRemoteHistory();
|
||||||
|
this.diskEnabled = this.application.historyManager!.isDiskEnabled();
|
||||||
|
this.autoOptimize = this.application.historyManager!.isAutoOptimizeEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadSessionHistory() {
|
||||||
|
this.sessionHistory = this.application.historyManager!.sessionHistoryForItem(this.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isfetchingRemoteHistory() {
|
||||||
|
return this.state.fetchingRemoteHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
set fetchingRemoteHistory(value: boolean) {
|
||||||
|
this.setState({
|
||||||
|
fetchingRemoteHistory: value
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchRemoteHistory() {
|
||||||
|
this.fetchingRemoteHistory = true;
|
||||||
|
this.remoteHistory = await this.application.historyManager!.remoteHistoryForItem(this.item)
|
||||||
|
.finally(() => {
|
||||||
|
this.fetchingRemoteHistory = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async openSessionRevision(revision: ItemHistoryEntry) {
|
||||||
|
this.application.presentRevisionPreviewModal(
|
||||||
|
revision.payload.uuid,
|
||||||
|
revision.payload.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async openRemoteRevision(revision: RemoteHistoryListEntry) {
|
||||||
|
this.fetchingRemoteHistory = true;
|
||||||
|
const remoteRevision = await this.application.historyManager!.fetchRemoteRevision(this.item.uuid, revision);
|
||||||
|
this.fetchingRemoteHistory = false;
|
||||||
|
if (!remoteRevision) {
|
||||||
|
this.application.alertService!.alert("The remote revision could not be loaded. Please try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.application.presentRevisionPreviewModal(
|
||||||
|
remoteRevision.payload.uuid,
|
||||||
|
remoteRevision.payload.content
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
classForSessionRevision(revision: ItemHistoryEntry) {
|
||||||
|
const vector = revision.operationVector();
|
||||||
|
if (vector === 0) {
|
||||||
|
return 'default';
|
||||||
|
} else if (vector === 1) {
|
||||||
|
return 'success';
|
||||||
|
} else if (vector === -1) {
|
||||||
|
return 'danger';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearItemSessionHistory() {
|
||||||
|
confirmDialog({
|
||||||
|
text: "Are you sure you want to delete the local session history for this note?",
|
||||||
|
confirmButtonStyle: "danger"
|
||||||
|
}).then((confirmed) => {
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.application.historyManager!.clearHistoryForItem(this.item).then(() => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.reloadSessionHistory();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllSessionHistory() {
|
||||||
|
confirmDialog({
|
||||||
|
text: "Are you sure you want to delete the local session history for all notes?",
|
||||||
|
confirmButtonStyle: "danger"
|
||||||
|
}).then((confirmed) => {
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.application.historyManager!.clearAllHistory().then(() => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.reloadSessionHistory();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get sessionHistoryEntries() {
|
||||||
|
return this.sessionHistory?.entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
get remoteHistoryEntries() {
|
||||||
|
return this.remoteHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSessionHistoryDiskSaving() {
|
||||||
|
const run = () => {
|
||||||
|
this.application.historyManager!.toggleDiskSaving().then(() => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.diskEnabled = this.application.historyManager!.isDiskEnabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (!this.application.historyManager!.isDiskEnabled()) {
|
||||||
|
confirmDialog({
|
||||||
|
text: "Are you sure you want to save history to disk? This will decrease general " +
|
||||||
|
"performance, especially as you type. You are advised to disable this feature " +
|
||||||
|
"if you experience any lagging.",
|
||||||
|
confirmButtonStyle: "danger"
|
||||||
|
}).then((confirmed) => {
|
||||||
|
if (confirmed) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSessionHistoryAutoOptimize() {
|
||||||
|
this.application.historyManager!.toggleAutoOptimize().then(() => {
|
||||||
|
this.$timeout(() => {
|
||||||
|
this.autoOptimize = this.application.historyManager!.autoOptimize;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
previewRemoteHistoryTitle(revision: RemoteHistoryListEntry) {
|
||||||
|
const createdAt = revision.created_at!;
|
||||||
|
return new Date(createdAt).toLocaleString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HistoryMenu extends WebDirective {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.restrict = 'E';
|
||||||
|
this.template = template;
|
||||||
|
this.controller = HistoryMenuCtrl;
|
||||||
|
this.controllerAs = 'ctrl';
|
||||||
|
this.bindToController = true;
|
||||||
|
this.scope = {
|
||||||
|
item: '=',
|
||||||
|
application: '='
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,5 +11,5 @@ export { PermissionsModal } from './permissionsModal';
|
|||||||
export { PrivilegesAuthModal } from './privilegesAuthModal';
|
export { PrivilegesAuthModal } from './privilegesAuthModal';
|
||||||
export { PrivilegesManagementModal } from './privilegesManagementModal';
|
export { PrivilegesManagementModal } from './privilegesManagementModal';
|
||||||
export { RevisionPreviewModal } from './revisionPreviewModal';
|
export { RevisionPreviewModal } from './revisionPreviewModal';
|
||||||
export { SessionHistoryMenu } from './sessionHistoryMenu';
|
export { HistoryMenu } from './historyMenu';
|
||||||
export { SyncResolutionMenu } from './syncResolutionMenu';
|
export { SyncResolutionMenu } from './syncResolutionMenu';
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class RevisionPreviewModalCtrl implements RevisionPreviewScope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async restore(asCopy: boolean) {
|
restore(asCopy: boolean) {
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
if (asCopy) {
|
if (asCopy) {
|
||||||
const contentCopy = Object.assign({}, this.content);
|
const contentCopy = Object.assign({}, this.content);
|
||||||
@@ -110,12 +110,14 @@ class RevisionPreviewModalCtrl implements RevisionPreviewScope {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!asCopy) {
|
if (!asCopy) {
|
||||||
if (await confirmDialog({
|
confirmDialog({
|
||||||
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
|
text: "Are you sure you want to replace the current note's contents with what you see in this preview?",
|
||||||
confirmButtonStyle: 'danger',
|
confirmButtonStyle: "danger"
|
||||||
})) {
|
}).then((confirmed) => {
|
||||||
run();
|
if (confirmed) {
|
||||||
}
|
run();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
import { WebDirective } from './../../types';
|
|
||||||
import { WebApplication } from '@/ui_models/application';
|
|
||||||
import template from '%/directives/session-history-menu.pug';
|
|
||||||
import { SNItem, ItemHistoryEntry, ItemHistory } from '@node_modules/snjs/dist/@types';
|
|
||||||
import { confirmDialog } from '@/services/alertService';
|
|
||||||
|
|
||||||
interface SessionHistoryScope {
|
|
||||||
application: WebApplication
|
|
||||||
item: SNItem
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionHistoryMenuCtrl implements SessionHistoryScope {
|
|
||||||
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
diskEnabled = false
|
|
||||||
autoOptimize = false
|
|
||||||
application!: WebApplication
|
|
||||||
item!: SNItem
|
|
||||||
entries!: ItemHistoryEntry[]
|
|
||||||
history!: ItemHistory
|
|
||||||
|
|
||||||
/* @ngInject */
|
|
||||||
constructor(
|
|
||||||
$timeout: ng.ITimeoutService
|
|
||||||
) {
|
|
||||||
this.$timeout = $timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
this.reloadHistory();
|
|
||||||
this.diskEnabled = this.application.historyManager!.isDiskEnabled();
|
|
||||||
this.autoOptimize = this.application.historyManager!.isAutoOptimizeEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadHistory() {
|
|
||||||
const history = this.application.historyManager!.historyForItem(this.item);
|
|
||||||
this.entries = history.entries.slice(0).sort((a, b) => {
|
|
||||||
return a.payload.updated_at! < b.payload.updated_at! ? 1 : -1;
|
|
||||||
});
|
|
||||||
this.history = history;
|
|
||||||
}
|
|
||||||
|
|
||||||
openRevision(revision: ItemHistoryEntry) {
|
|
||||||
this.application.presentRevisionPreviewModal(
|
|
||||||
revision.payload.uuid,
|
|
||||||
revision.payload.content
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
classForRevision(revision: ItemHistoryEntry) {
|
|
||||||
const vector = revision.operationVector();
|
|
||||||
if (vector === 0) {
|
|
||||||
return 'default';
|
|
||||||
} else if (vector === 1) {
|
|
||||||
return 'success';
|
|
||||||
} else if (vector === -1) {
|
|
||||||
return 'danger';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearItemHistory() {
|
|
||||||
if (await confirmDialog({
|
|
||||||
text: "Are you sure you want to delete the local session history for this note?",
|
|
||||||
confirmButtonStyle: 'danger',
|
|
||||||
})) {
|
|
||||||
this.application.historyManager!.clearHistoryForItem(this.item).then(() => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.reloadHistory();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAllHistory() {
|
|
||||||
if (confirmDialog({
|
|
||||||
text: "Are you sure you want to delete the local session history for all notes?",
|
|
||||||
confirmButtonStyle: 'danger'
|
|
||||||
})) {
|
|
||||||
this.application.historyManager!.clearAllHistory().then(() => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.reloadHistory();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleDiskSaving() {
|
|
||||||
const run = () => {
|
|
||||||
this.application.historyManager!.toggleDiskSaving().then(() => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.diskEnabled = this.application.historyManager!.isDiskEnabled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if (!this.application.historyManager!.isDiskEnabled()) {
|
|
||||||
if (await confirmDialog({
|
|
||||||
text: `Are you sure you want to save history to disk? This will decrease general
|
|
||||||
performance, especially as you type. You are advised to disable this feature
|
|
||||||
if you experience any lagging.`,
|
|
||||||
confirmButtonStyle: 'danger',
|
|
||||||
})) {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleAutoOptimize() {
|
|
||||||
this.application.historyManager!.toggleAutoOptimize().then(() => {
|
|
||||||
this.$timeout(() => {
|
|
||||||
this.autoOptimize = this.application.historyManager!.autoOptimize;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SessionHistoryMenu extends WebDirective {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.restrict = 'E';
|
|
||||||
this.template = template;
|
|
||||||
this.controller = SessionHistoryMenuCtrl;
|
|
||||||
this.controllerAs = 'ctrl';
|
|
||||||
this.bindToController = true;
|
|
||||||
this.scope = {
|
|
||||||
item: '=',
|
|
||||||
application: '='
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -183,14 +183,14 @@
|
|||||||
application='self.application'
|
application='self.application'
|
||||||
)
|
)
|
||||||
.sk-app-bar-item(
|
.sk-app-bar-item(
|
||||||
click-outside=`self.setMenuState('showSessionHistory', false)`,
|
click-outside=`self.setMenuState('showHistory', false)`,
|
||||||
is-open='self.state.showSessionHistory',
|
is-open='self.state.showHistory',
|
||||||
ng-click="self.toggleMenu('showSessionHistory')"
|
ng-click="self.toggleMenu('showHistory')"
|
||||||
)
|
)
|
||||||
.sk-label Session History
|
.sk-label History
|
||||||
session-history-menu(
|
history-menu(
|
||||||
item='self.note',
|
item='self.note',
|
||||||
ng-if='self.state.showSessionHistory',
|
ng-if='self.state.showHistory',
|
||||||
application='self.application'
|
application='self.application'
|
||||||
)
|
)
|
||||||
#editor-content.editor-content(ng-if='!self.note.errorDecrypting')
|
#editor-content.editor-content(ng-if='!self.note.errorDecrypting')
|
||||||
|
|||||||
55
app/assets/templates/directives/history-menu.pug
Normal file
55
app/assets/templates/directives/history-menu.pug
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#history-menu.sn-component
|
||||||
|
.sk-menu-panel.dropdown-menu
|
||||||
|
.sk-menu-panel-header
|
||||||
|
.sk-menu-panel-header-title Session
|
||||||
|
.sk-menu-panel-header-subtitle {{ctrl.sessionHistoryEntries.length || 'No'}} revisions
|
||||||
|
a.sk-a.info.sk-h5(
|
||||||
|
ng-click='ctrl.showSessionOptions = !ctrl.showSessionOptions; $event.stopPropagation();'
|
||||||
|
) Options
|
||||||
|
div(ng-if='ctrl.showSessionOptions')
|
||||||
|
menu-row(
|
||||||
|
action='ctrl.clearItemSessionHistory()'
|
||||||
|
label="'Clear note local history'"
|
||||||
|
)
|
||||||
|
menu-row(
|
||||||
|
action='ctrl.clearAllSessionHistory()'
|
||||||
|
label="'Clear all local history'"
|
||||||
|
)
|
||||||
|
menu-row(
|
||||||
|
action='ctrl.toggleSessionHistoryAutoOptimize()'
|
||||||
|
label="(ctrl.autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'")
|
||||||
|
.sk-sublabel
|
||||||
|
| Automatically cleans up small revisions to conserve space.
|
||||||
|
menu-row(
|
||||||
|
action='ctrl.toggleSessionHistoryDiskSaving()'
|
||||||
|
label="(ctrl.diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'"
|
||||||
|
)
|
||||||
|
.sk-sublabel
|
||||||
|
| Saving to disk is not recommended. Decreases performance and increases app
|
||||||
|
| loading time and memory footprint.
|
||||||
|
menu-row(
|
||||||
|
ng-repeat='revision in ctrl.sessionHistoryEntries track by $index'
|
||||||
|
action='ctrl.openSessionRevision(revision);'
|
||||||
|
label='revision.previewTitle()'
|
||||||
|
)
|
||||||
|
.sk-sublabel.opaque(ng-class='ctrl.classForSessionRevision(revision)')
|
||||||
|
| {{revision.previewSubTitle()}}
|
||||||
|
.sk-menu-panel-header
|
||||||
|
.sk-menu-panel-header-title Remote
|
||||||
|
.sk-menu-panel-header-subtitle {{ctrl.remoteHistoryEntries.length || 'No'}} revisions
|
||||||
|
a.sk-a.info.sk-h5(
|
||||||
|
ng-click='ctrl.showRemoteOptions = !ctrl.showRemoteOptions; $event.stopPropagation();'
|
||||||
|
) Options
|
||||||
|
div(ng-if='ctrl.showRemoteOptions')
|
||||||
|
menu-row(
|
||||||
|
action='ctrl.fetchRemoteHistory()'
|
||||||
|
label="'Refresh'"
|
||||||
|
disabled="ctrl.isfetchingRemoteHistory"
|
||||||
|
spinner-class="ctrl.isfetchingRemoteHistory ? 'info' : null")
|
||||||
|
.sk-sublabel
|
||||||
|
| Fetch history from server.
|
||||||
|
menu-row(
|
||||||
|
ng-repeat='revision in ctrl.remoteHistoryEntries track by $index'
|
||||||
|
action='ctrl.openRemoteRevision(revision);'
|
||||||
|
label='ctrl.previewRemoteHistoryTitle(revision);'
|
||||||
|
)
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#session-history-menu.sn-component
|
|
||||||
.sk-menu-panel.dropdown-menu
|
|
||||||
.sk-menu-panel-header
|
|
||||||
.sk-menu-panel-header-title {{ctrl.history.entries.length || 'No'}} revisions
|
|
||||||
a.sk-a.info.sk-h5(
|
|
||||||
ng-click='ctrl.showOptions = !ctrl.showOptions; $event.stopPropagation();'
|
|
||||||
) Options
|
|
||||||
div(ng-if='ctrl.showOptions')
|
|
||||||
menu-row(
|
|
||||||
action='ctrl.clearItemHistory()'
|
|
||||||
label="'Clear note local history'"
|
|
||||||
)
|
|
||||||
menu-row(
|
|
||||||
action='ctrl.clearAllHistory()'
|
|
||||||
label="'Clear all local history'"
|
|
||||||
)
|
|
||||||
menu-row(
|
|
||||||
action='ctrl.toggleAutoOptimize()'
|
|
||||||
label="(ctrl.autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'")
|
|
||||||
.sk-sublabel
|
|
||||||
| Automatically cleans up small revisions to conserve space.
|
|
||||||
menu-row(
|
|
||||||
action='ctrl.toggleDiskSaving()'
|
|
||||||
label="(ctrl.diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'"
|
|
||||||
)
|
|
||||||
.sk-sublabel
|
|
||||||
| Saving to disk is not recommended. Decreases performance and increases app
|
|
||||||
| loading time and memory footprint.
|
|
||||||
menu-row(
|
|
||||||
ng-repeat='revision in ctrl.entries'
|
|
||||||
action='ctrl.openRevision(revision);'
|
|
||||||
label='revision.previewTitle()'
|
|
||||||
)
|
|
||||||
.sk-sublabel.opaque(ng-class='ctrl.classForRevision(revision)')
|
|
||||||
| {{revision.previewSubTitle()}}
|
|
||||||
Reference in New Issue
Block a user