notes infinite scroll
This commit is contained in:
@@ -32,6 +32,11 @@ angular.module('app.frontend')
|
|||||||
|
|
||||||
var isFirstLoad = true;
|
var isFirstLoad = true;
|
||||||
|
|
||||||
|
this.notesToDisplay = 20;
|
||||||
|
this.paginate = function() {
|
||||||
|
this.notesToDisplay += 20
|
||||||
|
}
|
||||||
|
|
||||||
this.tagDidChange = function(tag, oldTag) {
|
this.tagDidChange = function(tag, oldTag) {
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
|
|
||||||
|
|||||||
@@ -167,82 +167,6 @@ angular.module('app.frontend')
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Import
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.importJSONData = function(data, password, callback) {
|
|
||||||
console.log("Importing data", data);
|
|
||||||
|
|
||||||
var onDataReady = function() {
|
|
||||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
|
||||||
items.forEach(function(item){
|
|
||||||
item.setDirty(true);
|
|
||||||
item.markAllReferencesDirty();
|
|
||||||
})
|
|
||||||
this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]});
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
if(data.auth_params) {
|
|
||||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
|
|
||||||
var mk = keys.mk;
|
|
||||||
try {
|
|
||||||
this.decryptItemsWithKey(data.items, mk);
|
|
||||||
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
|
||||||
data.items.forEach(function(item){
|
|
||||||
item.enc_item_key = null;
|
|
||||||
item.auth_hash = null;
|
|
||||||
})
|
|
||||||
onDataReady();
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Error decrypting", e);
|
|
||||||
alert("There was an error decrypting your items. Make sure the password you entered is correct and try again.");
|
|
||||||
callback(false, null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
} else {
|
|
||||||
onDataReady();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Export
|
|
||||||
*/
|
|
||||||
|
|
||||||
this.itemsDataFile = function(ek) {
|
|
||||||
var textFile = null;
|
|
||||||
var makeTextFile = function (text) {
|
|
||||||
var data = new Blob([text], {type: 'text/json'});
|
|
||||||
|
|
||||||
// If we are replacing a previously generated file we need to
|
|
||||||
// manually revoke the object URL to avoid memory leaks.
|
|
||||||
if (textFile !== null) {
|
|
||||||
window.URL.revokeObjectURL(textFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
textFile = window.URL.createObjectURL(data);
|
|
||||||
|
|
||||||
// returns a URL you can use as a href
|
|
||||||
return textFile;
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
|
||||||
var itemParams = new ItemParams(item, ek);
|
|
||||||
return itemParams.paramsForExportFile();
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
items: items
|
|
||||||
}
|
|
||||||
|
|
||||||
// auth params are only needed when encrypted with a standard file key
|
|
||||||
data["auth_params"] = this.getAuthParams();
|
|
||||||
|
|
||||||
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.staticifyObject = function(object) {
|
this.staticifyObject = function(object) {
|
||||||
return JSON.parse(JSON.stringify(object));
|
return JSON.parse(JSON.stringify(object));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
angular.module('app.frontend').directive('infiniteScroll', [
|
||||||
|
'$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
|
||||||
|
return {
|
||||||
|
link: function(scope, elem, attrs) {
|
||||||
|
elem.css('overflow-x', 'hidden');
|
||||||
|
elem.css('height', 'inherit');
|
||||||
|
|
||||||
|
var offset = parseInt(attrs.threshold) || 0;
|
||||||
|
var e = elem[0]
|
||||||
|
|
||||||
|
elem.on('scroll', function(){
|
||||||
|
if(scope.$eval(attrs.canLoad) && e.scrollTop + e.offsetHeight >= e.scrollHeight - offset) {
|
||||||
|
scope.$apply(attrs.infiniteScroll);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]);
|
||||||
@@ -85,7 +85,6 @@ class AccountMenu {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Import/Export */
|
/* Import/Export */
|
||||||
|
|
||||||
$scope.archiveFormData = {encrypted: $scope.user ? true : false};
|
$scope.archiveFormData = {encrypted: $scope.user ? true : false};
|
||||||
@@ -97,16 +96,20 @@ class AccountMenu {
|
|||||||
|
|
||||||
var ek = $scope.archiveFormData.encrypted ? syncManager.masterKey : null;
|
var ek = $scope.archiveFormData.encrypted ? syncManager.masterKey : null;
|
||||||
|
|
||||||
link.href = authManager.itemsDataFile(ek);
|
link.href = $scope.itemsDataFile(ek);
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.submitImportPassword = function() {
|
||||||
|
$scope.performImport($scope.importData.data, $scope.importData.password);
|
||||||
|
}
|
||||||
|
|
||||||
$scope.performImport = function(data, password) {
|
$scope.performImport = function(data, password) {
|
||||||
$scope.importData.loading = true;
|
$scope.importData.loading = true;
|
||||||
// allow loading indicator to come up with timeout
|
// allow loading indicator to come up with timeout
|
||||||
$timeout(function(){
|
$timeout(function(){
|
||||||
authManager.importJSONData(data, password, function(success, response){
|
$scope.importJSONData(data, password, function(success, response){
|
||||||
console.log("Import response:", success, response);
|
// console.log("Import response:", success, response);
|
||||||
$scope.importData.loading = false;
|
$scope.importData.loading = false;
|
||||||
if(success) {
|
if(success) {
|
||||||
$scope.importData = null;
|
$scope.importData = null;
|
||||||
@@ -117,10 +120,6 @@ class AccountMenu {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.submitImportPassword = function() {
|
|
||||||
$scope.performImport($scope.importData.data, $scope.importData.password);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.importFileSelected = function(files) {
|
$scope.importFileSelected = function(files) {
|
||||||
$scope.importData = {};
|
$scope.importData = {};
|
||||||
|
|
||||||
@@ -147,6 +146,79 @@ class AccountMenu {
|
|||||||
return allNotes.length + "/" + allNotes.length + " notes encrypted";
|
return allNotes.length + "/" + allNotes.length + " notes encrypted";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.importJSONData = function(data, password, callback) {
|
||||||
|
console.log("Importing data", data);
|
||||||
|
|
||||||
|
var onDataReady = function() {
|
||||||
|
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
||||||
|
items.forEach(function(item){
|
||||||
|
item.setDirty(true);
|
||||||
|
item.markAllReferencesDirty();
|
||||||
|
})
|
||||||
|
|
||||||
|
syncManager.sync(callback, {additionalFields: ["created_at", "updated_at"]});
|
||||||
|
}.bind(this)
|
||||||
|
|
||||||
|
if(data.auth_params) {
|
||||||
|
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
|
||||||
|
var mk = keys.mk;
|
||||||
|
try {
|
||||||
|
EncryptionHelper.decryptMultipleItems(data.items, mk);
|
||||||
|
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
|
||||||
|
data.items.forEach(function(item){
|
||||||
|
item.enc_item_key = null;
|
||||||
|
item.auth_hash = null;
|
||||||
|
})
|
||||||
|
onDataReady();
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("Error decrypting", e);
|
||||||
|
alert("There was an error decrypting your items. Make sure the password you entered is correct and try again.");
|
||||||
|
callback(false, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
} else {
|
||||||
|
onDataReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Export
|
||||||
|
*/
|
||||||
|
|
||||||
|
$scope.itemsDataFile = function(ek) {
|
||||||
|
var textFile = null;
|
||||||
|
var makeTextFile = function (text) {
|
||||||
|
var data = new Blob([text], {type: 'text/json'});
|
||||||
|
|
||||||
|
// If we are replacing a previously generated file we need to
|
||||||
|
// manually revoke the object URL to avoid memory leaks.
|
||||||
|
if (textFile !== null) {
|
||||||
|
window.URL.revokeObjectURL(textFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
textFile = window.URL.createObjectURL(data);
|
||||||
|
|
||||||
|
// returns a URL you can use as a href
|
||||||
|
return textFile;
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
|
||||||
|
var itemParams = new ItemParams(item, ek);
|
||||||
|
return itemParams.paramsForExportFile();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
items: items
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth params are only needed when encrypted with a standard file key
|
||||||
|
data["auth_params"] = authManager.getAuthParams();
|
||||||
|
|
||||||
|
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
app/assets/javascripts/app/services/filters/startFrom.js
Normal file
6
app/assets/javascripts/app/services/filters/startFrom.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Start from filter
|
||||||
|
angular.module('app.frontend').filter('startFrom', function() {
|
||||||
|
return function(input, start) {
|
||||||
|
return input.slice(start);
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -76,7 +76,7 @@ class ModelManager {
|
|||||||
|
|
||||||
this.notifySyncObserversOfModels(models);
|
this.notifySyncObserversOfModels(models);
|
||||||
|
|
||||||
this.sortItems();
|
// this.sortItems();
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ class SyncManager {
|
|||||||
return this.serverURL + "/items/sync";
|
return this.serverURL + "/items/sync";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set syncToken(token) {
|
||||||
|
console.log("setting token", token);
|
||||||
|
this._syncToken = token;
|
||||||
|
localStorage.setItem("syncToken", token);
|
||||||
|
}
|
||||||
|
|
||||||
|
get syncToken() {
|
||||||
|
if(!this._syncToken) {
|
||||||
|
this._syncToken = localStorage.getItem("syncToken");
|
||||||
|
}
|
||||||
|
return this._syncToken;
|
||||||
|
}
|
||||||
|
|
||||||
sync(callback, options = {}) {
|
sync(callback, options = {}) {
|
||||||
|
|
||||||
if(this.syncStatus.syncOpInProgress) {
|
if(this.syncStatus.syncOpInProgress) {
|
||||||
@@ -75,6 +88,8 @@ class SyncManager {
|
|||||||
|
|
||||||
var allDirtyItems = this.modelManager.getDirtyItems();
|
var allDirtyItems = this.modelManager.getDirtyItems();
|
||||||
|
|
||||||
|
console.log("Syncing dirty items", allDirtyItems);
|
||||||
|
|
||||||
// we want to write all dirty items to disk only if the user is offline, or if the sync op fails
|
// we want to write all dirty items to disk only if the user is offline, or if the sync op fails
|
||||||
// if the sync op succeeds, these items will be written to disk by handling the "saved_items" response from the server
|
// if the sync op succeeds, these items will be written to disk by handling the "saved_items" response from the server
|
||||||
if(this.authManager.offline()) {
|
if(this.authManager.offline()) {
|
||||||
@@ -113,10 +128,13 @@ class SyncManager {
|
|||||||
request.sync_token = this.syncToken;
|
request.sync_token = this.syncToken;
|
||||||
request.cursor_token = this.cursorToken;
|
request.cursor_token = this.cursorToken;
|
||||||
|
|
||||||
|
console.log("Syncing with token", request.sync_token, request.cursor_token);
|
||||||
|
|
||||||
request.post().then(function(response) {
|
request.post().then(function(response) {
|
||||||
|
console.log("Sync completion", response.plain());
|
||||||
|
|
||||||
this.modelManager.clearDirtyItems(subItems);
|
this.modelManager.clearDirtyItems(subItems);
|
||||||
this.syncStatus.error = null;
|
this.syncStatus.error = null;
|
||||||
this.syncToken = response.sync_token;
|
|
||||||
this.cursorToken = response.cursor_token;
|
this.cursorToken = response.cursor_token;
|
||||||
|
|
||||||
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
||||||
@@ -134,8 +152,13 @@ class SyncManager {
|
|||||||
this.syncStatus.syncOpInProgress = false;
|
this.syncStatus.syncOpInProgress = false;
|
||||||
this.syncStatus.current += subItems.length;
|
this.syncStatus.current += subItems.length;
|
||||||
|
|
||||||
|
// set the sync token at the end, so that if any errors happen above, you can resync
|
||||||
|
this.syncToken = response.sync_token;
|
||||||
|
|
||||||
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
|
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
|
||||||
|
setTimeout(function () {
|
||||||
this.sync(callback, options);
|
this.sync(callback, options);
|
||||||
|
}.bind(this), 10); // wait 10ms to allow UI to update
|
||||||
} else {
|
} else {
|
||||||
if(callback) {
|
if(callback) {
|
||||||
callback(response);
|
callback(response);
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
margin-bottom: 10px !important;
|
margin-bottom: 10px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mr-5 {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.faded {
|
.faded {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
@@ -230,8 +234,9 @@
|
|||||||
cursor: default;
|
cursor: default;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
button.light {
|
button.light {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -249,25 +254,6 @@
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(gray, 0.10);
|
background-color: rgba(gray, 0.10);
|
||||||
}
|
}
|
||||||
|
|
||||||
.execution-spinner {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.storage-text {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.half-button {
|
.half-button {
|
||||||
@@ -293,25 +279,6 @@
|
|||||||
cursor: default !important;
|
cursor: default !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-password {
|
|
||||||
margin-top: 14px;
|
|
||||||
|
|
||||||
> .field {
|
|
||||||
display: block;
|
|
||||||
margin: 5px 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.encryption-confirmation {
|
|
||||||
position: relative;
|
|
||||||
.buttons {
|
|
||||||
.cancel {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-right: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.disabled {
|
a.disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -323,6 +290,11 @@ a.disabled {
|
|||||||
border: 1px solid #515263;
|
border: 1px solid #515263;
|
||||||
border-right-color: transparent;
|
border-right-color: transparent;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&.blue {
|
||||||
|
border: 1px solid $blue-color;
|
||||||
|
border-right-color: transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
|
|||||||
@@ -29,7 +29,9 @@
|
|||||||
%h2 {{user.email}}
|
%h2 {{user.email}}
|
||||||
%p {{server}}
|
%p {{server}}
|
||||||
|
|
||||||
%p.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"} Syncing: {{syncStatus.current}}/{{syncStatus.total}}
|
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"}
|
||||||
|
.spinner.inline.mr-5.blue
|
||||||
|
Syncing: {{syncStatus.current}}/{{syncStatus.total}}
|
||||||
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
|
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
|
||||||
|
|
||||||
.medium-v-space
|
.medium-v-space
|
||||||
@@ -53,7 +55,7 @@
|
|||||||
%a.block{"ng-click" => "downloadDataArchive()"} Download Data Archive
|
%a.block{"ng-click" => "downloadDataArchive()"} Download Data Archive
|
||||||
|
|
||||||
%label.block.mt-5
|
%label.block.mt-5
|
||||||
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "ctrl.importFileSelected(files)"}
|
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}
|
||||||
.fake-link Import Data from Archive
|
.fake-link Import Data from Archive
|
||||||
|
|
||||||
%div{"ng-if" => "importData.requestPassword"}
|
%div{"ng-if" => "importData.requestPassword"}
|
||||||
@@ -61,6 +63,6 @@
|
|||||||
%input{"type" => "text", "ng-model" => "importData.password"}
|
%input{"type" => "text", "ng-model" => "importData.password"}
|
||||||
%button{"ng-click" => "submitImportPassword()"} Decrypt & Import
|
%button{"ng-click" => "submitImportPassword()"} Decrypt & Import
|
||||||
|
|
||||||
.spinner{"ng-if" => "importData.loading"}
|
.spinner.mt-10{"ng-if" => "importData.loading"}
|
||||||
|
|
||||||
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data
|
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data
|
||||||
|
|||||||
@@ -34,13 +34,15 @@
|
|||||||
%button.light{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.enableRepeatAction(action, extension)"} Enable
|
%button.light{"ng-if" => "!extensionManager.isRepeatActionEnabled(action)", "ng-click" => "extensionManager.enableRepeatAction(action, extension)"} Enable
|
||||||
%button.light.mt-10{"ng-if" => "!action.running && !action.repeat_mode", "ng-click" => "selectedAction(action, extension)"}
|
%button.light.mt-10{"ng-if" => "!action.running && !action.repeat_mode", "ng-click" => "selectedAction(action, extension)"}
|
||||||
Perform Action
|
Perform Action
|
||||||
.spinner.execution-spinner.mb-5.centered.center-align.block{"ng-if" => "action.running"}
|
.spinner.mb-5.centered.center-align.block{"ng-if" => "action.running"}
|
||||||
%p.mb-5.mt-5.small{"ng-if" => "!action.error && action.lastExecuted && !action.running"}
|
%p.mb-5.mt-5.small{"ng-if" => "!action.error && action.lastExecuted && !action.running"}
|
||||||
Last run {{action.lastExecuted | appDateTime}}
|
Last run {{action.lastExecuted | appDateTime}}
|
||||||
%label.red{"ng-if" => "action.error"}
|
%label.red{"ng-if" => "action.error"}
|
||||||
Error performing action.
|
Error performing action.
|
||||||
|
|
||||||
%a.block.center-align.mt-10{"ng-click" => "deleteExtension(extension)"} Remove extension
|
%a.block.center-align.mt-10{"ng-click" => "extension.showURL = !extension.showURL"} Show URL
|
||||||
|
%p.center-align.wrap{"ng-if" => "extension.showURL"} {{extension.url}}
|
||||||
|
%a.block.center-align.mt-5{"ng-click" => "deleteExtension(extension)"} Remove extension
|
||||||
|
|
||||||
.large-v-space
|
.large-v-space
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
%li
|
%li
|
||||||
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
|
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
|
||||||
|
|
||||||
.note{"ng-repeat" => "note in ctrl.tag.notes | filter: ctrl.filterNotes",
|
%div{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
||||||
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
.note{"ng-repeat" => "note in ctrl.tag.notes | limitTo:ctrl.notesToDisplay | filter: ctrl.filterNotes",
|
||||||
|
"ng-click" => "ctrl.selectNote(note)"}
|
||||||
.name{"ng-if" => "note.title"}
|
.name{"ng-if" => "note.title"}
|
||||||
{{note.title}}
|
{{note.title}}
|
||||||
.note-preview
|
.note-preview
|
||||||
|
|||||||
Reference in New Issue
Block a user