2
Gemfile
2
Gemfile
@@ -23,6 +23,8 @@ gem 'sdoc', '~> 0.4.0', group: :doc
|
||||
# Used for 'respond_to' feature
|
||||
gem 'responders', '~> 2.0'
|
||||
|
||||
gem 'tzinfo-data'
|
||||
|
||||
group :development, :test do
|
||||
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
|
||||
gem 'byebug'
|
||||
|
||||
@@ -76,6 +76,7 @@ GEM
|
||||
erubi (1.7.1)
|
||||
execjs (2.7.0)
|
||||
ffi (1.9.23)
|
||||
ffi (1.9.23-x64-mingw32)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
haml (5.0.4)
|
||||
@@ -99,6 +100,8 @@ GEM
|
||||
nio4r (2.3.0)
|
||||
nokogiri (1.8.2)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
nokogiri (1.8.2-x64-mingw32)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
non-stupid-digest-assets (1.0.9)
|
||||
sprockets (>= 2.0)
|
||||
puma (3.11.4)
|
||||
@@ -172,6 +175,8 @@ GEM
|
||||
tilt (2.0.8)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2018.7)
|
||||
tzinfo (>= 1.0.0)
|
||||
uglifier (4.1.10)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
web-console (3.5.1)
|
||||
@@ -185,6 +190,7 @@ GEM
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
bower-rails (~> 0.10.0)
|
||||
@@ -207,8 +213,9 @@ DEPENDENCIES
|
||||
sdoc (~> 0.4.0)
|
||||
secure_headers
|
||||
spring
|
||||
tzinfo-data
|
||||
uglifier
|
||||
web-console (= 3.5.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.3
|
||||
1.17.1
|
||||
|
||||
@@ -5,7 +5,7 @@ module.exports = function(grunt) {
|
||||
watch: {
|
||||
haml: {
|
||||
files: ['app/assets/templates/**/*.haml'],
|
||||
tasks: ['newer:haml', 'ngtemplates', 'concat:app', 'babel', 'browserify', 'concat:dist'],
|
||||
tasks: ['newer:haml', 'ngtemplates', 'concat:app', 'babel', 'browserify', 'concat:dist', 'ngAnnotate'],
|
||||
options: {
|
||||
spawn: false,
|
||||
},
|
||||
@@ -166,6 +166,6 @@ module.exports = function(grunt) {
|
||||
grunt.registerTask('default', ['haml', 'ngtemplates', 'sass', 'concat:app', 'babel', 'browserify',
|
||||
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
|
||||
|
||||
grunt.registerTask('vendor', ['concat:app', 'babel', 'browserify',
|
||||
grunt.registerTask('vendor', ['concat:app', 'sass', 'babel', 'browserify',
|
||||
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
|
||||
};
|
||||
|
||||
@@ -22,10 +22,12 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
})
|
||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager, syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory) {
|
||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
|
||||
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory, privilegesManager) {
|
||||
|
||||
this.spellcheck = true;
|
||||
this.componentManager = componentManager;
|
||||
this.componentStack = [];
|
||||
|
||||
$rootScope.$on("sync:taking-too-long", function(){
|
||||
this.syncTakingTooLong = true;
|
||||
@@ -87,11 +89,14 @@ angular.module('app')
|
||||
}
|
||||
});
|
||||
|
||||
// Observe editor changes to see if the current note should update its editor
|
||||
|
||||
modelManager.addItemSyncObserver("editor-component-observer", "SN|Component", (allItems, validItems, deletedItems, source) => {
|
||||
if(!this.note) { return; }
|
||||
|
||||
// Reload componentStack in case new ones were added or removed
|
||||
this.reloadComponentStackArray();
|
||||
|
||||
// Observe editor changes to see if the current note should update its editor
|
||||
var editors = allItems.filter(function(item) {
|
||||
return item.isEditor();
|
||||
});
|
||||
@@ -157,24 +162,7 @@ angular.module('app')
|
||||
}
|
||||
|
||||
this.editorForNote = function(note) {
|
||||
let editors = componentManager.componentsForArea("editor-editor");
|
||||
for(var editor of editors) {
|
||||
if(editor.isExplicitlyEnabledForItem(note)) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
// No editor found for note. Use default editor, if note does not prefer system editor
|
||||
if(!note.getAppDataItem("prefersPlainEditor")) {
|
||||
return editors.filter((e) => {return e.isDefaultEditor()})[0];
|
||||
}
|
||||
}
|
||||
|
||||
this.onEditorMenuClick = function() {
|
||||
// App bar menu item click
|
||||
this.showEditorMenu = !this.showEditorMenu;
|
||||
this.showMenu = false;
|
||||
this.showExtensions = false;
|
||||
return componentManager.editorForNote(note);
|
||||
}
|
||||
|
||||
this.closeAllMenus = function() {
|
||||
@@ -183,6 +171,17 @@ angular.module('app')
|
||||
this.showExtensions = false;
|
||||
}
|
||||
|
||||
this.toggleMenu = function(menu) {
|
||||
this[menu] = !this[menu];
|
||||
|
||||
let allMenus = ['showMenu', 'showEditorMenu', 'showExtensions', 'showSessionHistory'];
|
||||
for(var candidate of allMenus) {
|
||||
if(candidate != menu) {
|
||||
this[candidate] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.editorMenuOnSelect = function(component) {
|
||||
if(!component || component.area == "editor-editor") {
|
||||
// if plain editor or other editor
|
||||
@@ -329,7 +328,7 @@ angular.module('app')
|
||||
}
|
||||
|
||||
this.showSavingStatus = function() {
|
||||
this.noteStatus = $sce.trustAsHtml("Saving...");
|
||||
this.noteStatus = {message: "Saving..."};
|
||||
}
|
||||
|
||||
this.showAllChangesSavedStatus = function() {
|
||||
@@ -340,7 +339,7 @@ angular.module('app')
|
||||
if(authManager.offline()) {
|
||||
status += " (offline)";
|
||||
}
|
||||
this.noteStatus = $sce.trustAsHtml(status);
|
||||
this.noteStatus = {message: status};
|
||||
}
|
||||
|
||||
this.showErrorStatus = function(error) {
|
||||
@@ -352,7 +351,7 @@ angular.module('app')
|
||||
}
|
||||
this.saveError = true;
|
||||
this.syncTakingTooLong = false;
|
||||
this.noteStatus = $sce.trustAsHtml(`<span class='error bold'>${error.message}</span><br>${error.desc}`)
|
||||
this.noteStatus = error;
|
||||
}
|
||||
|
||||
this.contentChanged = function() {
|
||||
@@ -382,16 +381,28 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.deleteNote = function() {
|
||||
if(this.note.locked) {
|
||||
alert("This note is locked. If you'd like to delete it, unlock it, and try again.");
|
||||
return;
|
||||
this.deleteNote = async function() {
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
if(this.note.locked) {
|
||||
alert("This note is locked. If you'd like to delete it, unlock it, and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
this.showMenu = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
this.showMenu = false;
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionDeleteNote)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionDeleteNote, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,6 +416,18 @@ angular.module('app')
|
||||
this.changesMade({dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.toggleProtectNote = function() {
|
||||
this.note.content.protected = !this.note.content.protected;
|
||||
this.changesMade({dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
|
||||
// Show privilegesManager if Protection is not yet set up
|
||||
privilegesManager.actionHasPrivilegesConfigured(PrivilegesManager.ActionViewProtectedNotes).then((configured) => {
|
||||
if(!configured) {
|
||||
privilegesManager.presentPrivilegesManagementModal();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.toggleNotePreview = function() {
|
||||
this.note.content.hidePreview = !this.note.content.hidePreview;
|
||||
this.changesMade({dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
@@ -499,7 +522,7 @@ angular.module('app')
|
||||
this.loadPreferences = function() {
|
||||
this.monospaceFont = authManager.getUserPrefValue("monospaceFont", "monospace");
|
||||
this.spellcheck = authManager.getUserPrefValue("spellcheck", true);
|
||||
this.marginResizersEnabled = authManager.getUserPrefValue("marginResizersEnabled", false);
|
||||
this.marginResizersEnabled = authManager.getUserPrefValue("marginResizersEnabled", true);
|
||||
|
||||
if(!document.getElementById("editor-content")) {
|
||||
// Elements have not yet loaded due to ng-if around wrapper
|
||||
@@ -601,7 +624,9 @@ angular.module('app')
|
||||
this.reloadComponentContext();
|
||||
}
|
||||
}, contextRequestHandler: (component) => {
|
||||
return this.note;
|
||||
if(component == this.selectedEditor || this.componentStack.includes(component)) {
|
||||
return this.note;
|
||||
}
|
||||
}, focusHandler: (component, focused) => {
|
||||
if(component.isEditor() && focused) {
|
||||
this.closeAllMenus();
|
||||
@@ -662,9 +687,18 @@ angular.module('app')
|
||||
}
|
||||
}});
|
||||
|
||||
this.reloadComponentStackArray = function() {
|
||||
this.componentStack = componentManager.componentsForArea("editor-stack").sort((a, b) => {
|
||||
// Careful here. For some reason (probably because re-assigning array everytime quickly destroys componentView elements, causing deallocs),
|
||||
// sorting by updated_at (or any other property that may always be changing)
|
||||
// causes weird problems with ext communication when changing notes or activating/deactivating in quick succession
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
this.reloadComponentContext = function() {
|
||||
// componentStack is used by the template to ng-repeat
|
||||
this.componentStack = componentManager.componentsForArea("editor-stack");
|
||||
this.reloadComponentStackArray();
|
||||
/*
|
||||
In the past, we were doing this looping code even if the note wasn't currently defined.
|
||||
The problem is if an editor stack item loaded first, requested to stream items, and the note was undefined,
|
||||
@@ -679,7 +713,7 @@ angular.module('app')
|
||||
if(this.note) {
|
||||
for(var component of this.componentStack) {
|
||||
if(component.active) {
|
||||
component.hidden = component.isExplicitlyDisabledForItem(this.note);
|
||||
component.hidden = !component.isExplicitlyEnabledForItem(this.note);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,13 +724,15 @@ angular.module('app')
|
||||
}
|
||||
|
||||
this.toggleStackComponentForCurrentItem = function(component) {
|
||||
if(component.hidden) {
|
||||
// If it's hidden, we want to show it
|
||||
// If it's not active, then hidden won't be set, and we mean to activate and show it.
|
||||
if(component.hidden || !component.active) {
|
||||
// Unhide, associate with current item
|
||||
component.hidden = false;
|
||||
this.associateComponentWithCurrentNote(component);
|
||||
if(!component.active) {
|
||||
componentManager.activateComponent(component);
|
||||
}
|
||||
this.associateComponentWithCurrentNote(component);
|
||||
componentManager.contextItemDidChangeInArea("editor-stack");
|
||||
} else {
|
||||
// not hidden, hide
|
||||
|
||||
@@ -23,7 +23,8 @@ angular.module('app')
|
||||
}
|
||||
})
|
||||
.controller('FooterCtrl', function ($rootScope, authManager, modelManager, $timeout, dbManager,
|
||||
syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager) {
|
||||
syncManager, storageManager, passcodeManager, componentManager, singletonManager, nativeExtManager,
|
||||
privilegesManager) {
|
||||
|
||||
authManager.checkForSecurityUpdate().then((available) => {
|
||||
this.securityUpdateAvailable = available;
|
||||
@@ -137,11 +138,70 @@ angular.module('app')
|
||||
|
||||
this.componentManager = componentManager;
|
||||
this.rooms = [];
|
||||
this.themesWithIcons = [];
|
||||
|
||||
modelManager.addItemSyncObserver("room-bar", "SN|Component", (allItems, validItems, deletedItems, source) => {
|
||||
this.rooms = modelManager.components.filter((candidate) => {return candidate.area == "rooms" && !candidate.deleted});
|
||||
});
|
||||
|
||||
modelManager.addItemSyncObserver("footer-bar-themes", "SN|Theme", (allItems, validItems, deletedItems, source) => {
|
||||
let themes = modelManager.validItemsForContentType("SN|Theme").filter((candidate) => {
|
||||
return !candidate.deleted && candidate.content.package_info.dock_icon;
|
||||
}).sort((a, b) => {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
|
||||
let differ = themes.length != this.themesWithIcons.length;
|
||||
|
||||
this.themesWithIcons = themes;
|
||||
|
||||
if(differ) {
|
||||
this.reloadDockShortcuts();
|
||||
}
|
||||
});
|
||||
|
||||
this.reloadDockShortcuts = function() {
|
||||
let shortcuts = [];
|
||||
for(var theme of this.themesWithIcons) {
|
||||
var icon = theme.content.package_info.dock_icon;
|
||||
if(!icon) {
|
||||
continue;
|
||||
}
|
||||
shortcuts.push({
|
||||
component: theme,
|
||||
icon: icon
|
||||
})
|
||||
}
|
||||
|
||||
this.dockShortcuts = shortcuts.sort((a, b) => {
|
||||
// circles first, then images
|
||||
|
||||
var aType = a.icon.type;
|
||||
var bType = b.icon.type;
|
||||
|
||||
if(aType == bType) {
|
||||
return 0;
|
||||
} else if(aType == "circle" && bType == "svg") {
|
||||
return -1;
|
||||
} else if(bType == "circle" && aType == "svg") {
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.initSvgForShortcut = function(shortcut) {
|
||||
var id = "dock-svg-" + shortcut.component.uuid;
|
||||
var element = document.getElementById(id);
|
||||
var parser = new DOMParser();
|
||||
var svg = shortcut.component.content.package_info.dock_icon.source;
|
||||
var doc = parser.parseFromString(svg, "image/svg+xml");
|
||||
element.appendChild(doc.documentElement);
|
||||
}
|
||||
|
||||
this.selectShortcut = function(shortcut) {
|
||||
componentManager.toggleComponent(shortcut.component);
|
||||
}
|
||||
|
||||
componentManager.registerHandler({identifier: "roomBar", areas: ["rooms", "modal"], activationHandler: (component) => {
|
||||
// RIP: There used to be code here that checked if component.active was true, and if so, displayed the component.
|
||||
// However, we no longer want to persist active state for footer extensions. If you open Extensions on one computer,
|
||||
@@ -172,7 +232,31 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.selectRoom = function(room) {
|
||||
room.showRoom = !room.showRoom;
|
||||
this.selectRoom = async function(room) {
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
room.showRoom = !room.showRoom;
|
||||
})
|
||||
}
|
||||
|
||||
if(!room.showRoom) {
|
||||
// About to show, check if has privileges
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageExtensions)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageExtensions, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
this.clickOutsideAccountMenu = function() {
|
||||
if(privilegesManager.authenticationInProgress()) {
|
||||
return;
|
||||
}
|
||||
this.showAccountMenu = false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
angular.module('app')
|
||||
.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
|
||||
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager) {
|
||||
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager,
|
||||
privilegesManager) {
|
||||
|
||||
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
|
||||
|
||||
@@ -10,6 +11,17 @@ angular.module('app')
|
||||
$rootScope.$broadcast('new-update-available', version);
|
||||
}
|
||||
|
||||
$rootScope.$on("panel-resized", (event, info) => {
|
||||
if(info.panel == "notes") { this.notesCollapsed = info.collapsed; }
|
||||
if(info.panel == "tags") { this.tagsCollapsed = info.collapsed; }
|
||||
|
||||
let appClass = "";
|
||||
if(this.notesCollapsed) { appClass += "collapsed-notes"; }
|
||||
if(this.tagsCollapsed) { appClass += " collapsed-tags"; }
|
||||
|
||||
$scope.appClass = appClass;
|
||||
})
|
||||
|
||||
/* Used to avoid circular dependencies where syncManager cannot be imported but rootScope can */
|
||||
$rootScope.sync = function(source) {
|
||||
syncManager.sync();
|
||||
@@ -83,14 +95,14 @@ angular.module('app')
|
||||
syncManager.loadLocalItems().then(() => {
|
||||
$timeout(() => {
|
||||
$scope.allTag.didLoad = true;
|
||||
$rootScope.$broadcast("initial-data-loaded");
|
||||
$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
|
||||
setInterval(function () {
|
||||
syncManager.sync();
|
||||
}, 30000);
|
||||
})
|
||||
|
||||
syncManager.sync();
|
||||
// refresh every 30s
|
||||
setInterval(function () {
|
||||
syncManager.sync();
|
||||
}, 30000);
|
||||
});
|
||||
|
||||
authManager.addEventHandler((event) => {
|
||||
@@ -196,7 +208,7 @@ angular.module('app')
|
||||
modelManager.setItemToBeDeleted(tag);
|
||||
syncManager.sync().then(() => {
|
||||
// force scope tags to update on sub directives
|
||||
$scope.safeApply();
|
||||
$rootScope.safeApply();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -218,7 +230,7 @@ angular.module('app')
|
||||
Shared Callbacks
|
||||
*/
|
||||
|
||||
$scope.safeApply = function(fn) {
|
||||
$rootScope.safeApply = function(fn) {
|
||||
var phase = this.$root.$$phase;
|
||||
if(phase == '$apply' || phase == '$digest')
|
||||
this.$eval(fn);
|
||||
@@ -250,7 +262,7 @@ angular.module('app')
|
||||
// when deleting items while ofline, we need to explictly tell angular to refresh UI
|
||||
setTimeout(function () {
|
||||
$rootScope.notifyDelete();
|
||||
$scope.safeApply();
|
||||
$rootScope.safeApply();
|
||||
}, 50);
|
||||
} else {
|
||||
$timeout(() => {
|
||||
|
||||
@@ -41,7 +41,6 @@ class LockScreen {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
angular.module('app').directive('lockScreen', () => new LockScreen);
|
||||
|
||||
@@ -31,7 +31,8 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
})
|
||||
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager, storageManager, desktopManager) {
|
||||
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
|
||||
storageManager, desktopManager, privilegesManager) {
|
||||
|
||||
this.panelController = {};
|
||||
this.searchSubmitted = false;
|
||||
@@ -44,6 +45,7 @@ angular.module('app')
|
||||
let prevSortValue = this.sortBy;
|
||||
|
||||
this.sortBy = authManager.getUserPrefValue("sortBy", "created_at");
|
||||
this.sortReverse = authManager.getUserPrefValue("sortReverse", false);
|
||||
|
||||
if(this.sortBy == "updated_at") {
|
||||
// use client_updated_at instead
|
||||
@@ -55,7 +57,6 @@ angular.module('app')
|
||||
this.selectFirstNote();
|
||||
})
|
||||
}
|
||||
this.sortDescending = this.sortBy != "title";
|
||||
|
||||
this.showArchived = authManager.getUserPrefValue("showArchived", false);
|
||||
this.hidePinned = authManager.getUserPrefValue("hidePinned", false);
|
||||
@@ -66,14 +67,18 @@ angular.module('app')
|
||||
let width = authManager.getUserPrefValue("notesPanelWidth");
|
||||
if(width) {
|
||||
this.panelController.setWidth(width);
|
||||
if(this.panelController.isCollapsed()) {
|
||||
$rootScope.$broadcast("panel-resized", {panel: "notes", collapsed: this.panelController.isCollapsed()})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loadPreferences();
|
||||
|
||||
this.onPanelResize = function(newWidth) {
|
||||
this.onPanelResize = function(newWidth, lastLeft, isAtMaxWidth, isCollapsed) {
|
||||
authManager.setUserPrefValue("notesPanelWidth", newWidth);
|
||||
authManager.syncUserPreferences();
|
||||
$rootScope.$broadcast("panel-resized", {panel: "notes", collapsed: isCollapsed})
|
||||
}
|
||||
|
||||
angular.element(document).ready(() => {
|
||||
@@ -203,19 +208,31 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.selectNote = function(note, viaClick = false) {
|
||||
this.selectNote = async function(note, viaClick = false) {
|
||||
if(!note) {
|
||||
this.createNewNote();
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectedNote = note;
|
||||
note.conflict_of = null; // clear conflict
|
||||
this.selectionMade()(note);
|
||||
this.selectedIndex = Math.max(this.visibleNotes().indexOf(note), 0);
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
this.selectedNote = note;
|
||||
note.conflict_of = null; // clear conflict
|
||||
this.selectionMade()(note);
|
||||
this.selectedIndex = Math.max(this.visibleNotes().indexOf(note), 0);
|
||||
|
||||
if(viaClick && this.isFiltering()) {
|
||||
desktopManager.searchText(this.noteFilter.text);
|
||||
if(viaClick && this.isFiltering()) {
|
||||
desktopManager.searchText(this.noteFilter.text);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(note.content.protected && await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionViewProtectedNotes)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionViewProtectedNotes, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,17 +315,21 @@ angular.module('app')
|
||||
|
||||
this.selectedSortByCreated = function() {
|
||||
this.setSortBy("created_at");
|
||||
this.sortDescending = true;
|
||||
}
|
||||
|
||||
this.selectedSortByUpdated = function() {
|
||||
this.setSortBy("client_updated_at");
|
||||
this.sortDescending = true;
|
||||
}
|
||||
|
||||
this.selectedSortByTitle = function() {
|
||||
this.setSortBy("title");
|
||||
this.sortDescending = false;
|
||||
}
|
||||
|
||||
this.toggleReverseSort = function() {
|
||||
this.selectedMenuItem();
|
||||
this.sortReverse = !this.sortReverse;
|
||||
authManager.setUserPrefValue("sortReverse", this.sortReverse);
|
||||
authManager.syncUserPreferences();
|
||||
}
|
||||
|
||||
this.setSortBy = function(type) {
|
||||
|
||||
@@ -47,13 +47,17 @@ angular.module('app')
|
||||
let width = authManager.getUserPrefValue("tagsPanelWidth");
|
||||
if(width) {
|
||||
this.panelController.setWidth(width);
|
||||
if(this.panelController.isCollapsed()) {
|
||||
$rootScope.$broadcast("panel-resized", {panel: "tags", collapsed: this.panelController.isCollapsed()})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.loadPreferences();
|
||||
|
||||
this.onPanelResize = function(newWidth) {
|
||||
this.onPanelResize = function(newWidth, lastLeft, isAtMaxWidth, isCollapsed) {
|
||||
authManager.setUserPrefValue("tagsPanelWidth", newWidth, true);
|
||||
$rootScope.$broadcast("panel-resized", {panel: "tags", collapsed: isCollapsed})
|
||||
}
|
||||
|
||||
this.componentManager = componentManager;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
angular
|
||||
.module('app')
|
||||
.directive( 'elemReady', function( $parse ) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function( $scope, elem, attrs ) {
|
||||
elem.ready(function(){
|
||||
$scope.$apply(function(){
|
||||
var func = $parse(attrs.elemReady);
|
||||
func($scope);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -2,9 +2,6 @@ angular.module('app').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]
|
||||
|
||||
|
||||
15
app/assets/javascripts/app/directives/functional/snEnter.js
Normal file
15
app/assets/javascripts/app/directives/functional/snEnter.js
Normal file
@@ -0,0 +1,15 @@
|
||||
angular
|
||||
.module('app')
|
||||
.directive('snEnter', function() {
|
||||
return function(scope, element, attrs) {
|
||||
element.bind("keydown keypress", function(event) {
|
||||
if(event.which === 13) {
|
||||
scope.$apply(function(){
|
||||
scope.$eval(attrs.snEnter, {'event': event});
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
@@ -10,10 +10,11 @@ class AccountMenu {
|
||||
}
|
||||
|
||||
controller($scope, $rootScope, authManager, modelManager, syncManager, storageManager, dbManager, passcodeManager,
|
||||
$timeout, $compile, archiveManager) {
|
||||
$timeout, $compile, archiveManager, privilegesManager) {
|
||||
'ngInject';
|
||||
|
||||
$scope.formData = {mergeLocal: true, ephemeral: false};
|
||||
|
||||
$scope.user = authManager.user;
|
||||
|
||||
syncManager.getServerURL().then((url) => {
|
||||
@@ -38,7 +39,6 @@ class AccountMenu {
|
||||
}
|
||||
|
||||
$scope.canAddPasscode = !authManager.isEphemeralSession();
|
||||
|
||||
$scope.syncStatus = syncManager.syncStatus;
|
||||
|
||||
$scope.submitMfaForm = function() {
|
||||
@@ -167,10 +167,27 @@ class AccountMenu {
|
||||
$scope.openPasswordWizard = function(type) {
|
||||
// Close the account menu
|
||||
$scope.close();
|
||||
|
||||
authManager.presentPasswordWizard(type);
|
||||
}
|
||||
|
||||
$scope.openPrivilegesModal = async function() {
|
||||
$scope.close();
|
||||
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
privilegesManager.presentPrivilegesManagementModal();
|
||||
})
|
||||
}
|
||||
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManagePrivileges)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManagePrivileges, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
// Allows indexeddb unencrypted logs to be deleted
|
||||
// clearAllModels will remove data from backing store, but not from working memory
|
||||
// See: https://github.com/standardnotes/desktop/issues/131
|
||||
@@ -229,36 +246,49 @@ class AccountMenu {
|
||||
})
|
||||
}
|
||||
|
||||
$scope.importFileSelected = function(files) {
|
||||
$scope.importData = {};
|
||||
$scope.importFileSelected = async function(files) {
|
||||
|
||||
var file = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
var data = JSON.parse(e.target.result);
|
||||
$timeout(function(){
|
||||
if(data.auth_params) {
|
||||
// request password
|
||||
$scope.importData.requestPassword = true;
|
||||
$scope.importData.data = data;
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
$scope.importData = {};
|
||||
|
||||
$timeout(() => {
|
||||
var element = document.getElementById("import-password-request");
|
||||
if(element) {
|
||||
element.scrollIntoView(false);
|
||||
var file = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
var data = JSON.parse(e.target.result);
|
||||
$timeout(function(){
|
||||
if(data.auth_params) {
|
||||
// request password
|
||||
$scope.importData.requestPassword = true;
|
||||
$scope.importData.data = data;
|
||||
|
||||
$timeout(() => {
|
||||
var element = document.getElementById("import-password-request");
|
||||
if(element) {
|
||||
element.scrollIntoView(false);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$scope.performImport(data, null);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
$scope.performImport(data, null);
|
||||
} catch (e) {
|
||||
alert("Unable to open file. Ensure it is a proper JSON file and try again.");
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
alert("Unable to open file. Ensure it is a proper JSON file and try again.");
|
||||
}
|
||||
}
|
||||
|
||||
reader.readAsText(file);
|
||||
})
|
||||
}
|
||||
|
||||
reader.readAsText(file);
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.importJSONData = function(data, password, callback) {
|
||||
@@ -316,7 +346,7 @@ class AccountMenu {
|
||||
Export
|
||||
*/
|
||||
|
||||
$scope.downloadDataArchive = function() {
|
||||
$scope.downloadDataArchive = async function() {
|
||||
archiveManager.downloadBackup($scope.archiveFormData.encrypted);
|
||||
}
|
||||
|
||||
@@ -362,6 +392,35 @@ class AccountMenu {
|
||||
Passcode Lock
|
||||
*/
|
||||
|
||||
$scope.passcodeAutoLockOptions = passcodeManager.getAutoLockIntervalOptions();
|
||||
|
||||
$scope.reloadAutoLockInterval = function() {
|
||||
passcodeManager.getAutoLockInterval().then((interval) => {
|
||||
$timeout(() => {
|
||||
$scope.selectedAutoLockInterval = interval;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
$scope.reloadAutoLockInterval();
|
||||
|
||||
$scope.selectAutoLockInterval = async function(interval) {
|
||||
let run = async () => {
|
||||
await passcodeManager.setAutoLockInterval(interval);
|
||||
$timeout(() => {
|
||||
$scope.reloadAutoLockInterval();
|
||||
});
|
||||
}
|
||||
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManagePasscode)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManagePasscode, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.hasPasscode = function() {
|
||||
return passcodeManager.hasPasscode();
|
||||
}
|
||||
@@ -377,10 +436,12 @@ class AccountMenu {
|
||||
return;
|
||||
}
|
||||
|
||||
let fn = $scope.formData.changingPasscode ? passcodeManager.changePasscode : passcodeManager.setPasscode;
|
||||
let fn = $scope.formData.changingPasscode ? passcodeManager.changePasscode.bind(passcodeManager) : passcodeManager.setPasscode.bind(passcodeManager);
|
||||
|
||||
fn(passcode, () => {
|
||||
$timeout(function(){
|
||||
$timeout(() => {
|
||||
$scope.formData.passcode = null;
|
||||
$scope.formData.confirmPasscode = null;
|
||||
$scope.formData.showPasscodeForm = false;
|
||||
var offline = authManager.offline();
|
||||
|
||||
@@ -393,27 +454,50 @@ class AccountMenu {
|
||||
})
|
||||
}
|
||||
|
||||
$scope.changePasscodePressed = function() {
|
||||
$scope.formData.changingPasscode = true;
|
||||
$scope.addPasscodeClicked();
|
||||
$scope.formData.changingPasscode = false;
|
||||
$scope.changePasscodePressed = async function() {
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
$scope.formData.changingPasscode = true;
|
||||
$scope.addPasscodeClicked();
|
||||
})
|
||||
}
|
||||
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManagePasscode)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManagePasscode, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.removePasscodePressed = function() {
|
||||
var signedIn = !authManager.offline();
|
||||
var message = "Are you sure you want to remove your local passcode?";
|
||||
if(!signedIn) {
|
||||
message += " This will remove encryption from your local data.";
|
||||
}
|
||||
if(confirm(message)) {
|
||||
passcodeManager.clearPasscode();
|
||||
$scope.removePasscodePressed = async function() {
|
||||
let run = () => {
|
||||
$timeout(() => {
|
||||
var signedIn = !authManager.offline();
|
||||
var message = "Are you sure you want to remove your local passcode?";
|
||||
if(!signedIn) {
|
||||
message += " This will remove encryption from your local data.";
|
||||
}
|
||||
if(confirm(message)) {
|
||||
passcodeManager.clearPasscode();
|
||||
|
||||
if(authManager.offline()) {
|
||||
syncManager.markAllItemsDirtyAndSaveOffline();
|
||||
// Don't create backup here, as if the user is temporarily removing the passcode to change it,
|
||||
// we don't want to write unencrypted data to disk.
|
||||
// $rootScope.$broadcast("major-data-change");
|
||||
}
|
||||
if(authManager.offline()) {
|
||||
syncManager.markAllItemsDirtyAndSaveOffline();
|
||||
// Don't create backup here, as if the user is temporarily removing the passcode to change it,
|
||||
// we don't want to write unencrypted data to disk.
|
||||
// $rootScope.$broadcast("major-data-change");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(await privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManagePasscode)) {
|
||||
privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManagePasscode, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,16 @@ class ComponentView {
|
||||
controller($scope, $rootScope, $timeout, componentManager, desktopManager, themeManager) {
|
||||
'ngInject';
|
||||
|
||||
$scope.onVisibilityChange = function() {
|
||||
if(document.visibilityState == "hidden") {
|
||||
return;
|
||||
}
|
||||
|
||||
if($scope.issueLoading) {
|
||||
$scope.reloadComponent();
|
||||
}
|
||||
}
|
||||
|
||||
$scope.themeHandlerIdentifier = "component-view-" + Math.random();
|
||||
componentManager.registerHandler({identifier: $scope.themeHandlerIdentifier, areas: ["themes"], activationHandler: (component) => {
|
||||
$scope.reloadThemeStatus();
|
||||
@@ -45,34 +55,58 @@ class ComponentView {
|
||||
return;
|
||||
}
|
||||
|
||||
// activationHandlers may be called multiple times, design below to be idempotent
|
||||
if(component.active) {
|
||||
$scope.loading = true;
|
||||
let iframe = componentManager.iframeForComponent(component);
|
||||
if(iframe) {
|
||||
// begin loading error handler. If onload isn't called in x seconds, display an error
|
||||
if($scope.loadTimeout) { $timeout.cancel($scope.loadTimeout);}
|
||||
$scope.loadTimeout = $timeout(() => {
|
||||
if($scope.loading) {
|
||||
$scope.issueLoading = true;
|
||||
}
|
||||
}, 3500);
|
||||
iframe.onload = function(event) {
|
||||
// console.log("iframe loaded for component", component.name, "cancelling load timeout", $scope.loadTimeout);
|
||||
$timeout.cancel($scope.loadTimeout);
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = false;
|
||||
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
||||
}.bind(this);
|
||||
}
|
||||
}
|
||||
},
|
||||
$timeout(() => {
|
||||
$scope.handleActivation();
|
||||
})
|
||||
},
|
||||
actionHandler: (component, action, data) => {
|
||||
if(action == "set-size") {
|
||||
componentManager.handleSetSizeEvent(component, data);
|
||||
}
|
||||
}}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.handleActivation = function() {
|
||||
// activationHandlers may be called multiple times, design below to be idempotent
|
||||
let component = $scope.component;
|
||||
if(!component.active) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
let iframe = componentManager.iframeForComponent(component);
|
||||
if(iframe) {
|
||||
// begin loading error handler. If onload isn't called in x seconds, display an error
|
||||
if($scope.loadTimeout) { $timeout.cancel($scope.loadTimeout);}
|
||||
$scope.loadTimeout = $timeout(() => {
|
||||
if($scope.loading) {
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = true;
|
||||
|
||||
if(!$scope.didAttemptReload) {
|
||||
$scope.didAttemptReload = true;
|
||||
$scope.reloadComponent();
|
||||
} else {
|
||||
// We'll attempt to reload when the tab gains focus
|
||||
document.addEventListener("visibilitychange", $scope.onVisibilityChange);
|
||||
}
|
||||
}
|
||||
}, 3500);
|
||||
iframe.onload = (event) => {
|
||||
$timeout.cancel($scope.loadTimeout);
|
||||
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
||||
|
||||
// Add small timeout to, as $scope.loading controls loading overlay,
|
||||
// which is used to avoid flicker when enabling extensions while having an enabled theme
|
||||
// we don't use ng-show because it causes problems with rendering iframes after timeout, for some reason.
|
||||
$timeout(() => {
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = false;
|
||||
}, 7)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
@@ -106,7 +140,11 @@ class ComponentView {
|
||||
|
||||
$scope.reloadComponent = function() {
|
||||
console.log("Reloading component", $scope.component);
|
||||
componentManager.reloadComponent($scope.component);
|
||||
// force iFrame to deinit, allows new one to be created
|
||||
$scope.componentValid = false;
|
||||
componentManager.reloadComponent($scope.component).then(() => {
|
||||
$scope.reloadStatus();
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reloadStatus = function(doManualReload = true) {
|
||||
@@ -114,7 +152,7 @@ class ComponentView {
|
||||
$scope.reloading = true;
|
||||
let previouslyValid = $scope.componentValid;
|
||||
|
||||
var expired, offlineRestricted, urlError;
|
||||
var offlineRestricted, urlError;
|
||||
|
||||
offlineRestricted = component.offlineOnly && !isDesktopApplication();
|
||||
|
||||
@@ -123,13 +161,23 @@ class ComponentView {
|
||||
||
|
||||
(isDesktopApplication() && (!component.local_url && !component.hasValidHostedUrl()))
|
||||
|
||||
expired = component.valid_until && component.valid_until <= new Date();
|
||||
$scope.expired = component.valid_until && component.valid_until <= new Date();
|
||||
|
||||
$scope.componentValid = !offlineRestricted && !urlError && !expired;
|
||||
// Here we choose our own readonly state based on custom logic. However, if a parent
|
||||
// wants to implement their own readonly logic, they can lock it.
|
||||
if(!component.lockReadonly) {
|
||||
component.readonly = $scope.expired;
|
||||
}
|
||||
|
||||
$scope.componentValid = !offlineRestricted && !urlError;
|
||||
|
||||
if(!$scope.componentValid) {
|
||||
// required to disable overlay
|
||||
$scope.loading = false;
|
||||
}
|
||||
|
||||
if(offlineRestricted) $scope.error = 'offline-restricted';
|
||||
else if(urlError) $scope.error = 'url-missing';
|
||||
else if(expired) $scope.error = 'expired';
|
||||
else $scope.error = null;
|
||||
|
||||
if($scope.componentValid !== previouslyValid) {
|
||||
@@ -139,7 +187,7 @@ class ComponentView {
|
||||
}
|
||||
}
|
||||
|
||||
if(expired && doManualReload) {
|
||||
if($scope.expired && doManualReload) {
|
||||
// Try reloading, handled by footer, which will open Extensions window momentarily to pull in latest data
|
||||
// Upon completion, this method, reloadStatus, will be called, upon where doManualReload will be false to prevent recursion.
|
||||
$rootScope.$broadcast("reload-ext-data");
|
||||
@@ -189,10 +237,10 @@ class ComponentView {
|
||||
}
|
||||
|
||||
desktopManager.deregisterUpdateObserver($scope.updateObserver);
|
||||
document.removeEventListener("visibilitychange", $scope.onVisibilityChange);
|
||||
}
|
||||
|
||||
$scope.$on("$destroy", function() {
|
||||
// console.log("Deregistering handler", $scope.identifier, $scope.component.name);
|
||||
$scope.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ class EditorMenu {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
|
||||
$scope.stack = componentManager.componentsForArea("editor-stack").sort((a, b) => {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
|
||||
$scope.isDesktop = isDesktopApplication();
|
||||
|
||||
$scope.defaultEditor = $scope.editors.filter((e) => {return e.isDefaultEditor()})[0];
|
||||
@@ -77,17 +73,10 @@ class EditorMenu {
|
||||
|
||||
if(component == $scope.selectedEditor) {
|
||||
return true;
|
||||
} else if(component.area == "editor-stack") {
|
||||
return $scope.stackComponentEnabled(component);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$scope.stackComponentEnabled = function(component) {
|
||||
return component.active && !component.isExplicitlyDisabledForItem($scope.currentItem);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ class MenuRow {
|
||||
this.scope = {
|
||||
action: "&",
|
||||
circle: "=",
|
||||
circleAlign: "=",
|
||||
label: "=",
|
||||
subtitle: "=",
|
||||
hasButton: "=",
|
||||
|
||||
@@ -32,6 +32,10 @@ class PanelResizer {
|
||||
scope.control.flash = function() {
|
||||
scope.flash();
|
||||
}
|
||||
|
||||
scope.control.isCollapsed = function() {
|
||||
return scope.isCollapsed();
|
||||
}
|
||||
}
|
||||
|
||||
controller($scope, $element, modelManager, actionsManager, $timeout, $compile) {
|
||||
@@ -52,16 +56,19 @@ class PanelResizer {
|
||||
// Handle Double Click Event
|
||||
var widthBeforeLastDblClick = 0;
|
||||
resizerColumn.ondblclick = () => {
|
||||
var collapsed = $scope.isCollapsed();
|
||||
$timeout(() => {
|
||||
if(collapsed) {
|
||||
var preClickCollapseState = $scope.isCollapsed();
|
||||
if(preClickCollapseState) {
|
||||
$scope.setWidth(widthBeforeLastDblClick || $scope.defaultWidth);
|
||||
} else {
|
||||
widthBeforeLastDblClick = lastWidth;
|
||||
$scope.setWidth(minWidth);
|
||||
}
|
||||
|
||||
$scope.finishSettingWidth();
|
||||
$scope.onResizeFinish()(lastWidth, lastLeft, $scope.isAtMaxWidth());
|
||||
|
||||
var newCollapseState = !preClickCollapseState;
|
||||
$scope.onResizeFinish()(lastWidth, lastLeft, $scope.isAtMaxWidth(), newCollapseState);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -274,7 +281,7 @@ class PanelResizer {
|
||||
let isMaxWidth = $scope.isAtMaxWidth();
|
||||
|
||||
if($scope.onResizeFinish) {
|
||||
$scope.onResizeFinish()(lastWidth, lastLeft, isMaxWidth);
|
||||
$scope.onResizeFinish()(lastWidth, lastLeft, isMaxWidth, $scope.isCollapsed());
|
||||
}
|
||||
|
||||
$scope.finishSettingWidth();
|
||||
|
||||
@@ -102,6 +102,9 @@ class PasswordWizard {
|
||||
if(preprocessor) {
|
||||
preprocessor(() => {
|
||||
next();
|
||||
}, () => {
|
||||
// on fail
|
||||
$scope.isContinuing = false;
|
||||
})
|
||||
} else {
|
||||
next();
|
||||
@@ -114,7 +117,7 @@ class PasswordWizard {
|
||||
|
||||
$scope.preprocessorForStep = function(step) {
|
||||
if(step == PasswordStep) {
|
||||
return (callback) => {
|
||||
return (onSuccess, onFail) => {
|
||||
$scope.showSpinner = true;
|
||||
$scope.continueTitle = "Generating Keys...";
|
||||
$timeout(() => {
|
||||
@@ -122,7 +125,9 @@ class PasswordWizard {
|
||||
$scope.showSpinner = false;
|
||||
$scope.continueTitle = DefaultContinueTitle;
|
||||
if(success) {
|
||||
callback();
|
||||
onSuccess();
|
||||
} else {
|
||||
onFail && onFail();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
class PrivilegesAuthModal {
|
||||
|
||||
constructor() {
|
||||
this.restrict = "E";
|
||||
this.templateUrl = "directives/privileges-auth-modal.html";
|
||||
this.scope = {
|
||||
action: "=",
|
||||
onSuccess: "=",
|
||||
onCancel: "=",
|
||||
};
|
||||
}
|
||||
|
||||
link($scope, el, attrs) {
|
||||
$scope.dismiss = function() {
|
||||
el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
controller($scope, privilegesManager, passcodeManager, authManager, $timeout) {
|
||||
'ngInject';
|
||||
|
||||
$scope.authenticationParameters = {};
|
||||
$scope.sessionLengthOptions = privilegesManager.getSessionLengthOptions();
|
||||
|
||||
privilegesManager.getSelectedSessionLength().then((length) => {
|
||||
$timeout(() => {
|
||||
$scope.selectedSessionLength = length;
|
||||
})
|
||||
})
|
||||
|
||||
$scope.selectSessionLength = function(length) {
|
||||
$scope.selectedSessionLength = length;
|
||||
}
|
||||
|
||||
privilegesManager.netCredentialsForAction($scope.action).then((credentials) => {
|
||||
$timeout(() => {
|
||||
$scope.requiredCredentials = credentials.sort();
|
||||
});
|
||||
});
|
||||
|
||||
$scope.promptForCredential = function(credential) {
|
||||
return privilegesManager.displayInfoForCredential(credential).prompt;
|
||||
}
|
||||
|
||||
$scope.cancel = function() {
|
||||
$scope.dismiss();
|
||||
$scope.onCancel && $scope.onCancel();
|
||||
}
|
||||
|
||||
$scope.isCredentialInFailureState = function(credential) {
|
||||
if(!$scope.failedCredentials) {
|
||||
return false;
|
||||
}
|
||||
return $scope.failedCredentials.find((candidate) => {
|
||||
return candidate == credential;
|
||||
}) != null;
|
||||
}
|
||||
|
||||
$scope.validate = function() {
|
||||
var failed = [];
|
||||
for(var cred of $scope.requiredCredentials) {
|
||||
var value = $scope.authenticationParameters[cred];
|
||||
if(!value || value.length == 0) {
|
||||
failed.push(cred);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.failedCredentials = failed;
|
||||
return failed.length == 0;
|
||||
}
|
||||
|
||||
$scope.submit = function() {
|
||||
if(!$scope.validate()) {
|
||||
return;
|
||||
}
|
||||
privilegesManager.authenticateAction($scope.action, $scope.authenticationParameters).then((result) => {
|
||||
$timeout(() => {
|
||||
if(result.success) {
|
||||
privilegesManager.setSessionLength($scope.selectedSessionLength);
|
||||
$scope.onSuccess();
|
||||
$scope.dismiss();
|
||||
} else {
|
||||
$scope.failedCredentials = result.failedCredentials;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').directive('privilegesAuthModal', () => new PrivilegesAuthModal);
|
||||
@@ -0,0 +1,88 @@
|
||||
class PrivilegesManagementModal {
|
||||
|
||||
constructor() {
|
||||
this.restrict = "E";
|
||||
this.templateUrl = "directives/privileges-management-modal.html";
|
||||
this.scope = {
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
link($scope, el, attrs) {
|
||||
$scope.dismiss = function() {
|
||||
el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
controller($scope, privilegesManager, passcodeManager, authManager, $timeout) {
|
||||
'ngInject';
|
||||
|
||||
$scope.dummy = {};
|
||||
|
||||
$scope.hasPasscode = passcodeManager.hasPasscode();
|
||||
$scope.hasAccount = !authManager.offline();
|
||||
|
||||
$scope.displayInfoForCredential = function(credential) {
|
||||
let info = privilegesManager.displayInfoForCredential(credential);
|
||||
if(credential == PrivilegesManager.CredentialLocalPasscode) {
|
||||
info["availability"] = $scope.hasPasscode;
|
||||
} else if(credential == PrivilegesManager.CredentialAccountPassword) {
|
||||
info["availability"] = $scope.hasAccount;
|
||||
} else {
|
||||
info["availability"] = true;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
$scope.displayInfoForAction = function(action) {
|
||||
return privilegesManager.displayInfoForAction(action).label;
|
||||
}
|
||||
|
||||
$scope.isCredentialRequiredForAction = function(action, credential) {
|
||||
if(!$scope.privileges) {
|
||||
return false;
|
||||
}
|
||||
return $scope.privileges.isCredentialRequiredForAction(action, credential);
|
||||
}
|
||||
|
||||
$scope.clearSession = function() {
|
||||
privilegesManager.clearSession().then(() => {
|
||||
$scope.reloadPrivileges();
|
||||
})
|
||||
}
|
||||
|
||||
$scope.reloadPrivileges = async function() {
|
||||
$scope.availableActions = privilegesManager.getAvailableActions();
|
||||
$scope.availableCredentials = privilegesManager.getAvailableCredentials();
|
||||
let sessionEndDate = await privilegesManager.getSessionExpirey();
|
||||
$scope.sessionExpirey = sessionEndDate.toLocaleString();
|
||||
$scope.sessionExpired = new Date() >= sessionEndDate;
|
||||
|
||||
$scope.credentialDisplayInfo = {};
|
||||
for(let cred of $scope.availableCredentials) {
|
||||
$scope.credentialDisplayInfo[cred] = $scope.displayInfoForCredential(cred);
|
||||
}
|
||||
|
||||
privilegesManager.getPrivileges().then((privs) => {
|
||||
$timeout(() => {
|
||||
$scope.privileges = privs;
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
$scope.checkboxValueChanged = function(action, credential) {
|
||||
$scope.privileges.toggleCredentialForAction(action, credential);
|
||||
privilegesManager.savePrivileges();
|
||||
}
|
||||
|
||||
$scope.reloadPrivileges();
|
||||
|
||||
$scope.cancel = function() {
|
||||
$scope.dismiss();
|
||||
$scope.onCancel && $scope.onCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').directive('privilegesManagementModal', () => new PrivilegesManagementModal);
|
||||
@@ -10,15 +10,56 @@ class RevisionPreviewModal {
|
||||
}
|
||||
|
||||
link($scope, el, attrs) {
|
||||
|
||||
$scope.dismiss = function() {
|
||||
el.remove();
|
||||
}
|
||||
$scope.el = el;
|
||||
}
|
||||
|
||||
controller($scope, modelManager, syncManager) {
|
||||
controller($scope, modelManager, syncManager, componentManager, $timeout) {
|
||||
'ngInject';
|
||||
|
||||
$scope.dismiss = function() {
|
||||
$scope.el.remove();
|
||||
$scope.$destroy();
|
||||
}
|
||||
|
||||
$scope.$on("$destroy", function() {
|
||||
if($scope.identifier) {
|
||||
componentManager.deregisterHandler($scope.identifier);
|
||||
}
|
||||
});
|
||||
|
||||
$scope.note = new SFItem({content: $scope.content, content_type: "Note"});
|
||||
// Set UUID to editoForNote can find proper editor,
|
||||
// but then generate new uuid for note as not to save changes to original, if editor makes changes.
|
||||
$scope.note.uuid = $scope.uuid;
|
||||
let editorForNote = componentManager.editorForNote($scope.note);
|
||||
$scope.note.uuid = SFJS.crypto.generateUUIDSync();
|
||||
|
||||
if(editorForNote) {
|
||||
// Create temporary copy, as a lot of componentManager is uuid based,
|
||||
// so might interfere with active editor. Be sure to copy only the content, as the
|
||||
// top level editor object has non-copyable properties like .window, which cannot be transfered
|
||||
let editorCopy = new SNComponent({content: editorForNote.content});
|
||||
editorCopy.readonly = true;
|
||||
editorCopy.lockReadonly = true;
|
||||
$scope.identifier = editorCopy.uuid;
|
||||
|
||||
componentManager.registerHandler({identifier: $scope.identifier, areas: ["editor-editor"],
|
||||
contextRequestHandler: (component) => {
|
||||
if(component == $scope.editor) {
|
||||
return $scope.note;
|
||||
}
|
||||
},
|
||||
componentForSessionKeyHandler: (key) => {
|
||||
if(key == $scope.editor.sessionKey) {
|
||||
return $scope.editor;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$scope.editor = editorCopy;
|
||||
}
|
||||
|
||||
|
||||
$scope.restore = function(asCopy) {
|
||||
if(!asCopy && !confirm("Are you sure you want to replace the current note's contents with what you see in this preview?")) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module('app')
|
||||
.filter('sortBy', function ($filter) {
|
||||
return function(items, sortBy) {
|
||||
return function(items, sortBy, reverse) {
|
||||
let sortValueFn = (a, b, pinCheck = false) => {
|
||||
if(!pinCheck) {
|
||||
if(a.pinned && b.pinned) {
|
||||
@@ -14,6 +14,11 @@ angular.module('app')
|
||||
var bValue = b[sortBy] || "";
|
||||
|
||||
let vector = 1;
|
||||
|
||||
if(reverse) {
|
||||
vector *= -1;
|
||||
}
|
||||
|
||||
if(sortBy == "title") {
|
||||
aValue = aValue.toLowerCase();
|
||||
bValue = bValue.toLowerCase();
|
||||
@@ -21,11 +26,11 @@ angular.module('app')
|
||||
if(aValue.length == 0 && bValue.length == 0) {
|
||||
return 0;
|
||||
} else if(aValue.length == 0 && bValue.length != 0) {
|
||||
return 1;
|
||||
return 1 * vector;
|
||||
} else if(aValue.length != 0 && bValue.length == 0) {
|
||||
return -1;
|
||||
return -1 * vector;
|
||||
} else {
|
||||
vector = -1;
|
||||
vector *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
app/assets/javascripts/app/models/privileges.js
Normal file
34
app/assets/javascripts/app/models/privileges.js
Normal file
@@ -0,0 +1,34 @@
|
||||
class SNPrivileges extends SFItem {
|
||||
|
||||
setCredentialsForAction(action, credentials) {
|
||||
this.content.desktopPrivileges[action] = credentials;
|
||||
}
|
||||
|
||||
getCredentialsForAction(action) {
|
||||
return this.content.desktopPrivileges[action] || [];
|
||||
}
|
||||
|
||||
toggleCredentialForAction(action, credential) {
|
||||
if(this.isCredentialRequiredForAction(action, credential)) {
|
||||
this.removeCredentialForAction(action, credential);
|
||||
} else {
|
||||
this.addCredentialForAction(action, credential);
|
||||
}
|
||||
}
|
||||
|
||||
removeCredentialForAction(action, credential) {
|
||||
_.pull(this.content.desktopPrivileges[action], credential);
|
||||
}
|
||||
|
||||
addCredentialForAction(action, credential) {
|
||||
var credentials = this.getCredentialsForAction(action);
|
||||
credentials.push(credential);
|
||||
this.setCredentialsForAction(action, credentials);
|
||||
}
|
||||
|
||||
isCredentialRequiredForAction(action, credential) {
|
||||
var credentialsRequired = this.getCredentialsForAction(action);
|
||||
return credentialsRequired.includes(credential);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -202,7 +202,7 @@ class ActionsManager {
|
||||
var scope = this.$rootScope.$new(true);
|
||||
scope.uuid = uuid;
|
||||
scope.content = content;
|
||||
var el = this.$compile( "<revision-preview-modal uuid='uuid' content='content' class='modal'></revision-preview-modal>" )(scope);
|
||||
var el = this.$compile( "<revision-preview-modal uuid='uuid' content='content' class='sk-modal'></revision-preview-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
class ArchiveManager {
|
||||
|
||||
constructor(passcodeManager, authManager, modelManager) {
|
||||
constructor(passcodeManager, authManager, modelManager, privilegesManager) {
|
||||
this.passcodeManager = passcodeManager;
|
||||
this.authManager = authManager;
|
||||
this.modelManager = modelManager;
|
||||
this.privilegesManager = privilegesManager;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -15,26 +16,36 @@ class ArchiveManager {
|
||||
}
|
||||
|
||||
async downloadBackupOfItems(items, encrypted) {
|
||||
// download in Standard File format
|
||||
var keys, authParams;
|
||||
if(encrypted) {
|
||||
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
|
||||
keys = this.passcodeManager.keys();
|
||||
authParams = this.passcodeManager.passcodeAuthParams();
|
||||
} else {
|
||||
keys = await this.authManager.keys();
|
||||
authParams = await this.authManager.getAuthParams();
|
||||
let run = async () => {
|
||||
// download in Standard File format
|
||||
var keys, authParams;
|
||||
if(encrypted) {
|
||||
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
|
||||
keys = this.passcodeManager.keys();
|
||||
authParams = this.passcodeManager.passcodeAuthParams();
|
||||
} else {
|
||||
keys = await this.authManager.keys();
|
||||
authParams = await this.authManager.getAuthParams();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.__itemsData(items, keys, authParams).then((data) => {
|
||||
let modifier = encrypted ? "Encrypted" : "Decrypted";
|
||||
this.__downloadData(data, `Standard Notes ${modifier} Backup - ${this.__formattedDate()}.txt`);
|
||||
this.__itemsData(items, keys, authParams).then((data) => {
|
||||
let modifier = encrypted ? "Encrypted" : "Decrypted";
|
||||
this.__downloadData(data, `Standard Notes ${modifier} Backup - ${this.__formattedDate()}.txt`);
|
||||
|
||||
// download as zipped plain text files
|
||||
if(!keys) {
|
||||
this.__downloadZippedItems(items);
|
||||
}
|
||||
})
|
||||
// download as zipped plain text files
|
||||
if(!keys) {
|
||||
this.__downloadZippedItems(items);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(await this.privilegesManager.actionRequiresPrivilege(PrivilegesManager.ActionManageBackups)) {
|
||||
this.privilegesManager.presentPrivilegesModal(PrivilegesManager.ActionManageBackups, () => {
|
||||
run();
|
||||
});
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -44,7 +44,7 @@ class AuthManager extends SFAuthManager {
|
||||
this.storageManager.setItemsMode(StorageManager.Ephemeral);
|
||||
} else {
|
||||
this.storageManager.setModelStorageMode(StorageManager.Fixed);
|
||||
this.storageManager.setItemsMode(this.storageManager.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed);
|
||||
this.storageManager.setItemsMode(this.storageManager.bestStorageMode());
|
||||
this.storageManager.setItem("ephemeral", JSON.stringify(false), StorageManager.Fixed);
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,13 @@ class AuthManager extends SFAuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
async verifyAccountPassword(password) {
|
||||
let authParams = await this.getAuthParams();
|
||||
let keys = await SFJS.crypto.computeEncryptionKeysForUser(password, authParams);
|
||||
let success = keys.mk === (await this.keys()).mk;
|
||||
return success;
|
||||
}
|
||||
|
||||
async checkForSecurityUpdate() {
|
||||
if(this.offline()) {
|
||||
return false;
|
||||
|
||||
@@ -33,7 +33,7 @@ class ComponentManager {
|
||||
desktopManager.registerUpdateObserver((component) => {
|
||||
// Reload theme if active
|
||||
if(component.active && component.isTheme()) {
|
||||
this.postActiveThemeToAllComponents();
|
||||
this.postActiveThemesToAllComponents();
|
||||
}
|
||||
})
|
||||
|
||||
@@ -147,14 +147,15 @@ class ComponentManager {
|
||||
});
|
||||
}
|
||||
|
||||
postActiveThemeToAllComponents() {
|
||||
postActiveThemesToAllComponents() {
|
||||
for(var component of this.components) {
|
||||
// Skip over components that are themes themselves,
|
||||
// or components that are not active, or components that don't have a window
|
||||
if(component.isTheme() || !component.active || !component.window) {
|
||||
continue;
|
||||
}
|
||||
this.postActiveThemeToComponent(component);
|
||||
|
||||
this.postActiveThemesToComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,14 +163,16 @@ class ComponentManager {
|
||||
return this.componentsForArea("themes").filter((theme) => {return theme.active});
|
||||
}
|
||||
|
||||
postActiveThemeToComponent(component) {
|
||||
var themes = this.getActiveThemes();
|
||||
var urls = themes.map((theme) => {
|
||||
urlsForActiveThemes() {
|
||||
let themes = this.getActiveThemes();
|
||||
return themes.map((theme) => {
|
||||
return this.urlForComponent(theme);
|
||||
})
|
||||
var data = {
|
||||
themes: urls
|
||||
}
|
||||
}
|
||||
|
||||
postActiveThemesToComponent(component) {
|
||||
let urls = this.urlsForActiveThemes();
|
||||
let data = { themes: urls }
|
||||
|
||||
this.sendMessageToComponent(component, {action: "themes", data: data})
|
||||
}
|
||||
@@ -291,7 +294,18 @@ class ComponentManager {
|
||||
}
|
||||
|
||||
componentForSessionKey(key) {
|
||||
return _.find(this.components, {sessionKey: key});
|
||||
let component = _.find(this.components, {sessionKey: key});
|
||||
if(!component) {
|
||||
for(let handler of this.handlers) {
|
||||
if(handler.componentForSessionKeyHandler) {
|
||||
component = handler.componentForSessionKeyHandler(key);
|
||||
if(component) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
handleMessage(component, message) {
|
||||
@@ -302,6 +316,24 @@ class ComponentManager {
|
||||
return;
|
||||
}
|
||||
|
||||
// Actions that won't succeeed with readonly mode
|
||||
let readwriteActions = [
|
||||
"save-items",
|
||||
"associate-item",
|
||||
"deassociate-item",
|
||||
"create-item",
|
||||
"create-items",
|
||||
"delete-items",
|
||||
"set-component-data"
|
||||
];
|
||||
|
||||
if(component.readonly && readwriteActions.includes(message.action)) {
|
||||
// A component can be marked readonly if changes should not be saved.
|
||||
// Particullary used for revision preview windows where the notes should not be savable.
|
||||
alert(`The extension ${component.name} is trying to save, but it is in a locked state and cannot accept changes.`);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
Possible Messages:
|
||||
set-size
|
||||
@@ -343,11 +375,13 @@ class ComponentManager {
|
||||
this.handleInstallLocalComponentMessage(component, message);
|
||||
} else if(message.action === "present-conflict-resolution") {
|
||||
this.handlePresentConflictResolutionMessage(component, message);
|
||||
} else if(message.action === "duplicate-item") {
|
||||
this.handleDuplicateItemMessage(component, message);
|
||||
}
|
||||
|
||||
// Notify observers
|
||||
for(let handler of this.handlers) {
|
||||
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
|
||||
if(handler.actionHandler && (handler.areas.includes(component.area) || handler.areas.includes("*"))) {
|
||||
this.timeout(function(){
|
||||
handler.actionHandler(component, message.action, message.data);
|
||||
})
|
||||
@@ -539,6 +573,24 @@ class ComponentManager {
|
||||
});
|
||||
}
|
||||
|
||||
handleDuplicateItemMessage(component, message) {
|
||||
var itemParams = message.data.item;
|
||||
var item = this.modelManager.findItem(itemParams.uuid);
|
||||
var requiredPermissions = [
|
||||
{
|
||||
name: "stream-items",
|
||||
content_types: [item.content_type]
|
||||
}
|
||||
];
|
||||
|
||||
this.runWithPermissions(component, requiredPermissions, () => {
|
||||
var duplicate = this.modelManager.duplicateItem(item);
|
||||
this.syncManager.sync();
|
||||
|
||||
this.replyToMessage(component, message, {item: this.jsonForItem(duplicate, component)});
|
||||
});
|
||||
}
|
||||
|
||||
handleCreateItemsMessage(component, message) {
|
||||
var responseItems = message.data.item ? [message.data.item] : message.data.items;
|
||||
let uniqueContentTypes = _.uniq(responseItems.map((item) => {return item.content_type}));
|
||||
@@ -629,22 +681,35 @@ class ComponentManager {
|
||||
}
|
||||
|
||||
handleToggleComponentMessage(sourceComponent, targetComponent, message) {
|
||||
if(targetComponent.area == "modal") {
|
||||
this.openModalComponent(targetComponent);
|
||||
this.toggleComponent(targetComponent);
|
||||
}
|
||||
|
||||
toggleComponent(component) {
|
||||
if(component.area == "modal") {
|
||||
this.openModalComponent(component);
|
||||
} else {
|
||||
if(targetComponent.active) {
|
||||
this.deactivateComponent(targetComponent);
|
||||
if(component.active) {
|
||||
this.deactivateComponent(component);
|
||||
} else {
|
||||
if(targetComponent.content_type == "SN|Theme" && !targetComponent.isLayerable()) {
|
||||
if(component.content_type == "SN|Theme") {
|
||||
// Deactive currently active theme if new theme is not layerable
|
||||
var activeThemes = this.getActiveThemes();
|
||||
for(var theme of activeThemes) {
|
||||
if(theme && !theme.isLayerable()) {
|
||||
this.deactivateComponent(theme);
|
||||
}
|
||||
|
||||
// Activate current before deactivating others, so as not to flicker
|
||||
this.activateComponent(component);
|
||||
|
||||
if(!component.isLayerable()) {
|
||||
setTimeout(() => {
|
||||
for(var theme of activeThemes) {
|
||||
if(theme && !theme.isLayerable()) {
|
||||
this.deactivateComponent(theme);
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
} else {
|
||||
this.activateComponent(component);
|
||||
}
|
||||
this.activateComponent(targetComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -765,14 +830,14 @@ class ComponentManager {
|
||||
var permissions = dialog.permissions;
|
||||
var component = dialog.component;
|
||||
var callback = dialog.callback;
|
||||
var el = this.$compile( "<permissions-modal component='component' permissions='permissions' callback='callback' class='modal'></permissions-modal>" )(dialog);
|
||||
var el = this.$compile( "<permissions-modal component='component' permissions='permissions' callback='callback' class='sk-modal'></permissions-modal>" )(dialog);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
openModalComponent(component) {
|
||||
var scope = this.$rootScope.$new(true);
|
||||
scope.component = component;
|
||||
var el = this.$compile( "<component-modal component='component' class='modal'></component-modal>" )(scope);
|
||||
var el = this.$compile( "<component-modal component='component' class='sk-modal'></component-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
@@ -782,6 +847,10 @@ class ComponentManager {
|
||||
|
||||
deregisterHandler(identifier) {
|
||||
var handler = _.find(this.handlers, {identifier: identifier});
|
||||
if(!handler) {
|
||||
console.log("Attempting to deregister non-existing handler");
|
||||
return;
|
||||
}
|
||||
this.handlers.splice(this.handlers.indexOf(handler), 1);
|
||||
}
|
||||
|
||||
@@ -805,34 +874,39 @@ class ComponentManager {
|
||||
data: {
|
||||
uuid: component.uuid,
|
||||
environment: isDesktopApplication() ? "desktop" : "web",
|
||||
platform: getPlatformString()
|
||||
platform: getPlatformString(),
|
||||
activeThemeUrls: this.urlsForActiveThemes()
|
||||
}
|
||||
});
|
||||
this.postActiveThemeToComponent(component);
|
||||
this.postActiveThemesToComponent(component);
|
||||
|
||||
this.desktopManager.notifyComponentActivation(component);
|
||||
}
|
||||
|
||||
/* Performs func in timeout, but syncronously, if used `await waitTimeout` */
|
||||
async waitTimeout(func) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.timeout(() => {
|
||||
func();
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
}
|
||||
// No longer used. See comment in activateComponent.
|
||||
// async waitTimeout(func) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// this.timeout(() => {
|
||||
// func();
|
||||
// resolve();
|
||||
// });
|
||||
// })
|
||||
// }
|
||||
|
||||
|
||||
async activateComponent(component, dontSync = false) {
|
||||
var didChange = component.active != true;
|
||||
|
||||
component.active = true;
|
||||
for(var handler of this.handlers) {
|
||||
for(let handler of this.handlers) {
|
||||
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
|
||||
// We want to run the handler in a $timeout so the UI updates, but we also don't want it to run asyncronously
|
||||
// so that the steps below this one are run before the handler. So we run in a waitTimeout.
|
||||
await this.waitTimeout(() => {
|
||||
// Update 12/18: We were using this.waitTimeout previously, however, that caused the iframe.onload callback to never be called
|
||||
// for some reason for iframes on desktop inside the revision-preview-modal. So we'll use safeApply instead. I'm not quite sure
|
||||
// where the original "so the UI updates" comment applies to, but we'll have to keep an eye out to see if this causes problems somewhere else.
|
||||
this.$rootScope.safeApply(() => {
|
||||
handler.activationHandler && handler.activationHandler(component);
|
||||
})
|
||||
}
|
||||
@@ -848,7 +922,7 @@ class ComponentManager {
|
||||
}
|
||||
|
||||
if(component.area == "themes") {
|
||||
this.postActiveThemeToAllComponents();
|
||||
this.postActiveThemesToAllComponents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -859,7 +933,8 @@ class ComponentManager {
|
||||
|
||||
for(let handler of this.handlers) {
|
||||
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
|
||||
await this.waitTimeout(() => {
|
||||
// See comment in activateComponent regarding safeApply and awaitTimeout
|
||||
this.$rootScope.safeApply(() => {
|
||||
handler.activationHandler && handler.activationHandler(component);
|
||||
})
|
||||
}
|
||||
@@ -881,7 +956,7 @@ class ComponentManager {
|
||||
})
|
||||
|
||||
if(component.area == "themes") {
|
||||
this.postActiveThemeToAllComponents();
|
||||
this.postActiveThemesToAllComponents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,7 +968,8 @@ class ComponentManager {
|
||||
|
||||
for(let handler of this.handlers) {
|
||||
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
|
||||
await this.waitTimeout(() => {
|
||||
// See comment in activateComponent regarding safeApply and awaitTimeout
|
||||
this.$rootScope.safeApply(() => {
|
||||
handler.activationHandler && handler.activationHandler(component);
|
||||
})
|
||||
}
|
||||
@@ -908,7 +984,7 @@ class ComponentManager {
|
||||
})
|
||||
|
||||
if(component.area == "themes") {
|
||||
this.postActiveThemeToAllComponents();
|
||||
this.postActiveThemesToAllComponents();
|
||||
}
|
||||
|
||||
//
|
||||
@@ -919,7 +995,8 @@ class ComponentManager {
|
||||
component.active = true;
|
||||
for(var handler of this.handlers) {
|
||||
if(handler.areas.includes(component.area) || handler.areas.includes("*")) {
|
||||
await this.waitTimeout(() => {
|
||||
// See comment in activateComponent regarding safeApply and awaitTimeout
|
||||
this.$rootScope.safeApply(() => {
|
||||
handler.activationHandler && handler.activationHandler(component);
|
||||
})
|
||||
}
|
||||
@@ -930,7 +1007,7 @@ class ComponentManager {
|
||||
}
|
||||
|
||||
if(component.area == "themes") {
|
||||
this.postActiveThemeToAllComponents();
|
||||
this.postActiveThemesToAllComponents();
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -981,10 +1058,7 @@ class ComponentManager {
|
||||
if(!iframe) {
|
||||
return;
|
||||
}
|
||||
var width = data.width;
|
||||
var height = data.height;
|
||||
iframe.width = width;
|
||||
iframe.height = height;
|
||||
|
||||
setSize(iframe, data);
|
||||
|
||||
// On Firefox, resizing a component iframe does not seem to have an effect with editor-stack extensions.
|
||||
@@ -1007,6 +1081,20 @@ class ComponentManager {
|
||||
}
|
||||
}
|
||||
|
||||
editorForNote(note) {
|
||||
let editors = this.componentsForArea("editor-editor");
|
||||
for(var editor of editors) {
|
||||
if(editor.isExplicitlyEnabledForItem(note)) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
|
||||
// No editor found for note. Use default editor, if note does not prefer system editor
|
||||
if(!note.getAppDataItem("prefersPlainEditor")) {
|
||||
return editors.filter((e) => {return e.isDefaultEditor()})[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,14 @@ class DesktopManager {
|
||||
this.searchHandler = handler;
|
||||
}
|
||||
|
||||
desktop_windowGainedFocus() {
|
||||
this.$rootScope.$broadcast("window-gained-focus");
|
||||
}
|
||||
|
||||
desktop_windowLostFocus() {
|
||||
this.$rootScope.$broadcast("window-lost-focus");
|
||||
}
|
||||
|
||||
desktop_onComponentInstallationComplete(componentData, error) {
|
||||
console.log("Web|Component Installation/Update Complete", componentData, error);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class HttpManager extends SFHttpManager {
|
||||
super($timeout);
|
||||
|
||||
this.setJWTRequestHandler(async () => {
|
||||
return storageManager.getItem("jwt");;
|
||||
return storageManager.getItem("jwt");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ SFModelManager.ContentTypeClassMapping = {
|
||||
"SN|Theme" : SNTheme,
|
||||
"SN|Component" : SNComponent,
|
||||
"SF|Extension" : SNServerExtension,
|
||||
"SF|MFA" : SNMfa
|
||||
"SF|MFA" : SNMfa,
|
||||
"SN|Privileges" : SNPrivileges
|
||||
};
|
||||
|
||||
SFItem.AppDomain = "org.standardnotes.sn";
|
||||
|
||||
@@ -1,100 +1,247 @@
|
||||
angular.module('app')
|
||||
.provider('passcodeManager', function () {
|
||||
const MillisecondsPerSecond = 1000;
|
||||
|
||||
this.$get = function($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) {
|
||||
return new PasscodeManager($rootScope, $timeout, modelManager, dbManager, authManager, storageManager);
|
||||
}
|
||||
class PasscodeManager {
|
||||
|
||||
function PasscodeManager($rootScope, $timeout, modelManager, dbManager, authManager, storageManager) {
|
||||
constructor($rootScope, authManager, storageManager, syncManager) {
|
||||
this.authManager = authManager;
|
||||
this.storageManager = storageManager;
|
||||
this.syncManager = syncManager;
|
||||
this.$rootScope = $rootScope;
|
||||
|
||||
this._hasPasscode = storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
|
||||
this._hasPasscode = this.storageManager.getItemSync("offlineParams", StorageManager.Fixed) != null;
|
||||
this._locked = this._hasPasscode;
|
||||
|
||||
this.isLocked = function() {
|
||||
return this._locked;
|
||||
}
|
||||
this.passcodeChangeObservers = [];
|
||||
|
||||
this.hasPasscode = function() {
|
||||
return this._hasPasscode;
|
||||
}
|
||||
this.configureAutoLock();
|
||||
}
|
||||
|
||||
this.keys = function() {
|
||||
return this._keys;
|
||||
}
|
||||
addPasscodeChangeObserver(callback) {
|
||||
this.passcodeChangeObservers.push(callback);
|
||||
}
|
||||
|
||||
this.passcodeAuthParams = function() {
|
||||
var authParams = JSON.parse(storageManager.getItemSync("offlineParams", StorageManager.Fixed));
|
||||
if(authParams && !authParams.version) {
|
||||
var keys = this.keys();
|
||||
if(keys && keys.ak) {
|
||||
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have thier version stored in authParams.
|
||||
authParams.version = "002";
|
||||
} else {
|
||||
authParams.version = "001";
|
||||
}
|
||||
}
|
||||
return authParams;
|
||||
}
|
||||
lockApplication() {
|
||||
window.location.reload();
|
||||
this.cancelAutoLockTimer();
|
||||
}
|
||||
|
||||
this.unlock = function(passcode, callback) {
|
||||
var params = this.passcodeAuthParams();
|
||||
SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
|
||||
if(keys.pw !== params.hash) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
isLocked() {
|
||||
return this._locked;
|
||||
}
|
||||
|
||||
this._keys = keys;
|
||||
this._authParams = params;
|
||||
this.decryptLocalStorage(keys, params).then(() => {
|
||||
this._locked = false;
|
||||
callback(true);
|
||||
})
|
||||
});
|
||||
}
|
||||
hasPasscode() {
|
||||
return this._hasPasscode;
|
||||
}
|
||||
|
||||
this.setPasscode = (passcode, callback) => {
|
||||
var uuid = SFJS.crypto.generateUUIDSync();
|
||||
keys() {
|
||||
return this._keys;
|
||||
}
|
||||
|
||||
SFJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
|
||||
let keys = results.keys;
|
||||
let authParams = results.authParams;
|
||||
async setAutoLockInterval(interval) {
|
||||
return this.storageManager.setItem(PasscodeManager.AutoLockIntervalKey, JSON.stringify(interval), StorageManager.FixedEncrypted);
|
||||
}
|
||||
|
||||
authParams.hash = keys.pw;
|
||||
this._keys = keys;
|
||||
this._hasPasscode = true;
|
||||
this._authParams = authParams;
|
||||
|
||||
// Encrypting will initially clear localStorage
|
||||
this.encryptLocalStorage(keys, authParams);
|
||||
|
||||
// After it's cleared, it's safe to write to it
|
||||
storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
|
||||
callback(true);
|
||||
});
|
||||
}
|
||||
|
||||
this.changePasscode = (newPasscode, callback) => {
|
||||
this.setPasscode(newPasscode, callback);
|
||||
}
|
||||
|
||||
this.clearPasscode = function() {
|
||||
storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
|
||||
storageManager.removeItem("offlineParams", StorageManager.Fixed);
|
||||
this._keys = null;
|
||||
this._hasPasscode = false;
|
||||
}
|
||||
|
||||
this.encryptLocalStorage = function(keys, authParams) {
|
||||
storageManager.setKeys(keys, authParams);
|
||||
// Switch to Ephemeral storage, wiping Fixed storage
|
||||
// Last argument is `force`, which we set to true because in the case of changing passcode
|
||||
storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
|
||||
}
|
||||
|
||||
this.decryptLocalStorage = async function(keys, authParams) {
|
||||
storageManager.setKeys(keys, authParams);
|
||||
return storageManager.decryptStorage();
|
||||
async getAutoLockInterval() {
|
||||
let interval = await this.storageManager.getItem(PasscodeManager.AutoLockIntervalKey, StorageManager.FixedEncrypted);
|
||||
if(interval) {
|
||||
return JSON.parse(interval);
|
||||
} else {
|
||||
return PasscodeManager.AutoLockIntervalNone;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
passcodeAuthParams() {
|
||||
var authParams = JSON.parse(this.storageManager.getItemSync("offlineParams", StorageManager.Fixed));
|
||||
if(authParams && !authParams.version) {
|
||||
var keys = this.keys();
|
||||
if(keys && keys.ak) {
|
||||
// If there's no version stored, and there's an ak, it has to be 002. Newer versions would have their version stored in authParams.
|
||||
authParams.version = "002";
|
||||
} else {
|
||||
authParams.version = "001";
|
||||
}
|
||||
}
|
||||
return authParams;
|
||||
}
|
||||
|
||||
async verifyPasscode(passcode) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
var params = this.passcodeAuthParams();
|
||||
let keys = await SFJS.crypto.computeEncryptionKeysForUser(passcode, params);
|
||||
if(keys.pw !== params.hash) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unlock(passcode, callback) {
|
||||
var params = this.passcodeAuthParams();
|
||||
SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
|
||||
if(keys.pw !== params.hash) {
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._keys = keys;
|
||||
this._authParams = params;
|
||||
this.decryptLocalStorage(keys, params).then(() => {
|
||||
this._locked = false;
|
||||
callback(true);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
setPasscode(passcode, callback) {
|
||||
var uuid = SFJS.crypto.generateUUIDSync();
|
||||
|
||||
SFJS.crypto.generateInitialKeysAndAuthParamsForUser(uuid, passcode).then((results) => {
|
||||
let keys = results.keys;
|
||||
let authParams = results.authParams;
|
||||
|
||||
authParams.hash = keys.pw;
|
||||
this._keys = keys;
|
||||
this._hasPasscode = true;
|
||||
this._authParams = authParams;
|
||||
|
||||
// Encrypting will initially clear localStorage
|
||||
this.encryptLocalStorage(keys, authParams);
|
||||
|
||||
// After it's cleared, it's safe to write to it
|
||||
this.storageManager.setItem("offlineParams", JSON.stringify(authParams), StorageManager.Fixed);
|
||||
callback(true);
|
||||
|
||||
this.notifyObserversOfPasscodeChange();
|
||||
});
|
||||
}
|
||||
|
||||
changePasscode(newPasscode, callback) {
|
||||
this.setPasscode(newPasscode, callback);
|
||||
}
|
||||
|
||||
clearPasscode() {
|
||||
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.Fixed); // Transfer from Ephemeral
|
||||
this.storageManager.removeItem("offlineParams", StorageManager.Fixed);
|
||||
this._keys = null;
|
||||
this._hasPasscode = false;
|
||||
|
||||
this.notifyObserversOfPasscodeChange();
|
||||
}
|
||||
|
||||
notifyObserversOfPasscodeChange() {
|
||||
for(var observer of this.passcodeChangeObservers) {
|
||||
observer();
|
||||
}
|
||||
}
|
||||
|
||||
encryptLocalStorage(keys, authParams) {
|
||||
this.storageManager.setKeys(keys, authParams);
|
||||
// Switch to Ephemeral storage, wiping Fixed storage
|
||||
// Last argument is `force`, which we set to true because in the case of changing passcode
|
||||
this.storageManager.setItemsMode(this.authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
|
||||
}
|
||||
|
||||
async decryptLocalStorage(keys, authParams) {
|
||||
this.storageManager.setKeys(keys, authParams);
|
||||
return this.storageManager.decryptStorage();
|
||||
}
|
||||
|
||||
configureAutoLock() {
|
||||
if(isDesktopApplication()) {
|
||||
// desktop only
|
||||
this.$rootScope.$on("window-lost-focus", () => {
|
||||
this.documentVisibilityChanged(false);
|
||||
})
|
||||
this.$rootScope.$on("window-gained-focus", () => {
|
||||
this.documentVisibilityChanged(true);
|
||||
})
|
||||
} else {
|
||||
// tab visibility listender, web only
|
||||
document.addEventListener('visibilitychange', (e) => {
|
||||
let visible = document.visibilityState == "visible";
|
||||
this.documentVisibilityChanged(visible);
|
||||
});
|
||||
}
|
||||
|
||||
PasscodeManager.AutoLockIntervalNone = 0;
|
||||
PasscodeManager.AutoLockIntervalImmediate = 1;
|
||||
PasscodeManager.AutoLockIntervalOneMinute = 60 * MillisecondsPerSecond;
|
||||
PasscodeManager.AutoLockIntervalFiveMinutes = 300 * MillisecondsPerSecond;
|
||||
PasscodeManager.AutoLockIntervalOneHour = 3600 * MillisecondsPerSecond;
|
||||
|
||||
PasscodeManager.AutoLockIntervalKey = "AutoLockIntervalKey";
|
||||
}
|
||||
|
||||
getAutoLockIntervalOptions() {
|
||||
return [
|
||||
{
|
||||
value: PasscodeManager.AutoLockIntervalNone,
|
||||
label: "Off"
|
||||
},
|
||||
{
|
||||
value: PasscodeManager.AutoLockIntervalImmediate,
|
||||
label: "Immediately"
|
||||
},
|
||||
{
|
||||
value: PasscodeManager.AutoLockIntervalOneMinute,
|
||||
label: "1m"
|
||||
},
|
||||
{
|
||||
value: PasscodeManager.AutoLockIntervalFiveMinutes,
|
||||
label: "5m"
|
||||
},
|
||||
{
|
||||
value: PasscodeManager.AutoLockIntervalOneHour,
|
||||
label: "1h"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
documentVisibilityChanged(visible) {
|
||||
if(visible) {
|
||||
// check to see if lockAfterDate is not null, and if the application isn't locked.
|
||||
// if that's the case, it needs to be locked immediately.
|
||||
if(this.lockAfterDate && new Date() > this.lockAfterDate && !this.isLocked()) {
|
||||
this.lockApplication();
|
||||
} else {
|
||||
if(!this.isLocked()) {
|
||||
this.syncManager.sync();
|
||||
}
|
||||
}
|
||||
this.cancelAutoLockTimer();
|
||||
} else {
|
||||
this.beginAutoLockTimer();
|
||||
}
|
||||
}
|
||||
|
||||
async beginAutoLockTimer() {
|
||||
var interval = await this.getAutoLockInterval();
|
||||
if(interval == PasscodeManager.AutoLockIntervalNone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a timeout if possible, but if the computer is put to sleep, timeouts won't work.
|
||||
// Need to set a date as backup. this.lockAfterDate does not need to be persisted, as
|
||||
// living in memory seems sufficient. If memory is cleared, then the application will lock anyway.
|
||||
let addToNow = (seconds) => {
|
||||
let date = new Date();
|
||||
date.setSeconds(date.getSeconds() + seconds);
|
||||
return date;
|
||||
}
|
||||
|
||||
this.lockAfterDate = addToNow(interval / MillisecondsPerSecond);
|
||||
this.lockTimeout = setTimeout(() => {
|
||||
this.lockApplication();
|
||||
// We don't need to look at this anymore since we've succeeded with timeout lock
|
||||
this.lockAfterDate = null;
|
||||
}, interval);
|
||||
}
|
||||
|
||||
cancelAutoLockTimer() {
|
||||
clearTimeout(this.lockTimeout);
|
||||
this.lockAfterDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').service('passcodeManager', PasscodeManager);
|
||||
|
||||
309
app/assets/javascripts/app/services/privilegesManager.js
Normal file
309
app/assets/javascripts/app/services/privilegesManager.js
Normal file
@@ -0,0 +1,309 @@
|
||||
class PrivilegesManager {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
presentPrivilegesModal(action, onSuccess, onCancel) {
|
||||
if(this.authenticationInProgress()) {
|
||||
onCancel && onCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
let customSuccess = () => {
|
||||
onSuccess && onSuccess();
|
||||
this.currentAuthenticationElement = null;
|
||||
}
|
||||
|
||||
let customCancel = () => {
|
||||
onCancel && onCancel();
|
||||
this.currentAuthenticationElement = null;
|
||||
}
|
||||
|
||||
var scope = this.$rootScope.$new(true);
|
||||
scope.action = action;
|
||||
scope.onSuccess = customSuccess;
|
||||
scope.onCancel = customCancel;
|
||||
var el = this.$compile( "<privileges-auth-modal action='action' on-success='onSuccess' on-cancel='onCancel' class='sk-modal'></privileges-auth-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
|
||||
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);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
authenticationInProgress() {
|
||||
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);
|
||||
@@ -6,10 +6,17 @@ class SessionHistory extends SFSessionHistoryManager {
|
||||
"Note" : NoteHistoryEntry
|
||||
}
|
||||
|
||||
// Session History can be encrypted with passcode keys. If it changes, we need to resave session
|
||||
// history with the new keys.
|
||||
passcodeManager.addPasscodeChangeObserver(() => {
|
||||
this.saveToDisk();
|
||||
})
|
||||
|
||||
var keyRequestHandler = async () => {
|
||||
let offline = authManager.offline();
|
||||
let auth_params = offline ? passcodeManager.passcodeAuthParams() : await authManager.getAuthParams();
|
||||
let keys = offline ? passcodeManager.keys() : await authManager.keys();
|
||||
|
||||
return {
|
||||
keys: keys,
|
||||
offline: offline,
|
||||
|
||||
@@ -24,9 +24,29 @@ class SingletonManager {
|
||||
|
||||
$rootScope.$on("initial-data-loaded", (event, data) => {
|
||||
this.resolveSingletons(modelManager.allItems, null, true);
|
||||
this.initialDataLoaded = true;
|
||||
})
|
||||
|
||||
/*
|
||||
If an item alternates its uuid on registration, singletonHandlers might need to update
|
||||
their local reference to the object, since the object reference will change on uuid alternation
|
||||
*/
|
||||
modelManager.addModelUuidChangeObserver("singleton-manager", (oldModel, newModel) => {
|
||||
for(var handler of this.singletonHandlers) {
|
||||
if(handler.singleton && SFPredicate.ItemSatisfiesPredicates(newModel, handler.predicates)) {
|
||||
// Reference is now invalid, calling resolveSingleton should update it
|
||||
handler.singleton = null;
|
||||
this.resolveSingletons([newModel]);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$rootScope.$on("sync:completed", (event, data) => {
|
||||
// Wait for initial data load before handling any sync. If we don't want for initial data load,
|
||||
// then the singleton resolver won't have the proper items to work with to determine whether to resolve or create.
|
||||
if(!this.initialDataLoaded) {
|
||||
return;
|
||||
}
|
||||
// The reason we also need to consider savedItems in consolidating singletons is in case of sync conflicts,
|
||||
// a new item can be created, but is never processed through "retrievedItems" since it is only created locally then saved.
|
||||
|
||||
@@ -108,7 +128,6 @@ class SingletonManager {
|
||||
var singleton = allExtantItemsMatchingPredicate[0];
|
||||
singletonHandler.singleton = singleton;
|
||||
singletonHandler.resolutionCallback(singleton);
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -174,6 +174,10 @@ class StorageManager extends SFStorageManager {
|
||||
return this.getItemSync("encryptedStorage", StorageManager.Fixed) !== null;
|
||||
}
|
||||
|
||||
bestStorageMode() {
|
||||
return this.hasPasscode() ? StorageManager.FixedEncrypted : StorageManager.Fixed;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Model Storage
|
||||
|
||||
@@ -11,7 +11,7 @@ class SyncManager extends SFSyncManager {
|
||||
scope.item1 = items[0];
|
||||
scope.item2 = items[1];
|
||||
scope.callback = callback;
|
||||
var el = this.$compile( "<conflict-resolution-modal item1='item1' item2='item2' callback='callback' class='modal'></conflict-resolution-modal>" )(scope);
|
||||
var el = this.$compile( "<conflict-resolution-modal item1='item1' item2='item2' callback='callback' class='sk-modal'></conflict-resolution-modal>" )(scope);
|
||||
angular.element(document.body).append(el);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,39 @@
|
||||
class ThemeManager {
|
||||
|
||||
constructor(componentManager, desktopManager) {
|
||||
constructor(componentManager, desktopManager, storageManager, passcodeManager) {
|
||||
this.componentManager = componentManager;
|
||||
this.storageManager = storageManager;
|
||||
this.desktopManager = desktopManager;
|
||||
this.activeThemes = [];
|
||||
|
||||
desktopManager.registerUpdateObserver((component) => {
|
||||
ThemeManager.CachedThemesKey = "cachedThemes";
|
||||
|
||||
this.registerObservers();
|
||||
|
||||
// When a passcode is added, all local storage will be encrypted (it doesn't know what was
|
||||
// originally saved as Fixed or FixedEncrypted). We want to rewrite cached themes here to Fixed
|
||||
// so that it's readable without authentication.
|
||||
passcodeManager.addPasscodeChangeObserver(() => {
|
||||
this.cacheThemes();
|
||||
})
|
||||
|
||||
// The desktop application won't have its applicationDataPath until the angular document is ready,
|
||||
// so it wont be able to resolve local theme urls until thats ready
|
||||
angular.element(document).ready(() => {
|
||||
this.activateCachedThemes();
|
||||
});
|
||||
}
|
||||
|
||||
activateCachedThemes() {
|
||||
let cachedThemes = this.getCachedThemes();
|
||||
let writeToCache = false;
|
||||
for(var theme of cachedThemes) {
|
||||
this.activateTheme(theme, writeToCache);
|
||||
}
|
||||
}
|
||||
|
||||
registerObservers() {
|
||||
this.desktopManager.registerUpdateObserver((component) => {
|
||||
// Reload theme if active
|
||||
if(component.active && component.isTheme()) {
|
||||
this.deactivateTheme(component);
|
||||
@@ -13,7 +43,7 @@ class ThemeManager {
|
||||
}
|
||||
})
|
||||
|
||||
componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => {
|
||||
this.componentManager.registerHandler({identifier: "themeManager", areas: ["themes"], activationHandler: (component) => {
|
||||
if(component.active) {
|
||||
this.activateTheme(component);
|
||||
} else {
|
||||
@@ -33,9 +63,17 @@ class ThemeManager {
|
||||
this.componentManager.deactivateComponent(theme);
|
||||
}
|
||||
}
|
||||
|
||||
this.decacheThemes();
|
||||
}
|
||||
|
||||
activateTheme(theme) {
|
||||
activateTheme(theme, writeToCache = true) {
|
||||
if(_.find(this.activeThemes, {uuid: theme.uuid})) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeThemes.push(theme);
|
||||
|
||||
var url = this.componentManager.urlForComponent(theme);
|
||||
var link = document.createElement("link");
|
||||
link.href = url;
|
||||
@@ -44,6 +82,10 @@ class ThemeManager {
|
||||
link.media = "screen,print";
|
||||
link.id = theme.uuid;
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
|
||||
if(writeToCache) {
|
||||
this.cacheThemes();
|
||||
}
|
||||
}
|
||||
|
||||
deactivateTheme(theme) {
|
||||
@@ -52,6 +94,36 @@ class ThemeManager {
|
||||
element.disabled = true;
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
|
||||
_.remove(this.activeThemes, {uuid: theme.uuid});
|
||||
|
||||
this.cacheThemes();
|
||||
}
|
||||
|
||||
async cacheThemes() {
|
||||
let mapped = await Promise.all(this.activeThemes.map(async (theme) => {
|
||||
let transformer = new SFItemParams(theme);
|
||||
let params = await transformer.paramsForLocalStorage();
|
||||
return params;
|
||||
}));
|
||||
let data = JSON.stringify(mapped);
|
||||
return this.storageManager.setItem(ThemeManager.CachedThemesKey, data, StorageManager.Fixed);
|
||||
}
|
||||
|
||||
async decacheThemes() {
|
||||
return this.storageManager.removeItem(ThemeManager.CachedThemesKey, StorageManager.Fixed);
|
||||
}
|
||||
|
||||
getCachedThemes() {
|
||||
let cachedThemes = this.storageManager.getItemSync(ThemeManager.CachedThemesKey, StorageManager.Fixed);
|
||||
if(cachedThemes) {
|
||||
let parsed = JSON.parse(cachedThemes);
|
||||
return parsed.map((theme) => {
|
||||
return new SNTheme(theme);
|
||||
});
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
$heading-height: 75px;
|
||||
|
||||
#editor-column {
|
||||
.locked {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
flex: 1 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.locked {
|
||||
opacity: 0.8;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
#editor-title-bar {
|
||||
@@ -19,7 +22,6 @@ $heading-height: 75px;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
background-color: white;
|
||||
border-bottom: none;
|
||||
z-index: $z-index-editor-title-bar;
|
||||
|
||||
@@ -27,7 +29,7 @@ $heading-height: 75px;
|
||||
overflow: visible;
|
||||
|
||||
> .title {
|
||||
font-size: 18px;
|
||||
font-size: var(--sn-stylekit-font-size-h1);
|
||||
font-weight: bold;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
@@ -41,9 +43,10 @@ $heading-height: 75px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
|
||||
&:disabled {
|
||||
color: black;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,22 +58,21 @@ $heading-height: 75px;
|
||||
position: absolute;
|
||||
|
||||
right: 20px;
|
||||
font-size: 12px;
|
||||
font-size: calc(var(--sn-stylekit-base-font-size) - 2px);
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
margin-top: 4px;
|
||||
text-align: right;
|
||||
color: rgba(black, 0.23);
|
||||
|
||||
&.error, .error {
|
||||
color: #f6a200;
|
||||
.desc, .message:not(.warning):not(.danger) {
|
||||
// color: var(--sn-stylekit-editor-foreground-color);
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-tags {
|
||||
clear: left;
|
||||
width: 100%;
|
||||
// height: 25px;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
|
||||
@@ -78,6 +80,11 @@ $heading-height: 75px;
|
||||
height: 50px;
|
||||
overflow: auto; // Required for expired sub to not overflow
|
||||
|
||||
.component-view {
|
||||
// see comment under main .component-view css defintion
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
iframe {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
@@ -88,6 +95,7 @@ $heading-height: 75px;
|
||||
|
||||
.tags-input {
|
||||
background-color: transparent;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
@@ -100,7 +108,7 @@ $heading-height: 75px;
|
||||
overflow-y: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background-color: white;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
position: relative;
|
||||
|
||||
@@ -113,12 +121,14 @@ $heading-height: 75px;
|
||||
font-family: monospace;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
padding: 15px;
|
||||
padding-top: 11px;
|
||||
font-size: 17px;
|
||||
font-size: var(--sn-stylekit-font-size-editor);
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
@@ -126,12 +136,25 @@ $heading-height: 75px;
|
||||
#editor-pane-component-stack {
|
||||
width: 100%;
|
||||
|
||||
// When two component stack items are expired and eat up full screen, this is required to scroll them.
|
||||
// overflow: auto;
|
||||
// When expired components, without this, requires scroll
|
||||
overflow: visible;
|
||||
|
||||
#component-stack-menu-bar {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.component-stack-item {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-top: 1px solid $bg-color;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
// we moved the border top from the .component-stack-item to the .iframe, as on parent,
|
||||
// it increases its height and caused unneccessary scrollbars on windows.
|
||||
border-top: 1px solid var(--sn-stylekit-border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
z-index: $z-index-footer-bar;
|
||||
}
|
||||
|
||||
#footer-bar .item {
|
||||
#footer-bar .sk-app-bar-item {
|
||||
z-index: $z-index-footer-bar-item;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
.panel {
|
||||
.sk-panel {
|
||||
max-height: 85vh;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
@@ -20,16 +20,28 @@
|
||||
min-width: 300px;
|
||||
z-index: $z-index-footer-bar-item-panel;
|
||||
margin-top: 15px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
&.dock-shortcut:hover .sk-app-bar-item-column {
|
||||
border-bottom: 2px solid var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: var(--sn-stylekit-foreground-color);
|
||||
|
||||
&:hover {
|
||||
fill: var(--sn-stylekit-info-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#account-panel {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
.sk-panel {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -43,6 +55,5 @@ a.disabled {
|
||||
|
||||
#footer-lock-icon {
|
||||
margin-left: 5px;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid gray;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
@@ -11,14 +11,34 @@
|
||||
Modified icons to fit ionicon’s grid from original.
|
||||
*/
|
||||
@font-face { font-family: "Ionicons"; src: url("../assets/ionicons.eot?v=2.0.0"); src: url("../assets/ionicons.eot?v=2.0.1#iefix") format("embedded-opentype"), url("../assets/ionicons.ttf?v=2.0.1") format("truetype"), url("../assets/ionicons.woff?v=2.0.1") format("woff"), url("../assets/ionicons.svg?v=2.0.1#Ionicons") format("svg"); font-weight: normal; font-style: normal; }
|
||||
.ion, .ionicons, .ion-ios-box:before, .ion-bookmark:before, .ion-locked:before { display: inline-block; font-family: "Ionicons"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; text-rendering: auto; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
|
||||
|
||||
.ion, .ionicons,
|
||||
.ion-ios-box:before,
|
||||
.ion-bookmark:before,
|
||||
.ion-locked:before,
|
||||
.ion-arrow-return-left:before,
|
||||
.ion-arrow-return-right:before,
|
||||
.ion-key:before,
|
||||
.ion-lock-combination:before,
|
||||
.ion-eye-disabled:before
|
||||
{
|
||||
display: inline-block; font-family: "Ionicons"; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; text-rendering: auto; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ion-ios-box:before { content: "\f3ec"; }
|
||||
|
||||
.ion-locked:before { content: "\f200"; }
|
||||
|
||||
.ion-bookmark:before {
|
||||
content: "\f26b";
|
||||
}
|
||||
.ion-bookmark:before { content: "\f26b"; }
|
||||
|
||||
.ion-arrow-return-left:before { content: "\f265"; }
|
||||
|
||||
.ion-arrow-return-right:before { content: "\f266"; }
|
||||
|
||||
.ion-key:before { content: "\f296"; }
|
||||
|
||||
.ion-lock-combination:before { content: "\f4d4"; }
|
||||
|
||||
.ion-eye-disabled:before { content: "\f306"; }
|
||||
|
||||
/*# sourceMappingURL=ionicons.css.map */
|
||||
|
||||
@@ -12,33 +12,24 @@
|
||||
bottom: 0;
|
||||
|
||||
z-index: $z-index-lock-screen;
|
||||
background-color: rgba(white, 0.5);
|
||||
color: black;
|
||||
font-size: 16px;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel {
|
||||
.sk-panel {
|
||||
width: 315px;
|
||||
flex-grow: 0;
|
||||
// border-radius: 0;
|
||||
|
||||
.header {
|
||||
.sk-panel-header {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
#passcode-reset {
|
||||
margin-top: 18px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
$main-text-color: black;
|
||||
$secondary-text-color: rgba($main-text-color, 0.8);
|
||||
$bg-color: #e3e3e3;
|
||||
$light-bg-color: #e9e9e9;
|
||||
$selection-color: $bg-color;
|
||||
$selected-text-color: black;
|
||||
$blue-color: #086dd6;
|
||||
|
||||
$z-index-editor-content: 10;
|
||||
|
||||
$z-index-editor-title-bar: 100;
|
||||
@@ -27,13 +19,13 @@ body {
|
||||
"Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans",
|
||||
"Droid Sans", "Helvetica Neue", sans-serif;
|
||||
color: $main-text-color;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
font-size: var(--sn-stylekit-base-font-size);
|
||||
margin: 0;
|
||||
background-color: $bg-color;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -41,11 +33,11 @@ body {
|
||||
}
|
||||
|
||||
.tinted {
|
||||
color: $blue-color;
|
||||
color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.tinted-selected {
|
||||
color: white;
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
|
||||
*:focus {outline:0;}
|
||||
@@ -61,7 +53,6 @@ input, button, select, textarea {
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue-color;
|
||||
text-decoration: none;
|
||||
|
||||
&.no-decoration {
|
||||
@@ -77,6 +68,16 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--sn-stylekit-info-color) !important; /* WebKit/Blink Browsers */
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--sn-stylekit-info-color) !important;
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
|
||||
p {
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -85,7 +86,7 @@ p {
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
background-color: $bg-color;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
}
|
||||
|
||||
$footer-height: 32px;
|
||||
@@ -115,7 +116,8 @@ $footer-height: 32px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
cursor: col-resize;
|
||||
background-color: rgba(black, 0.1);
|
||||
// needs to be a color that works on main bg and contrast bg
|
||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||
opacity: 0;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
@@ -145,7 +147,8 @@ $footer-height: 32px;
|
||||
|
||||
&.collapsed {
|
||||
opacity: 1;
|
||||
background-color: #DDDDDD;
|
||||
// so it blends in with editor a bit more
|
||||
background-color: var(--sn-stylekit-editor-background-color);
|
||||
}
|
||||
|
||||
&.dragging {
|
||||
@@ -169,7 +172,6 @@ $footer-height: 32px;
|
||||
padding-bottom: 0px;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - #{$footer-height});
|
||||
font-size: 17px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -181,13 +183,12 @@ $footer-height: 32px;
|
||||
> .content {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
background-color: white;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-title-bar {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
|
||||
.padded {
|
||||
padding: 0 14px;
|
||||
@@ -197,7 +198,8 @@ $footer-height: 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
// This was causing problems with tags + button cutting off on right when the panel is a certain size
|
||||
// overflow: hidden;
|
||||
|
||||
> .title {
|
||||
white-space: nowrap;
|
||||
@@ -205,24 +207,6 @@ $footer-height: 32px;
|
||||
width: 80%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .add-button {
|
||||
$button-bg: #e9e9e9;
|
||||
color: lighten($main-text-color, 40%);
|
||||
font-size: 18px;
|
||||
width: 45px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
background-color: $button-bg;
|
||||
border-radius: 4px;
|
||||
font-weight: normal;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($button-bg, 5%);
|
||||
color: lighten($main-text-color, 40%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.app-bar {
|
||||
.item {
|
||||
.sk-app-bar {
|
||||
.sk-app-bar-item {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,4 @@
|
||||
margin-top: 5px;
|
||||
width: 280px;
|
||||
max-height: calc(85vh - 90px);
|
||||
background-color: white;
|
||||
color: $selected-text-color;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
#permissions-modal {
|
||||
width: 350px;
|
||||
.panel {
|
||||
.sk-panel {
|
||||
border-radius: 0;
|
||||
background-color: white;
|
||||
}
|
||||
.content {
|
||||
.sk-panel-content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
.footer {
|
||||
.sk-panel-footer {
|
||||
padding-bottom: 1.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
#privileges-modal {
|
||||
width: 700px;
|
||||
|
||||
table {
|
||||
margin-bottom: 12px;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
border-color: var(--sn-stylekit-contrast-border-color);
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
color: var(--sn-stylekit-contrast-foreground-color);
|
||||
|
||||
th, td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid var(--sn-stylekit-contrast-border-color);
|
||||
}
|
||||
|
||||
tr:nth-child(2n) {
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.priv-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#password-wizard {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#item-preview-modal {
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
width: 800px;
|
||||
height: 500px;
|
||||
}
|
||||
@@ -37,21 +72,17 @@
|
||||
|
||||
.border {
|
||||
height: 100%;
|
||||
background-color: rgba(black, 0.1);
|
||||
background-color: var(--sn-stylekit-border-color);
|
||||
width: 1px;
|
||||
margin: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.modal {
|
||||
.sk-modal {
|
||||
position: fixed;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
@@ -62,54 +93,57 @@
|
||||
z-index: $z-index-modal;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(gray, 0.3);
|
||||
color: black;
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--sn-stylekit-contrast-foreground-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.sn-component {
|
||||
height: 100%;
|
||||
.panel {
|
||||
.sk-panel {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.auto-height {
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.large {
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
width: 900px;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
&.medium {
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
width: 700px;
|
||||
height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
&.small {
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
width: 700px;
|
||||
height: 344px;
|
||||
}
|
||||
}
|
||||
|
||||
.background {
|
||||
.sk-modal-background {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
> .content {
|
||||
> .sk-modal-content {
|
||||
overflow-y: auto;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
@@ -135,6 +169,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// required so that .loading-overlay absolute works properly wrt to modal components. However, seems to break #note-tags-component-container.
|
||||
// I couldn't find any solution to this other than to customize .component-view position back to inherit for note-tags-component-container.
|
||||
position: relative;
|
||||
|
||||
// not sure why we need this. Removed because it creates unncessary scroll bars. Tested on folders extension, creates horizontal scrollbar at bottom on windows
|
||||
// overflow: auto;
|
||||
// Update: we needed that because when we display the expired Extended view, it allows it to scroll vertically.
|
||||
@@ -144,8 +182,24 @@
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
iframe {
|
||||
flex: 1;
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
background-color: var(--sn-stylekit-editor-background-color);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
iframe {
|
||||
// We're disabling flex: 1; because on Firefox, it causes weird sizing issues with component stack items.
|
||||
// Not sure yet if totally required.
|
||||
// Update: The extensions manager doesn't display correctly without it
|
||||
// flex-grow: 1 should fix that.
|
||||
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#notes-column, .notes {
|
||||
border-left: 1px solid #dddddd;
|
||||
border-right: 1px solid #dddddd;
|
||||
border-left: 1px solid var(--sn-stylekit-border-color);
|
||||
border-right: 1px solid var(--sn-stylekit-border-color);
|
||||
|
||||
font-size: var(--sn-stylekit-font-size-h2);
|
||||
|
||||
width: 350px;
|
||||
flex-grow: 0;
|
||||
@@ -21,15 +23,11 @@
|
||||
font-size: 18px;
|
||||
|
||||
.section-title-bar-header .title {
|
||||
color: rgba(black, 0.40);
|
||||
color: var(--sn-stylekit-neutral-color);
|
||||
width: calc(90% - 45px);
|
||||
}
|
||||
}
|
||||
|
||||
#notes-add-button {
|
||||
|
||||
}
|
||||
|
||||
#notes-menu-bar {
|
||||
position: relative;
|
||||
margin-top: 14px;
|
||||
@@ -42,7 +40,7 @@
|
||||
position: relative;
|
||||
|
||||
.filter-bar {
|
||||
background-color: $light-bg-color;
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
border-radius: 4px;
|
||||
height: 100%;
|
||||
color: #909090;
|
||||
@@ -60,9 +58,9 @@
|
||||
border-radius: 50%;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
color: white;
|
||||
cursor: default;
|
||||
background-color: gray;
|
||||
background-color: var(--sn-stylekit-neutral-color);
|
||||
color: var(--sn-stylekit-neutral-contrast-color);
|
||||
font-size: 10px;
|
||||
line-height: 17px;
|
||||
text-align: center;
|
||||
@@ -73,7 +71,7 @@
|
||||
transition: background-color 0.15s linear;
|
||||
|
||||
&:hover {
|
||||
background-color: $blue-color;
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,9 +100,8 @@
|
||||
.note {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid $bg-color;
|
||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
|
||||
> .name {
|
||||
font-weight: 600;
|
||||
@@ -137,11 +134,11 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
$line-height: 18px;
|
||||
.default-preview, .plain-preview {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1; /* number of lines to show */
|
||||
$line-height: 18px;
|
||||
line-height: $line-height; /* fallback */
|
||||
max-height: calc(#{$line-height} * 1); /* fallback */
|
||||
}
|
||||
@@ -158,14 +155,53 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $blue-color;
|
||||
color: white;
|
||||
.note-flag {
|
||||
color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
.pinned {
|
||||
color: white;
|
||||
.note-flags {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.note-flag {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
progress {
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: var(--sn-stylekit-info-color);
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
|
||||
.note-flag {
|
||||
color: var(--sn-stylekit-info-contrast-color);
|
||||
}
|
||||
|
||||
progress {
|
||||
background-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
color: var(--sn-stylekit-secondary-background-color);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: var(--sn-stylekit-secondary-foreground-color);
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: var(--sn-stylekit-secondary-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.windows-web, .windows-desktop, .linux-web, .linux-desktop {
|
||||
|
||||
$thumb-color: #dfdfdf;
|
||||
$track-border-color: #E7E7E7;
|
||||
$thumb-width: 4px;
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 17px;
|
||||
height: 18px;
|
||||
border-left: 0.5px solid $track-border-color;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
height: 6px;
|
||||
border: $thumb-width solid rgba(0, 0, 0, 0);
|
||||
background-clip: padding-box;
|
||||
-webkit-border-radius: 10px;
|
||||
background-color: $thumb-color;
|
||||
-webkit-box-shadow: inset -1px -1px 0px rgba(0, 0, 0, 0.05), inset 1px 1px 0px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-button {
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
.sn-component {
|
||||
|
||||
.notification {
|
||||
.sk-notification {
|
||||
&.unpadded {
|
||||
padding: 0;
|
||||
padding-bottom: 0 !important;
|
||||
@@ -12,13 +12,13 @@
|
||||
}
|
||||
|
||||
.bordered-row {
|
||||
border-bottom: 1px solid rgba(black, 0.1);
|
||||
border-top: 1px solid rgba(black, 0.1);
|
||||
border-bottom: 1px solid var(--sn-stylekit-border-color);
|
||||
border-top: 1px solid var(--sn-stylekit-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.app-bar {
|
||||
.sk-app-bar {
|
||||
&.no-top-edge {
|
||||
border-top: 0;
|
||||
}
|
||||
@@ -26,10 +26,8 @@
|
||||
|
||||
}
|
||||
|
||||
.panel {
|
||||
color: black;
|
||||
|
||||
.header {
|
||||
.sk-panel {
|
||||
.sk-panel-header {
|
||||
.close-button {
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
@@ -37,25 +35,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
min-height: 39px;
|
||||
}
|
||||
|
||||
|
||||
.button-group.stretch {
|
||||
.button:not(.featured) {
|
||||
.sk-button-group.stretch {
|
||||
.sk-button:not(.featured) {
|
||||
// Default buttons that are not featured and stretched should have larger vertical padding
|
||||
padding: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $blue-color;
|
||||
}
|
||||
}
|
||||
|
||||
#session-history-menu {
|
||||
.menu-panel .row .sublabel.opaque {
|
||||
.sk-menu-panel .sk-menu-panel-row .sk-sublabel.opaque {
|
||||
opacity: 1.0
|
||||
}
|
||||
}
|
||||
|
||||
button.sk-button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -6,27 +6,18 @@
|
||||
-webkit-user-select: none;
|
||||
|
||||
&, #tags-content {
|
||||
background-color: #f6f6f6;
|
||||
background-color: var(--sn-stylekit-secondary-background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#tags-title-bar {
|
||||
color: black;
|
||||
padding-top: 14px;
|
||||
padding-bottom: 16px;
|
||||
color: var(--sn-stylekit-secondary-foreground-color);
|
||||
padding-top: 15px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
font-size: 12px;
|
||||
color: rgba(black, 0.8);
|
||||
}
|
||||
|
||||
#tag-add-button {
|
||||
background-color: #d7d7d7;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(#d7d7d7, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
@@ -52,7 +43,6 @@
|
||||
cursor: pointer;
|
||||
transition: height .1s ease-in-out;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
|
||||
> .info {
|
||||
height: 20px;
|
||||
@@ -62,7 +52,7 @@
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
float: left;
|
||||
color: $main-text-color;
|
||||
color: var(--sn-stylekit-secondary-foreground-color);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-overflow: ellipsis;
|
||||
@@ -76,6 +66,7 @@
|
||||
right: 17px;
|
||||
padding-top: 1px;
|
||||
font-weight: bold;
|
||||
color: var(--sn-stylekit-neutral-color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,32 +88,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
$tags-selected-color: #dbdbdb;
|
||||
|
||||
&.selected {
|
||||
background-color: $tags-selected-color;
|
||||
color: $selected-text-color;
|
||||
> .title {
|
||||
color: $selected-text-color;
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
|
||||
/* When a note is dragged over tag */
|
||||
&.over {
|
||||
background-color: $tags-selected-color;
|
||||
color: $selected-text-color;
|
||||
border: 2px dashed white;
|
||||
&:hover:not(.selected), &.selected {
|
||||
background-color: var(--sn-stylekit-secondary-contrast-background-color);
|
||||
color: var(--sn-stylekit-secondary-contrast-foreground-color);
|
||||
> .title {
|
||||
color: $selected-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
background-color: $tags-selected-color;
|
||||
color: $selected-text-color;
|
||||
> .title {
|
||||
color: $selected-text-color;
|
||||
color: var(--sn-stylekit-secondary-contrast-foreground-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@import "app/main";
|
||||
@import "app/ui";
|
||||
@import "app/scrollbars";
|
||||
@import "app/footer";
|
||||
@import "app/tags";
|
||||
@import "app/notes";
|
||||
|
||||
@@ -1,139 +1,169 @@
|
||||
.sn-component
|
||||
.panel#account-panel
|
||||
.header
|
||||
%h1.title Account
|
||||
%a.close-button{"ng-click" => "close()"} Close
|
||||
.content
|
||||
|
||||
.panel-section.hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"}
|
||||
%h1.title Sign in or register to enable sync and end-to-end encryption.
|
||||
.panel-row
|
||||
.panel-row
|
||||
.button-group.stretch
|
||||
.button.info.featured{"ng-click" => "formData.showLogin = true"}
|
||||
.label Sign In
|
||||
.button.info.featured{"ng-click" => "formData.showRegister = true"}
|
||||
.label Register
|
||||
%p
|
||||
.sk-panel#account-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Account
|
||||
%a.sk-a.info.close-button{"ng-click" => "close()"} Close
|
||||
.sk-panel-content
|
||||
.sk-panel-section.sk-panel-hero{"ng-if" => "!user && !formData.showLogin && !formData.showRegister && !formData.mfa"}
|
||||
.sk-panel-row
|
||||
.sk-h1 Sign in or register to enable sync and end-to-end encryption.
|
||||
.sk-panel-row
|
||||
.sk-button-group.stretch
|
||||
.sk-button.info.featured{"ng-click" => "formData.showLogin = true"}
|
||||
.sk-label Sign In
|
||||
.sk-button.info.featured{"ng-click" => "formData.showRegister = true"}
|
||||
.sk-label Register
|
||||
.sk-panel-row.sk-p
|
||||
Standard Notes is free on every platform, and comes standard with sync and encryption.
|
||||
|
||||
.panel-section{"ng-if" => "formData.showLogin || formData.showRegister"}
|
||||
%h3.title.panel-row
|
||||
.sk-panel-section{"ng-if" => "formData.showLogin || formData.showRegister"}
|
||||
.sk-panel-section-title
|
||||
{{formData.showLogin ? "Sign In" : "Register"}}
|
||||
|
||||
%form.panel-form{"ng-submit" => "submitAuthForm()"}
|
||||
%input{:placeholder => 'Email', "sn-autofocus" => 'true', "should-focus" => "true", :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
|
||||
%input{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'}
|
||||
%input{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf'}
|
||||
%form.sk-panel-form{"ng-submit" => "submitAuthForm()"}
|
||||
.sk-panel-section
|
||||
%input.sk-input.contrast{:placeholder => 'Email', "sn-autofocus" => 'true', "should-focus" => "true", :name => 'email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
|
||||
%input.sk-input.contrast{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password', 'sn-enter' => 'submitAuthForm()'}
|
||||
%input.sk-input.contrast{:placeholder => 'Confirm Password', "ng-if" => "formData.showRegister", :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.password_conf', 'sn-enter' => 'submitAuthForm()'}
|
||||
.sk-panel-row
|
||||
%a.sk-panel-row.sk-bold{"ng-click" => "formData.showAdvanced = !formData.showAdvanced"}
|
||||
Advanced Options
|
||||
|
||||
%a.panel-row{"ng-click" => "formData.showAdvanced = !formData.showAdvanced"}
|
||||
Advanced Options
|
||||
.notification.info{"ng-if" => "formData.showRegister"}
|
||||
%h2.title No Password Reset.
|
||||
.text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.
|
||||
|
||||
.notification.unpadded.default.advanced-options.panel-row{"ng-if" => "formData.showAdvanced"}
|
||||
.panel-column.stretch
|
||||
%h4.title.panel-row.padded-row Advanced Options
|
||||
.sk-notification.unpadded.contrast.advanced-options.sk-panel-row{"ng-if" => "formData.showAdvanced"}
|
||||
.sk-panel-column.stretch
|
||||
.sk-notification-title.sk-panel-row.padded-row Advanced Options
|
||||
%div.bordered-row.padded-row
|
||||
%label Sync Server Domain
|
||||
%input.form-control.mt-5{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
|
||||
%label.padded-row{"ng-if" => "formData.showLogin"}
|
||||
%input{"type" => "checkbox", "ng-model" => "formData.strictSignin"}
|
||||
%label.sk-label Sync Server Domain
|
||||
%input.sk-input.mt-5.sk-base{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
|
||||
%label.sk-label.padded-row{"ng-if" => "formData.showLogin"}
|
||||
%input.sk-input{"type" => "checkbox", "ng-model" => "formData.strictSignin"}
|
||||
Use strict sign in
|
||||
%span
|
||||
%a{"href" => "https://standardnotes.org/help/security", "target" => "_blank"} (Learn more)
|
||||
|
||||
.button-group.stretch.panel-row.form-submit
|
||||
%button.button.info.featured{"type" => "submit", "ng-disabled" => "formData.authenticating"}
|
||||
.label {{formData.showLogin ? "Sign In" : "Register"}}
|
||||
.sk-panel-section.form-submit{"ng-if" => "!formData.authenticating"}
|
||||
.sk-button-group.stretch
|
||||
.sk-button.info.featured{'ng-click' => 'submitAuthForm()', "ng-disabled" => "formData.authenticating"}
|
||||
.sk-label {{formData.showLogin ? "Sign In" : "Register"}}
|
||||
|
||||
%label
|
||||
%input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"}
|
||||
Stay signed in
|
||||
%label{"ng-if" => "notesAndTagsCount() > 0"}
|
||||
%input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
|
||||
Merge local data ({{notesAndTagsCount()}} notes and tags)
|
||||
.sk-notification.neutral{"ng-if" => "formData.showRegister"}
|
||||
.sk-notification-title No Password Reset.
|
||||
.sk-notification-text Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.
|
||||
|
||||
%em.block.center-align.mt-10{"ng-if" => "formData.status", "style" => "font-size: 14px;"}
|
||||
{{formData.status}}
|
||||
.sk-panel-section.no-bottom-pad{"ng-if" => "formData.status"}
|
||||
.sk-horizontal-group
|
||||
.sk-spinner.small.neutral
|
||||
.sk-label {{formData.status}}
|
||||
|
||||
.panel-section{"ng-if" => "formData.mfa"}
|
||||
%form{"ng-submit" => "submitMfaForm()"}
|
||||
%p {{formData.mfa.message}}
|
||||
%input.form-control.mt-10{:placeholder => "Enter Code", "sn-autofocus" => "true", "should-focus" => "true", :autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'}
|
||||
.button-group.stretch.panel-row.form-submit
|
||||
%button.button.info.featured{"type" => "submit"}
|
||||
.label Sign In
|
||||
.sk-panel-section.no-bottom-pad{"ng-if" => "!formData.authenticating"}
|
||||
%label.sk-panel-row.justify-left
|
||||
.sk-horizontal-group
|
||||
%input{"type" => "checkbox", "ng-model" => "formData.ephemeral", "ng-true-value" => "false", "ng-false-value" => "true"}
|
||||
Stay signed in
|
||||
%label.sk-panel-row.justify-left{"ng-if" => "notesAndTagsCount() > 0"}
|
||||
.sk-panel-row
|
||||
%input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
|
||||
Merge local data ({{notesAndTagsCount()}} notes and tags)
|
||||
|
||||
.sk-panel-section{"ng-if" => "formData.mfa"}
|
||||
%form.sk-panel-form{"ng-submit" => "submitMfaForm()"}
|
||||
.sk-p.sk-panel-row {{formData.mfa.message}}
|
||||
.sk-panel-row
|
||||
%input.sk-input.contrast{:placeholder => "Enter Code", "sn-autofocus" => "true", "should-focus" => "true", :autofocus => "true", :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'}
|
||||
.sk-button-group.stretch.sk-panel-row.form-submit{"ng-if" => "!formData.status"}
|
||||
%button.sk-button.info.featured{"type" => "submit"}
|
||||
.sk-label Sign In
|
||||
.sk-panel-section.no-bottom-pad{"ng-if" => "formData.status"}
|
||||
.sk-panel-row
|
||||
.sk-panel-row
|
||||
.sk-horizontal-group
|
||||
.sk-spinner.small.neutral
|
||||
.sk-label {{formData.status}}
|
||||
|
||||
%div{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"}
|
||||
.panel-section{"ng-if" => "user"}
|
||||
.notification.danger{"ng-if" => "syncStatus.error"}
|
||||
%h2.title Sync Unreachable
|
||||
.text Hmm...we can't seem to sync your account. The reason: {{syncStatus.error.message}}
|
||||
%p
|
||||
%a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Need help?
|
||||
.panel-row
|
||||
%h2.title.wrap {{user.email}}
|
||||
.horizontal-group{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
|
||||
.spinner.small.info
|
||||
.sublabel
|
||||
.sk-panel-section{"ng-if" => "user"}
|
||||
.sk-notification.danger{"ng-if" => "syncStatus.error"}
|
||||
.sk-notification-title Sync Unreachable
|
||||
.sk-notification-text Hmm...we can't seem to sync your account. The reason: {{syncStatus.error.message}}
|
||||
%a.sk-a.info-contrast.sk-bold.sk-panel-row{"href" => "https://standardnotes.org/help", "target" => "_blank"} Need help?
|
||||
|
||||
.sk-panel-row
|
||||
.sk-panel-column
|
||||
.sk-h1.sk-bold.wrap {{user.email}}
|
||||
.sk-subtitle.subtle.normal {{server}}
|
||||
.sk-horizontal-group{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
|
||||
.sk-spinner.small.info
|
||||
.sk-sublabel
|
||||
{{"Syncing" + (syncStatus.total > 0 ? ":" : "")}}
|
||||
%span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}}
|
||||
|
||||
.subtitle.subtle.normal {{server}}
|
||||
|
||||
.panel-row
|
||||
.sk-panel-row
|
||||
|
||||
%a.panel-row.condensed{"ng-click" => "openPasswordWizard('change-pw')"} Change Password
|
||||
%a.panel-row.justify-left.condensed.success{"ng-if" => "securityUpdateAvailable", "ng-click" => "openPasswordWizard('upgrade-security')"}
|
||||
.inline.circle.small.success.mr-8
|
||||
%a.sk-a.info.sk-panel-row.condensed{"ng-click" => "openPasswordWizard('change-pw')"}
|
||||
Change Password
|
||||
%a.sk-a.info.sk-panel-row.condensed{"ng-show" => "user", "ng-click" => "openPrivilegesModal('')"}
|
||||
Manage Privileges
|
||||
%a.sk-panel-row.justify-left.condensed.success{"ng-if" => "securityUpdateAvailable", "ng-click" => "openPasswordWizard('upgrade-security')"}
|
||||
.inline.sk-circle.small.success.mr-8
|
||||
.inline Security Update Available
|
||||
|
||||
.panel-section
|
||||
%h3.title.panel-row Encryption
|
||||
%h5.subtitle.info.panel-row{"ng-if" => "encryptionEnabled()"}
|
||||
.sk-panel-section
|
||||
.sk-panel-section-title Encryption
|
||||
.sk-panel-section-subtitle.info{"ng-if" => "encryptionEnabled()"}
|
||||
{{encryptionStatusForNotes()}}
|
||||
%p
|
||||
%p.sk-p
|
||||
{{encryptionStatusString()}}
|
||||
|
||||
.panel-section
|
||||
%h3.title.panel-row Passcode Lock
|
||||
.sk-panel-section
|
||||
.sk-panel-section-title Passcode Lock
|
||||
%div{"ng-if" => "!hasPasscode()"}
|
||||
%div{"ng-if" => "canAddPasscode"}
|
||||
.panel-row{"ng-if" => "!formData.showPasscodeForm"}
|
||||
.button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();"}
|
||||
.label Add Passcode
|
||||
.sk-panel-row{"ng-if" => "!formData.showPasscodeForm"}
|
||||
.sk-button.info{"ng-click" => "addPasscodeClicked(); $event.stopPropagation();"}
|
||||
.sk-label Add Passcode
|
||||
|
||||
%p Add an app passcode to lock the app and encrypt on-device key storage.
|
||||
%p.sk-p Add an app passcode to lock the app and encrypt on-device key storage.
|
||||
|
||||
%div{"ng-if" => "!canAddPasscode"}
|
||||
%p Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the "Stay signed in" option checked.
|
||||
%p.sk-p Adding a passcode is not supported in temporary sessions. Please sign out, then sign back in with the "Stay signed in" option checked.
|
||||
|
||||
%form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"}
|
||||
%input.form-control{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
%input.form-control{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"}
|
||||
.button-group.stretch.panel-row.form-submit
|
||||
%button.button.info{"type" => "submit"}
|
||||
.label Set Passcode
|
||||
%a.panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel
|
||||
%form.sk-panel-form{"ng-if" => "formData.showPasscodeForm", "ng-submit" => "submitPasscodeForm()"}
|
||||
.sk-panel-row
|
||||
%input.sk-input.contrast{:type => 'password', "ng-model" => "formData.passcode", "placeholder" => "Passcode", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
%input.sk-input.contrast{:type => 'password', "ng-model" => "formData.confirmPasscode", "placeholder" => "Confirm Passcode"}
|
||||
.sk-button-group.stretch.sk-panel-row.form-submit
|
||||
%button.sk-button.info{"type" => "submit"}
|
||||
.sk-label Set Passcode
|
||||
%a.neutral.sk-a.sk-panel-row{"ng-click" => "formData.showPasscodeForm = false"} Cancel
|
||||
|
||||
%div{"ng-if" => "hasPasscode() && !formData.showPasscodeForm"}
|
||||
.panel-row
|
||||
%p
|
||||
Passcode lock is enabled.
|
||||
%span{"ng-if" => "isDesktopApplication()"} Your passcode will be required on new sessions after app quit.
|
||||
.panel-row.justify-left
|
||||
.horizontal-group
|
||||
%a.info{"ng-click" => "changePasscodePressed()"} Change Passcode
|
||||
%a.danger{"ng-click" => "removePasscodePressed()"} Remove Passcode
|
||||
.sk-p
|
||||
Passcode lock is enabled.
|
||||
|
||||
.sk-notification.contrast
|
||||
.sk-notification-title Options
|
||||
.sk-notification-text
|
||||
.sk-panel-row
|
||||
.sk-horizontal-group
|
||||
.sk-h4.sk-bold Autolock
|
||||
%a.sk-a.info{"ng-repeat" => "option in passcodeAutoLockOptions", "ng-click" => "selectAutoLockInterval(option.value)",
|
||||
"ng-class" => "{'boxed' : option.value == selectedAutoLockInterval}"}
|
||||
{{option.label}}
|
||||
.sk-p The autolock timer begins when the window or tab loses focus.
|
||||
|
||||
.sk-panel-row
|
||||
%a.sk-a.info.sk-panel-row.condensed{"ng-show" => "!user", "ng-click" => "openPrivilegesModal('')"} Manage Privileges
|
||||
%a.sk-a.info.sk-panel-row.condensed{"ng-click" => "changePasscodePressed()"} Change Passcode
|
||||
%a.sk-a.danger.sk-panel-row.condensed{"ng-click" => "removePasscodePressed()"} Remove Passcode
|
||||
|
||||
.panel-section{"ng-if" => "!importData.loading"}
|
||||
%h3.title Data Backups
|
||||
%form.panel-form{"ng-if" => "encryptedBackupsAvailable()"}
|
||||
.input-group
|
||||
.sk-panel-section{"ng-if" => "!importData.loading"}
|
||||
.sk-panel-section-title Data Backups
|
||||
.sk-p
|
||||
Download a backup of all your data.
|
||||
.sk-panel-row
|
||||
%form.sk-panel-form.sk-panel-row{"ng-if" => "encryptedBackupsAvailable()"}
|
||||
.sk-input-group
|
||||
%label
|
||||
%input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "true", "ng-change" => "archiveFormData.encrypted = true"}
|
||||
Encrypted
|
||||
@@ -141,30 +171,30 @@
|
||||
%input{"type" => "radio", "ng-model" => "archiveFormData.encrypted", "ng-value" => "false", "ng-change" => "archiveFormData.encrypted = false"}
|
||||
Decrypted
|
||||
|
||||
.button-group
|
||||
.button.info{"ng-click" => "downloadDataArchive()"}
|
||||
.label Download Backup
|
||||
.sk-button-group.sk-panel-row.justify-left
|
||||
.sk-button.info{"ng-click" => "downloadDataArchive()"}
|
||||
.sk-label Download Backup
|
||||
|
||||
%label.button.info
|
||||
%label.sk-button.info
|
||||
%input{"type" => "file", "style" => "display: none;", "file-change" => "->", "handler" => "importFileSelected(files)"}
|
||||
.label Import From Backup
|
||||
.sk-label Import Backup
|
||||
|
||||
%span{"ng-if" => "isDesktopApplication()"} Backups are automatically created on desktop and can be managed via the "Backups" top-level menu.
|
||||
|
||||
#import-password-request{"ng-if" => "importData.requestPassword"}
|
||||
%form.panel-form.stretch{"ng-submit" => "submitImportPassword()"}
|
||||
%form.sk-panel-form.stretch{"ng-submit" => "submitImportPassword()"}
|
||||
%p Enter the account password associated with the import file.
|
||||
%input.form-control.mt-5{:type => 'password', "placeholder" => "Enter File Account Password", "ng-model" => "importData.password", "autofocus" => "true"}
|
||||
.button-group.stretch.panel-row.form-submit
|
||||
%button.button.info{"type" => "submit"}
|
||||
.label Decrypt & Import
|
||||
%input.sk-input.mt-5{:type => 'password', "placeholder" => "Enter File Account Password", "ng-model" => "importData.password", "autofocus" => "true"}
|
||||
.sk-button-group.stretch.sk-panel-row.form-submit
|
||||
%button.sk-button.info{"type" => "submit"}
|
||||
.sk-label Decrypt & Import
|
||||
%p
|
||||
Importing from backup will not overwrite existing data, but instead create a duplicate of any differing data.
|
||||
%p If you'd like to import only a selection of items instead of the whole file, please use the Batch Manager extension.
|
||||
.panel-row
|
||||
.spinner.small.info{"ng-if" => "importData.loading"}
|
||||
.footer
|
||||
%a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"}
|
||||
.sk-panel-row
|
||||
.sk-spinner.small.info{"ng-if" => "importData.loading"}
|
||||
.sk-panel-footer
|
||||
%a.sk-a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"}
|
||||
Cancel
|
||||
%a.right{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"}
|
||||
%a.sk-a.right.danger{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"}
|
||||
{{ user ? "Sign out and clear local data" : "Clear all local data" }}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
.sn-component
|
||||
.menu-panel.dropdown-menu
|
||||
.sk-menu-panel.dropdown-menu
|
||||
|
||||
%a.no-decoration{"ng-if" => "extensions.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"}
|
||||
%menu-row{"label" => "'Download Actions'"}
|
||||
|
||||
%div{"ng-repeat" => "extension in extensions"}
|
||||
.header{"ng-click" => "extension.hide = !extension.hide; $event.stopPropagation();"}
|
||||
.column
|
||||
%h4.title {{extension.name}}
|
||||
.spinner.small.loading{"ng-if" => "extension.loading"}
|
||||
.sk-menu-panel-header{"ng-click" => "extension.hide = !extension.hide; $event.stopPropagation();"}
|
||||
.sk-menu-panel-column
|
||||
.sk-menu-panel-header-title {{extension.name}}
|
||||
.sk-spinner.small.loading{"ng-if" => "extension.loading"}
|
||||
%div{"ng-if" => "extension.hide"} …
|
||||
|
||||
%menu-row{"ng-if" => "!extension.hide", "ng-repeat" => "action in extension.actionsWithContextForItem(item)",
|
||||
"action" => "executeAction(action, extension);", "label" => "action.label", "subtitle" => "action.desc",
|
||||
"spinner-class" => "action.running ? 'info' : null", "sub-rows" => "action.subrows"}
|
||||
.sublabel{"ng-if" => "action.access_type"}
|
||||
.sk-sublabel{"ng-if" => "action.access_type"}
|
||||
Uses
|
||||
%strong {{action.access_type}}
|
||||
access to this note.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.background{"ng-click" => "dismiss()"}
|
||||
.sk-modal-background{"ng-click" => "dismiss()"}
|
||||
|
||||
.content{"ng-attr-id" => "component-content-outer-{{component.uuid}}"}
|
||||
.sk-modal-content{"ng-attr-id" => "component-content-outer-{{component.uuid}}"}
|
||||
.sn-component
|
||||
.panel{"ng-attr-id" => "component-content-inner-{{component.uuid}}"}
|
||||
.header
|
||||
%h1.title
|
||||
.sk-panel{"ng-attr-id" => "component-content-inner-{{component.uuid}}"}
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title
|
||||
{{component.name}}
|
||||
%a.close-button.info{"ng-click" => "dismiss()"} Close
|
||||
%a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
|
||||
%component-view.component-view{"component" => "component"}
|
||||
|
||||
@@ -1,103 +1,61 @@
|
||||
.sn-component{"ng-if" => "issueLoading"}
|
||||
.app-bar.no-edges.no-top-edge
|
||||
.sk-app-bar.no-edges.no-top-edge
|
||||
.left
|
||||
.item
|
||||
.label.warning There was an issue loading {{component.name}}.
|
||||
.sk-app-bar-item
|
||||
.sk-label.warning There was an issue loading {{component.name}}.
|
||||
.right
|
||||
.item{"ng-click" => "reloadComponent()"}
|
||||
.label Reload
|
||||
.sk-app-bar-item{"ng-click" => "reloadComponent()"}
|
||||
.sk-label Reload
|
||||
|
||||
.sn-component{"ng-if" => "showNoThemesMessage"}
|
||||
.app-bar.no-edges.no-top-edge
|
||||
.sk-app-bar.no-edges.no-top-edge
|
||||
.left
|
||||
.item
|
||||
.label.warning This extension does not support themes.
|
||||
.sk-app-bar-item
|
||||
.sk-label.warning This extension does not support themes.
|
||||
.right
|
||||
.item{"ng-click" => "noThemesMessageDismiss()"}
|
||||
.label Dismiss
|
||||
.item{"ng-click" => "disableActiveTheme()"}
|
||||
.label Disable Active Theme
|
||||
.sk-app-bar-item{"ng-click" => "noThemesMessageDismiss()"}
|
||||
.sk-label Dismiss
|
||||
.sk-app-bar-item{"ng-click" => "disableActiveTheme()"}
|
||||
.sk-label Disable Active Theme
|
||||
|
||||
.sn-component{"ng-if" => "error == 'expired'"}
|
||||
.panel.static
|
||||
.content
|
||||
.panel-section.stretch
|
||||
%h2.title Unable to load Standard Notes Extended
|
||||
%p Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}.
|
||||
%p
|
||||
Please visit
|
||||
%a{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} dashboard.standardnotes.org
|
||||
to renew your subscription.
|
||||
.panel-row
|
||||
.panel-column
|
||||
%p
|
||||
%strong To reload your account status:
|
||||
%p
|
||||
%ol
|
||||
%li
|
||||
Open the
|
||||
%strong Extensions
|
||||
menu located in the lower left corner of the app to refresh your account status.
|
||||
%li Click Reload below.
|
||||
|
||||
.panel-row
|
||||
.button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"}
|
||||
.label Reload
|
||||
.spinner.info.small{"ng-if" => "reloading"}
|
||||
|
||||
.panel-row
|
||||
.panel-section
|
||||
%p{"ng-if" => "component.isEditor()"}
|
||||
<strong>Otherwise</strong>, please follow the steps below to disable any external editors,
|
||||
so you can edit your note using the plain text editor instead.
|
||||
|
||||
%p To temporarily disable this extension:
|
||||
|
||||
.panel-row
|
||||
.button.info{"ng-click" => "destroy()"}
|
||||
.label Disable Extension
|
||||
.spinner.info.small{"ng-if" => "reloading"}
|
||||
|
||||
.panel-row
|
||||
|
||||
%div{"ng-if" => "component.isEditor()"}
|
||||
%p To disassociate this note from this editor:
|
||||
|
||||
%ol
|
||||
%li Click the "Editor" menu item above (under the note title).
|
||||
%li Select "Plain Editor".
|
||||
%li Repeat this for every note you'd like to access. You can also delete the editor completely to disable it for all notes. To do so, click "Extensions" in the lower left corner of the app, then, for every editor, click "Uninstall".
|
||||
|
||||
%p
|
||||
Need help? Please email us at
|
||||
%a{"href" => "mailto:hello@standardnotes.org", "target" => "_blank"} hello@standardnotes.org
|
||||
or check out the
|
||||
%a{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help
|
||||
page.
|
||||
.sn-component{"ng-if" => "expired"}
|
||||
.sk-app-bar
|
||||
.left
|
||||
.sk-app-bar-item
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.danger.small
|
||||
.sk-app-bar-item-column
|
||||
%a.sk-label.sk-base{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"}
|
||||
Your Extended subscription expired on {{component.dateToLocalizedString(component.valid_until)}}.
|
||||
Extensions are in a read-only state.
|
||||
.right
|
||||
.sk-app-bar-item
|
||||
.sk-app-bar-item-column
|
||||
%a.sk-label{"href" => "https://standardnotes.org/help", "target" => "_blank"} Help
|
||||
|
||||
.sn-component{"ng-if" => "error == 'offline-restricted'"}
|
||||
.panel.static
|
||||
.content
|
||||
.panel-section.stretch
|
||||
%h2.title You have restricted this extension to be used offline only.
|
||||
.sk-panel.static
|
||||
.sk-panel-content
|
||||
.sk-panel-section.stretch
|
||||
.sk-panel-section-title You have restricted this extension to be used offline only.
|
||||
%p Offline extensions are not available in the Web app.
|
||||
.panel-row
|
||||
.panel-column
|
||||
.sk-panel-row
|
||||
.sk-panel-column
|
||||
%p You can either:
|
||||
%p
|
||||
%ul
|
||||
%li <strong>Enable the Hosted option</strong> for this extension by opening the 'Extensions' menu and toggling 'Use hosted when local is unavailable' under this extension's options. Then press Reload below.
|
||||
%li <strong>Use the Desktop application.</strong>
|
||||
.panel-row
|
||||
.button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"}
|
||||
.label Reload
|
||||
.spinner.info.small{"ng-if" => "reloading"}
|
||||
.sk-panel-row
|
||||
.sk-button.info{"ng-if" => "!reloading", "ng-click" => "reloadStatus()"}
|
||||
.sk-label Reload
|
||||
.sk-spinner.info.small{"ng-if" => "reloading"}
|
||||
|
||||
.sn-component{"ng-if" => "error == 'url-missing'"}
|
||||
.panel.static
|
||||
.content
|
||||
.panel-section.stretch
|
||||
%h2.title This extension is not installed correctly.
|
||||
.sk-panel.static
|
||||
.sk-panel-content
|
||||
.sk-panel-section.stretch
|
||||
.sk-panel-section-title This extension is not installed correctly.
|
||||
%p Please uninstall {{component.name}}, then re-install it.
|
||||
|
||||
%p
|
||||
@@ -111,3 +69,5 @@
|
||||
"sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-modals allow-forms",
|
||||
"data-component-id" => "{{component.uuid}}"}
|
||||
Loading
|
||||
|
||||
.loading-overlay{"ng-if" => "loading"}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
.modal.large#conflict-resolution-modal
|
||||
.content
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title Conflicted items — choose which version to keep
|
||||
.horizontal-group
|
||||
%a.close-button.info{"ng-click" => "keepItem1()"} Keep left
|
||||
%a.close-button.info{"ng-click" => "keepItem2()"} Keep right
|
||||
%a.close-button.info{"ng-click" => "keepBoth()"} Keep both
|
||||
%a.close-button.info{"ng-click" => "export()"} Export
|
||||
%a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
|
||||
.content.selectable
|
||||
.panel-section
|
||||
.sn-component
|
||||
.sk-modal.large#conflict-resolution-modal
|
||||
.sk-modal-background
|
||||
.sk-modal-content
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
%h1.sk-panel-header-title Conflicted items — choose which version to keep
|
||||
.sk-horizontal-group
|
||||
%a.sk-a.info.close-button{"ng-click" => "keepItem1()"} Keep left
|
||||
%a.sk-a.info.close-button{"ng-click" => "keepItem2()"} Keep right
|
||||
%a.sk-a.info.close-button{"ng-click" => "keepBoth()"} Keep both
|
||||
%a.sk-a.info.close-button{"ng-click" => "export()"} Export
|
||||
%a.sk-a.info.close-button{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
|
||||
.sk-panel-content.selectable
|
||||
.sk-panel-section
|
||||
%h3
|
||||
%strong Content type:
|
||||
{{contentType}}
|
||||
%p You may wish to look at the "created_at" and "updated_at" fields of the items to gain better context in deciding which to keep.
|
||||
#items
|
||||
.panel.static#item1.item.border-color
|
||||
.sk-panel.static#item1.item
|
||||
%p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item1Content}}
|
||||
|
||||
.border
|
||||
|
||||
.panel.static#item2.item
|
||||
.sk-panel.static#item2.item
|
||||
%p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{item2Content}}
|
||||
|
||||
@@ -1,26 +1,17 @@
|
||||
.sn-component
|
||||
.menu-panel.dropdown-menu
|
||||
.section
|
||||
.header
|
||||
%h4.title Note Editor
|
||||
.sk-menu-panel.dropdown-menu
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Note Editor
|
||||
%menu-row{"label" => "'Plain Editor'", "circle" => "selectedEditor == null && 'success'", "action" => "selectComponent(null)"}
|
||||
|
||||
%menu-row{"ng-repeat" => "editor in editors", "action" => "selectComponent(editor)", "label" => "editor.name",
|
||||
"circle" => "selectedEditor === editor && 'success'",
|
||||
"has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'",
|
||||
"button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"}
|
||||
.column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(editor)"}
|
||||
%strong.red.medium-text{"ng-if" => "editor.conflict_of"} Conflicted copy
|
||||
.sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(editor)"} Running Locally
|
||||
.sk-menu-panel-column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(editor)"}
|
||||
%strong.danger.medium-text{"ng-if" => "editor.conflict_of"} Conflicted copy
|
||||
.sk-sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(editor)"} Running Locally
|
||||
|
||||
%a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"}
|
||||
%menu-row{"label" => "'Download More Editors'"}
|
||||
|
||||
.section{"ng-if" => "stack.length > 0"}
|
||||
.header
|
||||
%h4.title Editor Stack
|
||||
%menu-row{"ng-repeat" => "component in stack", "action" => "selectComponent(component)", "label" => "component.name",
|
||||
"circle" => "stackComponentEnabled(component) ? 'success' : 'danger'"}
|
||||
.column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(component)"}
|
||||
%strong.red.medium-text{"ng-if" => "component.conflict_of"} Conflicted copy
|
||||
.sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(component)"} Running Locally
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
.modal.small.auto-height
|
||||
.content
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title {{title}}
|
||||
%a.close-button{"ng-click" => "dismiss()"} Close
|
||||
.content
|
||||
.panel-section
|
||||
%p.panel-row {{message}}
|
||||
.panel-row
|
||||
.panel-column.stretch
|
||||
%form{"ng-submit" => "submit()"}
|
||||
%input.form-control{:type => '{{type}}', "ng-model" => "formData.input", "placeholder" => "{{placeholder}}", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
.sn-component
|
||||
.sk-modal.small.auto-height
|
||||
.sk-modal-background
|
||||
.sk-modal-content
|
||||
.sn-component
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-h1.sk-panel-header-title {{title}}
|
||||
%a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
|
||||
.sk-panel-content
|
||||
.sk-panel-section
|
||||
.sk-p.sk-panel-row {{message}}
|
||||
.sk-panel-row
|
||||
.sk-panel-column.stretch
|
||||
%form{"ng-submit" => "submit()"}
|
||||
%input.sk-input.contrast{:type => '{{type}}', "ng-model" => "formData.input", "placeholder" => "{{placeholder}}", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
|
||||
.footer
|
||||
%a.right{"ng-click" => "submit()"}
|
||||
Submit
|
||||
.sk-panel-footer
|
||||
%a.sk-a.info.right{"ng-click" => "submit()"}
|
||||
Submit
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
.row{"ng-attr-title" => "{{desc}}", "ng-click" => "onClick($event)"}
|
||||
.column
|
||||
.sk-menu-panel-row.row{"ng-attr-title" => "{{desc}}", "ng-click" => "onClick($event)"}
|
||||
.sk-menu-panel-column
|
||||
.left
|
||||
.column{"ng-if" => "circle"}
|
||||
.circle.small{"ng-class" => "circle"}
|
||||
.column{"ng-class" => "{'faded' : faded || disabled}"}
|
||||
.label
|
||||
.sk-menu-panel-column{"ng-if" => "circle && (!circleAlign || circleAlign == 'left')"}
|
||||
.sk-circle.small{"ng-class" => "circle"}
|
||||
.sk-menu-panel-column{"ng-class" => "{'faded' : faded || disabled}"}
|
||||
.sk-label
|
||||
{{label}}
|
||||
.sublabel{"ng-if" => "subtitle"}
|
||||
.sk-sublabel{"ng-if" => "subtitle"}
|
||||
{{subtitle}}
|
||||
%ng-transclude
|
||||
.subrows{"ng-if" => "subRows && subRows.length > 0"}
|
||||
.sk-menu-panel-subrows{"ng-if" => "subRows && subRows.length > 0"}
|
||||
%menu-row{"ng-repeat" => "row in subRows", "action" => "row.onClick()",
|
||||
"label" => "row.label", "subtitle" => "row.subtitle", "spinner-class" => "row.spinnerClass"}
|
||||
|
||||
.column{"ng-if" => "hasButton"}
|
||||
.button.info{"ng-click" => "clickButton($event)", "ng-class" => "buttonClass"}
|
||||
{{buttonText}}
|
||||
.sk-menu-panel-column{"ng-if" => "circle && circleAlign == 'right'"}
|
||||
.sk-circle.small{"ng-class" => "circle"}
|
||||
|
||||
.column{"ng-if" => "spinnerClass"}
|
||||
.spinner.small{"ng-class" => "spinnerClass"}
|
||||
.sk-menu-panel-column{"ng-if" => "hasButton"}
|
||||
.sk-button{"ng-click" => "clickButton($event)", "ng-class" => "buttonClass"}
|
||||
.sk-label {{buttonText}}
|
||||
|
||||
.sk-menu-panel-column{"ng-if" => "spinnerClass"}
|
||||
.sk-spinner.small{"ng-class" => "spinnerClass"}
|
||||
|
||||
@@ -1,108 +1,104 @@
|
||||
#password-wizard.modal.small.auto-height
|
||||
.content
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title {{title}}
|
||||
%a.close-button{"ng-click" => "dismiss()"} Close
|
||||
.content
|
||||
.sn-component
|
||||
#password-wizard.sk-modal.small.auto-height
|
||||
.sk-modal-background
|
||||
.sk-modal-content
|
||||
.sn-component
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title {{title}}
|
||||
%a.sk-a.info.close-button{"ng-click" => "dismiss()"} Close
|
||||
.sk-panel-content
|
||||
|
||||
%div{"ng-if" => "step == 0"}
|
||||
%div{"ng-if" => "changePassword"}
|
||||
%h2.title.panel-row Change your password
|
||||
%p
|
||||
Changing your password involves changing your encryption key, which requires your data to be re-encrypted and synced.
|
||||
If you have many items, syncing your data can take several minutes.
|
||||
%p.panel-row
|
||||
%strong You must keep the application window open during this process.
|
||||
%div{"ng-if" => "securityUpdate"}
|
||||
%h2.title.panel-row Perform security update
|
||||
%p
|
||||
A new update is available for your account. Updates address improvements and enhancements to our security specification.
|
||||
This process will guide you through the update, and perform the steps necessary with your supervision.
|
||||
%p
|
||||
For more information about security updates, please visit
|
||||
%a{"href" => "https://standardnotes.org/help/security", "target" => "_blank"} standardnotes.org/help/security.
|
||||
|
||||
%p.panel-row
|
||||
.info Press Continue to proceed.
|
||||
|
||||
.panel-row
|
||||
.panel-row
|
||||
|
||||
.panel-section{"ng-if" => "step > 0"}
|
||||
|
||||
%h3.title.panel-row Step {{step}} — {{titleForStep(step)}}
|
||||
|
||||
%div{"ng-if" => "step == 1"}
|
||||
%p.panel-row
|
||||
As a result of this process, the entirety of your data will be re-encrypted and synced to your account. This is a generally safe process,
|
||||
but unforeseen factors like poor network connectivity or a sudden shutdown of your computer may cause this process to fail.
|
||||
It's best to be on the safe side before large operations such as this one.
|
||||
.panel-row
|
||||
.panel-row
|
||||
.button-group
|
||||
.button.info{"ng-click" => "downloadBackup(true)"}
|
||||
.label Download Encrypted Backup
|
||||
.button.info{"ng-click" => "downloadBackup(false)"}
|
||||
.label Download Decrypted Backup
|
||||
|
||||
%div{"ng-if" => "step == 2"}
|
||||
%p.panel-row
|
||||
As a result of this process, your encryption keys will change.
|
||||
Any device on which you use Standard Notes will need to end its session. After this process completes, you will be asked to sign back in.
|
||||
|
||||
%p.bold.panel-row.info-i Please sign out of all applications (excluding this one), including:
|
||||
%ul
|
||||
%li Desktop
|
||||
%li Web (Chrome, Firefox, Safari)
|
||||
%li Mobile (iOS and Android)
|
||||
%p.panel-row
|
||||
If you do not currently have access to a device you're signed in on, you may proceed,
|
||||
but must make signing out and back in the first step upon gaining access to that device.
|
||||
%p.panel-row Press Continue only when you have completed signing out of all your devices.
|
||||
|
||||
|
||||
%div{"ng-if" => "step == 3"}
|
||||
%div{"ng-if" => "step == 0"}
|
||||
%div{"ng-if" => "changePassword"}
|
||||
%p.sk-p.sk-panel-row
|
||||
Changing your password involves changing your encryption key, which requires your data to be re-encrypted and synced.
|
||||
If you have many items, syncing your data can take several minutes.
|
||||
%p.sk-p.sk-panel-row You must keep the application window open during this process.
|
||||
%div{"ng-if" => "securityUpdate"}
|
||||
%p.panel-row Enter your current password. We'll run this through our encryption scheme to generate strong new encryption keys.
|
||||
.panel-row
|
||||
.panel-row
|
||||
.panel-column.stretch
|
||||
%form
|
||||
%input.form-control{:type => 'password', "ng-model" => "formData.currentPassword", "placeholder" => "Current Password", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
%input.form-control{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPassword", "placeholder" => "New Password"}
|
||||
%input.form-control{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPasswordConfirmation", "placeholder" => "Confirm New Password"}
|
||||
%p.sk-p.sk-panel-row
|
||||
A new update is available for your account. Updates address improvements and enhancements to our security specification.
|
||||
This process will guide you through the update, and perform the steps necessary with your supervision.
|
||||
%p.sk-p.sk-panel-row
|
||||
For more information about security updates, please visit
|
||||
%a.sk-a.info{"href" => "https://standardnotes.org/help/security", "target" => "_blank"} standardnotes.org/help/security.
|
||||
|
||||
%div{"ng-if" => "step == 4"}
|
||||
%p.panel-row
|
||||
Your data is being re-encrypted with your new keys and synced to your account.
|
||||
%p.panel-row.danger
|
||||
Do not close this window until this process completes.
|
||||
%p.sk-panel-row.sk-p
|
||||
.info Press Continue to proceed.
|
||||
|
||||
.panel-row
|
||||
.panel-column
|
||||
.spinner.small.inline.info.mr-5{"ng-if" => "formData.processing"}
|
||||
.inline.bold{"ng-class" => "{'info' : !formData.statusError, 'error' : formData.statusError}"}
|
||||
{{formData.status}}
|
||||
.panel-column{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
|
||||
%p.info
|
||||
Syncing {{syncStatus.current}}/{{syncStatus.total}}
|
||||
.sk-panel-section{"ng-if" => "step > 0"}
|
||||
|
||||
%div{"ng-if" => "step == 5"}
|
||||
%div{"ng-if" => "changePassword"}
|
||||
%p.panel-row Your password has been successfully changed.
|
||||
%div{"ng-if" => "securityUpdate"}
|
||||
%p.panel-row
|
||||
The security update has been successfully applied to your account.
|
||||
%p.panel-row
|
||||
%strong Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
|
||||
.sk-panel-section-title Step {{step}} — {{titleForStep(step)}}
|
||||
|
||||
%p.panel-row You may now sign back in on all your devices and close this window.
|
||||
%div{"ng-if" => "step == 1"}
|
||||
%p.sk-panel-row.sk-p
|
||||
As a result of this process, the entirety of your data will be re-encrypted and synced to your account. This is a generally safe process,
|
||||
but unforeseen factors like poor network connectivity or a sudden shutdown of your computer may cause this process to fail.
|
||||
It's best to be on the safe side before large operations such as this one.
|
||||
.sk-panel-row
|
||||
.sk-panel-row
|
||||
.sk-button-group
|
||||
.sk-button.info{"ng-click" => "downloadBackup(true)"}
|
||||
.sk-label Download Encrypted Backup
|
||||
.sk-button.info{"ng-click" => "downloadBackup(false)"}
|
||||
.sk-label Download Decrypted Backup
|
||||
|
||||
.footer
|
||||
.empty
|
||||
%a.right{"ng-click" => "continue()", "ng-disabled" => "lockContinue", "ng-class" => "{'disabled' : lockContinue}"}
|
||||
.spinner.small.inline.info.mr-5{"ng-if" => "showSpinner"}
|
||||
{{continueTitle}}
|
||||
%div{"ng-if" => "step == 2"}
|
||||
%p.sk-p.sk-panel-row
|
||||
As a result of this process, your encryption keys will change.
|
||||
Any device on which you use Standard Notes will need to end its session. After this process completes, you will be asked to sign back in.
|
||||
|
||||
%p.sk-p.bold.sk-panel-row.info-i Please sign out of all applications (excluding this one), including:
|
||||
%ul
|
||||
%li.sk-p Desktop
|
||||
%li.sk-p Web (Chrome, Firefox, Safari)
|
||||
%li.sk-p Mobile (iOS and Android)
|
||||
%p.sk-p.sk-panel-row
|
||||
If you do not currently have access to a device you're signed in on, you may proceed,
|
||||
but must make signing out and back in the first step upon gaining access to that device.
|
||||
%p.sk-p.sk-panel-row Press Continue only when you have completed signing out of all your devices.
|
||||
|
||||
|
||||
%div{"ng-if" => "step == 3"}
|
||||
%div{"ng-if" => "changePassword"}
|
||||
%div{"ng-if" => "securityUpdate"}
|
||||
%p.sk-panel-row Enter your current password. We'll run this through our encryption scheme to generate strong new encryption keys.
|
||||
.sk-panel-row
|
||||
.sk-panel-row
|
||||
.sk-panel-column.stretch
|
||||
%form.sk-panel-form
|
||||
%input.sk-input.contrast{:type => 'password', "ng-model" => "formData.currentPassword", "placeholder" => "Current Password", "sn-autofocus" => "true", "should-focus" => "true"}
|
||||
%input.sk-input.contrast{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPassword", "placeholder" => "New Password"}
|
||||
%input.sk-input.contrast{"ng-if" => "changePassword", :type => 'password', "ng-model" => "formData.newPasswordConfirmation", "placeholder" => "Confirm New Password"}
|
||||
|
||||
%div{"ng-if" => "step == 4"}
|
||||
%p.sk-panel-row
|
||||
Your data is being re-encrypted with your new keys and synced to your account.
|
||||
%p.sk-panel-row.danger{"ng-if" => "lockContinue"}
|
||||
Do not close this window until this process completes.
|
||||
|
||||
.sk-panel-row
|
||||
.sk-panel-column
|
||||
.sk-spinner.small.inline.info.mr-5{"ng-if" => "formData.processing"}
|
||||
.inline.bold{"ng-class" => "{'info' : !formData.statusError, 'error' : formData.statusError}"}
|
||||
{{formData.status}}
|
||||
.sk-panel-column{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
|
||||
%p.info
|
||||
Syncing {{syncStatus.current}}/{{syncStatus.total}}
|
||||
|
||||
%div{"ng-if" => "step == 5"}
|
||||
%div{"ng-if" => "changePassword"}
|
||||
%p.sk-p.sk-panel-row.info-i Your password has been successfully changed.
|
||||
%div{"ng-if" => "securityUpdate"}
|
||||
%p.sk-p.sk-panel-row.info-i
|
||||
The security update has been successfully applied to your account.
|
||||
%p.sk-p.sk-panel-row
|
||||
Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.
|
||||
|
||||
%p.sk-p.sk-panel-row You may now sign back in on all your devices and close this window.
|
||||
|
||||
.sk-panel-footer
|
||||
.empty
|
||||
%a.sk-a.info.right{"ng-click" => "continue()", "ng-disabled" => "lockContinue", "ng-class" => "{'disabled' : lockContinue}"}
|
||||
.sk-spinner.small.inline.info.mr-5{"ng-if" => "showSpinner"}
|
||||
{{continueTitle}}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
.background{"ng-click" => "deny()"}
|
||||
.sk-modal-background{"ng-click" => "deny()"}
|
||||
|
||||
.content#permissions-modal
|
||||
.sk-modal-content#permissions-modal
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title Activate Extension
|
||||
%a.close-button.info{"ng-click" => "deny()"} Cancel
|
||||
.content
|
||||
.panel-section
|
||||
.panel-row
|
||||
%h3
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Activate Extension
|
||||
%a.sk-a.info.close-button{"ng-click" => "deny()"} Cancel
|
||||
.sk-panel-content
|
||||
.sk-panel-section
|
||||
.sk-panel-row
|
||||
.sk-h2
|
||||
%strong {{component.name}}
|
||||
would like to interact with your
|
||||
{{permissionsString()}}
|
||||
|
||||
.panel-row
|
||||
%p
|
||||
.sk-panel-row
|
||||
%p.sk-p
|
||||
Extensions use an offline messaging system to communicate. Learn more at
|
||||
%a{"href" => "https://standardnotes.org/permissions", "target" => "_blank"} https://standardnotes.org/permissions.
|
||||
.footer
|
||||
.button.info.big.block.bold{"ng-click" => "accept()"} Continue
|
||||
%a.sk-a.info{"href" => "https://standardnotes.org/permissions", "target" => "_blank"} https://standardnotes.org/permissions.
|
||||
.sk-panel-footer
|
||||
.sk-button.info.big.block.bold{"ng-click" => "accept()"}
|
||||
.sk-label Continue
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.sk-modal-background{"ng-click" => "cancel()"}
|
||||
|
||||
.sk-modal-content#privileges-modal
|
||||
.sn-component
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Authentication Required
|
||||
%a.close-button.info{"ng-click" => "cancel()"} Cancel
|
||||
.sk-panel-content
|
||||
.sk-panel-section
|
||||
%div{"ng-repeat" => "credential in requiredCredentials"}
|
||||
.sk-p.sk-bold.sk-panel-row
|
||||
%strong {{promptForCredential(credential)}}
|
||||
.sk-panel-row
|
||||
%input.sk-input.contrast{"type" => "password", "ng-model" => "authenticationParameters[credential]",
|
||||
"sn-autofocus" => "true", "should-focus" => "$index == 0", "sn-enter" => "submit()"}
|
||||
.sk-panel-row
|
||||
%label.sk-label.danger{"ng-if" => "isCredentialInFailureState(credential)"} Invalid authentication. Please try again.
|
||||
.sk-panel-row
|
||||
.sk-panel-row
|
||||
.sk-horizontal-group
|
||||
.sk-p.sk-bold Remember For
|
||||
%a.sk-a.info{"ng-repeat" => "option in sessionLengthOptions", "ng-click" => "selectSessionLength(option.value)",
|
||||
"ng-class" => "{'boxed' : option.value == selectedSessionLength}"}
|
||||
{{option.label}}
|
||||
|
||||
.sk-panel-footer.extra-padding
|
||||
.sk-button.info.big.block.bold{"ng-click" => "submit()"}
|
||||
.sk-label Submit
|
||||
@@ -0,0 +1,41 @@
|
||||
.sk-modal-background{"ng-click" => "cancel()"}
|
||||
|
||||
.sk-modal-content#privileges-modal
|
||||
.sn-component
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Manage Privileges
|
||||
%a.sk-a.close-button.info{"ng-click" => "cancel()"} Done
|
||||
.sk-panel-content
|
||||
.sk-panel-section
|
||||
%table.sk-table
|
||||
%thead
|
||||
%tr
|
||||
%th
|
||||
%th{"ng-repeat" => "cred in availableCredentials"}
|
||||
.priv-header
|
||||
%strong {{credentialDisplayInfo[cred].label}}
|
||||
.sk-p.font-small{"style" => "margin-top: 2px", "ng-show" => "!credentialDisplayInfo[cred].availability"} Not Configured
|
||||
%tbody
|
||||
%tr{"ng-repeat" => "action in availableActions"}
|
||||
%td
|
||||
.sk-p {{displayInfoForAction(action)}}
|
||||
%th{"ng-repeat" => "credential in availableCredentials"}
|
||||
%input{"type" => "checkbox", "ng-disabled" => "!credentialDisplayInfo[credential].availability", "ng-checked" => "isCredentialRequiredForAction(action, credential)", "ng-click" => "checkboxValueChanged(action, credential)"}
|
||||
|
||||
.sk-panel-section{"ng-if" => "sessionExpirey && !sessionExpired"}
|
||||
.sk-p.sk-panel-row You will not be asked to authenticate until {{sessionExpirey}}.
|
||||
%a.sk-a.sk-panel-row.info{"ng-click" => "clearSession()"} Clear Session
|
||||
.sk-panel-footer
|
||||
.sk-h2.sk-bold About Privileges
|
||||
.sk-panel-section.no-bottom-pad
|
||||
.sk-panel-row
|
||||
.text-content
|
||||
.sk-p
|
||||
Privileges represent interface level authentication for accessing certain items and features.
|
||||
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
|
||||
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.
|
||||
@@ -1,13 +1,17 @@
|
||||
.modal.medium#item-preview-modal
|
||||
.content
|
||||
.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title Preview
|
||||
.horizontal-group
|
||||
%a.close-button.info{"ng-click" => "restore(false)"} Restore
|
||||
%a.close-button.info{"ng-click" => "restore(true)"} Restore as copy
|
||||
%a.close-button.info{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
|
||||
.content.selectable
|
||||
%h2 {{content.title}}
|
||||
%p.normal{"style" => "white-space: pre-wrap; font-size: 16px;"} {{content.text}}
|
||||
.sn-component
|
||||
.sk-modal.medium#item-preview-modal
|
||||
.sk-modal-background
|
||||
.sk-modal-content
|
||||
.sn-component
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Preview
|
||||
.sk-horizontal-group
|
||||
%a.sk-a.info.close-button{"ng-click" => "restore(false)"} Restore
|
||||
%a.sk-a.info.close-button{"ng-click" => "restore(true)"} Restore as copy
|
||||
%a.sk-a.info.close-button{"ng-click" => "dismiss(); $event.stopPropagation()"} Close
|
||||
.sk-panel-content.selectable{"ng-if" => "!editor"}
|
||||
.sk-h2 {{content.title}}
|
||||
%p.normal.sk-p{"style" => "white-space: pre-wrap; font-size: 16px;"} {{content.text}}
|
||||
|
||||
%component-view.component-view{"ng-if" => "editor", "component" => "editor"}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
.sn-component#session-history-menu
|
||||
.menu-panel.dropdown-menu
|
||||
.header
|
||||
.column
|
||||
%h4.title {{history.entries.length || 'No'}} revisions
|
||||
%h4{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"}
|
||||
%a Options
|
||||
.sk-menu-panel.dropdown-menu
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-column
|
||||
.sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions
|
||||
.sk-menu-panel-column{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"}
|
||||
%a.sk-a.info.sk-menu-panel-header-title Options
|
||||
|
||||
%div{"ng-if" => "showOptions"}
|
||||
%menu-row{"label" => "'Clear note local history'", "action" => "clearItemHistory()"}
|
||||
%menu-row{"label" => "'Clear all local history'", "action" => "clearAllHistory()"}
|
||||
%menu-row{"label" => "(autoOptimize ? 'Disable' : 'Enable') + ' auto cleanup'", "action" => "toggleAutoOptimize()"}
|
||||
.sublabel
|
||||
.sk-sublabel
|
||||
Automatically cleans up small revisions to conserve space.
|
||||
%menu-row{"label" => "(diskEnabled ? 'Disable' : 'Enable') + ' saving history to disk'", "action" => "toggleDiskSaving()"}
|
||||
.sublabel
|
||||
.sk-sublabel
|
||||
Saving to disk may increase app loading time and memory footprint.
|
||||
|
||||
%menu-row{"ng-repeat" => "revision in entries",
|
||||
"action" => "openRevision(revision);",
|
||||
"label" => "revision.previewTitle()"}
|
||||
.sublabel.opaque{"ng-class" => "classForRevision(revision)"}
|
||||
.sk-sublabel.opaque{"ng-class" => "classForRevision(revision)"}
|
||||
{{revision.previewSubTitle()}}
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
.section.editor#editor-column{"aria-label" => "Note"}
|
||||
.section.editor#editor-column.sn-component{"aria-label" => "Note"}
|
||||
.sn-component
|
||||
.app-bar.no-edges{"ng-if" => "ctrl.note.locked", "ng-init" => "ctrl.lockText = 'Note Locked'", "ng-mouseover" => "ctrl.lockText = 'Unlock'", "ng-mouseleave" => "ctrl.lockText = 'Note Locked'"}
|
||||
.sk-app-bar.no-edges{"ng-if" => "ctrl.note.locked", "ng-init" => "ctrl.lockText = 'Note Locked'", "ng-mouseover" => "ctrl.lockText = 'Unlock'", "ng-mouseleave" => "ctrl.lockText = 'Note Locked'"}
|
||||
.left
|
||||
.item{"ng-click" => "ctrl.toggleLockNote()"}
|
||||
.label.warning
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.toggleLockNote()"}
|
||||
.sk-label.warning
|
||||
%i.icon.ion-locked
|
||||
{{ctrl.lockText}}
|
||||
|
||||
#editor-title-bar.section-title-bar{"ng-show" => "ctrl.note && !ctrl.note.errorDecrypting", "ng-class" => "{'locked' : ctrl.note.locked }"}
|
||||
.title
|
||||
%input.input#note-title-editor{"ng-model" => "ctrl.note.title", "ng-keyup" => "$event.keyCode == 13 && ctrl.saveTitle($event)",
|
||||
"ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "ng-blur" => "ctrl.onNameBlur()",
|
||||
"select-on-click" => "true", "ng-disabled" => "ctrl.note.locked"}
|
||||
"ng-change" => "ctrl.nameChanged()", "ng-focus" => "ctrl.onNameFocus()", "ng-blur" => "ctrl.onNameBlur()",
|
||||
"select-on-click" => "true", "ng-disabled" => "ctrl.note.locked"}
|
||||
|
||||
#save-status{"ng-class" => "{'error bold': ctrl.syncTakingTooLong}", "ng-bind-html" => "ctrl.noteStatus"}
|
||||
#save-status
|
||||
.message{"ng-class" => "{'warning sk-bold': ctrl.syncTakingTooLong, 'danger sk-bold': ctrl.saveError}"} {{ctrl.noteStatus.message}}
|
||||
.desc{"ng-show" => "ctrl.noteStatus.desc"} {{ctrl.noteStatus.desc}}
|
||||
|
||||
.editor-tags
|
||||
#note-tags-component-container{"ng-if" => "ctrl.tagsComponent"}
|
||||
@@ -23,43 +25,44 @@
|
||||
"spellcheck" => "false", "ng-disabled" => "ctrl.note.locked"}
|
||||
|
||||
.sn-component{"ng-if" => "ctrl.note"}
|
||||
.app-bar.no-edges
|
||||
.sk-app-bar.no-edges#editor-menu-bar
|
||||
.left
|
||||
.item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
|
||||
.label Options
|
||||
.menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"}
|
||||
.section
|
||||
.header
|
||||
%h4.title Note Options
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showMenu')", "ng-class" => "{'selected' : ctrl.showMenu}", "click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
|
||||
.sk-label Options
|
||||
.sk-menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"}
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Note Options
|
||||
%menu-row{"label" => "ctrl.note.pinned ? 'Unpin' : 'Pin'", "action" => "ctrl.selectedMenuItem(true); ctrl.togglePin()", "desc" => "'Pin or unpin a note from the top of your list'"}
|
||||
%menu-row{"label" => "ctrl.note.archived ? 'Unarchive' : 'Archive'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleArchiveNote()", "desc" => "'Archive or unarchive a note from your Archived system tag'"}
|
||||
%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.hidePreview ? 'Unhide Preview' : 'Hide Preview'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
|
||||
%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'"}
|
||||
|
||||
.section
|
||||
.header
|
||||
%h4.title Global Display
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Global Display
|
||||
|
||||
%menu-row{"label" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'default'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('monospaceFont')",
|
||||
%menu-row{"label" => "'Monospace Font'", "circle" => "ctrl.monospaceFont ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('monospaceFont')",
|
||||
"desc" => "'Toggles the font style for the default editor'", "subtitle" => "ctrl.selectedEditor ? 'Not available with editor extensions' : null", "disabled" => "ctrl.selectedEditor"}
|
||||
|
||||
%menu-row{"label" => "'Spellcheck'", "circle" => "ctrl.spellcheck ? 'success' : 'default'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('spellcheck')",
|
||||
%menu-row{"label" => "'Spellcheck'", "circle" => "ctrl.spellcheck ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('spellcheck')",
|
||||
"desc" => "'Toggles spellcheck for the default editor'", "subtitle" => "ctrl.selectedEditor ? 'Not available with editor extensions' : null", "disabled" => "ctrl.selectedEditor"}
|
||||
|
||||
%menu-row{"label" => "'Margin Resizers'", "circle" => "ctrl.marginResizersEnabled ? 'success' : 'default'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('marginResizersEnabled')",
|
||||
%menu-row{"label" => "'Margin Resizers'", "circle" => "ctrl.marginResizersEnabled ? 'success' : 'neutral'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleKey('marginResizersEnabled')",
|
||||
"desc" => "'Allows for editor left and right margins to be resized'", "faded" => "!ctrl.marginResizersEnabled"}
|
||||
|
||||
.item{"ng-click" => "ctrl.onEditorMenuClick()", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
|
||||
.label Editor
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showEditorMenu')", "ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
|
||||
.sk-label Editor
|
||||
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.editorMenuOnSelect", "selected-editor" => "ctrl.selectedEditor", "current-item" => "ctrl.note"}
|
||||
|
||||
.item{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "ng-class" => "{'selected' : ctrl.showExtensions}", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
|
||||
.label Actions
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showExtensions')", "ng-class" => "{'selected' : ctrl.showExtensions}", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
|
||||
.sk-label Actions
|
||||
%actions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
|
||||
|
||||
.item{"ng-click" => "ctrl.showSessionHistory = !ctrl.showSessionHistory; ctrl.showMenu = false; ctrl.showEditorMenu = false;", "click-outside" => "ctrl.showSessionHistory = false;", "is-open" => "ctrl.showSessionHistory"}
|
||||
.label Session History
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.toggleMenu('showSessionHistory')", "click-outside" => "ctrl.showSessionHistory = false;", "is-open" => "ctrl.showSessionHistory"}
|
||||
.sk-label Session History
|
||||
%session-history-menu{"ng-if" => "ctrl.showSessionHistory", "item" => "ctrl.note"}
|
||||
|
||||
.editor-content#editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting"}
|
||||
@@ -78,7 +81,15 @@
|
||||
.section{"ng-if" => "ctrl.note.errorDecrypting"}
|
||||
%p.medium-padding{"style" => "padding-top: 0 !important;"} There was an error decrypting this item. Ensure you are running the latest version of this app, then sign out and sign back in to try again.
|
||||
|
||||
#editor-pane-component-stack
|
||||
#editor-pane-component-stack{"ng-show" => "ctrl.note"}
|
||||
#component-stack-menu-bar.sk-app-bar.no-edges{"ng-if" => "ctrl.componentStack.length"}
|
||||
.left
|
||||
.sk-app-bar-item{"ng-repeat" => "component in ctrl.componentStack", "ng-click" => "ctrl.toggleStackComponentForCurrentItem(component)"}
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.small{"ng-class" => "{'info' : !component.hidden && component.active, 'neutral' : component.hidden || !component.active}"}
|
||||
.sk-app-bar-item-column
|
||||
.sk-label {{component.name}}
|
||||
|
||||
.sn-component
|
||||
%component-view.component-view.component-stack-item.border-color{"ng-repeat" => "component in ctrl.componentStack",
|
||||
%component-view.component-view.component-stack-item{"ng-repeat" => "component in ctrl.componentStack",
|
||||
"ng-if" => "component.active", "ng-show" => "!component.hidden", "manual-dealloc" => "true", "component" => "component"}
|
||||
|
||||
@@ -1,44 +1,55 @@
|
||||
.sn-component
|
||||
#footer-bar.app-bar.no-edges
|
||||
#footer-bar.sk-app-bar.no-edges
|
||||
.left
|
||||
.item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"}
|
||||
.column
|
||||
.circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'default')"}
|
||||
.column
|
||||
.label.title{"ng-class" => "{red: ctrl.error}"} Account
|
||||
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.clickOutsideAccountMenu()", "is-open" => "ctrl.showAccountMenu"}
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.small{"ng-class" => "ctrl.error ? 'danger' : (ctrl.getUser() ? 'info' : 'neutral')"}
|
||||
.sk-app-bar-item-column
|
||||
.sk-label.title{"ng-class" => "{red: ctrl.error}"} Account
|
||||
%account-menu{"ng-click" => "$event.stopPropagation()", "ng-if" => "ctrl.showAccountMenu", "on-successful-auth" => "ctrl.onAuthSuccess", "close-function" => "ctrl.closeAccountMenu"}
|
||||
|
||||
.item
|
||||
%a.no-decoration.label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"}
|
||||
.sk-app-bar-item
|
||||
%a.no-decoration.sk-label.title{"href" => "https://standardnotes.org/help", "target" => "_blank"}
|
||||
Help
|
||||
|
||||
.item.border
|
||||
.sk-app-bar-item.border
|
||||
|
||||
.item{"ng-repeat" => "room in ctrl.rooms track by room.uuid"}
|
||||
.column{"ng-click" => "ctrl.selectRoom(room)"}
|
||||
.label {{room.name}}
|
||||
.sk-app-bar-item{"ng-repeat" => "room in ctrl.rooms track by room.uuid"}
|
||||
.sk-app-bar-item-column{"ng-click" => "ctrl.selectRoom(room)"}
|
||||
.sk-label {{room.name}}
|
||||
%component-modal{"ng-if" => "room.showRoom", "component" => "room", "on-dismiss" => "ctrl.onRoomDismiss"}
|
||||
|
||||
|
||||
.right
|
||||
|
||||
.item{"ng-if" => "ctrl.securityUpdateAvailable", "ng-click" => "ctrl.openSecurityUpdate()"}
|
||||
%span.success.label Security update available.
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.securityUpdateAvailable", "ng-click" => "ctrl.openSecurityUpdate()"}
|
||||
%span.success.sk-label Security update available.
|
||||
|
||||
.item{"ng-if" => "ctrl.newUpdateAvailable == true", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"}
|
||||
%span.info.label New update available.
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.newUpdateAvailable == true", "ng-click" => "ctrl.clickedNewUpdateAnnouncement()"}
|
||||
%span.info.sk-label New update available.
|
||||
|
||||
.item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"}
|
||||
.label.subtle
|
||||
.sk-app-bar-item.no-pointer{"ng-if" => "ctrl.lastSyncDate && !ctrl.isRefreshing"}
|
||||
.sk-label.subtle
|
||||
Last refreshed {{ctrl.lastSyncDate | appDateTime}}
|
||||
.item{"ng-if" => "ctrl.lastSyncDate && ctrl.isRefreshing"}
|
||||
.spinner.small
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.lastSyncDate && ctrl.isRefreshing"}
|
||||
.sk-spinner.small
|
||||
|
||||
.item{"ng-if" => "ctrl.offline"}
|
||||
.label Offline
|
||||
.item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"}
|
||||
.label Refresh
|
||||
.sk-app-bar-item{"ng-if" => "ctrl.offline"}
|
||||
.sk-label Offline
|
||||
.sk-app-bar-item{"ng-if" => "!ctrl.offline", "ng-click" => "ctrl.refreshData()"}
|
||||
.sk-label Refresh
|
||||
|
||||
.item#lock-item{"ng-if" => "ctrl.hasPasscode()"}
|
||||
.label
|
||||
.sk-app-bar-item.border{"ng-if" => "ctrl.dockShortcuts.length > 0"}
|
||||
|
||||
.sk-app-bar-item.dock-shortcut{"ng-repeat" => "shortcut in ctrl.dockShortcuts"}
|
||||
.sk-app-bar-item-column{"ng-click" => "ctrl.selectShortcut(shortcut)", "ng-class" => "{'underline': shortcut.component.active}"}
|
||||
.div{"ng-if" => "shortcut.icon.type == 'circle'"}
|
||||
.sk-circle.small{"ng-style" => "{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}"}
|
||||
.div{"ng-if" => "shortcut.icon.type == 'svg'"}
|
||||
.svg-item{"ng-attr-id" => "dock-svg-{{shortcut.component.uuid}}", "elem-ready" => "ctrl.initSvgForShortcut(shortcut)"}
|
||||
|
||||
.sk-app-bar-item.border{"ng-if" => "ctrl.hasPasscode()"}
|
||||
.sk-app-bar-item#lock-item{"ng-if" => "ctrl.hasPasscode()", "title" => "Locks application and wipes unencrypted data from memory."}
|
||||
.sk-label
|
||||
%i.icon.ion-locked#footer-lock-icon{"ng-if" => "ctrl.hasPasscode()", "ng-click" => "ctrl.lockApp()"}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.main-ui-view{"ng-class" => "platform"}
|
||||
|
||||
%lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"}
|
||||
.app#app{"ng-if" => "!needsUnlock"}
|
||||
|
||||
.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"}
|
||||
|
||||
%notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"}
|
||||
|
||||
%editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"}
|
||||
|
||||
%footer{"ng-if" => "!needsUnlock"}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
#lock-screen.sn-component
|
||||
.panel
|
||||
.header
|
||||
%h1.title Passcode Required
|
||||
.content
|
||||
.panel-section
|
||||
%form.panel-form.panel-row{"ng-submit" => "submitPasscodeForm()"}
|
||||
.panel-column.stretch
|
||||
%input.panel-row{:type => 'password',
|
||||
.sk-panel
|
||||
.sk-panel-header
|
||||
.sk-panel-header-title Passcode Required
|
||||
.sk-panel-content
|
||||
.sk-panel-section
|
||||
%form.sk-panel-form.sk-panel-row{"ng-submit" => "submitPasscodeForm()"}
|
||||
.sk-panel-column.stretch
|
||||
%input.center-text.sk-input.contrast{:type => 'password',
|
||||
"ng-model" => "formData.passcode", "autofocus" => "true", "sn-autofocus" => "true", "should-focus" => "true",
|
||||
"placeholder" => "Enter Passcode", "autocomplete" => "new-password"}
|
||||
.button-group.stretch.panel-row.form-submit
|
||||
%button.button.info{"type" => "submit"}
|
||||
.label Unlock
|
||||
.sk-button-group.stretch.sk-panel-row.form-submit
|
||||
%button.sk-button.info{"type" => "submit"}
|
||||
.sk-label Unlock
|
||||
|
||||
#passcode-reset
|
||||
%a.default{"ng-if" => "!formData.showRecovery", "ng-click" => "forgotPasscode()"} Forgot Passcode?
|
||||
.sk-panel-footer
|
||||
#passcode-reset
|
||||
%a.sk-a.neutral{"ng-if" => "!formData.showRecovery", "ng-click" => "forgotPasscode()"} Forgot?
|
||||
|
||||
%div{"ng-if" => "formData.showRecovery"}
|
||||
%p
|
||||
If you forgot your local passcode, your only option is to clear all your local data from this device
|
||||
and sign back in to your account.
|
||||
%a.danger{"ng-click" => "beginDeleteData()"} Delete Local Data
|
||||
%div{"ng-if" => "formData.showRecovery"}
|
||||
.sk-p
|
||||
If you forgot your local passcode, your only option is to clear your local data from this device
|
||||
and sign back in to your account.
|
||||
.sk-panel-row
|
||||
%a.sk-a.danger.center-text{"ng-click" => "beginDeleteData()"} Delete Local Data
|
||||
|
||||
@@ -1,38 +1,40 @@
|
||||
.section.notes#notes-column{"aria-label" => "Notes"}
|
||||
.sn-component.section.notes#notes-column{"aria-label" => "Notes"}
|
||||
.content
|
||||
.section-title-bar#notes-title-bar
|
||||
.padded
|
||||
.section-title-bar-header
|
||||
.title {{ctrl.panelTitle()}}
|
||||
.add-button#notes-add-button{"ng-click" => "ctrl.createNewNote()", "title" => "Create a new note in the selected tag"} +
|
||||
.sk-button.contrast.wide{"ng-click" => "ctrl.createNewNote()", "title" => "Create a new note in the selected tag"}
|
||||
.sk-label +
|
||||
.filter-section{"role" => "search"}
|
||||
%input.filter-bar#search-bar.mousetrap{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search",
|
||||
"ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true", "ng-blur" => "ctrl.onFilterEnter()", "ng-keyup" => "$event.keyCode == 13 && ctrl.onFilterEnter();",
|
||||
"title" => "Searches notes in the currently selected tag"}
|
||||
#search-clear-button{"ng-if" => "ctrl.noteFilter.text", "ng-click" => "ctrl.clearFilterText();"} ✕
|
||||
.sn-component#notes-menu-bar
|
||||
.app-bar.no-edges
|
||||
.sk-app-bar.no-edges
|
||||
.left
|
||||
.item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "ng-class" => "{'selected' : ctrl.showMenu}"}
|
||||
.column
|
||||
.label
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.showMenu = !ctrl.showMenu", "ng-class" => "{'selected' : ctrl.showMenu}"}
|
||||
.sk-app-bar-item-column
|
||||
.sk-label
|
||||
Options
|
||||
.column
|
||||
.sublabel {{ctrl.optionsSubtitle()}}
|
||||
.sk-app-bar-item-column
|
||||
.sk-sublabel {{ctrl.optionsSubtitle()}}
|
||||
|
||||
.sn-component{"ng-if" => "ctrl.showMenu"}
|
||||
.menu-panel.dropdown-menu
|
||||
.section
|
||||
.header
|
||||
%h4.title Sort By
|
||||
.sk-menu-panel.dropdown-menu{"ng-if" => "ctrl.showMenu"}
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Sort By
|
||||
.sk-button.sk-secondary-contrast{"ng-click" => "ctrl.toggleReverseSort()"}
|
||||
.sk-label
|
||||
%i.icon{"ng-class" => "{'ion-arrow-return-left' : ctrl.sortReverse == false, 'ion-arrow-return-right' : ctrl.sortReverse == true }"}
|
||||
|
||||
%menu-row{"label" => "'Date Added'", "circle" => "ctrl.sortBy == 'created_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()", "desc" => "'Sort notes by newest first'"}
|
||||
%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'"}
|
||||
%menu-row{"label" => "'Date Added'", "circle" => "ctrl.sortBy == 'created_at' && 'success'", "action" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()", "desc" => "'Sort notes by newest first'"}
|
||||
%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'"}
|
||||
|
||||
.section{"ng-if" => "!ctrl.tag.isSmartTag()"}
|
||||
.header
|
||||
%h4.title Display
|
||||
.sk-menu-panel-section{"ng-if" => "!ctrl.tag.isSmartTag()"}
|
||||
.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.'"}
|
||||
@@ -42,26 +44,31 @@
|
||||
|
||||
.scrollable
|
||||
.infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
||||
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy | limitTo:ctrl.notesToDisplay)) track by note.uuid",
|
||||
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy:ctrl.sortReverse | limitTo:ctrl.notesToDisplay)) track by note.uuid",
|
||||
"ng-click" => "ctrl.selectNote(note, true)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
||||
%strong.red.medium-text{"ng-if" => "note.conflict_of"} Conflicted copy
|
||||
%strong.red.medium-text{"ng-if" => "note.errorDecrypting"} Unable to Decrypt
|
||||
|
||||
.pinned.tinted{"ng-if" => "note.pinned", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"}
|
||||
%i.icon.ion-bookmark
|
||||
%strong.medium-text Pinned
|
||||
.note-flags
|
||||
.pinned.note-flag{"ng-if" => "note.pinned"}
|
||||
%i.icon.ion-bookmark
|
||||
%strong.medium-text Pinned
|
||||
|
||||
.archived.tinted{"ng-if" => "note.archived && !ctrl.tag.isSmartTag()", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"}
|
||||
%i.icon.ion-ios-box
|
||||
%strong.medium-text Archived
|
||||
.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
|
||||
{{note.title}}
|
||||
|
||||
.note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview"}
|
||||
.note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected"}
|
||||
.html-preview{"ng-if" => "note.content.preview_html", "ng-bind-html" => "note.content.preview_html"}
|
||||
.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}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.section.tags#tags-column{"aria-label" => "Tags"}
|
||||
.sn-component.section.tags#tags-column{"aria-label" => "Tags"}
|
||||
|
||||
.component-view-container{"ng-if" => "ctrl.component.active"}
|
||||
%component-view.component-view{"component" => "ctrl.component"}
|
||||
@@ -6,8 +6,10 @@
|
||||
#tags-content.content{"ng-if" => "!(ctrl.component && ctrl.component.active)"}
|
||||
#tags-title-bar.section-title-bar
|
||||
.section-title-bar-header
|
||||
.title Tags
|
||||
.add-button#tag-add-button{"ng-click" => "ctrl.clickedAddNewTag()", "title" => "Create a new tag"} +
|
||||
.sk-h3.title
|
||||
%span.sk-bold Tags
|
||||
.sk-button.sk-secondary-contrast.wide{"ng-click" => "ctrl.clickedAddNewTag()", "title" => "Create a new tag"}
|
||||
.sk-label +
|
||||
|
||||
.scrollable
|
||||
.infinite-scroll
|
||||
|
||||
BIN
dist/assets/ionicons.eot
vendored
BIN
dist/assets/ionicons.eot
vendored
Binary file not shown.
32
dist/assets/ionicons.svg
vendored
Normal file → Executable file
32
dist/assets/ionicons.svg
vendored
Normal file → Executable file
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!--
|
||||
2018-1-19: Created with FontForge (http://fontforge.org)
|
||||
2018-12-13: Created with FontForge (http://fontforge.org)
|
||||
-->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||
<metadata>
|
||||
Created by FontForge 20170925 at Fri Jan 19 12:18:20 2018
|
||||
Created by FontForge 20170925 at Thu Dec 13 12:46:07 2018
|
||||
By mo
|
||||
Copyright (c) 2018, mo
|
||||
</metadata>
|
||||
<defs>
|
||||
<font id="Ionicons" horiz-adv-x="192" >
|
||||
<font id="Ionicons" horiz-adv-x="384" >
|
||||
<font-face
|
||||
font-family="Ionicons"
|
||||
font-weight="400"
|
||||
@@ -19,17 +19,33 @@ Copyright (c) 2018, mo
|
||||
panose-1="2 0 5 3 0 0 0 0 0 0"
|
||||
ascent="448"
|
||||
descent="-64"
|
||||
bbox="0 -32 384 416"
|
||||
bbox="0 -64 448 448"
|
||||
underline-thickness="25.6"
|
||||
underline-position="-51.2"
|
||||
unicode-range="U+F200-F3EC"
|
||||
unicode-range="U+F200-F4D4"
|
||||
/>
|
||||
<missing-glyph />
|
||||
<glyph glyph-name="ion-bookmark" unicode=""
|
||||
d="M184 384c4 0 8 -4 8 -8v-56h-192v56c0 4 4 8 8 8h176zM0 0v304h192v-304l-96 96z" />
|
||||
<glyph glyph-name="ion-ios-box" unicode="" horiz-adv-x="320"
|
||||
d="M16 48v192h288v-192h-288zM128 208c-9 0 -16 -7 -16 -16s7 -16 16 -16h64c9 0 16 7 16 16s-7 16 -16 16h-64zM0 336h320v-80h-16h-288h-16v80z" />
|
||||
<glyph glyph-name="ion-locked" unicode="" horiz-adv-x="384"
|
||||
<glyph glyph-name="ion-lock-combination" unicode=""
|
||||
d="M320 271c39 -35 64 -86 64 -143c0 -106 -86 -192 -192 -192s-192 86 -192 192c0 57 25 108 64 143v49c0 71 57 128 128 128s128 -57 128 -128v-49zM96 320v-26c28 16 61 26 96 26s68 -10 96 -26v26c0 53 -43 96 -96 96s-96 -43 -96 -96zM192 -32c88 0 160 72 160 160
|
||||
s-72 160 -160 160s-160 -72 -160 -160s72 -160 160 -160zM192 272c80 0 144 -64 144 -144s-64 -144 -144 -144s-144 64 -144 144s64 144 144 144zM315 95c2 9 4 18 4 29h-7v7h8c0 11 -2 20 -5 30l-25 -7l-4 12l25 8c-4 10 -8 18 -14 26l-11 -8l-4 6l11 8c-6 8 -13 16 -21 22
|
||||
l-15 -21l-11 8l15 20c-8 6 -17 11 -27 14l-4 -13l-6 2l5 13c-9 3 -20 5 -29 5v-13v-6h-16v6v13c-11 -1 -19 -2 -29 -5l4 -12l-6 -2l-4 12c-10 -3 -19 -8 -27 -14l15 -20l-10 -8l-15 21c-8 -6 -16 -14 -22 -22l11 -8l-4 -5l-10 8c-6 -8 -10 -17 -14 -27l24 -8l-5 -12l-25 7
|
||||
c-3 -10 -3 -19 -3 -30h6v-7h-5c0 -11 2 -20 4 -29l24 7l4 -12l-24 -8c4 -10 8 -19 14 -27l9 7l4 -5l-9 -7c6 -8 13 -16 21 -22l15 21l10 -8l-15 -20c8 -5 17 -11 27 -14l3 11l7 -2l-4 -11c10 -3 18 -4 29 -5v13v6h16v-6v-13c9 1 19 2 29 5l-5 12l6 2l4 -12c10 3 19 8 27 14
|
||||
l-15 20l11 8l15 -21c8 6 15 14 21 22l-11 7l4 6l10 -8c6 8 11 17 15 27l-25 8l4 12zM111 128c0 45 36 81 81 81s81 -36 81 -81s-36 -81 -81 -81s-81 36 -81 81z" />
|
||||
<glyph glyph-name="ion-arrow-return-right" unicode=""
|
||||
d="M384 256l-128 -96v64h-192v-128h248c4 0 8 -4 8 -8v-48c0 -4 -4 -8 -8 -8h-304c-4 0 -8 4 -8 8v240c0 4 4 8 8 8h248v64z" />
|
||||
<glyph glyph-name="ion-key" unicode="" horiz-adv-x="192"
|
||||
d="M144 147c28 -17 48 -48 48 -83c0 -53 -43 -96 -96 -96s-96 43 -96 96c0 36 19 67 48 84c0 0 7 16 12 41c0 4 11 6 11 12v20c0 5 -7 9 -7 11v8v8c0 2 0 4 1 6c0 1 1 1 1 2l2 1l4 4v1c2 2 3 4 3 6c0 1 1 8 1 9c0 3 -2 6 -4 8l-1 1l-4 4v0l-1 1v0c-2 2 -3 5 -3 8v7
|
||||
c0 3 2 7 4 9v0l5 5v0c2 2 4 3 4 6v24c0 3 -2 6 -4 8v1l-4 4l-1 1c-2 2 -3 5 -3 8v18c0 11 0 19 9 23c3 1 14 3 23 3c20 0 30 -8 32 -36c0 0 7 -81 9 -143s7 -90 7 -90zM96 0c18 0 32 14 32 32s-14 32 -32 32s-32 -14 -32 -32s14 -32 32 -32z" />
|
||||
<glyph glyph-name="ion-eye-disabled" unicode="" horiz-adv-x="448"
|
||||
d="M344 290c41 -27 76 -66 104 -100c-51 -54 -124 -135 -224 -135c-36 0 -65 8 -92 22l-77 -77l-23 23l71 71c-36 25 -68 61 -103 98c78 85 142 137 224 137c34 0 64 -9 92 -23l77 78l23 -23zM134 192c0 -19 6 -36 16 -51l27 27c-4 7 -5 15 -5 24c0 29 23 53 52 53h8
|
||||
c-5 -6 -8 -13 -8 -21c0 -3 0 -5 1 -8l51 50c-15 11 -33 17 -52 17c-49 0 -90 -41 -90 -91zM224 101c49 0 90 41 90 91c0 19 -6 37 -16 52l-50 -51c3 -1 5 -1 8 -1c8 0 14 3 20 7v-7c0 -29 -23 -53 -52 -53c-9 0 -17 2 -24 6l-28 -27c15 -11 33 -17 52 -17z" />
|
||||
<glyph glyph-name="ion-arrow-return-left" unicode=""
|
||||
d="M128 352v-64h248c4 0 8 -4 8 -8v-240c0 -4 -4 -8 -8 -8h-304c-4 0 -8 4 -8 8v48c0 4 4 8 8 8h248v128h-192v-64l-128 96z" />
|
||||
<glyph glyph-name="ion-bookmark" unicode="" horiz-adv-x="192"
|
||||
d="M184 384c4 0 8 -4 8 -8v-56h-192v56c0 4 4 8 8 8h176zM0 0v304h192v-304l-96 96z" />
|
||||
<glyph glyph-name="ion-locked" unicode=""
|
||||
d="M22 -32c-12 0 -22 10 -22 22v212c0 12 10 22 22 22h3h19v31c0 42 17 87 43 115s64 46 105 46v0v0c41 0 79 -18 105 -46s43 -73 43 -115v-31h22c12 0 22 -10 22 -22v-212c0 -12 -10 -22 -22 -22h-340zM97 255v-31h17h155h18v31c0 27 -10 61 -28 80v0v1
|
||||
c-18 19 -42 29 -67 29v0v0c-25 0 -49 -10 -67 -29v-1v0c-18 -19 -28 -53 -28 -80z" />
|
||||
</font>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
dist/assets/ionicons.ttf
vendored
Normal file → Executable file
BIN
dist/assets/ionicons.ttf
vendored
Normal file → Executable file
Binary file not shown.
BIN
dist/assets/ionicons.woff
vendored
Normal file → Executable file
BIN
dist/assets/ionicons.woff
vendored
Normal file → Executable file
Binary file not shown.
3265
package-lock.json
generated
3265
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "2.3.18",
|
||||
"version": "3.0.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,7 +23,7 @@
|
||||
"bower": "^1.8.0",
|
||||
"chai": "^4.1.2",
|
||||
"connect": "^3.6.6",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt": "^1.0.3",
|
||||
"grunt-angular-templates": "^1.1.0",
|
||||
"grunt-babel": "^6.0.0",
|
||||
"grunt-browserify": "^5.0.0",
|
||||
@@ -44,7 +44,8 @@
|
||||
"mocha": "^5.2.0",
|
||||
"serve-static": "^1.13.2",
|
||||
"sn-models": "0.1.9",
|
||||
"sn-stylekit": "1.0.15",
|
||||
"standard-file-js": "0.3.19"
|
||||
"sn-stylekit": "2.0.13",
|
||||
"standard-file-js": "0.3.22",
|
||||
"grunt-shell": "^2.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Submodule public/extensions/batch-manager updated: c05c783038...db7b790e33
Reference in New Issue
Block a user