Merge branch 'master' into after-delete-select-first

This commit is contained in:
Mo Bitar
2017-03-01 17:36:57 -06:00
committed by GitHub
66 changed files with 1360 additions and 1278 deletions

View File

@@ -86,7 +86,6 @@ module.exports = function(grunt) {
'vendor/assets/bower_components/angular-ui-router/release/angular-ui-router.js',
'vendor/assets/bower_components/lodash/dist/lodash.min.js',
'vendor/assets/bower_components/restangular/dist/restangular.js',
'vendor/assets/bower_components/marked/lib/marked.js',
'vendor/assets/javascripts/crypto/*.js'
],
dest: 'vendor/assets/javascripts/lib.js',

View File

@@ -15,87 +15,64 @@ angular.module('app.frontend')
bindToController: true,
link:function(scope, elem, attrs, ctrl) {
/**
* Insert 4 spaces when a tab key is pressed,
* only used when inside of the text editor.
* If the shift key is pressed first, this event is
* not fired.
*/
var handleTab = function (event) {
if (!event.shiftKey && event.which == 9) {
event.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
var spaces = " ";
// Insert 4 spaces
this.value = this.value.substring(0, start)
+ spaces + this.value.substring(end);
// Place cursor 4 spaces away from where
// the tab key was pressed
this.selectionStart = this.selectionEnd = start + 4;
}
}
var handler = function(event) {
if (event.ctrlKey || event.metaKey) {
switch (String.fromCharCode(event.which).toLowerCase()) {
case 's':
event.preventDefault();
$timeout(function(){
ctrl.saveNote(event);
});
break;
case 'e':
event.preventDefault();
$timeout(function(){
ctrl.clickedEditNote();
})
break;
case 'm':
event.preventDefault();
$timeout(function(){
ctrl.toggleMarkdown();
})
break;
case 'o':
event.preventDefault();
$timeout(function(){
ctrl.toggleFullScreen();
})
break;
}
}
};
window.addEventListener('keydown', handler);
var element = document.getElementById("note-text-editor");
element.addEventListener('keydown', handleTab);
scope.$on('$destroy', function(){
window.removeEventListener('keydown', handler);
})
scope.$watch('ctrl.note', function(note, oldNote){
if(note) {
ctrl.setNote(note, oldNote);
} else {
ctrl.note = {};
}
});
}
}
})
.controller('EditorCtrl', function ($sce, $timeout, authManager, markdownRenderer, $rootScope, extensionManager, syncManager) {
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager) {
window.addEventListener("message", function(event){
if(event.data.status) {
this.postNoteToExternalEditor();
} else {
var id = event.data.id;
var text = event.data.text;
var data = event.data.data;
if(this.note.uuid === id) {
this.note.text = text;
if(data) {
var changesMade = this.customEditor.setData(id, data);
if(changesMade) {
this.customEditor.setDirty(true);
}
}
this.changesMade();
}
}
}.bind(this), false);
this.setNote = function(note, oldNote) {
this.editorMode = 'edit';
var currentEditor = this.customEditor;
this.customEditor = null;
this.showExtensions = false;
this.showMenu = false;
this.loadTagsString();
var setEditor = function(editor) {
this.customEditor = editor;
this.postNoteToExternalEditor();
}.bind(this)
var editor = this.editorForNote(note);
if(editor) {
if(currentEditor !== editor) {
// switch after timeout, so that note data isnt posted to current editor
$timeout(function(){
setEditor(editor);
}.bind(this));
} else {
// switch immediately
setEditor(editor);
}
} else {
this.customEditor = null;
}
if(note.safeText().length == 0 && note.dummy) {
this.focusTitle(100);
}
@@ -109,19 +86,50 @@ angular.module('app.frontend')
}
}
this.hasAvailableExtensions = function() {
return extensionManager.extensionsInContextOfItem(this.note).length > 0;
this.selectedEditor = function(editor) {
this.showEditorMenu = false;
if(this.customEditor && editor !== this.customEditor) {
this.customEditor.removeItemAsRelationship(this.note);
this.customEditor.setDirty(true);
}
if(editor.default) {
this.customEditor = null;
} else {
this.customEditor = editor;
this.customEditor.addItemAsRelationship(this.note);
this.customEditor.setDirty(true);
}
}.bind(this)
this.editorForNote = function(note) {
var editors = modelManager.itemsForContentType("SN|Editor");
for(var editor of editors) {
if(_.includes(editor.notes, note)) {
return editor;
}
}
return null;
}
this.onPreviewDoubleClick = function() {
this.editorMode = 'edit';
this.focusEditor(100);
this.postNoteToExternalEditor = function() {
var externalEditorElement = document.getElementById("editor-iframe");
if(externalEditorElement) {
externalEditorElement.contentWindow.postMessage({text: this.note.text, data: this.customEditor.dataForKey(this.note.uuid), id: this.note.uuid}, '*');
}
}
this.hasAvailableExtensions = function() {
return extensionManager.extensionsInContextOfItem(this.note).length > 0;
}
this.focusEditor = function(delay) {
setTimeout(function(){
var element = document.getElementById("note-text-editor");
element.focus();
if(element) {
element.focus();
}
}, delay)
}
@@ -135,10 +143,6 @@ angular.module('app.frontend')
this.showMenu = false;
}
this.renderedContent = function() {
return markdownRenderer.renderHtml(markdownRenderer.renderedContentForText(this.note.safeText()));
}
var statusTimeout;
this.saveNote = function($event) {
@@ -198,7 +202,6 @@ angular.module('app.frontend')
}
this.onContentFocus = function() {
this.showSampler = false;
$rootScope.$broadcast("editorFocused");
}
@@ -209,12 +212,7 @@ angular.module('app.frontend')
this.toggleFullScreen = function() {
this.fullscreen = !this.fullscreen;
if(this.fullscreen) {
if(this.editorMode == 'edit') {
// refocus
this.focusEditor(0);
}
} else {
this.focusEditor(0);
}
}
@@ -222,19 +220,6 @@ angular.module('app.frontend')
this.showMenu = false;
}
this.toggleMarkdown = function() {
if(this.editorMode == 'preview') {
this.editorMode = 'edit';
this.focusEditor(0);
} else {
this.editorMode = 'preview';
}
}
this.clickedMenu = function() {
this.showMenu = !this.showMenu;
}
this.deleteNote = function() {
if(confirm("Are you sure you want to delete this note?")) {
this.remove()(this.note);

View File

@@ -1,8 +1,31 @@
angular.module('app.frontend')
.controller('HomeCtrl', function ($scope, $rootScope, $timeout, modelManager, syncManager, authManager) {
$rootScope.bodyClass = "app-body-class";
.controller('HomeCtrl', function ($scope, $stateParams, $rootScope, $timeout, modelManager, syncManager, authManager) {
function autoSignInFromParams() {
if(!authManager.offline()) {
// check if current account
if(syncManager.serverURL == $stateParams.server && authManager.user.email == $stateParams.email) {
// already signed in, return
return;
} else {
// sign out
syncManager.destroyLocalData(function(){
window.location.reload();
})
}
} else {
authManager.login($stateParams.server, $stateParams.email, $stateParams.pw, function(response){
window.location.reload();
})
}
}
if($stateParams.server && $stateParams.email) {
autoSignInFromParams();
}
syncManager.loadLocalItems(function(items) {
$scope.allTag.didLoad = true;
$scope.$apply();
syncManager.sync(null);
@@ -12,7 +35,9 @@ angular.module('app.frontend')
}, 30000);
});
$scope.allTag = new Tag({all: true});
var allTag = new Tag({all: true});
allTag.needsLoad = true;
$scope.allTag = allTag;
$scope.allTag.title = "All";
$scope.tags = modelManager.tags;
$scope.allTag.notes = modelManager.notes;
@@ -31,6 +56,7 @@ angular.module('app.frontend')
modelManager.createRelationshipBetweenItems(note, tag);
}
note.setDirty(true);
syncManager.sync();
}

View File

@@ -18,7 +18,16 @@ angular.module('app.frontend')
link:function(scope, elem, attrs, ctrl) {
scope.$watch('ctrl.tag', function(tag, oldTag){
if(tag) {
ctrl.tagDidChange(tag, oldTag);
if(tag.needsLoad) {
scope.$watch('ctrl.tag.didLoad', function(didLoad){
if(didLoad) {
tag.needsLoad = false;
ctrl.tagDidChange(tag, oldTag);
}
});
} else {
ctrl.tagDidChange(tag, oldTag);
}
}
});
}
@@ -26,6 +35,8 @@ angular.module('app.frontend')
})
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager) {
this.sortBy = localStorage.getItem("sortBy") || "created_at";
$rootScope.$on("editorFocused", function(){
this.showMenu = false;
}.bind(this))
@@ -34,8 +45,6 @@ angular.module('app.frontend')
this.selectFirstNote(false);
}.bind(this))
var isFirstLoad = true;
this.notesToDisplay = 20;
this.paginate = function() {
this.notesToDisplay += 20
@@ -49,20 +58,16 @@ angular.module('app.frontend')
}
this.noteFilter.text = "";
this.setNotes(tag.notes);
}
tag.notes.forEach(function(note){
this.setNotes = function(notes) {
notes.forEach(function(note){
note.visible = true;
})
this.selectFirstNote(false);
if(isFirstLoad) {
$timeout(function(){
this.createNewNote();
isFirstLoad = false;
}.bind(this))
} else if(tag.notes.length == 0) {
this.createNewNote();
}
var createNew = notes.length == 0;
this.selectFirstNote(createNew);
}
this.selectedTagDelete = function() {
@@ -71,7 +76,7 @@ angular.module('app.frontend')
}
this.selectFirstNote = function(createNew) {
var visibleNotes = this.tag.notes.filter(function(note){
var visibleNotes = this.sortedNotes.filter(function(note){
return note.visible;
});
@@ -114,4 +119,22 @@ angular.module('app.frontend')
}
}.bind(this), 100)
}
this.selectedMenuItem = function() {
this.showMenu = false;
}
this.selectedSortByCreated = function() {
this.setSortBy("created_at");
}
this.selectedSortByUpdated = function() {
this.setSortBy("updated_at");
}
this.setSortBy = function(type) {
this.sortBy = type;
localStorage.setItem("sortBy", type);
}
});

View File

@@ -37,6 +37,7 @@ class Item {
updateFromJSON(json) {
_.merge(this, json);
if(this.created_at) {
this.created_at = new Date(this.created_at);
this.updated_at = new Date(this.updated_at);
@@ -112,6 +113,10 @@ class Item {
this.setDirty(true);
}
locallyClearAllReferences() {
}
mergeMetadataFromItem(item) {
_.merge(this, _.omit(item, ["content"]));
}

View File

@@ -0,0 +1,89 @@
class Editor extends Item {
constructor(json_obj) {
super(json_obj);
if(!this.notes) {
this.notes = [];
}
if(!this.data) {
this.data = {};
}
}
mapContentToLocalProperties(contentObject) {
super.mapContentToLocalProperties(contentObject)
this.url = contentObject.url;
this.name = contentObject.name;
this.data = contentObject.data || {};
}
structureParams() {
var params = {
url: this.url,
name: this.name,
data: this.data
};
_.merge(params, super.structureParams());
return params;
}
referenceParams() {
var references = _.map(this.notes, function(note){
return {uuid: note.uuid, content_type: note.content_type};
})
return references;
}
addItemAsRelationship(item) {
if(item.content_type == "Note") {
if(!_.find(this.notes, item)) {
this.notes.push(item);
}
}
super.addItemAsRelationship(item);
}
removeItemAsRelationship(item) {
if(item.content_type == "Note") {
_.pull(this.notes, item);
}
super.removeItemAsRelationship(item);
}
removeAllRelationships() {
super.removeAllRelationships();
this.notes = [];
}
locallyClearAllReferences() {
super.locallyClearAllReferences();
this.notes = [];
}
allReferencedObjects() {
return this.notes;
}
toJSON() {
return {uuid: this.uuid}
}
get content_type() {
return "SN|Editor";
}
setData(key, value) {
var dataHasChanged = JSON.stringify(this.data[key]) !== JSON.stringify(value);
if(dataHasChanged) {
this.data[key] = value;
return true;
}
return false;
}
dataForKey(key) {
return this.data[key] || {};
}
}

View File

@@ -79,6 +79,7 @@ class Extension extends Item {
super.mapContentToLocalProperties(contentObject)
this.name = contentObject.name;
this.url = contentObject.url;
this.supported_types = contentObject.supported_types;
this.actions = contentObject.actions.map(function(action){
return new Action(action);
})
@@ -99,7 +100,8 @@ class Extension extends Item {
var params = {
name: this.name,
url: this.url,
actions: this.actions
actions: this.actions,
supported_types: this.supported_types
};
_.merge(params, super.structureParams());

View File

@@ -56,6 +56,14 @@ class Note extends Item {
this.tags = [];
}
locallyClearAllReferences() {
super.locallyClearAllReferences();
this.tags.forEach(function(tag){
_.pull(tag.notes, this);
}.bind(this))
this.tags = [];
}
isBeingRemovedLocally() {
this.tags.forEach(function(tag){
_.pull(tag.notes, this);

View File

@@ -55,6 +55,15 @@ class Tag extends Item {
this.notes = [];
}
locallyClearAllReferences() {
super.locallyClearAllReferences();
this.notes.forEach(function(note){
_.pull(note.tags, this);
}.bind(this))
this.notes = [];
}
isBeingRemovedLocally() {
this.notes.forEach(function(note){
_.pull(note.tags, this);

View File

@@ -7,7 +7,7 @@ angular.module('app.frontend')
})
.state('home', {
url: '/',
url: '/?server&email&pw',
parent: 'base',
views: {
'content@' : {

View File

@@ -73,13 +73,12 @@ angular.module('app.frontend')
}
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
var mk = keys.mk;
var requestUrl = url + "/auth/sign_in";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = {password: keys.pw, email: email};
_.merge(request, params);
request.post().then(function(response){
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw);
this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
callback(response);
}.bind(this))
.catch(function(response){
@@ -91,7 +90,9 @@ angular.module('app.frontend')
}
this.handleAuthResponse = function(response, email, url, authParams, mk, pw) {
localStorage.setItem("server", url);
if(url) {
localStorage.setItem("server", url);
}
localStorage.setItem("user", JSON.stringify(response.plain().user));
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
localStorage.setItem("mk", mk);
@@ -101,13 +102,12 @@ angular.module('app.frontend')
this.register = function(url, email, password, callback) {
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
var mk = keys.mk;
var requestUrl = url + "/auth";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = _.merge({password: keys.pw, email: email}, authParams);
_.merge(request, params);
request.post().then(function(response){
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw);
this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
callback(response);
}.bind(this))
.catch(function(response){
@@ -117,55 +117,26 @@ angular.module('app.frontend')
}.bind(this));
}
// this.changePassword = function(current_password, new_password) {
// this.getAuthParamsForEmail(email, function(authParams){
// if(!authParams) {
// callback(null);
// return;
// }
// Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams), function(currentKeys) {
// Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams), function(newKeys){
// var data = {};
// data.current_password = currentKeys.pw;
// data.password = newKeys.pw;
// data.password_confirmation = newKeys.pw;
//
// var user = this.user;
//
// this._performPasswordChange(currentKeys, newKeys, function(response){
// if(response && !response.error) {
// // this.showNewPasswordForm = false;
// // reencrypt data with new mk
// this.reencryptAllItemsAndSave(user, newKeys.mk, currentKeys.mk, function(success){
// if(success) {
// this.setMk(newKeys.mk);
// alert("Your password has been changed and your data re-encrypted.");
// } else {
// // rollback password
// this._performPasswordChange(newKeys, currentKeys, function(response){
// alert("There was an error changing your password. Your password has been rolled back.");
// window.location.reload();
// })
// }
// }.bind(this));
// } else {
// // this.showNewPasswordForm = false;
// alert("There was an error changing your password. Please try again.");
// }
// }.bind(this))
// }.bind(this));
// }.bind(this));
// }.bind(this));
// }
this.changePassword = function(email, new_password, callback) {
Neeto.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){
var requestUrl = localStorage.getItem("server") + "/auth/change_pw";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = _.merge({new_password: keys.pw}, authParams);
_.merge(request, params);
this._performPasswordChange = function(url, email, current_keys, new_keys, callback) {
var requestUrl = url + "/auth";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email};
_.merge(request, params);
request.patch().then(function(response){
callback(response);
})
request.post().then(function(response){
this.handleAuthResponse(response, email, null, authParams, keys.mk, keys.pw);
callback(response.plain());
}.bind(this))
.catch(function(response){
var error = response.data;
if(!error) {
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
}
console.log("Change pw error", response);
callback({error: error});
})
}.bind(this));
}
this.staticifyObject = function(object) {

View File

@@ -102,7 +102,6 @@ class DBManager {
this.openDatabase((db) => {
var request = db.transaction("items", "readwrite").objectStore("items").delete(item.uuid);
request.onsuccess = function(event) {
console.log("Successfully deleted item", item.uuid);
if(callback) {
callback(true);
}

View File

@@ -0,0 +1,27 @@
angular
.module('app.frontend')
.directive('clickOutside', ['$document', function($document) {
return {
restrict: 'A',
replace: false,
link : function($scope, $element, attrs) {
var didApplyClickOutside = false;
$element.bind('click', function(e) {
didApplyClickOutside = false;
if (attrs.isOpen) {
e.stopPropagation();
}
});
$document.bind('click', function() {
if(!didApplyClickOutside) {
$scope.$apply(attrs.clickOutside);
didApplyClickOutside = true;
}
})
}
}
}]);

View File

@@ -20,11 +20,15 @@ angular
});
function showSpinner() {
if(scope.hidePromise) {
$timeout.cancel(scope.hidePromise);
scope.hidePromise = null;
}
showElement(true);
}
function hideSpinner() {
$timeout(showElement.bind(this, false), getDelay());
scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
}
function showElement(show) {

View File

@@ -6,19 +6,15 @@ class AccountMenu {
this.scope = {};
}
controller($scope, authManager, modelManager, syncManager, $timeout) {
controller($scope, authManager, modelManager, syncManager, dbManager, $timeout) {
'ngInject';
$scope.formData = {url: syncManager.serverURL};
$scope.formData = {mergeLocal: true, url: syncManager.serverURL};
$scope.user = authManager.user;
$scope.server = syncManager.serverURL;
$scope.syncStatus = syncManager.syncStatus;
$scope.changePasswordPressed = function() {
$scope.showNewPasswordForm = !$scope.showNewPasswordForm;
}
$scope.encryptionKey = function() {
return syncManager.masterKey;
}
@@ -31,25 +27,61 @@ class AccountMenu {
return `${$scope.server}/dashboard/?server=${$scope.server}&id=${$scope.user.email}&pw=${$scope.serverPassword()}`;
}
$scope.newPasswordData = {};
$scope.showPasswordChangeForm = function() {
$scope.newPasswordData.showForm = true;
}
$scope.submitPasswordChange = function() {
$scope.passwordChangeData.status = "Generating New Keys...";
$timeout(function(){
if(data.password != data.password_confirmation) {
alert("Your new password does not match its confirmation.");
return;
}
if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) {
alert("Your new password does not match its confirmation.");
$scope.newPasswordData.status = null;
return;
}
authManager.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){
var email = $scope.user.email;
if(!email) {
alert("We don't have your email stored. Please log out then log back in to fix this issue.");
$scope.newPasswordData.status = null;
return;
}
$scope.newPasswordData.status = "Generating New Keys...";
$scope.newPasswordData.showForm = false;
// perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes)
syncManager.sync(function(response){
authManager.changePassword(email, $scope.newPasswordData.newPassword, function(response){
if(response.error) {
alert("There was an error changing your password. Please try again.");
$scope.newPasswordData.status = null;
return;
}
// re-encrypt all items
$scope.newPasswordData.status = "Re-encrypting all items with your new key...";
modelManager.setAllItemsDirty();
syncManager.sync(function(response){
if(response.error) {
alert("There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.")
return;
}
$scope.newPasswordData.status = "Successfully changed password and re-encrypted all items.";
$timeout(function(){
alert("Your password has been changed, and your items successfully re-encrypted and synced. You must sign out of all other signed in applications and sign in again, or else you may corrupt your data.")
$scope.newPasswordData = {};
}, 1000)
});
})
})
}
$scope.loginSubmitPressed = function() {
$scope.formData.status = "Generating Login Keys...";
console.log("logging in with url", $scope.formData.url);
$timeout(function(){
authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, function(response){
if(!response || response.error) {
@@ -66,6 +98,11 @@ class AccountMenu {
}
$scope.submitRegistrationForm = function() {
var confirmation = prompt("Please confirm your password. Note that because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.")
if(confirmation !== $scope.formData.user_password) {
alert("The two passwords you entered do not match. Please try again.");
return;
}
$scope.formData.status = "Generating Account Keys...";
$timeout(function(){
@@ -81,10 +118,32 @@ class AccountMenu {
})
}
$scope.localNotesCount = function() {
return modelManager.filteredNotes.length;
}
$scope.mergeLocalChanged = function() {
if(!$scope.formData.mergeLocal) {
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
$scope.formData.mergeLocal = true;
}
}
}
$scope.onAuthSuccess = function() {
syncManager.markAllItemsDirtyAndSaveOffline(function(){
var block = function() {
window.location.reload();
})
}
if($scope.formData.mergeLocal) {
syncManager.markAllItemsDirtyAndSaveOffline(function(){
block();
})
} else {
dbManager.clearAllItems(function(){
block();
})
}
}
$scope.destroyLocalData = function() {
@@ -116,6 +175,8 @@ class AccountMenu {
$scope.importData = null;
if(!response) {
alert("There was an error importing your data. Please try again.");
} else {
alert("Your data was successfully imported.")
}
})
})
@@ -149,12 +210,11 @@ class AccountMenu {
}
$scope.importJSONData = function(data, password, callback) {
console.log("Importing data", data);
var onDataReady = function() {
var items = modelManager.mapResponseItemsToLocalModels(data.items);
items.forEach(function(item){
item.setDirty(true);
item.deleted = false;
item.markAllReferencesDirty();
})

View File

@@ -11,6 +11,8 @@ class ContextualExtensionsMenu {
controller($scope, modelManager, extensionManager) {
'ngInject';
$scope.renderData = {};
$scope.extensions = _.map(extensionManager.extensionsInContextOfItem($scope.item), function(ext){
return _.cloneDeep(ext);
});
@@ -27,12 +29,30 @@ class ContextualExtensionsMenu {
}
$scope.executeAction = function(action, extension) {
if(action.verb == "nested") {
action.showNestedActions = !action.showNestedActions;
return;
}
action.running = true;
extensionManager.executeAction(action, extension, $scope.item, function(response){
action.running = false;
$scope.handleActionResponse(action, response);
})
}
$scope.handleActionResponse = function(action, response) {
switch (action.verb) {
case "render": {
var item = response.item;
if(item.content_type == "Note") {
$scope.renderData.title = item.title;
$scope.renderData.text = item.text;
$scope.renderData.showRenderModal = true;
}
}
}
}
$scope.accessTypeForExtension = function(extension) {
return extensionManager.extensionUsesEncryptedData(extension) ? "encrypted" : "decrypted";
}

View File

@@ -0,0 +1,73 @@
class EditorMenu {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/editor-menu.html";
this.scope = {
callback: "&",
selectedEditor: "="
};
}
controller($scope, modelManager, extensionManager, syncManager) {
'ngInject';
$scope.formData = {};
let editorContentType = "SN|Editor";
let defaultEditor = {
default: true,
name: "Plain"
}
$scope.sysEditors = [defaultEditor];
$scope.editors = modelManager.itemsForContentType(editorContentType);
$scope.editorForUrl = function(url) {
return $scope.editors.filter(function(editor){return editor.url == url})[0];
}
$scope.selectEditor = function(editor) {
$scope.callback()(editor);
}
$scope.deleteEditor = function(editor) {
if(confirm("Are you sure you want to delete this editor?")) {
modelManager.setItemToBeDeleted(editor);
syncManager.sync();
_.pull($scope.editors, editor);
}
}
$scope.submitNewEditorRequest = function() {
var editor = createEditor($scope.formData.url);
modelManager.addItem(editor);
editor.setDirty(true);
syncManager.sync();
$scope.editors.push(editor);
$scope.formData = {};
}
function createEditor(url) {
var name = getParameterByName("name", url);
return modelManager.createItem({
content_type: editorContentType,
url: url,
name: name
})
}
function getParameterByName(name, url) {
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
}
}
angular.module('app.frontend').directive('editorMenu', () => new EditorMenu);

View File

@@ -21,7 +21,14 @@ class GlobalExtensionsMenu {
if($scope.newExtensionData.url) {
extensionManager.addExtension($scope.newExtensionData.url, function(response){
if(!response) {
alert("Unable to register this extension. Make sure the link is valid and try again.");
if($scope.newExtensionData.url.indexOf("type=sf") != -1) {
alert("Unable to register this extension. You are attempting to register a Standard File extension in Standard Notes. You should instead open your Standard File Dashboard and register this extension there.");
} else if($scope.newExtensionData.url.indexOf("name=") != -1) {
// user is mistakenly trying to register editor extension, most likely
alert("Unable to register this extension. It looks like you may be trying to install an editor extension. To do that, click 'Editor' under the current note's title.");
} else {
alert("Unable to register this extension. Make sure the link is valid and try again.");
}
} else {
$scope.newExtensionData.url = "";
$scope.showNewExtensionForm = false;

View File

@@ -28,7 +28,7 @@ class ExtensionManager {
extensionsInContextOfItem(item) {
return this.extensions.filter(function(ext){
return ext.actionsWithContextForItem(item).length > 0;
return _.includes(ext.supported_types, item.content_type) || ext.actionsWithContextForItem(item).length > 0;
})
}
@@ -152,9 +152,29 @@ class ExtensionManager {
case "get": {
this.Restangular.oneUrl(action.url, action.url).get().then(function(response){
action.error = false;
var items = response.items;
this.modelManager.mapResponseItemsToLocalModels(items);
customCallback(items);
var items = response.items || [response.item];
EncryptionHelper.decryptMultipleItems(items, localStorage.getItem("mk"));
items = this.modelManager.mapResponseItemsToLocalModels(items);
for(var item of items) {
item.setDirty(true);
}
this.syncManager.sync(null);
customCallback({items: items});
}.bind(this))
.catch(function(response){
action.error = true;
customCallback(null);
})
break;
}
case "render": {
this.Restangular.oneUrl(action.url, action.url).get().then(function(response){
action.error = false;
EncryptionHelper.decryptItem(response.item, localStorage.getItem("mk"));
var item = this.modelManager.createItem(response.item);
customCallback({item: item});
}.bind(this))
.catch(function(response){
action.error = true;
@@ -281,7 +301,11 @@ class ExtensionManager {
}
outgoingParamsForItem(item, extension) {
var itemParams = new ItemParams(item, this.syncManager.masterKey);
var ek = this.syncManager.masterKey;
if(!this.extensionUsesEncryptedData(extension)) {
ek = null;
}
var itemParams = new ItemParams(item, ek);
return itemParams.paramsForExtension();
}

View File

@@ -1,4 +1,3 @@
// Start from filter
angular.module('app.frontend').filter('startFrom', function() {
return function(input, start) {
return input.slice(start);

View File

@@ -0,0 +1,5 @@
angular.module('app.frontend').filter('trusted', ['$sce', function ($sce) {
return function(url) {
return $sce.trustAsResourceUrl(url);
};
}]);

View File

@@ -60,11 +60,13 @@ class SNCrypto {
}
base64(text) {
return CryptoJS.enc.Utf8.parse(text).toString(CryptoJS.enc.Base64)
// return CryptoJS.enc.Utf8.parse(text).toString(CryptoJS.enc.Base64)
return window.btoa(text);
}
base64Decode(base64String) {
return CryptoJS.enc.Base64.parse(base64String).toString(CryptoJS.enc.Utf8)
// return CryptoJS.enc.Base64.parse(base64String).toString(CryptoJS.enc.Utf8)
return window.atob(base64String);
}
sha256(text) {

View File

@@ -1,14 +1,8 @@
class EncryptionHelper {
static encryptItem(item, key) {
var item_key = null;
if(item.enc_item_key) {
// we reuse the key, but this is optional
item_key = Neeto.crypto.decryptText(item.enc_item_key, key);
} else {
item_key = Neeto.crypto.generateRandomEncryptionKey();
item.enc_item_key = Neeto.crypto.encryptText(item_key, key);
}
var item_key = Neeto.crypto.generateRandomEncryptionKey();
item.enc_item_key = Neeto.crypto.encryptText(item_key, key);
var ek = Neeto.crypto.firstHalfOfKey(item_key);
var ak = Neeto.crypto.secondHalfOfKey(item_key);

View File

@@ -1,21 +0,0 @@
angular.module('app.frontend')
.service('markdownRenderer', function ($sce) {
marked.setOptions({
breaks: true,
sanitize: true
});
this.renderedContentForText = function(text) {
if(!text || text.length == 0) {
return "";
}
return marked(text);
}
this.renderHtml = function(html_code) {
return $sce.trustAsHtml(html_code);
};
});

View File

@@ -8,6 +8,7 @@ class ModelManager {
this.itemChangeObservers = [];
this.items = [];
this._extensions = [];
this.acceptableContentTypes = ["Note", "Tag", "Extension", "SN|Editor"];
}
get allItems() {
@@ -58,15 +59,17 @@ class ModelManager {
}
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
var models = [];
var models = [], processedObjects = [];
// first loop should add and process items
for (var json_obj of items) {
json_obj = _.omit(json_obj, omitFields || [])
var item = this.findItem(json_obj["uuid"]);
if(json_obj["deleted"] == true) {
if(item) {
this.removeItemLocally(item)
}
continue;
if(json_obj["deleted"] == true || !_.includes(this.acceptableContentTypes, json_obj["content_type"])) {
if(item) {
this.removeItemLocally(item)
}
continue;
}
_.omit(json_obj, omitFields);
@@ -79,11 +82,16 @@ class ModelManager {
this.addItem(item);
if(json_obj.content) {
this.resolveReferencesForItem(item);
}
models.push(item);
processedObjects.push(json_obj);
}
// second loop should process references
for (var index in processedObjects) {
var json_obj = processedObjects[index];
if(json_obj.content) {
this.resolveReferencesForItem(models[index]);
}
}
this.notifySyncObserversOfModels(models);
@@ -120,6 +128,8 @@ class ModelManager {
item = new Tag(json_obj);
} else if(json_obj.content_type == "Extension") {
item = new Extension(json_obj);
} else if(json_obj.content_type == "SN|Editor") {
item = new Editor(json_obj);
} else {
item = new Item(json_obj);
}
@@ -144,9 +154,7 @@ class ModelManager {
}
} else if(item.content_type == "Note") {
if(!_.find(this.notes, {uuid: item.uuid})) {
this.notes.splice(_.sortedLastIndexBy(this.notes, item, function(item){
return -item.created_at;
}), 0, item);
this.notes.unshift(item);
}
} else if(item.content_type == "Extension") {
if(!_.find(this._extensions, {uuid: item.uuid})) {
@@ -167,11 +175,13 @@ class ModelManager {
}
resolveReferencesForItem(item) {
item.locallyClearAllReferences();
var contentObject = item.contentObject;
if(!contentObject.references) {
return;
}
for(var reference of contentObject.references) {
var referencedItem = this.findItem(reference.uuid);
if(referencedItem) {
@@ -225,6 +235,17 @@ class ModelManager {
item.removeAllRelationships();
}
/* Used when changing encryption key */
setAllItemsDirty() {
var relevantItems = this.allItems.filter(function(item){
return _.includes(this.acceptableContentTypes, item.content_type);
}.bind(this));
for(var item of relevantItems) {
item.setDirty(true);
}
}
removeItemLocally(item, callback) {
_.pull(this.items, item);

View File

@@ -98,10 +98,37 @@ class SyncManager {
return this._cursorToken;
}
get queuedCallbacks() {
if(!this._queuedCallbacks) {
this._queuedCallbacks = [];
}
return this._queuedCallbacks;
}
clearQueuedCallbacks() {
this._queuedCallbacks = [];
}
callQueuedCallbacksAndCurrent(currentCallback, response) {
var allCallbacks = this.queuedCallbacks;
if(currentCallback) {
allCallbacks.push(currentCallback);
}
if(allCallbacks.length) {
for(var eachCallback of allCallbacks) {
eachCallback(response);
}
this.clearQueuedCallbacks();
}
}
sync(callback, options = {}) {
if(this.syncStatus.syncOpInProgress) {
this.repeatOnCompletion = true;
if(callback) {
this.queuedCallbacks.push(callback);
}
console.log("Sync op in progress; returning.");
return;
}
@@ -116,18 +143,17 @@ class SyncManager {
return;
}
var isContinuationSync = this.needsMoreSync;
var isContinuationSync = this.syncStatus.needsMoreSync;
this.repeatOnCompletion = false;
this.syncStatus.syncOpInProgress = true;
let submitLimit = 100;
var subItems = allDirtyItems.slice(0, submitLimit);
if(subItems.length < allDirtyItems.length) {
// more items left to be synced, repeat
this.needsMoreSync = true;
this.syncStatus.needsMoreSync = true;
} else {
this.needsMoreSync = false;
this.syncStatus.needsMoreSync = false;
}
if(!isContinuationSync) {
@@ -153,12 +179,14 @@ class SyncManager {
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
// merge only metadata for saved items
// we write saved items to disk now because it clears their dirty status then saves
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
var omitFields = ["content", "auth_hash"];
var saved = this.handleItemsResponse(response.saved_items, omitFields);
this.handleUnsavedItemsResponse(response.unsaved)
this.writeItemsToLocalStorage(saved, false, null);
this.writeItemsToLocalStorage(retrieved, false, null);
@@ -169,14 +197,17 @@ class SyncManager {
this.syncToken = response.sync_token;
this.cursorToken = response.cursor_token;
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
if(this.cursorToken || this.syncStatus.needsMoreSync) {
setTimeout(function () {
this.sync(callback, options);
}.bind(this), 10); // wait 10ms to allow UI to update
} else if(this.repeatOnCompletion) {
this.repeatOnCompletion = false;
setTimeout(function () {
this.sync(callback, options);
}.bind(this), 10); // wait 10ms to allow UI to update
} else {
if(callback) {
callback(response);
}
this.callQueuedCallbacksAndCurrent(callback, response);
}
}.bind(this))
@@ -190,9 +221,7 @@ class SyncManager {
this.$rootScope.$broadcast("sync:error", error);
if(callback) {
callback({error: "Sync error"});
}
this.callQueuedCallbacksAndCurrent(callback, {error: "Sync error"});
}.bind(this))
}
@@ -211,7 +240,7 @@ class SyncManager {
var item = this.modelManager.findItem(itemResponse.uuid);
var error = mapping.error;
if(error.tag == "uuid_conflict") {
// uuid conflicts can occur if a user attempts to import an old data archive with uuids form the old account into a new account
// uuid conflicts can occur if a user attempts to import an old data archive with uuids from the old account into a new account
this.modelManager.alternateUUIDForItem(item, handleNext);
}
++i;

View File

@@ -72,45 +72,3 @@
}
}
}
.section-menu {
padding-top: 0px;
width: 100%;
position: absolute;
padding-left: inherit;
padding-right: inherit;
left: 0;
right: 0;
bottom: 0px;
background-color: #f1f1f1;
color: $selected-text-color;
height: 28px;
cursor: default;
ol, ul {
margin-top: 5px;
margin-bottom: 10px;
&.dropdown-menu {
margin-top: 10px;
}
}
ul {
li {
text-align: left;
&.sep {
margin: 6px;
display: block;
}
a {
font-size: 13px;
font-weight: bold;
padding: 0 0;
}
}
}
}

View File

@@ -1,70 +0,0 @@
.dropdown-menu.contextual-menu {
.extension {
&:not(:first-child) {
margin-top: 18px;
}
.ext-header {
background-color: #ededed;
border-bottom: 1px solid #d3d3d3;
padding-top: 12px;
padding-left: 10px;
padding-bottom: 10px;
position: relative;
> .name {
font-size: 14px;
}
> .access {
font-size: 12px;
opacity: 0.5;
font-weight: normal;
margin-top: 2px;
}
> .loading {
position: absolute;
height: 15px;
width: 15px;
right: 10px;
top: 20px;
}
}
ul {
margin-top: 0px;
margin-bottom: 0px;
list-style:none;
padding-left:0;
li {
cursor: pointer;
height: auto;
&.action {
padding: 10px;
border-bottom: 1px solid rgba(black, 0.1);
background-color: rgba(white, 0.9);
&:hover {
background-color: rgba(gray, 0.05);
}
> .name {
font-weight: bold;
font-size: 14px;
}
> .desc {
font-weight: normal;
opacity: 0.5;
margin-top: 1px;
font-size: 12px;
}
}
}
}
}
}

View File

@@ -1,5 +1,10 @@
$heading-height: 75px;
.editor {
width: 60%;
flex: 1 50%;
min-width: 300px;
display: flex;
flex-direction: column;
overflow-y: hidden;
&.fullscreen {
width: 100%;
@@ -12,253 +17,130 @@
.section-title-bar {
border-bottom: none !important;
&.fullscreen {
opacity: 0.0;
-webkit-transition: all 300ms ease-in-out;
-moz-transition: all 300ms ease-in-out;
-ms-transition: all 300ms ease-in-out;
-o-transition: all 300ms ease-in-out;
transition: all 300ms ease-in-out;
&:hover {
opacity: 1.0;
}
// z-index: -1;
}
height: $heading-height !important;
}
$heading-height: 100px;
.section-menu {
flex: 1 0 28px;
max-height: 28px;
}
}
.editor-heading {
.editor-heading {
position: absolute;
width: 100%;
padding: 15px;
width: 100%;
padding: 15px;
padding-top: 0px;
background-color: white;
min-height: $heading-height;
padding-right: 10px;
&.fullscreen {
position: relative;
}
> .title {
font-size: 18px;
font-weight: bold;
padding-top: 0px;
background-color: white;
min-height: $heading-height;
width: 100%;
padding-right: 10px;
> .title {
font-size: 18px;
> .input {
float: left;
text-overflow:ellipsis;
width: 90%;
font-weight: bold;
padding-top: 0px;
width: 100%;
> .input {
float: left;
text-overflow:ellipsis;
width: 90%;
font-weight: bold;
border: none;
outline: none;
background-color: transparent;
&:disabled {
color: black;
}
}
}
.save-status {
width: 20% !important;
float: right;
position: absolute;
right: 20px;
font-size: 12px;
text-transform: none;
font-weight: normal;
margin-top: 4px;
width: 120px;
text-align: right;
color: rgba(black, 0.23);
}
.tags {
clear: left;
width: 100%;
height: 25px;
.tags-input {
background-color: transparent;
width: 100%;
border: none;
}
}
}
.fullscreen-ghost-bar {
position: absolute;
width: 20%;
height: 200px;
z-index: 100;
}
.editor-content {
max-height: 100%;
height: 100%;
clear: both;
min-width: 0;
padding-left: 10px;
padding-right: 10px;
overflow: auto;
text-align: center; // centers children div horizontally
z-index: 10;
padding-top: $heading-height;
&.fullscreen {
padding-top: 0px;
}
.sampler-container {
margin-top: 10px;
padding: 15px;
padding-top: 17px;
font-size: 17px;
// opacity: 0.5;
}
.sampler {
// opacity: 0.5;
color: rgba(black, 0.3);
}
.editable {
font-family: monospace;
max-height: 100%;
height: 100%;
width: 100%;
border: none;
outline: none;
padding: 15px;
padding-top: 17px;
font-size: 17px;
resize: none;
&.fullscreen {
padding: 85px 10%;
max-width: 1200px;
display: inline-block;;
}
}
.preview {
// font-family: monospace;
max-height: 100%;
height: 100%;
line-height: 23px;
overflow-y: scroll;
padding: 0px 15px;
text-align: left;
&.fullscreen {
padding: 85px 10%;
max-width: 1200px;
display: inline-block;;
}
}
}
}
.markdown {
margin-left: 15px;
float: right;
text-align: right;
right: 0;
}
.full-screen-button {
cursor: pointer;
position: absolute;
top: 25px;
right: 20px;
z-index: 100;
}
ol {
list-style-type: decimal;
list-style-position: inside;
-webkit-margin-before: 1em;
-webkit-margin-after: 1em;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
-webkit-padding-start: 0px;
}
.nav-tabs {
a {
color: black;
text-decoration: none;
}
a {
background-color: transparent;
&:disabled {
color: black;
}
}
}
.save-status {
width: 20% !important;
float: right;
position: absolute;
right: 20px;
font-size: 12px;
text-transform: none;
font-weight: normal;
margin-top: 4px;
width: 120px;
text-align: right;
color: rgba(black, 0.23);
}
.tags {
clear: left;
width: 100%;
height: 25px;
.tags-input {
background-color: transparent;
width: 100%;
border: none;
}
}
}
.editor-content {
flex: 1;
z-index: 10;
overflow-y: hidden;
height: 100%;
display: flex;
background-color: white;
&.fullscreen {
padding-top: 0px;
}
#editor-iframe {
flex: 1;
width: 100%;
}
.editable {
font-family: monospace;
flex: 1;
overflow-y: scroll;
width: 100%;
border: none;
outline: none;
padding: 15px;
padding-top: 11px;
font-size: 17px;
resize: none;
&.fullscreen {
padding: 85px 10%;
max-width: 1200px;
display: inline-block;
}
}
}
.nav {
padding-left: 0;
margin-bottom: 0;
list-style: none;
ul {
font-weight: bold;
}
}
.nav-pills>li {
float: left;
}
.nav>li {
position: relative;
display: block;
}
.nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover {
color: #fff;
background-color: #337ab7;
}
.nav-pills>li>a {
border-radius: 4px;
}
.nav>li>a {
position: relative;
display: block;
padding: 10px 15px;
}
.nav-tabs>li {
float: left;
margin-bottom: -1px;
}
.nav>li {
position: relative;
display: block;
}
.nav-tabs>li.active>a, .nav-tabs>li.active>a:focus, .nav-tabs>li.active>a:hover {
color: black;
cursor: pointer;
background-color: #fff;
border: 1px solid #ddd;
border-bottom-color: transparent;
}
.nav-tabs>li>a {
margin-right: 2px;
line-height: 1.42857143;
border: 1px solid transparent;
// border-radius: 4px 4px 0 0;
cursor: pointer;
}
.nav>li>a {
position: relative;
display: block;
padding: 10px 15px;
font-weight: bold;
user-select: none;
}

View File

@@ -0,0 +1,112 @@
.extension-render-modal {
position: fixed;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 10000;
width: 100vw;
height: 100vh;
background-color: rgba(gray, 0.3);
.content {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-color: white;
width: 700px;
height: 500px;
margin: auto;
padding: 25px;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
overflow-y: scroll;
}
}
.menu-section-footer {
background-color: #ededed;
border-top: 1px solid #d3d3d3;
position: relative;
padding: 10px;
}
.menu-section-header {
background-color: #ededed;
border-bottom: 1px solid #d3d3d3;
position: relative;
padding-top: 12px;
padding-left: 10px;
padding-bottom: 10px;
> .title {
font-size: 14px;
font-weight: bold;
}
> .subtitle {
font-size: 12px;
opacity: 0.5;
font-weight: normal;
margin-top: 2px;
}
> .loading {
position: absolute;
height: 15px;
width: 15px;
right: 10px;
top: 20px;
}
}
.dropdown-menu.editor-menu {
overflow-y: scroll;
max-height: 85vh;
&:not(:first-child) {
margin-top: 18px;
}
ul {
margin-top: 0px;
margin-bottom: 0px;
list-style:none;
padding-left:0;
li {
cursor: pointer;
height: auto;
&.menu-item {
padding: 10px;
border-bottom: 1px solid rgba(black, 0.1);
background-color: rgba(white, 0.9);
&:hover {
background-color: rgba(gray, 0.05);
}
&.nonactive {
cursor: default;
&:hover {
background-color: rgba(white, 0.9) !important;
}
}
.menu-item-title {
font-weight: bold;
font-size: 14px;
}
.menu-item-subtitle {
font-weight: normal;
opacity: 0.5;
margin-top: 1px;
font-size: 12px;
}
}
}
}
}

View File

@@ -1,92 +0,0 @@
@font-face {
font-family: ProximaNova;
src: font-url('ProximaNova/ProximaNova-Regular.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Regular.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Regular.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Regular.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-style: italic;
src: font-url('ProximaNova/ProximaNova-Regular-Italic.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Regular-Italic.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Regular-Italic.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Regular-Italic.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: 600;
src: font-url('ProximaNova/ProximaNova-Semibold.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Semibold.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Semibold.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Semibold.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: 600;
font-style: italic;
src: font-url('ProximaNova/ProximaNova-Semibold-Italic.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Semibold-Italic.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Semibold-Italic.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Semibold-Italic.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: bold;
src: font-url('ProximaNova/ProximaNova-Bold.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Bold.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Bold.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Bold.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: bold;
font-style: italic;
src: font-url('ProximaNova/ProximaNova-Bold-Italic.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Bold-Italic.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Bold-Italic.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Bold-Italic.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: 100;
src: font-url('fonts/ProximaNova-Thin.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Thin.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Thin.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Thin.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: 100;
font-style: italic;
src: font-url('fonts/ProximaNova-Thin-Italic.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Thin-Italic.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Thin-Italic.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Thin-Italic.svg') format('svg');
}
@font-face {
font-family: ProximaNova;
font-weight: 900;
src: font-url('ProximaNova/ProximaNova-Extrabold.eot');
src: local(''),
font-url('ProximaNova/ProximaNova-Extrabold.woff') format('woff'),
font-url('ProximaNova/ProximaNova-Extrabold.ttf') format('truetype'),
font-url('ProximaNova/ProximaNova-Extrabold.svg') format('svg');
}

View File

@@ -1,141 +1,3 @@
.pull-left {
float: left !important;
}
.pull-right {
float: right !important;
}
.mt-1 {
margin-top: 1px !important;
}
.mt-2 {
margin-top: 2px !important;
}
.mt-5 {
margin-top: 5px !important;
}
.mt-10 {
margin-top: 10px !important;
}
.mt-15 {
margin-top: 15px !important;
}
.mt-25 {
margin-top: 25px !important;
}
.mb-0 {
margin-bottom: 0px !important;
}
.mb-5 {
margin-bottom: 5px !important;
}
.mb-10 {
margin-bottom: 10px !important;
}
.mr-5 {
margin-right: 5px;
}
.faded {
opacity: 0.5;
}
.center-align {
text-align: center !important;
}
.center {
margin-left: auto !important;
margin-right: auto !important;
}
.block {
display: block !important;
}
.wrap {
word-wrap: break-word;
}
.one-line-overflow {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.small-v-space {
height: 6px;
display: block;
}
.medium-v-space {
height: 12px;
display: block;
}
.large-v-space {
height: 24px;
display: block;
}
.small-padding {
padding: 5px !important;
}
.medium-padding {
padding: 10px !important;
}
.pb-4 {
padding-bottom: 4px !important;
}
.pb-6 {
padding-bottom: 6px !important;
}
.pb-10 {
padding-bottom: 10px !important;
}
.large-padding {
padding: 22px !important;
}
.red {
color: red !important;
}
.blue {
color: $blue-color;
}
.bold {
font-weight: bold !important;
}
.normal {
font-weight: normal !important;
}
.small {
font-size: 10px !important;
}
.inline {
display: inline-block;
}
.fake-link {
font-weight: bold;
cursor: pointer;
@@ -146,6 +8,11 @@
}
}
h2 {
margin-bottom: 0px;
margin-top: 0px;
}
.footer-bar {
position: relative;
width: 100%;
@@ -190,10 +57,6 @@
display: block;
}
h2 {
margin-bottom: 0px;
margin-top: 0px;
}
h3 {
font-size: 14px !important;

View File

@@ -29,8 +29,8 @@ $blue-color: #086dd6;
}
}
[ng\:cloak], [ng-cloak], .ng-cloak {
display: none !important;
.blue {
color: $blue-color;
}
html,
@@ -45,6 +45,7 @@ body {
height: 100%;
font-size: 20px;
margin: 0;
background-color: $bg-color;
}
.dark-button {
@@ -72,19 +73,6 @@ a {
}
}
pre {
padding: 16px;
overflow: auto;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
code {
word-wrap: break-word;
line-height: 1.45;
}
p {
overflow: auto;
}
@@ -93,35 +81,21 @@ p {
min-height: 100vh;
height: 100vh;
overflow: auto;
}
.app-body-class {
height: 100%;
background-color: $bg-color;
min-width: 100px;
min-width: 900px;
}
$header-height: 25px;
.app-container {
display: table;
background-color: $bg-color;
width: 100%;
height: calc(100% - #{$header-height});
padding-top: 0px;
font-size: 0;
margin-top: 0;
}
$section-header-height: 70px;
.app {
height: 100%;
// height: 100%;
height: calc(100% - #{$header-height});
width: 100%;
display: table-row;
display: flex;
vertical-align: top;
overflow: hidden;
position: relative;
.light-button {
background-color: $bg-color;
@@ -140,15 +114,8 @@ $section-header-height: 70px;
.section {
padding-bottom: 0px;
display: block;
height: 100%;
max-height: calc(100vh - #{$header-height});
float: left;
overflow-y: auto;
overflow-x: hidden;
min-width: 0;
font-size: 17px;
.scrollable {
@@ -161,7 +128,6 @@ $section-header-height: 70px;
max-height: 100%;
background-color: white;
position: relative;
box-shadow: 0px 0px 2px rgba(gray, 0.3);
}
.section-title-bar {
@@ -198,3 +164,41 @@ $section-header-height: 70px;
}
}
}
.section-menu {
width: 100%;
padding-top: 0px;
padding-left: 21px;
padding-right: 21px;
background-color: #f1f1f1;
color: $selected-text-color;
height: 28px;
cursor: default;
ol, ul {
margin-top: 5px;
margin-bottom: 10px;
&.dropdown-menu {
margin-top: 10px;
}
}
ul {
li {
text-align: left;
&.sep {
margin: 6px;
display: block;
}
a {
font-size: 13px;
font-weight: bold;
padding: 0 0;
}
}
}
}

View File

@@ -7,15 +7,11 @@
$screen-xs: 480px !default;
//** Deprecated `$screen-xs-min` as of v3.2.0
$screen-xs-min: $screen-xs !default;
//** Deprecated `$screen-phone` as of v3.0.1
$screen-phone: $screen-xs-min !default;
// Small screen / tablet
//** Deprecated `$screen-sm` as of v3.0.1
$screen-sm: 768px !default;
$screen-sm-min: $screen-sm !default;
//** Deprecated `$screen-tablet` as of v3.0.1
$screen-tablet: $screen-sm-min !default;
// Medium screen / desktop
//** Deprecated `$screen-md` as of v3.0.1
@@ -67,111 +63,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
*:focus {outline:0;}
.navbar {
min-height: 0px !important;
background-color: white;
height: 80px;
margin-bottom: 0px;
padding-top: 10px;
border-radius: 0px;
}
@media (min-width: 768px) {
.navbar {
border-radius: 4px;
}
}
.navbar {
position: relative;
min-height: 50px;
margin-bottom: 20px;
border: 1px solid transparent;
}
@media (min-width: 768px) {
.navbar-header {
float: left;
}
}
.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse {
margin-right: 0;
margin-left: 0;
}
@media (min-width: 768px) {
.navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand {
// margin-left: -15px;
}
}
.navbar-brand {
float: left;
padding: 15px 15px;
font-size: 18px;
line-height: 20px;
height: 50px;
}
@media (min-width: 768px) {
.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse {
// margin-right: 0;
// margin-left: 0;
}
}
.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse {
// margin-right: -15px;
// margin-left: -15px;
}
@media (min-width: 768px) {
.navbar-collapse.collapse {
display: block !important;
height: auto !important;
padding-bottom: 0;
overflow: visible !important;
}
}
@media (min-width: 768px) {
.navbar-collapse {
width: auto;
border-top: 0;
box-shadow: none;
}
}
.navbar-collapse {
overflow-x: visible;
padding-right: 15px;
padding-left: 15px;
border-top: 1px solid transparent;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
-webkit-overflow-scrolling: touch;
}
.collapse {
display: none;
}
@media (min-width: 768px) {
.navbar-right {
float: right !important;
// margin-right: -15px;
}
}
@media (min-width: 768px) {
.navbar-text {
float: left;
margin-left: 15px;
margin-right: 15px;
}
}
.navbar-text {
margin-top: 15px;
margin-bottom: 15px;
}
.dropup, .dropdown {
position: relative;
}
@@ -232,10 +123,6 @@ button:focus {outline:0;}
right: 0;
}
.open > .dropdown-menu {
display: block;
}
.btn {
display: inline-block;
margin-bottom: 0;
@@ -262,23 +149,6 @@ button:focus {outline:0;}
width: 100%;
}
// ul, menu, dir {
// display: block;
// list-style-type: disc;
// -webkit-margin-before: 1em;
// -webkit-margin-after: 1em;
// -webkit-margin-start: 0px;
// -webkit-margin-end: 0px;
// -webkit-padding-start: 40px;
// }
.dropdown-menu .divider {
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5;
}
.panel {
position: absolute;
right: 0px;
@@ -290,59 +160,35 @@ button:focus {outline:0;}
background-color: white;
}
.panel-top {
bottom: 0px;
}
.panel-left {
left: -50px;
}
.panel-right {
left: 0px;
}
.panel-centered {
position: relative;
width: 400px;
margin: 0 auto;
padding: 10px;
text-align: center;
}
.panel-body {
padding: 15px;
}
.form-control {
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857;
color: #555555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857;
color: #555555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.has-feedback {
position: relative;
}
.form-tag {
margin-bottom: 15px;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
.checkbox {
@@ -352,63 +198,3 @@ input, button, select, textarea {
margin-right: auto;
margin-bottom: 10px;
}
.btn-link {
background-color: transparent;
-webkit-box-shadow: none;
box-shadow: none;
text-decoration: none;
}
.btn-link:hover, .btn-link:focus {
color: #23527c;
text-decoration: underline;
background-color: transparent;
}
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated-fast {
-webkit-animation-duration: 0.5s;
animation-duration: 0.5s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.fadeInDown {
-webkit-animation-name: fadeInDown;
animation-name: fadeInDown;
}
.fadeIn {
-webkit-animation-name: fadeIn;
animation-name: fadeIn;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeInDown {
0% {
opacity: 0;
-webkit-transform: translate3d(0,-100%,0);
transform: translate3d(0,-100%,0);
}
100% {
opacity: 1;
-webkit-transform: none;
transform: none;
}
}

View File

@@ -1,8 +1,11 @@
.notes {
width: 25%;
border-left: 1px solid #dddddd;
border-right: 1px solid #dddddd;
flex: 1 20%;
max-width: 350px;
min-width: 170px;
$notes-title-bar-height: 130px;
.notes-title-bar {

View File

@@ -0,0 +1,270 @@
.pull-left {
float: left !important;
}
.pull-right {
float: right !important;
}
.mt-1 {
margin-top: 1px !important;
}
.mt-2 {
margin-top: 2px !important;
}
.mt-5 {
margin-top: 5px !important;
}
.mt-10 {
margin-top: 10px !important;
}
.mt-15 {
margin-top: 15px !important;
}
.mt-20 {
margin-top: 20px !important;
}
.mt-25 {
margin-top: 25px !important;
}
.mt-50 {
margin-top: 50px !important;
}
.mt-100 {
margin-top: 100px !important;
}
.mb-0 {
margin-bottom: 0px !important;
}
.mb-5 {
margin-bottom: 5px !important;
}
.mb-10 {
margin-bottom: 10px !important;
}
.mr-5 {
margin-right: 5px;
}
.mr-10 {
margin-right: 10px;
}
.mr-20 {
margin-right: 20px;
}
.pb-0 {
padding-bottom: 0px !important;
}
.faded {
opacity: 0.5;
}
.center-align {
text-align: center !important;
}
.center {
margin-left: auto !important;
margin-right: auto !important;
}
.block {
display: block !important;
}
.wrap {
word-wrap: break-word;
word-break: break-all;
}
.one-line-overflow {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.small-v-space {
height: 6px;
display: block;
}
.medium-v-space {
height: 12px;
display: block;
}
.large-v-space {
height: 24px;
display: block;
}
.small-padding {
padding: 5px !important;
}
.medium-padding {
padding: 10px !important;
}
.pb-4 {
padding-bottom: 4px !important;
}
.pb-6 {
padding-bottom: 6px !important;
}
.pb-10 {
padding-bottom: 10px !important;
}
.large-padding {
padding: 22px !important;
}
.red {
color: red !important;
}
.bold {
font-weight: bold !important;
}
.normal {
font-weight: normal !important;
}
.small {
font-size: 10px !important;
}
.medium {
font-size: 14px !important;
}
.inline {
display: inline-block !important;
&.top {
vertical-align: top;
}
&.middle {
vertical-align: middle;
}
}
button {
cursor: pointer;
}
input.form-control {
margin-bottom: 10px;
border-radius: 0px;
min-height: 39px;
font-size: 14px;
padding-left: 6px;
}
button {
border: none;
@mixin wide-button() {
font-weight: bold;
text-align: center;
padding: 10px;
font-size: 16px;
// min-width: 200px;
&:hover {
text-decoration: underline;
}
}
&.black {
@include wide-button();
background-color: black;
color: white;
}
&.white {
@include wide-button();
background-color: white;
color: black;
border: 1px solid rgba(gray, 0.2);
}
}
.gray-bg {
background-color: #f6f6f6;
border: 1px solid #f2f2f2;
}
.white-bg {
background-color: white;
border: 1px solid rgba(gray, 0.2);
}
.col-container {
// white-space: nowrap;
}
@mixin col() {
display: inline-block;
vertical-align: top;
white-space: normal;
}
.col-10 {
width: 10%;
@include col();
}
.col-15 {
width: 15%;
@include col();
}
.col-20 {
width: 20%;
@include col();
}
.col-45 {
width: 45%;
@include col();
}
.col-50 {
width: 50%;
@include col();
}
.col-80 {
width: 80%;
@include col();
}
.relative {
position: relative !important;
}
.absolute {
position: absolute !important;
}

View File

@@ -1,5 +1,8 @@
.tags {
width: 15%;
// width: 15%;
flex: 1 10%;
max-width: 180px;
min-width: 100px;
$tags-title-bar-height: 55px;

View File

@@ -1,5 +1,6 @@
$dark-gray: #2e2e2e;
@import "app/standard";
@import "app/mostrap";
@import "app/main";
@import "app/common";
@@ -7,41 +8,4 @@ $dark-gray: #2e2e2e;
@import "app/tags";
@import "app/notes";
@import "app/editor";
@import "app/directives";
@font-face {
font-family: 'icomoon';
src: url('icomoon/icomoon.eot');
src: url('icomoon/icomoon.eot') format('embedded-opentype'),
url('icomoon/icomoon.ttf') format('truetype'),
url('icomoon/icomoon.woff') format('woff'),
url('icomoon/icomoon.svg') format('svg');
font-weight: normal;
font-style: normal;
}
[class^="icon-"], [class*=" icon-"] {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'icomoon' !important;
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
// line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 10px;
}
.inline-icon {
display: inline-block;
margin-left: 2px;
}
.icon-markdown:before {
content: "\e901";
}
@import "app/extensions";

View File

@@ -8,6 +8,10 @@
%input.form-control{:name => 'server', :placeholder => 'Server URL', :required => true, :type => 'text', 'ng-model' => 'formData.url'}
%input.form-control{:autofocus => 'autofocus', :name => 'email', :placeholder => 'Email', :required => true, :type => 'email', 'ng-model' => 'formData.email'}
%input.form-control{:placeholder => 'Password', :name => 'password', :required => true, :type => 'password', 'ng-model' => 'formData.user_password'}
.checkbox{"ng-if" => "localNotesCount() > 0"}
%label
%input{"type" => "checkbox", "ng-model" => "formData.mergeLocal", "ng-bind" => "true", "ng-change" => "mergeLocalChanged()"}
Merge local notes ({{localNotesCount()}} notes)
%div{"ng-if" => "!formData.status"}
%button.btn.dark-button.half-button{"ng-click" => "loginSubmitPressed()", "data-style" => "expand-right", "data-size" => "s", "state" => "buttonState"}
@@ -28,6 +32,13 @@
%div{"ng-if" => "user"}
%h2 {{user.email}}
%p {{server}}
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
.spinner.inline.mr-5.blue
{{"Syncing" + (syncStatus.total > 0 ? ":" : "")}}
%span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}}
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
%a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} → Standard File Dashboard
%a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials
%section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"}
%label.block
@@ -36,13 +47,23 @@
%label.block.mt-5.mb-0
Server password:
.wrap.normal.mt-1 {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}}
%a.block.mt-5{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"}
.spinner.inline.mr-5.blue
Syncing
%span{"ng-if" => "syncStatus.total > 0"}: {{syncStatus.current}}/{{syncStatus.total}}
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
%a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password
%section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"}
%p.bold Change Password (Beta)
%p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key.
%p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process.
%p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account.
%p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding.
%div.mt-10{"ng-if" => "!newPasswordData.status"}
%a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue
%a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel
%div.mt-10{"ng-if" => "newPasswordData.showForm"}
%form
%input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"}
%input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"}
%button.btn.dark-button.btn-block{"ng-click" => "submitPasswordChange()"} Submit
%p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}}
.medium-v-space
@@ -77,4 +98,4 @@
.spinner.mt-10{"ng-if" => "importData.loading"}
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} Destroy all local data
%a.block.mt-25.red{"ng-click" => "destroyLocalData()"} {{ user ? "Sign out and clear local data" : "Clear all local data" }}

View File

@@ -1,14 +1,28 @@
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.contextual-menu
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.editor-menu
.extension{"ng-repeat" => "extension in extensions"}
.ext-header
.name {{extension.name}}
.access
.menu-section-header
.title {{extension.name}}
.subtitle
Can access your data
%strong {{accessTypeForExtension(extension)}}
.spinner.loading{"ng-if" => "extension.loading"}
%ul
%li.action{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension)"}
.name {{action.label}}
.desc {{action.desc}}
%li.menu-item{"ng-repeat" => "action in extension.actionsWithContextForItem(item)", "ng-click" => "executeAction(action, extension)"}
.menu-item-title {{action.label}}
.menu-item-subtitle {{action.desc}}
%div{"ng-if" => "action.showNestedActions"}
%ul.mt-10
%li.menu-item.white-bg{"ng-repeat" => "subaction in action.subactions", "ng-click" => "executeAction(subaction, extension); $event.stopPropagation()", "style" => "margin-top: -1px;"}
.menu-item-title {{subaction.label}}
.menu-item-subtitle {{subaction.desc}}
%span{"ng-if" => "subaction.running"}
.spinner{"style" => "margin-top: 3px;"}
%span{"ng-if" => "action.running"}
.spinner{"style" => "margin-top: 3px;"}
.extension-render-modal{"ng-if" => "renderData.showRenderModal", "ng-click" => "renderData.showRenderModal = false"}
.content
%h2 {{renderData.title}}
%p.normal{"style" => "white-space: pre-wrap; font-family: monospace; font-size: 16px;"} {{renderData.text}}

View File

@@ -0,0 +1,23 @@
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark.editor-menu
.menu-section-header
.title System Editors
%ul
%li.menu-item{"ng-repeat" => "editor in sysEditors", "ng-click" => "selectEditor(editor)"}
%span.pull-left.mr-10{"ng-if" => "!selectedEditor"} ✓
.menu-item-title.pull-left {{editor.name}}
%div{"ng-if" => "editors.length > 0"}
.menu-section-header
.title External Editors
.subtitle Can access your current note decrypted.
%ul
%li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor(editor)"}
%span.pull-left.mr-10{"ng-if" => "selectedEditor == editor"} ✓
.pull-left{"style" => "width: 60%"}
.menu-item-title {{editor.name}}
.menu-item-subtitle.wrap {{editor.url}}
.pull-right
%button.white.medium.inline.top{"style" => "width: 50px; height: 40px;", "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} ☓
.menu-section-footer.mt-10
%input.form-control{"ng-model" => "formData.url", "placeholder" => "Add new editor via URL", "ng-keyup" => "$event.keyCode == 13 && submitNewEditorRequest()"}
%a.block.blue{"href" => "https://standardnotes.org/extensions", "target" => "_blank"} Available Editors

View File

@@ -1,8 +1,8 @@
.panel.panel-default.account-panel.panel-right
.panel-body
%div{"style" => "font-size: 18px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed
%div{"style" => "font-size: 15px;", "ng-if" => "!extensionManager.extensions.length"} No extensions installed
%div{"ng-if" => "extensionManager.extensions.length"}
%section.gray-bg.inline-h.mb-10.medium-padding{"ng-repeat" => "extension in extensionManager.extensions", "ng-init" => "extension.formData = {}"}
%section.gray-bg.inline-h.mb-10.medium-padding{"ng-repeat" => "extension in extensionManager.extensions | orderBy: 'name'", "ng-init" => "extension.formData = {}"}
%h3.center-align {{extension.name}}
.center-align.centered.mt-10
%label.block.normal Send data:

View File

@@ -1,46 +1,44 @@
.section.editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen}"}
.content
.section-title-bar.editor-heading{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
.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()",
"select-on-click" => "true"}
.save-status{"ng-class" => "{'red bold': ctrl.saveError}", "ng-bind-html" => "ctrl.noteStatus"}
.tags
%input.tags-input{"type" => "text", "ng-keyup" => "$event.keyCode == 13 && ctrl.updateTagsFromTagsString($event, ctrl.tagsString)",
"ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"}
.section-menu
%ul.nav.nav-pills
%li.dropdown
%a.dropdown-toggle{"ng-click" => "ctrl.clickedMenu(); ctrl.showExtensions = false"}
File
%span.caret
%span.sr-only
.section-title-bar.editor-heading{"ng-if" => "ctrl.note", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
.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()",
"select-on-click" => "true"}
.save-status{"ng-class" => "{'red bold': ctrl.saveError}", "ng-bind-html" => "ctrl.noteStatus"}
.tags
%input.tags-input{"type" => "text", "ng-keyup" => "$event.keyCode == 13 && $event.target.blur();",
"ng-model" => "ctrl.tagsString", "placeholder" => "#tags", "ng-blur" => "ctrl.updateTagsFromTagsString($event, ctrl.tagsString)"}
.section-menu{"ng-if" => "ctrl.note"}
%ul.nav
%li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showMenu = false;", "is-open" => "ctrl.showMenu"}
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu; ctrl.showExtensions = false;"}
Menu
%span.caret
%span.sr-only
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"}
.text Toggle Fullscreen
.shortcut Cmd + O
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleMarkdown()"}
.text Toggle Markdown Preview
.shortcut Cmd + M
%li{"ng-click" => "ctrl.deleteNote()"}
.text Delete
%li.sep
%li.dropdown{"ng-if" => "ctrl.hasAvailableExtensions()"}
%a.dropdown-toggle{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false"}
Extensions
%span.caret
%span.sr-only
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li{"ng-click" => "ctrl.selectedMenuItem(); ctrl.toggleFullScreen()"}
.text Toggle Fullscreen
%li{"ng-click" => "ctrl.deleteNote()"}
.text Delete Note
.markdown.icon{"ng-if" => "ctrl.editorMode == 'preview'", "ng-click" => "ctrl.showMarkdown = !ctrl.showMarkdown"}
.icon-markdown
.panel.panel-default.info-panel{"ng-if" => "ctrl.showMarkdown"}
.panel-body{"style" => "text-align: center; color: black;"}
This editor is Markdown enabled.
%li.sep
%li.dropdown.pull-left.mr-10{"click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
%a.dropdown-toggle{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false;"}
Editor
%span.caret
%span.sr-only
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.customEditor"}
.editor-content{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
%textarea.editable#note-text-editor{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-show" => "ctrl.editorMode == 'edit'", "ng-model" => "ctrl.note.text",
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"}
.preview{"ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-if" => "ctrl.editorMode == 'preview'", "ng-bind-html" => "ctrl.renderedContent()", "ng-dblclick" => "ctrl.onPreviewDoubleClick()"}
%li.sep
%li.dropdown.pull-left{"ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
%a.dropdown-toggle{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false;"}
Extensions
%span.caret
%span.sr-only
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
.editor-content{"ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
%iframe#editor-iframe{"ng-if" => "ctrl.customEditor", "ng-src" => "{{ctrl.customEditor.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%;"}
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.customEditor", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text",
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()"}

View File

@@ -1,10 +1,10 @@
.footer-bar
.pull-left
.footer-bar-link
.footer-bar-link{"click-outside" => "ctrl.showAccountMenu = false;", "is-open" => "ctrl.showAccountMenu"}
%a{"ng-click" => "ctrl.accountMenuPressed()", "ng-class" => "{red: ctrl.error}"} Account
%account-menu{"ng-if" => "ctrl.showAccountMenu"}
.footer-bar-link
.footer-bar-link{"click-outside" => "ctrl.showExtensionsMenu = false;", "is-open" => "ctrl.showExtensionsMenu"}
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions
%global-extensions-menu{"ng-if" => "ctrl.showExtensionsMenu"}

View File

@@ -1,12 +1,11 @@
.main-ui-view
.app-container
.app
%tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag",
"tags" => "tags"}
.app
%tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "will-select" => "tagsWillMakeSelection", "selection-made" => "tagsSelectionMade", "all-tag" => "allTag",
"tags" => "tags"}
%notes-section{"remove-tag" => "notesRemoveTag", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade",
"tag" => "selectedTag", "remove" => "deleteNote"}
%notes-section{"remove-tag" => "notesRemoveTag", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade",
"tag" => "selectedTag", "remove" => "deleteNote"}
%editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"}
%editor-section{"note" => "selectedNote", "remove" => "deleteNote", "save" => "saveNote", "update-tags" => "updateTagsForNote"}
%header

View File

@@ -1,6 +0,0 @@
.about.animated.fadeIn
.title About
.summary Namewhale helps you find a unique name for your startup. Using an intelligent, seed-based algorithm, names are generated based on the sound, style, and feel of the seed words you chose.
.links
%a{"href" => "https://itunes.apple.com/us/app/namewhale/id1028881375?ls=1&mt=8", "target" => "_blank"} Namewhale on the AppStore
%a{"href" => "https://twitter.com/namewhale", "target" => "_blank"} @namewhale

View File

@@ -1,4 +0,0 @@
%footer.footer{"ng-class" => "footerClass"}
.container
.row
.footer-about-section

View File

@@ -10,17 +10,25 @@
%ul.nav.nav-pills
%li.dropdown
%a.dropdown-toggle{"ng-click" => "ctrl.showMenu = !ctrl.showMenu"}
Tag options
Menu
%span.caret
%span.sr-only
%ul.dropdown-menu.dropdown-menu-left.nt-dropdown-menu.dark{"ng-if" => "ctrl.showMenu"}
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByCreated()"}
%span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'created_at'"} ✓
Sort by date created
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedSortByUpdated()"}
%span.top.mt-5.mr-5{"ng-if" => "ctrl.sortBy == 'updated_at'"} ✓
Sort by date updated
%li
%a.text{"ng-click" => "ctrl.selectedMenuItem(); ctrl.selectedTagDelete()"} Delete Tag
.scrollable
.infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
.note{"ng-repeat" => "note in ctrl.tag.notes | filter: ctrl.filterNotes | limitTo:ctrl.notesToDisplay",
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | orderBy: ctrl.sortBy:true | limitTo:ctrl.notesToDisplay))",
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
.name{"ng-if" => "note.title"}
{{note.title}}

View File

@@ -1,10 +1,10 @@
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
after_action :set_csrf_cookie
after_action :allow_iframe
layout :false
def frontend
@@ -13,8 +13,13 @@ class ApplicationController < ActionController::Base
rescue_from ActionView::MissingTemplate do |exception|
end
protected
def allow_iframe
response.headers.except! 'X-Frame-Options'
end
def set_app_domain
@appDomain = request.domain
@appDomain << ':' + request.port.to_s unless request.port.blank?

View File

@@ -37,7 +37,7 @@
</head>
<body ng-class="bodyClass">
<body>
<div ui-view="content"></div>
</body>

View File

@@ -9,8 +9,7 @@
"dependencies": {
"angular": "1.6.1",
"angular-ui-router": "^0.3.2",
"restangular": "^1.6.1",
"marked": "0.3.6"
"restangular": "^1.6.1"
},
"resolutions": {
"angular": "1.6.1"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1000 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/favicon/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 B

After

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,17 +1,18 @@
{
"name": "Neeto",
"icons": [
{
"src": "\/favicon\/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image\/png"
},
{
"src": "\/favicon\/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image\/png"
}
],
"theme_color": "#ffffff",
"display": "standalone"
}
"name": "Standard Notes",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -2,19 +2,19 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="500.000000pt" height="500.000000pt" viewBox="0 0 500.000000 500.000000"
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,500.000000) scale(0.100000,-0.100000)"
<g transform="translate(0.000000,16.000000) scale(0.003200,-0.003200)"
fill="#000000" stroke="none">
<path d="M230 2490 l0 -2270 2270 0 2270 0 0 2270 0 2270 -2270 0 -2270 0 0
-2270z m4340 0 l0 -2070 -2070 0 -2070 0 0 2070 0 2070 2070 0 2070 0 0 -2070z"/>
<path d="M2388 3475 c-2 -2 -26 -5 -55 -9 -78 -9 -161 -30 -231 -59 -254 -102
-404 -309 -438 -602 -5 -45 -9 -1139 -5 -1237 1 -15 20 -16 204 -15 l202 2 2
597 c1 509 4 606 17 659 50 192 187 296 402 304 201 8 343 -64 417 -212 46
-91 48 -126 49 -757 l0 -594 206 2 207 1 -1 600 c0 330 -3 629 -7 665 -15 143
-80 299 -165 399 -109 128 -290 220 -487 247 -49 6 -311 14 -317 9z"/>
<path d="M2368 3475 c-2 -1 -23 -5 -48 -8 -298 -37 -515 -205 -603 -467 -14
-41 -29 -91 -33 -110 -4 -19 -9 -328 -11 -687 l-4 -651 168 1 168 2 2 606 c2
648 4 683 54 782 82 162 231 240 449 234 255 -7 412 -140 454 -387 3 -14 5
-298 5 -632 l1 -606 168 1 167 2 0 645 c-1 634 -1 646 -23 730 -81 306 -261
468 -597 535 -31 7 -311 15 -317 10z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1005 B

View File

@@ -3,7 +3,7 @@ describe("app.frontend", function() {
beforeEach(module('app.frontend'));
describe('Home Controller', function() {
var scope;
beforeEach(inject(function($rootScope, $controller, modelManager) {
scope = $rootScope.$new();
@@ -13,10 +13,6 @@ describe("app.frontend", function() {
});
}));
it('should have a body class', function() {
expect(scope.bodyClass).toEqual('app-body-class');
});
it('should have an All tag', function() {
expect(scope.allTag).toBeDefined();
expect(scope.allTag.title).toEqual("All");
@@ -31,7 +27,7 @@ describe("app.frontend", function() {
scope.tagsAddNew("testTag");
expect($modelManager.items).toContain("testTag");
});
});
});

Binary file not shown.

View File

@@ -1,15 +0,0 @@
<?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" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="keyboard" d="M416 384c17.664 0 32 14.304 32 32s-14.336 32-32 32-32-14.304-32-32 14.336-32 32-32zM512 512c17.696 0 32 14.304 32 32s-14.304 32-32 32-32-14.304-32-32 14.304-32 32-32zM416 640c17.664 0 32 14.304 32 32s-14.336 32-32 32-32-14.304-32-32 14.336-32 32-32zM608 640c17.696 0 32 14.304 32 32s-14.304 32-32 32-32-14.304-32-32 14.304-32 32-32zM320 512c17.664 0 32 14.304 32 32s-14.336 32-32 32-32-14.304-32-32 14.336-32 32-32zM800 640c17.696 0 32 14.304 32 32s-14.304 32-32 32-32-14.304-32-32 14.304-32 32-32zM704 512c17.696 0 32 14.304 32 32s-14.304 32-32 32-32-14.304-32-32 14.304-32 32-32zM608 384c17.696 0 32 14.304 32 32s-14.304 32-32 32-32-14.304-32-32 14.304-32 32-32zM864 576c-17.696 0-32-14.304-32-32s14.304-32 32-32 32 14.304 32 32-14.304 32-32 32zM960 832h-896c-35.328 0-64-28.672-64-64v-640c0-35.328 28.672-64 64-64h896c35.328 0 64 28.672 64 64v640c0 35.328-28.672 64-64 64zM960 160c0-17.696-14.304-32-32-32h-832c-17.664 0-32 14.304-32 32v576c0 17.696 14.336 32 32 32h832c17.696 0 32-14.304 32-32v-576zM736 256h-448c-17.664 0-32-14.304-32-32s14.336-32 32-32h448c17.696 0 32 14.304 32 32s-14.304 32-32 32zM160 576c-17.664 0-32-14.304-32-32s14.336-32 32-32 32 14.304 32 32-14.336 32-32 32zM224 448c-17.664 0-32-14.304-32-32s14.336-32 32-32 32 14.304 32 32-14.336 32-32 32zM224 640c17.664 0 32 14.304 32 32s-14.336 32-32 32-32-14.304-32-32 14.336-32 32-32zM800 448c-17.696 0-32-14.304-32-32s14.304-32 32-32 32 14.304 32 32-14.304 32-32 32z" />
<glyph unicode="&#xe901;" glyph-name="markdown" d="M950.154 768h-876.308c-40.719 0-73.846-33.127-73.846-73.846v-492.308c0-40.721 33.127-73.846 73.846-73.846h876.308c40.721 0 73.846 33.125 73.846 73.846v492.308c0 40.719-33.125 73.846-73.846 73.846zM576 256.125l-128-0.125v192l-96-123.077-96 123.077v-192h-128v384h128l96-128 96 128 128 0.125v-384zM767.091 224.125l-159.091 223.875h96v192h128v-192h96l-160.909-223.875z" />
<glyph unicode="&#xe989;" glyph-name="enlarge" d="M1024 960h-416l160-160-192-192 96-96 192 192 160-160zM1024-64v416l-160-160-192 192-96-96 192-192-160-160zM0-64h416l-160 160 192 192-96 96-192-192-160 160zM0 960v-416l160 160 192-192 96 96-192 192 160 160z" />
<glyph unicode="&#xe98f;" glyph-name="lock" d="M592 512h-16v192c0 105.87-86.13 192-192 192h-128c-105.87 0-192-86.13-192-192v-192h-16c-26.4 0-48-21.6-48-48v-480c0-26.4 21.6-48 48-48h544c26.4 0 48 21.6 48 48v480c0 26.4-21.6 48-48 48zM192 704c0 35.29 28.71 64 64 64h128c35.29 0 64-28.71 64-64v-192h-256v192z" />
<glyph unicode="&#xea9c;" glyph-name="rss2" d="M928 960h-832c-52.8 0-96-43.2-96-96v-832c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v832c0 52.8-43.2 96-96 96zM279 128.8c-48 0-87 38.6-87 86.6 0 47.6 39 86.8 87 86.8 48.2 0 87-39.2 87-86.8 0-48-39-86.6-87-86.6zM497.4 128c0 81.8-31.8 158.8-89.4 216.4-57.8 57.8-134.4 89.6-216 89.6v125.2c237.6 0 431.2-193.4 431.2-431.2h-125.8zM719.6 128c0 291-236.6 528-527.4 528v125.2c360 0 653-293.2 653-653.2h-125.6z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Binary file not shown.