@@ -56,7 +56,7 @@ angular.module('app')
|
||||
// Before checking if isMappingSourceRetrieved, we check if this item was deleted via a local source,
|
||||
// such as alternating uuids during sign in. Otherwise, we only want to make interface updates if it's a
|
||||
// remote retrieved source.
|
||||
if(this.note.deleted) {
|
||||
if(this.note.deleted || this.note.content.trashed) {
|
||||
$rootScope.notifyDelete();
|
||||
return;
|
||||
}
|
||||
@@ -381,7 +381,7 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteNote = async function() {
|
||||
this.deleteNote = async function(permanently) {
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
if(this.note.locked) {
|
||||
@@ -390,8 +390,15 @@ angular.module('app')
|
||||
}
|
||||
|
||||
let title = this.note.safeTitle().length ? `'${this.note.title}'` : "this note";
|
||||
if(confirm(`Are you sure you want to delete ${title}?`)) {
|
||||
this.remove()(this.note);
|
||||
let message = permanently ? `Are you sure you want to permanently delete ${title}?`
|
||||
: `Are you sure you want to move ${title} to the trash?`
|
||||
if(confirm(message)) {
|
||||
if(permanently) {
|
||||
this.remove()(this.note);
|
||||
} else {
|
||||
this.note.content.trashed = true;
|
||||
this.changesMade({dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
}
|
||||
this.showMenu = false;
|
||||
}
|
||||
});
|
||||
@@ -406,6 +413,27 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.restoreTrashedNote = function() {
|
||||
this.note.content.trashed = false;
|
||||
this.changesMade({dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.deleteNotePermanantely = function() {
|
||||
this.deleteNote(true);
|
||||
}
|
||||
|
||||
this.getTrashCount = function() {
|
||||
return modelManager.trashedItems().length;
|
||||
}
|
||||
|
||||
this.emptyTrash = function() {
|
||||
let count = this.getTrashCount();
|
||||
if(confirm(`Are you sure you want to permanently delete ${count} note(s)?`)) {
|
||||
modelManager.emptyTrash();
|
||||
syncManager.sync();
|
||||
}
|
||||
}
|
||||
|
||||
this.togglePin = function() {
|
||||
this.note.setAppDataItem("pinned", !this.note.pinned);
|
||||
this.changesMade({dontUpdatePreviews: true});
|
||||
|
||||
@@ -34,6 +34,26 @@ angular.module('app')
|
||||
this.securityUpdateAvailable = authManager.securityUpdateAvailable;
|
||||
})
|
||||
|
||||
$rootScope.$on("did-begin-local-backup", () => {
|
||||
$timeout(() => {
|
||||
this.arbitraryStatusMessage = "Saving local backup...";
|
||||
})
|
||||
});
|
||||
|
||||
$rootScope.$on("did-finish-local-backup", (event, data) => {
|
||||
$timeout(() => {
|
||||
if(data.success) {
|
||||
this.arbitraryStatusMessage = "Successfully saved backup.";
|
||||
} else {
|
||||
this.arbitraryStatusMessage = "Unable to save local backup.";
|
||||
}
|
||||
|
||||
$timeout(() => {
|
||||
this.arbitraryStatusMessage = null;
|
||||
}, 2000)
|
||||
})
|
||||
});
|
||||
|
||||
this.openSecurityUpdate = function() {
|
||||
authManager.presentPasswordWizard("upgrade-security");
|
||||
}
|
||||
|
||||
@@ -40,10 +40,6 @@ angular.module('app')
|
||||
openDatabase();
|
||||
// Retrieve local data and begin sycing timer
|
||||
initiateSync();
|
||||
// Configure "All" psuedo-tag
|
||||
loadAllTag();
|
||||
// Configure "Archived" psuedo-tag
|
||||
loadArchivedTag();
|
||||
}
|
||||
|
||||
if(passcodeManager.isLocked()) {
|
||||
@@ -94,7 +90,6 @@ angular.module('app')
|
||||
|
||||
syncManager.loadLocalItems().then(() => {
|
||||
$timeout(() => {
|
||||
$scope.allTag.didLoad = true;
|
||||
$rootScope.$broadcast("initial-data-loaded"); // This needs to be processed first before sync is called so that singletonManager observers function properly.
|
||||
syncManager.sync();
|
||||
// refresh every 30s
|
||||
@@ -113,25 +108,6 @@ angular.module('app')
|
||||
})
|
||||
}
|
||||
|
||||
function loadAllTag() {
|
||||
var allTag = new SNTag({content: {title: "All"}});
|
||||
allTag.all = true;
|
||||
allTag.needsLoad = true;
|
||||
$scope.allTag = allTag;
|
||||
$scope.tags = modelManager.tags;
|
||||
$scope.allTag.notes = modelManager.notes;
|
||||
}
|
||||
|
||||
function loadArchivedTag() {
|
||||
var archiveTag = new SNSmartTag({content: {title: "Archived", predicate: ["archived", "=", true]}});
|
||||
Object.defineProperty(archiveTag, "notes", {
|
||||
get: () => {
|
||||
return modelManager.notesMatchingPredicate(archiveTag.content.predicate);
|
||||
}
|
||||
});
|
||||
$scope.archiveTag = archiveTag;
|
||||
}
|
||||
|
||||
/*
|
||||
Editor Callbacks
|
||||
*/
|
||||
@@ -220,7 +196,7 @@ angular.module('app')
|
||||
$scope.notesAddNew = function(note) {
|
||||
modelManager.addItem(note);
|
||||
|
||||
if(!$scope.selectedTag.all && !$scope.selectedTag.isSmartTag()) {
|
||||
if(!$scope.selectedTag.isSmartTag()) {
|
||||
$scope.selectedTag.addItemAsRelationship(note);
|
||||
$scope.selectedTag.setDirty(true);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,13 @@ angular.module('app')
|
||||
this.loadPreferences();
|
||||
});
|
||||
|
||||
modelManager.addItemSyncObserver("note-list", "Note", (allItems, validItems, deletedItems, source, sourceKey) => {
|
||||
// Note has changed values, reset its flags
|
||||
for(var note of allItems) {
|
||||
note.flags = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.loadPreferences = function() {
|
||||
let prevSortValue = this.sortBy;
|
||||
|
||||
@@ -129,7 +136,7 @@ angular.module('app')
|
||||
if(this.isFiltering()) {
|
||||
return `${this.tag.notes.filter((i) => {return i.visible;}).length} search results`;
|
||||
} else if(this.tag) {
|
||||
return `${this.tag.title} notes`;
|
||||
return `${this.tag.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,19 +150,63 @@ angular.module('app')
|
||||
base += " Title";
|
||||
}
|
||||
|
||||
if(!this.tag || !this.tag.isSmartTag()) {
|
||||
// These rules don't apply for smart tags
|
||||
if(this.showArchived) {
|
||||
base += " | + Archived"
|
||||
}
|
||||
if(this.hidePinned) {
|
||||
base += " | – Pinned"
|
||||
}
|
||||
if(this.showArchived) {
|
||||
base += " | + Archived"
|
||||
}
|
||||
if(this.hidePinned) {
|
||||
base += " | – Pinned"
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
this.getNoteFlags = (note) => {
|
||||
if(note.flags) {
|
||||
return note.flags;
|
||||
}
|
||||
|
||||
let flags = [];
|
||||
|
||||
if(note.pinned) {
|
||||
flags.push({
|
||||
text: "Pinned",
|
||||
class: "info"
|
||||
})
|
||||
}
|
||||
|
||||
if(note.archived) {
|
||||
flags.push({
|
||||
text: "Archived",
|
||||
class: "warning"
|
||||
})
|
||||
}
|
||||
|
||||
if(note.content.protected) {
|
||||
flags.push({
|
||||
text: "Protected",
|
||||
class: "success"
|
||||
})
|
||||
}
|
||||
|
||||
if(note.locked) {
|
||||
flags.push({
|
||||
text: "Locked",
|
||||
class: "neutral"
|
||||
})
|
||||
}
|
||||
|
||||
if(note.content.trashed) {
|
||||
flags.push({
|
||||
text: "Deleted",
|
||||
class: "danger"
|
||||
})
|
||||
}
|
||||
|
||||
note.flags = flags;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
this.toggleKey = function(key) {
|
||||
this[key] = !this[key];
|
||||
authManager.setUserPrefValue(key, this[key]);
|
||||
@@ -254,13 +305,17 @@ angular.module('app')
|
||||
this.noteFilter = {text : ''};
|
||||
|
||||
this.filterNotes = function(note) {
|
||||
var canShowArchived = false, canShowPinned = true;
|
||||
let canShowArchived = this.showArchived, canShowPinned = !this.hidePinned;
|
||||
let isTrash = this.tag.content.isTrashTag;
|
||||
|
||||
if(!isTrash && note.content.trashed) {
|
||||
note.visible = false;
|
||||
return note.visible;
|
||||
}
|
||||
|
||||
var isSmartTag = this.tag.isSmartTag();
|
||||
if(isSmartTag) {
|
||||
canShowArchived = this.tag.isReferencingArchivedNotes();
|
||||
} else {
|
||||
canShowArchived = this.showArchived;
|
||||
canShowPinned = !this.hidePinned;
|
||||
canShowArchived = canShowArchived || this.tag.content.isArchiveTag || isTrash;
|
||||
}
|
||||
|
||||
if((note.archived && !canShowArchived) || (note.pinned && !canShowPinned)) {
|
||||
@@ -339,11 +394,15 @@ angular.module('app')
|
||||
}
|
||||
|
||||
this.shouldShowTags = function(note) {
|
||||
if(this.hideTags) {
|
||||
if(this.hideTags || note.content.protected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(this.tag.all) {
|
||||
if(this.tag.content.isAllTag) {
|
||||
return note.tags && note.tags.length > 0;
|
||||
}
|
||||
|
||||
if(this.tag.isSmartTag()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ angular.module('app')
|
||||
selectionMade: "&",
|
||||
save: "&",
|
||||
tags: "=",
|
||||
allTag: "=",
|
||||
archiveTag: "=",
|
||||
updateNoteTag: "&",
|
||||
removeTag: "&"
|
||||
},
|
||||
@@ -17,25 +15,23 @@ angular.module('app')
|
||||
controller: 'TagsCtrl',
|
||||
controllerAs: 'ctrl',
|
||||
bindToController: true,
|
||||
|
||||
link:function(scope, elem, attrs, ctrl) {
|
||||
scope.$watch('ctrl.tags', function(newTags){
|
||||
if(newTags) {
|
||||
ctrl.setTags(newTags);
|
||||
}
|
||||
});
|
||||
|
||||
scope.$watch('ctrl.allTag', function(allTag){
|
||||
if(allTag) {
|
||||
ctrl.setAllTag(allTag);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.controller('TagsCtrl', function ($rootScope, modelManager, $timeout, componentManager, authManager) {
|
||||
.controller('TagsCtrl', function ($rootScope, modelManager, syncManager, $timeout, componentManager, authManager) {
|
||||
// Wrap in timeout so that selectTag is defined
|
||||
$timeout(() => {
|
||||
this.smartTags = modelManager.getSmartTags();
|
||||
this.selectTag(this.smartTags[0]);
|
||||
})
|
||||
|
||||
var initialLoad = true;
|
||||
syncManager.addEventHandler((syncEvent, data) => {
|
||||
if(syncEvent == "local-data-loaded"
|
||||
|| syncEvent == "sync:completed"
|
||||
|| syncEvent == "local-data-incremental-load") {
|
||||
this.tags = modelManager.tags;
|
||||
this.smartTags = modelManager.getSmartTags();
|
||||
}
|
||||
});
|
||||
|
||||
this.panelController = {};
|
||||
|
||||
@@ -69,40 +65,27 @@ angular.module('app')
|
||||
}.bind(this), actionHandler: function(component, action, data){
|
||||
if(action === "select-item") {
|
||||
if(data.item.content_type == "Tag") {
|
||||
var tag = modelManager.findItem(data.item.uuid);
|
||||
let tag = modelManager.findItem(data.item.uuid);
|
||||
if(tag) {
|
||||
this.selectTag(tag);
|
||||
}
|
||||
} else if(data.item.content_type == "SN|SmartTag") {
|
||||
var tag = new SNSmartTag(data.item);
|
||||
Object.defineProperty(tag, "notes", {
|
||||
get: () => {
|
||||
return modelManager.notesMatchingPredicate(tag.content.predicate);
|
||||
}
|
||||
});
|
||||
this.selectTag(tag);
|
||||
let smartTag = new SNSmartTag(data.item);
|
||||
this.selectTag(smartTag);
|
||||
}
|
||||
} else if(action === "clear-selection") {
|
||||
this.selectTag(this.allTag);
|
||||
this.selectTag(this.smartTags[0]);
|
||||
}
|
||||
}.bind(this)});
|
||||
|
||||
this.setAllTag = function(allTag) {
|
||||
this.selectTag(this.allTag);
|
||||
}
|
||||
|
||||
this.setTags = function(tags) {
|
||||
if(initialLoad) {
|
||||
initialLoad = false;
|
||||
this.selectTag(this.allTag);
|
||||
} else {
|
||||
if(tags && tags.length > 0) {
|
||||
this.selectTag(tags[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.selectTag = function(tag) {
|
||||
if(tag.isSmartTag()) {
|
||||
Object.defineProperty(tag, "notes", {
|
||||
get: () => {
|
||||
return modelManager.notesMatchingSmartTag(tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.selectedTag = tag;
|
||||
tag.conflict_of = null; // clear conflict
|
||||
this.selectionMade()(tag);
|
||||
@@ -161,12 +144,12 @@ angular.module('app')
|
||||
|
||||
this.selectedDeleteTag = function(tag) {
|
||||
this.removeTag()(tag);
|
||||
this.selectTag(this.allTag);
|
||||
this.selectTag(this.smartTags[0]);
|
||||
}
|
||||
|
||||
this.noteCount = function(tag) {
|
||||
var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){
|
||||
return !note.archived;
|
||||
return !note.archived && !note.content.trashed;
|
||||
});
|
||||
return validNotes.length;
|
||||
}
|
||||
|
||||
@@ -182,6 +182,13 @@ class DesktopManager {
|
||||
this.majorDataChangeHandler = handler;
|
||||
}
|
||||
|
||||
desktop_didBeginBackup() {
|
||||
this.$rootScope.$broadcast("did-begin-local-backup");
|
||||
}
|
||||
|
||||
desktop_didFinishBackup(success) {
|
||||
this.$rootScope.$broadcast("did-finish-local-backup", {success: success});
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').service('desktopManager', DesktopManager);
|
||||
|
||||
@@ -21,7 +21,7 @@ class MigrationManager extends SFMigrationManager {
|
||||
return {
|
||||
name: "editor-to-component",
|
||||
content_type: "SN|Editor",
|
||||
handler: (editors) => {
|
||||
handler: async (editors) => {
|
||||
// Convert editors to components
|
||||
for(var editor of editors) {
|
||||
// If there's already a component for this url, then skip this editor
|
||||
@@ -60,7 +60,8 @@ class MigrationManager extends SFMigrationManager {
|
||||
return {
|
||||
name: "component-url-to-hosted-url",
|
||||
content_type: "SN|Component",
|
||||
handler: (components) => {
|
||||
handler: async (components) => {
|
||||
let hasChanges = false;
|
||||
var notes = this.modelManager.validItemsForContentType("Note");
|
||||
for(var note of notes) {
|
||||
for(var component of components) {
|
||||
@@ -69,10 +70,14 @@ class MigrationManager extends SFMigrationManager {
|
||||
note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain);
|
||||
note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain);
|
||||
note.setDirty(true, true); // dont update client date
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.syncManager.sync();
|
||||
|
||||
if(hasChanges) {
|
||||
this.syncManager.sync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ class ModelManager extends SFModelManager {
|
||||
this.components = [];
|
||||
|
||||
this.storageManager = storageManager;
|
||||
|
||||
this.buildSystemSmartTags();
|
||||
}
|
||||
|
||||
handleSignout() {
|
||||
@@ -114,9 +116,44 @@ class ModelManager extends SFModelManager {
|
||||
}
|
||||
}
|
||||
|
||||
notesMatchingPredicate(predicate) {
|
||||
notesMatchingSmartTag(tag) {
|
||||
let contentTypePredicate = new SFPredicate("content_type", "=", "Note");
|
||||
return this.itemsMatchingPredicates([contentTypePredicate, predicate]);
|
||||
let predicates = [contentTypePredicate, tag.content.predicate];
|
||||
if(!tag.content.isTrashTag) {
|
||||
let notTrashedPredicate = new SFPredicate("content.trashed", "=", false);
|
||||
predicates.push(notTrashedPredicate);
|
||||
}
|
||||
return this.itemsMatchingPredicates(predicates);
|
||||
}
|
||||
|
||||
trashSmartTag() {
|
||||
return this.systemSmartTags.find((tag) => tag.content.isTrashTag);
|
||||
}
|
||||
|
||||
trashedItems() {
|
||||
return this.notesMatchingSmartTag(this.trashSmartTag());
|
||||
}
|
||||
|
||||
emptyTrash() {
|
||||
let notes = this.trashedItems();
|
||||
for(let note of notes) {
|
||||
this.setItemToBeDeleted(note);
|
||||
}
|
||||
}
|
||||
|
||||
buildSystemSmartTags() {
|
||||
this.systemSmartTags = SNSmartTag.systemSmartTags();
|
||||
}
|
||||
|
||||
getSmartTagWithId(id) {
|
||||
return this.getSmartTags().find((candidate) => candidate.uuid == id);
|
||||
}
|
||||
|
||||
getSmartTags() {
|
||||
let userTags = this.validItemsForContentType("SN|SmartTag").sort((a, b) => {
|
||||
return a.content.title < b.content.title ? -1 : 1;
|
||||
});
|
||||
return this.systemSmartTags.concat(userTags);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +170,10 @@ class ModelManager extends SFModelManager {
|
||||
"SN|Editor" : "editor",
|
||||
"SN|Theme" : "theme",
|
||||
"SF|Extension" : "server extension",
|
||||
"SF|MFA" : "two-factor authentication setting"
|
||||
"SF|MFA" : "two-factor authentication setting",
|
||||
"SN|FileSafe|Credentials": "FileSafe credential",
|
||||
"SN|FileSafe|FileMetadata": "FileSafe file",
|
||||
"SN|FileSafe|Integration": "FileSafe integration"
|
||||
}[contentType];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +1,31 @@
|
||||
class PrivilegesManager {
|
||||
class PrivilegesManager extends SFPrivilegesManager {
|
||||
|
||||
constructor(passcodeManager, authManager, syncManager, singletonManager, modelManager, storageManager, $rootScope, $compile) {
|
||||
super(modelManager, syncManager, singletonManager);
|
||||
|
||||
constructor(passcodeManager, authManager, singletonManager, modelManager, storageManager, $rootScope, $compile) {
|
||||
this.passcodeManager = passcodeManager;
|
||||
this.authManager = authManager;
|
||||
this.singletonManager = singletonManager;
|
||||
this.modelManager = modelManager;
|
||||
this.storageManager = storageManager;
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
|
||||
this.loadPrivileges();
|
||||
|
||||
PrivilegesManager.CredentialAccountPassword = "CredentialAccountPassword";
|
||||
PrivilegesManager.CredentialLocalPasscode = "CredentialLocalPasscode";
|
||||
|
||||
PrivilegesManager.ActionManageExtensions = "ActionManageExtensions";
|
||||
PrivilegesManager.ActionManageBackups = "ActionManageBackups";
|
||||
PrivilegesManager.ActionViewProtectedNotes = "ActionViewProtectedNotes";
|
||||
PrivilegesManager.ActionManagePrivileges = "ActionManagePrivileges";
|
||||
PrivilegesManager.ActionManagePasscode = "ActionManagePasscode";
|
||||
PrivilegesManager.ActionDeleteNote = "ActionDeleteNote";
|
||||
|
||||
PrivilegesManager.SessionExpiresAtKey = "SessionExpiresAtKey";
|
||||
PrivilegesManager.SessionLengthKey = "SessionLengthKey";
|
||||
|
||||
PrivilegesManager.SessionLengthNone = 0;
|
||||
PrivilegesManager.SessionLengthFiveMinutes = 300;
|
||||
PrivilegesManager.SessionLengthOneHour = 3600;
|
||||
PrivilegesManager.SessionLengthOneWeek = 604800;
|
||||
|
||||
this.availableActions = [
|
||||
PrivilegesManager.ActionViewProtectedNotes,
|
||||
PrivilegesManager.ActionDeleteNote,
|
||||
PrivilegesManager.ActionManagePasscode,
|
||||
PrivilegesManager.ActionManageBackups,
|
||||
PrivilegesManager.ActionManageExtensions,
|
||||
PrivilegesManager.ActionManagePrivileges,
|
||||
]
|
||||
|
||||
this.availableCredentials = [
|
||||
PrivilegesManager.CredentialAccountPassword,
|
||||
PrivilegesManager.CredentialLocalPasscode
|
||||
];
|
||||
|
||||
this.sessionLengths = [
|
||||
PrivilegesManager.SessionLengthNone,
|
||||
PrivilegesManager.SessionLengthFiveMinutes,
|
||||
PrivilegesManager.SessionLengthOneHour,
|
||||
PrivilegesManager.SessionLengthOneWeek,
|
||||
PrivilegesManager.SessionLengthIndefinite
|
||||
]
|
||||
}
|
||||
|
||||
getAvailableActions() {
|
||||
return this.availableActions;
|
||||
}
|
||||
|
||||
getAvailableCredentials() {
|
||||
return this.availableCredentials;
|
||||
this.setDelegate({
|
||||
isOffline: async () => {
|
||||
return authManager.offline();
|
||||
},
|
||||
hasLocalPasscode: async () => {
|
||||
return passcodeManager.hasPasscode();
|
||||
},
|
||||
saveToStorage: async (key, value) => {
|
||||
return storageManager.setItem(key, value, storageManager.bestStorageMode());
|
||||
},
|
||||
getFromStorage: async (key) => {
|
||||
return storageManager.getItem(key, storageManager.bestStorageMode());
|
||||
},
|
||||
verifyAccountPassword: async (password) => {
|
||||
return authManager.verifyAccountPassword(password);
|
||||
},
|
||||
verifyLocalPasscode: async (passcode) => {
|
||||
return passcodeManager.verifyPasscode(passcode);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
presentPrivilegesModal(action, onSuccess, onCancel) {
|
||||
@@ -86,25 +54,6 @@ class PrivilegesManager {
|
||||
this.currentAuthenticationElement = el;
|
||||
}
|
||||
|
||||
async netCredentialsForAction(action) {
|
||||
let credentials = (await this.getPrivileges()).getCredentialsForAction(action);
|
||||
let netCredentials = [];
|
||||
|
||||
for(var cred of credentials) {
|
||||
if(cred == PrivilegesManager.CredentialAccountPassword) {
|
||||
if(!this.authManager.offline()) {
|
||||
netCredentials.push(cred);
|
||||
}
|
||||
} else if(cred == PrivilegesManager.CredentialLocalPasscode) {
|
||||
if(this.passcodeManager.hasPasscode()) {
|
||||
netCredentials.push(cred);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return netCredentials;
|
||||
}
|
||||
|
||||
presentPrivilegesManagementModal() {
|
||||
var scope = this.$rootScope.$new(true);
|
||||
var el = this.$compile( "<privileges-management-modal class='sk-modal'></privileges-management-modal>")(scope);
|
||||
@@ -115,195 +64,6 @@ class PrivilegesManager {
|
||||
return this.currentAuthenticationElement != null;
|
||||
}
|
||||
|
||||
async loadPrivileges() {
|
||||
return new Promise((resolve, reject) => {
|
||||
let prefsContentType = "SN|Privileges";
|
||||
let contentTypePredicate = new SFPredicate("content_type", "=", prefsContentType);
|
||||
this.singletonManager.registerSingleton([contentTypePredicate], (resolvedSingleton) => {
|
||||
this.privileges = resolvedSingleton;
|
||||
if(!this.privileges.content.desktopPrivileges) {
|
||||
this.privileges.content.desktopPrivileges = {};
|
||||
}
|
||||
resolve(resolvedSingleton);
|
||||
}, (valueCallback) => {
|
||||
// Safe to create. Create and return object.
|
||||
var privs = new SNPrivileges({content_type: prefsContentType});
|
||||
this.modelManager.addItem(privs);
|
||||
privs.setDirty(true);
|
||||
this.$rootScope.sync();
|
||||
valueCallback(privs);
|
||||
resolve(privs);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getPrivileges() {
|
||||
if(this.privileges) {
|
||||
return this.privileges;
|
||||
} else {
|
||||
return this.loadPrivileges();
|
||||
}
|
||||
}
|
||||
|
||||
displayInfoForCredential(credential) {
|
||||
let metadata = {}
|
||||
|
||||
metadata[PrivilegesManager.CredentialAccountPassword] = {
|
||||
label: "Account Password",
|
||||
prompt: "Please enter your account password."
|
||||
}
|
||||
|
||||
metadata[PrivilegesManager.CredentialLocalPasscode] = {
|
||||
label: "Local Passcode",
|
||||
prompt: "Please enter your local passcode."
|
||||
}
|
||||
|
||||
return metadata[credential];
|
||||
}
|
||||
|
||||
displayInfoForAction(action) {
|
||||
let metadata = {};
|
||||
|
||||
metadata[PrivilegesManager.ActionManageExtensions] = {
|
||||
label: "Manage Extensions"
|
||||
};
|
||||
|
||||
metadata[PrivilegesManager.ActionManageBackups] = {
|
||||
label: "Download/Import Backups"
|
||||
};
|
||||
|
||||
metadata[PrivilegesManager.ActionViewProtectedNotes] = {
|
||||
label: "View Protected Notes"
|
||||
};
|
||||
|
||||
metadata[PrivilegesManager.ActionManagePrivileges] = {
|
||||
label: "Manage Privileges"
|
||||
};
|
||||
|
||||
metadata[PrivilegesManager.ActionManagePasscode] = {
|
||||
label: "Manage Passcode"
|
||||
}
|
||||
|
||||
metadata[PrivilegesManager.ActionDeleteNote] = {
|
||||
label: "Delete Notes"
|
||||
}
|
||||
|
||||
return metadata[action];
|
||||
}
|
||||
|
||||
getSessionLengthOptions() {
|
||||
return [
|
||||
{
|
||||
value: PrivilegesManager.SessionLengthNone,
|
||||
label: "Don't Remember"
|
||||
},
|
||||
{
|
||||
value: PrivilegesManager.SessionLengthFiveMinutes,
|
||||
label: "5 Minutes"
|
||||
},
|
||||
{
|
||||
value: PrivilegesManager.SessionLengthOneHour,
|
||||
label: "1 Hour"
|
||||
},
|
||||
{
|
||||
value: PrivilegesManager.SessionLengthOneWeek,
|
||||
label: "1 Week"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
async setSessionLength(length) {
|
||||
let addToNow = (seconds) => {
|
||||
let date = new Date();
|
||||
date.setSeconds(date.getSeconds() + seconds);
|
||||
return date;
|
||||
}
|
||||
|
||||
let expiresAt = addToNow(length);
|
||||
|
||||
return Promise.all([
|
||||
this.storageManager.setItem(PrivilegesManager.SessionExpiresAtKey, JSON.stringify(expiresAt), this.storageManager.bestStorageMode()),
|
||||
this.storageManager.setItem(PrivilegesManager.SessionLengthKey, JSON.stringify(length), this.storageManager.bestStorageMode()),
|
||||
])
|
||||
}
|
||||
|
||||
async clearSession() {
|
||||
return this.setSessionLength(PrivilegesManager.SessionLengthNone);
|
||||
}
|
||||
|
||||
async getSelectedSessionLength() {
|
||||
let length = await this.storageManager.getItem(PrivilegesManager.SessionLengthKey, this.storageManager.bestStorageMode());
|
||||
if(length) {
|
||||
return JSON.parse(length);
|
||||
} else {
|
||||
return PrivilegesManager.SessionLengthNone;
|
||||
}
|
||||
}
|
||||
|
||||
async getSessionExpirey() {
|
||||
let expiresAt = await this.storageManager.getItem(PrivilegesManager.SessionExpiresAtKey, this.storageManager.bestStorageMode());
|
||||
if(expiresAt) {
|
||||
return new Date(JSON.parse(expiresAt));
|
||||
} else {
|
||||
return new Date();
|
||||
}
|
||||
}
|
||||
|
||||
async actionHasPrivilegesConfigured(action) {
|
||||
return (await this.netCredentialsForAction(action)).length > 0;
|
||||
}
|
||||
|
||||
async actionRequiresPrivilege(action) {
|
||||
let expiresAt = await this.getSessionExpirey();
|
||||
if(expiresAt > new Date()) {
|
||||
return false;
|
||||
}
|
||||
return (await this.netCredentialsForAction(action)).length > 0;
|
||||
}
|
||||
|
||||
async savePrivileges() {
|
||||
let privs = await this.getPrivileges();
|
||||
privs.setDirty(true);
|
||||
this.$rootScope.sync();
|
||||
}
|
||||
|
||||
async authenticateAction(action, credentialAuthMapping) {
|
||||
var requiredCredentials = (await this.netCredentialsForAction(action));
|
||||
var successfulCredentials = [], failedCredentials = [];
|
||||
|
||||
for(let requiredCredential of requiredCredentials) {
|
||||
var passesAuth = await this._verifyAuthenticationParameters(requiredCredential, credentialAuthMapping[requiredCredential]);
|
||||
if(passesAuth) {
|
||||
successfulCredentials.push(requiredCredential);
|
||||
} else {
|
||||
failedCredentials.push(requiredCredential);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: failedCredentials.length == 0,
|
||||
successfulCredentials: successfulCredentials,
|
||||
failedCredentials: failedCredentials
|
||||
}
|
||||
}
|
||||
|
||||
async _verifyAuthenticationParameters(credential, value) {
|
||||
|
||||
let verifyAccountPassword = async (password) => {
|
||||
return this.authManager.verifyAccountPassword(password);
|
||||
}
|
||||
|
||||
let verifyLocalPasscode = async (passcode) => {
|
||||
return this.passcodeManager.verifyPasscode(passcode);
|
||||
}
|
||||
|
||||
if(credential == PrivilegesManager.CredentialAccountPassword) {
|
||||
return verifyAccountPassword(value);
|
||||
} else if(credential == PrivilegesManager.CredentialLocalPasscode) {
|
||||
return verifyLocalPasscode(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module('app').service('privilegesManager', PrivilegesManager);
|
||||
|
||||
@@ -115,17 +115,8 @@
|
||||
}
|
||||
|
||||
.tags-string {
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pinned {
|
||||
.icon {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-top: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.note-preview {
|
||||
@@ -163,9 +154,45 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.note-flag {
|
||||
margin-right: 10px;
|
||||
.flag {
|
||||
padding: 4px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
border-radius: 2px;
|
||||
margin-right: 4px;
|
||||
|
||||
&.info {
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: var(--sn-stylekit-success-color);
|
||||
color: var(--sn-stylekit-success-contrast-color);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--sn-stylekit-warning-color);
|
||||
color: var(--sn-stylekit-warning-contrast-color);
|
||||
}
|
||||
|
||||
&.neutral {
|
||||
background-color: var(--sn-stylekit-neutral-color);
|
||||
color: var(--sn-stylekit-neutral-contrast-color);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: var(--sn-stylekit-danger-color);
|
||||
color: var(--sn-stylekit-danger-contrast-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,8 +213,9 @@
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
|
||||
.note-flag {
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
.note-flags .flag {
|
||||
background-color: var(--sn-stylekit-info-contrast-color);
|
||||
color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
progress {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#tags-title-bar {
|
||||
.tags-title-section {
|
||||
color: var(--sn-stylekit-secondary-foreground-color);
|
||||
padding-top: 15px;
|
||||
padding-bottom: 8px;
|
||||
@@ -37,6 +37,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.no-tags-placeholder {
|
||||
padding: 0px 12px;
|
||||
font-size: 12px;
|
||||
opacity: 0.4;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
min-height: 30px;
|
||||
padding: 5px 12px;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
.sk-label Disable Active Theme
|
||||
|
||||
.sn-component{"ng-if" => "expired"}
|
||||
.sk-app-bar
|
||||
.sk-app-bar.no-edges.no-top-edge
|
||||
.left
|
||||
.sk-app-bar-item
|
||||
.sk-app-bar-item-column
|
||||
|
||||
@@ -36,6 +36,6 @@
|
||||
Note that when your application is unlocked, your data exists in temporary memory in an unencrypted state.
|
||||
Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
|
||||
%p.sk-p
|
||||
Privileges sync across your other devices (not including mobile); however, note that if you require
|
||||
Privileges sync across your other devices; however, note that if you require
|
||||
a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode
|
||||
requirement will be ignored on that device.
|
||||
|
||||
@@ -38,7 +38,14 @@
|
||||
%menu-row{"label" => "ctrl.note.locked ? 'Unlock' : 'Lock'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleLockNote()", "desc" => "'Locking notes prevents unintentional editing'"}
|
||||
%menu-row{"label" => "ctrl.note.content.protected ? 'Unprotect' : 'Protect'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()", "desc" => "'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'"}
|
||||
%menu-row{"label" => "'Preview'", "circle" => "ctrl.note.content.hidePreview ? 'danger' : 'success'", "circle-align" => "'right'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
|
||||
%menu-row{"label" => "'Delete'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "desc" => "'Delete this note permanently from all your devices'"}
|
||||
%menu-row{"ng-show" => "!ctrl.note.content.trashed", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "desc" => "'Send this note to the trash'"}
|
||||
|
||||
.sk-menu-panel-section{"ng-if" => "ctrl.note.content.trashed"}
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Trash
|
||||
%menu-row{"label" => "'Restore Note'", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "desc" => "'Undelete this note and restore it back into your notes'"}
|
||||
%menu-row{"label" => "'Delete Forever'", "action" => "ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()", "desc" => "'Delete this note permanently from all your devices'"}
|
||||
%menu-row{"label" => "'Empty Trash'", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "desc" => "'Permanently delete all notes in the trash'"}
|
||||
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
|
||||
@@ -21,12 +21,17 @@
|
||||
%component-modal{"ng-if" => "room.showRoom", "component" => "room", "on-dismiss" => "ctrl.onRoomDismiss"}
|
||||
|
||||
|
||||
.center
|
||||
.sk-app-bar-item{"ng-show" => "ctrl.arbitraryStatusMessage"}
|
||||
.sk-app-bar-item-column
|
||||
%span.neutral.sk-label {{ctrl.arbitraryStatusMessage}}
|
||||
|
||||
.right
|
||||
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.securityUpdateAvailable", "ng-click" => "ctrl.openSecurityUpdate()"}
|
||||
.sk-app-bar-item{"ng-show" => "ctrl.securityUpdateAvailable", "ng-click" => "ctrl.openSecurityUpdate()"}
|
||||
%span.success.sk-label Security update available.
|
||||
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.newUpdateAvailable == true", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"}
|
||||
.sk-app-bar-item{"ng-show" => "ctrl.newUpdateAvailable == true", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"}
|
||||
%span.info.sk-label New update available.
|
||||
|
||||
.sk-app-bar-item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
.app#app{"ng-if" => "!needsUnlock", "ng-class" => "appClass"}
|
||||
%tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade",
|
||||
"all-tag" => "allTag", "archive-tag" => "archiveTag", "tags" => "tags", "remove-tag" => "removeTag"}
|
||||
"tags" => "tags", "remove-tag" => "removeTag"}
|
||||
%notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"}
|
||||
%editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"}
|
||||
|
||||
|
||||
@@ -32,12 +32,13 @@
|
||||
%menu-row{"label" => "'Date Modified'", "circle" => "ctrl.sortBy == 'client_updated_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()", "desc" => "'Sort notes with the most recently updated first'"}
|
||||
%menu-row{"label" => "'Title'", "circle" => "ctrl.sortBy == 'title' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByTitle()", "desc" => "'Sort notes alphabetically by their title'"}
|
||||
|
||||
.sk-menu-panel-section{"ng-if" => "!ctrl.tag.isSmartTag()"}
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Display
|
||||
|
||||
%menu-row{"label" => "'Archived Notes'", "circle" => "ctrl.showArchived ? 'success' : 'danger'", "faded" => "!ctrl.showArchived", "action" => "ctrl.selectedMenuItem(); ctrl.toggleKey('showArchived')", "desc" => "'Archived notes are usually hidden. You can explicitly show them with this option.'"}
|
||||
%menu-row{"label" => "'Pinned Notes'", "circle" => "ctrl.hidePinned ? 'danger' : 'success'", "faded" => "ctrl.hidePinned", "action" => "ctrl.selectedMenuItem(); ctrl.toggleKey('hidePinned')", "desc" => "'Pinned notes always appear on top. You can hide them temporarily with this option so you can focus on other notes in the list.'"}
|
||||
|
||||
%menu-row{"label" => "'Note Preview'", "circle" => "ctrl.hideNotePreview ? 'danger' : 'success'", "faded" => "ctrl.hideNotePreview", "action" => "ctrl.selectedMenuItem(); ctrl.toggleKey('hideNotePreview')", "desc" => "'Hide the note preview for a more condensed list of notes'"}
|
||||
%menu-row{"label" => "'Date'", "circle" => "ctrl.hideDate ? 'danger' : 'success'","faded" => "ctrl.hideDate", "action" => "ctrl.selectedMenuItem(); ctrl.toggleKey('hideDate')", "desc" => "'Hide the date displayed in each row'"}
|
||||
%menu-row{"label" => "'Tags'", "circle" => "ctrl.hideTags ? 'danger' : 'success'","faded" => "ctrl.hideTags", "action" => "ctrl.selectedMenuItem(); ctrl.toggleKey('hideTags')", "desc" => "'Hide the list of tags associated with each note'"}
|
||||
@@ -49,23 +50,11 @@
|
||||
%strong.red.medium-text{"ng-if" => "note.conflict_of"} Conflicted copy
|
||||
%strong.red.medium-text{"ng-if" => "note.errorDecrypting"} Unable to Decrypt
|
||||
|
||||
.note-flags
|
||||
.pinned.note-flag{"ng-if" => "note.pinned"}
|
||||
%i.icon.ion-bookmark
|
||||
%strong.medium-text Pinned
|
||||
.note-flags{"ng-show" => "ctrl.getNoteFlags(note).length > 0"}
|
||||
.flag{"ng-repeat" => "flag in ctrl.getNoteFlags(note)", "ng-class" => "flag.class"}
|
||||
.label {{flag.text}}
|
||||
|
||||
.archived.note-flag{"ng-if" => "note.archived && !ctrl.tag.isSmartTag()"}
|
||||
%i.icon.ion-ios-box
|
||||
%strong.medium-text Archived
|
||||
|
||||
.tags-string{"ng-if" => "ctrl.shouldShowTags(note)"}
|
||||
.faded {{note.savedTagsString || note.tagsString()}}
|
||||
|
||||
.name{"ng-if" => "note.title"}
|
||||
%span.note-flag{"ng-show" => "note.locked"}
|
||||
%i.icon.ion-locked.medium-text
|
||||
%span.note-flag{"ng-show" => "note.content.protected"}
|
||||
%i.icon.ion-eye-disabled
|
||||
.name{"ng-show" => "note.title"}
|
||||
{{note.title}}
|
||||
|
||||
.note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected"}
|
||||
@@ -73,8 +62,11 @@
|
||||
.plain-preview{"ng-if" => "!note.content.preview_html && note.content.preview_plain"} {{note.content.preview_plain}}
|
||||
.default-preview{"ng-if" => "!note.content.preview_html && !note.content.preview_plain"} {{note.text}}
|
||||
|
||||
.date.faded{"ng-if" => "!ctrl.hideDate"}
|
||||
%span{"ng-if" => "ctrl.sortBy == 'client_updated_at'"} Modified {{note.updatedAtString() || 'Now'}}
|
||||
%span{"ng-if" => "ctrl.sortBy != 'client_updated_at'"} {{note.createdAtString() || 'Now'}}
|
||||
.date.faded{"ng-show" => "!ctrl.hideDate"}
|
||||
%span{"ng-show" => "ctrl.sortBy == 'client_updated_at'"} Modified {{note.updatedAtString() || 'Now'}}
|
||||
%span{"ng-show" => "ctrl.sortBy != 'client_updated_at'"} {{note.createdAtString() || 'Now'}}
|
||||
|
||||
.tags-string{"ng-show" => "ctrl.shouldShowTags(note)"}
|
||||
.faded {{note.savedTagsString || note.tagsString()}}
|
||||
|
||||
%panel-resizer{"panel-id" => "'notes-column'", "default-width" => 300, "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"}
|
||||
|
||||
@@ -4,19 +4,25 @@
|
||||
%component-view.component-view{"component" => "ctrl.component"}
|
||||
|
||||
#tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"}
|
||||
#tags-title-bar.section-title-bar
|
||||
.tags-title-section.section-title-bar
|
||||
.section-title-bar-header
|
||||
.sk-h3.title
|
||||
%span.sk-bold Tags
|
||||
%span.sk-bold Views
|
||||
.sk-button.sk-secondary-contrast.wide{"ng-click" => "ctrl.clickedAddNewTag()", "title" => "Create a new tag"}
|
||||
.sk-label +
|
||||
|
||||
.scrollable
|
||||
.infinite-scroll
|
||||
.tag{"ng-if" => "ctrl.allTag", "ng-click" => "ctrl.selectTag(ctrl.allTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.allTag}"}
|
||||
.tag{"ng-repeat" => "tag in ctrl.smartTags", "ng-click" => "ctrl.selectTag(tag)",
|
||||
"ng-class" => "{'selected' : ctrl.selectedTag == tag, 'faded' : !tag.content.isAllTag}"}
|
||||
.info
|
||||
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.allTag.title"}
|
||||
.count {{ctrl.noteCount(ctrl.allTag)}}
|
||||
%input.title{"ng-disabled" => "true", "ng-model" => "tag.title"}
|
||||
.count{"ng-show" => "tag.content.isAllTag"} {{ctrl.noteCount(tag)}}
|
||||
|
||||
.tags-title-section.section-title-bar
|
||||
.section-title-bar-header
|
||||
.sk-h3.title
|
||||
%span.sk-bold Tags
|
||||
.tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"}
|
||||
.info
|
||||
%input.title{"ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title",
|
||||
@@ -31,8 +37,8 @@
|
||||
%a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-if" => "!ctrl.editingTag"} Rename
|
||||
%a.item{"ng-click" => "ctrl.saveTag($event, tag)", "ng-if" => "ctrl.editingTag"} Save
|
||||
%a.item{"ng-click" => "ctrl.selectedDeleteTag(tag)"} Delete
|
||||
.tag.faded{"ng-if" => "ctrl.archiveTag", "ng-click" => "ctrl.selectTag(ctrl.archiveTag)", "ng-class" => "{'selected' : ctrl.selectedTag == ctrl.archiveTag}"}
|
||||
.info
|
||||
%input.title{"ng-disabled" => "true", "ng-model" => "ctrl.archiveTag.title"}
|
||||
|
||||
.no-tags-placeholder{"ng-show" => "ctrl.tags.length == 0"}
|
||||
No tags. Create one using the add button above.
|
||||
|
||||
%panel-resizer{"panel-id" => "'tags-column'", "default-width" => 150, "on-resize-finish" => "ctrl.onPanelResize", "control" => "ctrl.panelController", "hoverable" => "true", "collapsable" => "true"}
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.0.2",
|
||||
"version": "3.0.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -7813,9 +7813,9 @@
|
||||
}
|
||||
},
|
||||
"sn-models": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/sn-models/-/sn-models-0.1.9.tgz",
|
||||
"integrity": "sha512-pkoNIHPjbUaFj8y6KwwkQIMf0Nm5LgvWSFVzYmWUL6FyKpCqf9Onlf2hhHX8HpD8BjJpeHQoBSMBxpbHtwkgYQ==",
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/sn-models/-/sn-models-0.1.12.tgz",
|
||||
"integrity": "sha512-fAiejL62ndUcEQ3ac4PURrZG0p3ZS+3jZsc35HvkF5vYKgHKhYR29VRXxUCPvTvNgn2F3U/55Aa5y9kn8XqoQg==",
|
||||
"dev": true
|
||||
},
|
||||
"sn-stylekit": {
|
||||
@@ -8097,9 +8097,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"standard-file-js": {
|
||||
"version": "0.3.22",
|
||||
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.22.tgz",
|
||||
"integrity": "sha512-nezAUHLX0K0DVtdofrWv2wuznXD8xPJLC/F/usGqtxvtqWbpH+MG99WtHidOd3D38qlGY7PER1nRbFT0WbA92w==",
|
||||
"version": "0.3.37",
|
||||
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.37.tgz",
|
||||
"integrity": "sha512-Je/vBxfWWHBrlDeLLW9Q4QLC+R9SxlYLhB8IpJVp6GZMHD+ET/LBPN+qlGJtUUI0Jr8HHJ1AUfEam9SNZYo1YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"static-extend": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.0.2",
|
||||
"version": "3.0.3",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -43,9 +43,9 @@
|
||||
"karma-phantomjs-launcher": "^1.0.2",
|
||||
"mocha": "^5.2.0",
|
||||
"serve-static": "^1.13.2",
|
||||
"sn-models": "0.1.9",
|
||||
"sn-models": "0.1.12",
|
||||
"sn-stylekit": "2.0.13",
|
||||
"standard-file-js": "0.3.22",
|
||||
"standard-file-js": "0.3.37",
|
||||
"grunt-shell": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user