1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@
|
||||
/app/assets/templates/generated/
|
||||
|
||||
/node_modules
|
||||
/dist
|
||||
/dist/javascripts/app.js
|
||||
/dist/javascripts/compiled.js
|
||||
/dist/javascripts/transpiled.js
|
||||
|
||||
49
.npmignore
49
.npmignore
@@ -1,37 +1,40 @@
|
||||
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile '~/.gitignore_global'
|
||||
|
||||
# OS & IDE
|
||||
.DS_Store
|
||||
|
||||
# Ignore bundler config.
|
||||
/.bundle
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
.git
|
||||
/.sass-cache
|
||||
/app
|
||||
/bin
|
||||
/config
|
||||
/db
|
||||
/lib
|
||||
/log
|
||||
/node_modules
|
||||
/test
|
||||
/vendor
|
||||
|
||||
/log/*
|
||||
!/log/.keep
|
||||
/tmp
|
||||
|
||||
/config/cap.yml
|
||||
|
||||
/app/assets/templates/generated/
|
||||
|
||||
/node_modules
|
||||
|
||||
/.sass-cache
|
||||
|
||||
# Ignore ENV variables config
|
||||
.env
|
||||
.ssh
|
||||
|
||||
dump.rdb
|
||||
|
||||
# Ignore compiled assets
|
||||
/public/assets
|
||||
|
||||
# Ignore user uploads
|
||||
/public/uploads/*
|
||||
!/public/uploads/.keep
|
||||
|
||||
.babelrc
|
||||
.gitignore
|
||||
.gitmodules
|
||||
.npmignore
|
||||
Capfile
|
||||
config.ru
|
||||
Gemfile
|
||||
Gemfile.lock
|
||||
Gruntfile.js
|
||||
package-lock.json
|
||||
package.json
|
||||
Rakefile
|
||||
testing-server.js
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
angular.module('app')
|
||||
|
||||
.constant('appVersion', '3.0.9')
|
||||
.constant('appVersion', '3.0.10')
|
||||
|
||||
;
|
||||
@@ -14,7 +14,7 @@ angular.module('app')
|
||||
bindToController: true,
|
||||
|
||||
link:function(scope, elem, attrs, ctrl) {
|
||||
scope.$watch('ctrl.note', function(note, oldNote){
|
||||
scope.$watch('ctrl.note', (note, oldNote) => {
|
||||
if(note) {
|
||||
ctrl.noteDidChange(note, oldNote);
|
||||
}
|
||||
@@ -24,20 +24,43 @@ angular.module('app')
|
||||
})
|
||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
|
||||
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory,
|
||||
privilegesManager, keyboardManager) {
|
||||
privilegesManager, keyboardManager, desktopManager) {
|
||||
|
||||
this.spellcheck = true;
|
||||
this.componentManager = componentManager;
|
||||
this.componentStack = [];
|
||||
this.isDesktop = isDesktopApplication();
|
||||
|
||||
$rootScope.$on("sync:taking-too-long", function(){
|
||||
this.syncTakingTooLong = true;
|
||||
}.bind(this));
|
||||
const MinimumStatusDurationMs = 400;
|
||||
|
||||
$rootScope.$on("sync:completed", function(){
|
||||
this.syncTakingTooLong = false;
|
||||
}.bind(this));
|
||||
syncManager.addEventHandler((eventName, data) => {
|
||||
if(!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(eventName == "sync:taking-too-long") {
|
||||
this.syncTakingTooLong = true;
|
||||
}
|
||||
|
||||
else if(eventName == "sync:completed") {
|
||||
this.syncTakingTooLong = false;
|
||||
if(this.note.dirty) {
|
||||
// if we're still dirty, don't change status, a sync is likely upcoming.
|
||||
} else {
|
||||
let savedItem = data.savedItems.find((item) => item.uuid == this.note.uuid);
|
||||
let isInErrorState = this.saveError;
|
||||
if(isInErrorState || savedItem) {
|
||||
this.showAllChangesSavedStatus();
|
||||
}
|
||||
}
|
||||
} else if(eventName == "sync:error") {
|
||||
// only show error status in editor if the note is dirty. Otherwise, it means the originating sync
|
||||
// came from somewhere else and we don't want to display an error here.
|
||||
if(this.note.dirty){
|
||||
this.showErrorStatus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Right now this only handles offline saving status changes.
|
||||
this.syncStatusObserver = syncManager.registerSyncStatusObserver((status) => {
|
||||
@@ -111,6 +134,9 @@ angular.module('app')
|
||||
// Look through editors again and find the most proper one
|
||||
var editor = this.editorForNote(this.note);
|
||||
this.selectedEditor = editor;
|
||||
if(!editor) {
|
||||
this.reloadFont();
|
||||
}
|
||||
});
|
||||
|
||||
this.noteDidChange = function(note, oldNote) {
|
||||
@@ -158,7 +184,7 @@ angular.module('app')
|
||||
}
|
||||
|
||||
if(oldNote && oldNote != note) {
|
||||
if(oldNote.hasChanges) {
|
||||
if(oldNote.dirty) {
|
||||
this.saveNote(oldNote);
|
||||
} else if(oldNote.dummy) {
|
||||
this.remove()(oldNote);
|
||||
@@ -198,14 +224,14 @@ angular.module('app')
|
||||
if(editor) {
|
||||
if(this.note.getAppDataItem("prefersPlainEditor") == true) {
|
||||
this.note.setAppDataItem("prefersPlainEditor", false);
|
||||
this.note.setDirty(true);
|
||||
modelManager.setItemDirty(this.note, true);
|
||||
}
|
||||
this.associateComponentWithCurrentNote(editor);
|
||||
} else {
|
||||
// Note prefers plain editor
|
||||
if(!this.note.getAppDataItem("prefersPlainEditor")) {
|
||||
this.note.setAppDataItem("prefersPlainEditor", true);
|
||||
this.note.setDirty(true);
|
||||
modelManager.setItemDirty(this.note, true);
|
||||
}
|
||||
$timeout(() => {
|
||||
this.reloadFont();
|
||||
@@ -245,85 +271,60 @@ angular.module('app')
|
||||
this.showMenu = false;
|
||||
}
|
||||
|
||||
var statusTimeout;
|
||||
this.EditorNgDebounce = 200;
|
||||
const SyncDebouce = 1000;
|
||||
const SyncNoDebounce = 100;
|
||||
|
||||
this.saveNote = function(note, callback, dontUpdateClientModified, dontUpdatePreviews) {
|
||||
// We don't want to update the client modified date if toggling lock for note.
|
||||
note.setDirty(true, dontUpdateClientModified);
|
||||
this.saveNote = function({bypassDebouncer, updateClientModified, dontUpdatePreviews}) {
|
||||
let note = this.note;
|
||||
note.dummy = false;
|
||||
|
||||
if(note.deleted) {
|
||||
alert("The note you are attempting to edit has been deleted, and is awaiting sync. Changes you make will be disregarded.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!modelManager.findItem(note.uuid)) {
|
||||
alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSavingStatus();
|
||||
|
||||
if(!dontUpdatePreviews) {
|
||||
let limit = 80;
|
||||
var text = note.text || "";
|
||||
var truncate = text.length > limit;
|
||||
note.content.preview_plain = text.substring(0, limit) + (truncate ? "..." : "");
|
||||
|
||||
// Clear dynamic previews if using plain editor
|
||||
note.content.preview_html = null;
|
||||
}
|
||||
|
||||
syncManager.sync().then((response) => {
|
||||
if(response && response.error) {
|
||||
if(!this.didShowErrorAlert) {
|
||||
modelManager.setItemDirty(note, true, updateClientModified);
|
||||
|
||||
if(this.saveTimeout) {
|
||||
$timeout.cancel(this.saveTimeout);
|
||||
}
|
||||
|
||||
let syncDebouceMs;
|
||||
if(authManager.offline() || bypassDebouncer) {
|
||||
syncDebouceMs = SyncNoDebounce;
|
||||
} else {
|
||||
syncDebouceMs = SyncDebouce;
|
||||
}
|
||||
|
||||
this.saveTimeout = $timeout(() => {
|
||||
syncManager.sync().then((response) => {
|
||||
if(response && response.error && !this.didShowErrorAlert) {
|
||||
this.didShowErrorAlert = true;
|
||||
alert("There was an error saving your note. Please try again.");
|
||||
}
|
||||
$timeout(() => {
|
||||
callback && callback(false);
|
||||
})
|
||||
} else {
|
||||
note.hasChanges = false;
|
||||
$timeout(() => {
|
||||
callback && callback(true);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let saveTimeout;
|
||||
this.changesMade = function({bypassDebouncer, dontUpdateClientModified, dontUpdatePreviews} = {}) {
|
||||
let note = this.note;
|
||||
note.dummy = false;
|
||||
|
||||
/* In the case of keystrokes, saving should go through a debouncer to avoid frequent calls.
|
||||
In the case of deleting or archiving a note, it should happen immediately before the note is switched out
|
||||
*/
|
||||
let delay = bypassDebouncer ? 0 : 275;
|
||||
|
||||
// In the case of archiving a note, the note is saved immediately, then switched to another note.
|
||||
// Usually note.hasChanges is set back to false after the saving delay, but in this case, because there is no delay,
|
||||
// we set it to false immediately so that it is not saved twice: once now, and the other on setNote in oldNote.hasChanges.
|
||||
note.hasChanges = bypassDebouncer ? false : true;
|
||||
|
||||
if(saveTimeout) $timeout.cancel(saveTimeout);
|
||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||
saveTimeout = $timeout(() => {
|
||||
this.showSavingStatus();
|
||||
note.dummy = false;
|
||||
// Make sure the note exists. A safety measure, as toggling between tags triggers deletes for dummy notes.
|
||||
// Race conditions have been fixed, but we'll keep this here just in case.
|
||||
if(!modelManager.findItem(note.uuid)) {
|
||||
alert("The note you are attempting to save can not be found or has been deleted. Changes you make will not be synced. Please copy this note's text and start a new note.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveNote(note, (success) => {
|
||||
if(success) {
|
||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||
statusTimeout = $timeout(() => {
|
||||
this.showAllChangesSavedStatus();
|
||||
}, 200)
|
||||
} else {
|
||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||
statusTimeout = $timeout(() => {
|
||||
this.showErrorStatus();
|
||||
}, 200)
|
||||
}
|
||||
}, dontUpdateClientModified, dontUpdatePreviews);
|
||||
}, delay)
|
||||
})
|
||||
}, syncDebouceMs)
|
||||
}
|
||||
|
||||
this.showSavingStatus = function() {
|
||||
this.noteStatus = {message: "Saving..."};
|
||||
this.setStatus({message: "Saving..."}, false);
|
||||
}
|
||||
|
||||
this.showAllChangesSavedStatus = function() {
|
||||
@@ -334,24 +335,40 @@ angular.module('app')
|
||||
if(authManager.offline()) {
|
||||
status += " (offline)";
|
||||
}
|
||||
this.noteStatus = {message: status};
|
||||
|
||||
this.setStatus({message: status});
|
||||
}
|
||||
|
||||
this.showErrorStatus = function(error) {
|
||||
if(!error) {
|
||||
error = {
|
||||
message: "Sync Unreachable",
|
||||
desc: "All changes saved offline"
|
||||
desc: "Changes saved offline"
|
||||
}
|
||||
}
|
||||
this.saveError = true;
|
||||
this.syncTakingTooLong = false;
|
||||
this.noteStatus = error;
|
||||
this.setStatus(error);
|
||||
}
|
||||
|
||||
this.setStatus = function(status, wait = true) {
|
||||
// Keep every status up for a minimum duration so it doesnt flash crazily.
|
||||
let waitForMs;
|
||||
if(!this.noteStatus || !this.noteStatus.date) {
|
||||
waitForMs = 0;
|
||||
} else {
|
||||
waitForMs = MinimumStatusDurationMs - (new Date() - this.noteStatus.date);
|
||||
}
|
||||
if(!wait || waitForMs < 0) {waitForMs = 0;}
|
||||
if(this.statusTimeout) $timeout.cancel(this.statusTimeout);
|
||||
this.statusTimeout = $timeout(() => {
|
||||
status.date = new Date();
|
||||
this.noteStatus = status;
|
||||
}, waitForMs)
|
||||
}
|
||||
|
||||
this.contentChanged = function() {
|
||||
// content changes should bypass manual debouncer as we use the built in ng-model-options debouncer
|
||||
this.changesMade({bypassDebouncer: true});
|
||||
this.saveNote({updateClientModified: true});
|
||||
}
|
||||
|
||||
this.onTitleEnter = function($event) {
|
||||
@@ -361,7 +378,7 @@ angular.module('app')
|
||||
}
|
||||
|
||||
this.onTitleChange = function() {
|
||||
this.changesMade({dontUpdatePreviews: true});
|
||||
this.saveNote({dontUpdatePreviews: true, updateClientModified: true});
|
||||
}
|
||||
|
||||
this.onNameFocus = function() {
|
||||
@@ -398,7 +415,7 @@ angular.module('app')
|
||||
this.remove()(this.note);
|
||||
} else {
|
||||
this.note.content.trashed = true;
|
||||
this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
}
|
||||
this.showMenu = false;
|
||||
}
|
||||
@@ -416,7 +433,7 @@ angular.module('app')
|
||||
|
||||
this.restoreTrashedNote = function() {
|
||||
this.note.content.trashed = false;
|
||||
this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.deleteNotePermanantely = function() {
|
||||
@@ -437,17 +454,17 @@ angular.module('app')
|
||||
|
||||
this.togglePin = function() {
|
||||
this.note.setAppDataItem("pinned", !this.note.pinned);
|
||||
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.toggleLockNote = function() {
|
||||
this.note.setAppDataItem("locked", !this.note.locked);
|
||||
this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.toggleProtectNote = function() {
|
||||
this.note.content.protected = !this.note.content.protected;
|
||||
this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
|
||||
// Show privilegesManager if Protection is not yet set up
|
||||
privilegesManager.actionHasPrivilegesConfigured(PrivilegesManager.ActionViewProtectedNotes).then((configured) => {
|
||||
@@ -459,12 +476,12 @@ angular.module('app')
|
||||
|
||||
this.toggleNotePreview = function() {
|
||||
this.note.content.hidePreview = !this.note.content.hidePreview;
|
||||
this.changesMade({bypassDebouncer: true, dontUpdateClientModified: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
}
|
||||
|
||||
this.toggleArchiveNote = function() {
|
||||
this.note.setAppDataItem("archived", !this.note.archived);
|
||||
this.changesMade({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
this.saveNote({bypassDebouncer: true, dontUpdatePreviews: true});
|
||||
$rootScope.$broadcast("noteArchived");
|
||||
}
|
||||
|
||||
@@ -627,6 +644,10 @@ angular.module('app')
|
||||
Components
|
||||
*/
|
||||
|
||||
this.onEditorLoad = function(editor) {
|
||||
desktopManager.redoSearch();
|
||||
}
|
||||
|
||||
componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: (component) => {
|
||||
if(component.area === "note-tags") {
|
||||
// Autocomplete Tags
|
||||
@@ -688,38 +709,17 @@ angular.module('app')
|
||||
if(data.item.content_type == "Tag") {
|
||||
var tag = modelManager.findItem(data.item.uuid);
|
||||
this.addTag(tag);
|
||||
|
||||
// Currently extensions are not notified of association until a full server sync completes.
|
||||
// We need a better system for this, but for now, we'll manually notify observers
|
||||
modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved);
|
||||
}
|
||||
}
|
||||
|
||||
else if(action === "deassociate-item") {
|
||||
var tag = modelManager.findItem(data.item.uuid);
|
||||
this.removeTag(tag);
|
||||
|
||||
// Currently extensions are not notified of association until a full server sync completes.
|
||||
// We need a better system for this, but for now, we'll manually notify observers
|
||||
modelManager.notifySyncObserversOfModels([tag], SFModelManager.MappingSourceLocalSaved);
|
||||
}
|
||||
|
||||
else if(action === "save-items" || action === "save-success" || action == "save-error") {
|
||||
else if(action === "save-items") {
|
||||
if(data.items.map((item) => {return item.uuid}).includes(this.note.uuid)) {
|
||||
|
||||
if(action == "save-items") {
|
||||
if(this.componentSaveTimeout) $timeout.cancel(this.componentSaveTimeout);
|
||||
this.componentSaveTimeout = $timeout(this.showSavingStatus.bind(this), 10);
|
||||
}
|
||||
|
||||
else {
|
||||
if(this.componentStatusTimeout) $timeout.cancel(this.componentStatusTimeout);
|
||||
if(action == "save-success") {
|
||||
this.componentStatusTimeout = $timeout(this.showAllChangesSavedStatus.bind(this), 400);
|
||||
} else {
|
||||
this.componentStatusTimeout = $timeout(this.showErrorStatus.bind(this), 400);
|
||||
}
|
||||
}
|
||||
this.showSavingStatus();
|
||||
}
|
||||
}
|
||||
}});
|
||||
@@ -785,7 +785,7 @@ angular.module('app')
|
||||
component.disassociatedItemIds.push(this.note.uuid);
|
||||
}
|
||||
|
||||
component.setDirty(true);
|
||||
modelManager.setItemDirty(component, true);
|
||||
syncManager.sync();
|
||||
}
|
||||
|
||||
@@ -796,7 +796,7 @@ angular.module('app')
|
||||
component.associatedItemIds.push(this.note.uuid);
|
||||
}
|
||||
|
||||
component.setDirty(true);
|
||||
modelManager.setItemDirty(component, true);
|
||||
syncManager.sync();
|
||||
}
|
||||
|
||||
@@ -887,7 +887,7 @@ angular.module('app')
|
||||
|
||||
$timeout(() => {
|
||||
this.note.text = editor.value;
|
||||
this.changesMade({bypassDebouncer: true});
|
||||
this.saveNote({bypassDebouncer: true});
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -3,11 +3,6 @@ angular.module('app')
|
||||
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager,
|
||||
privilegesManager, statusManager) {
|
||||
|
||||
// Lock syncing until local data is loaded. Syncing may be called from a variety of places,
|
||||
// such as when the window focuses, for example. We don't want sync to occur until all local items are loaded,
|
||||
// otherwise, if sync happens first, then load, the load may override synced values.
|
||||
syncManager.lockSyncing();
|
||||
|
||||
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
|
||||
|
||||
$scope.platform = getPlatformString();
|
||||
@@ -98,11 +93,7 @@ angular.module('app')
|
||||
this.syncStatus = statusManager.replaceStatusWithString(this.syncStatus, encryptionEnabled ? `Decrypting ${notesString}` : `Loading ${notesString}`);
|
||||
}
|
||||
|
||||
syncManager.loadLocalItems(incrementalCallback).then(() => {
|
||||
|
||||
// First unlock after initially locked to wait for local data loaded.
|
||||
syncManager.unlockSyncing();
|
||||
|
||||
syncManager.loadLocalItems({incrementalCallback}).then(() => {
|
||||
$timeout(() => {
|
||||
$rootScope.$broadcast("initial-data-loaded"); // This needs to be processed first before sync is called so that singletonManager observers function properly.
|
||||
// Perform integrity check on first sync
|
||||
@@ -172,9 +163,10 @@ angular.module('app')
|
||||
|
||||
for(var tagToRemove of toRemove) {
|
||||
tagToRemove.removeItemAsRelationship(note);
|
||||
tagToRemove.setDirty(true);
|
||||
}
|
||||
|
||||
modelManager.setItemsDirty(toRemove, true);
|
||||
|
||||
var tags = [];
|
||||
for(var tagString of stringTags) {
|
||||
var existingRelationship = _.find(note.tags, {title: tagString});
|
||||
@@ -185,9 +177,10 @@ angular.module('app')
|
||||
|
||||
for(var tag of tags) {
|
||||
tag.addItemAsRelationship(note);
|
||||
tag.setDirty(true);
|
||||
}
|
||||
|
||||
modelManager.setItemsDirty(tags, true);
|
||||
|
||||
syncManager.sync();
|
||||
}
|
||||
|
||||
@@ -219,7 +212,8 @@ angular.module('app')
|
||||
$scope.removeTag(tag);
|
||||
return;
|
||||
}
|
||||
tag.setDirty(true);
|
||||
|
||||
modelManager.setItemDirty(tag, true);
|
||||
syncManager.sync().then(callback);
|
||||
modelManager.resortTag(tag);
|
||||
}
|
||||
@@ -244,10 +238,11 @@ angular.module('app')
|
||||
|
||||
$scope.notesAddNew = function(note) {
|
||||
modelManager.addItem(note);
|
||||
modelManager.setItemDirty(note);
|
||||
|
||||
if(!$scope.selectedTag.isSmartTag()) {
|
||||
$scope.selectedTag.addItemAsRelationship(note);
|
||||
$scope.selectedTag.setDirty(true);
|
||||
modelManager.setItemDirty($scope.selectedTag, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +303,7 @@ angular.module('app')
|
||||
|
||||
window.addEventListener('drop', (event) => {
|
||||
event.preventDefault();
|
||||
alert("Please use FileSafe to attach images and files. Learn more at standardnotes.org/filesafe.")
|
||||
alert("Please use FileSafe or the Bold Editor to attach images and files. Learn more at standardnotes.org/filesafe.")
|
||||
}, false)
|
||||
|
||||
|
||||
|
||||
@@ -40,14 +40,28 @@ angular.module('app')
|
||||
modelManager.removeItemLocally(this.selectedNote);
|
||||
_.pull(this.notes, this.selectedNote);
|
||||
this.selectedNote = null;
|
||||
this.selectNote(null);
|
||||
|
||||
// We now want to see if the user will download any items from the server.
|
||||
// If the next sync completes and our notes are still 0, we need to create a dummy.
|
||||
this.createDummyOnSynCompletionIfNoNotes = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
syncManager.addEventHandler((syncEvent, data) => {
|
||||
if(syncEvent == "local-data-loaded") {
|
||||
this.localDataLoaded = true;
|
||||
this.needsHandleDataLoad = true;
|
||||
if(this.notes.length == 0) {
|
||||
this.createNewNote();
|
||||
}
|
||||
} else if(syncEvent == "sync:completed") {
|
||||
// Pad with a timeout just to be extra patient
|
||||
$timeout(() => {
|
||||
if(this.createDummyOnSynCompletionIfNoNotes && this.notes.length == 0) {
|
||||
this.createDummyOnSynCompletionIfNoNotes = false;
|
||||
this.createNewNote();
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,13 +69,6 @@ angular.module('app')
|
||||
// reload our notes
|
||||
this.reloadNotes();
|
||||
|
||||
if(this.needsHandleDataLoad) {
|
||||
this.needsHandleDataLoad = false;
|
||||
if(this.tag && this.notes.length == 0) {
|
||||
this.createNewNote();
|
||||
}
|
||||
}
|
||||
|
||||
// Note has changed values, reset its flags
|
||||
let notes = allItems.filter((item) => item.content_type == "Note");
|
||||
for(let note of notes) {
|
||||
@@ -230,6 +237,9 @@ angular.module('app')
|
||||
if(this.hidePinned) {
|
||||
base += " | – Pinned"
|
||||
}
|
||||
if(this.sortReverse) {
|
||||
base += " | Reversed"
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
@@ -286,6 +296,13 @@ angular.module('app')
|
||||
})
|
||||
}
|
||||
|
||||
if(note.deleted) {
|
||||
flags.push({
|
||||
text: "Deletion Pending Sync",
|
||||
class: "danger"
|
||||
})
|
||||
}
|
||||
|
||||
note.flags = flags;
|
||||
|
||||
return flags;
|
||||
@@ -302,13 +319,14 @@ angular.module('app')
|
||||
|
||||
this.showMenu = false;
|
||||
|
||||
if(this.selectedNote && this.selectedNote.dummy) {
|
||||
if(oldTag) {
|
||||
if(this.selectedNote) {
|
||||
if(this.selectedNote.dummy && oldTag) {
|
||||
_.remove(oldTag.notes, this.selectedNote);
|
||||
}
|
||||
}
|
||||
|
||||
this.noteFilter.text = "";
|
||||
desktopManager.searchText();
|
||||
|
||||
this.setNotes(tag.notes);
|
||||
|
||||
@@ -317,8 +335,14 @@ angular.module('app')
|
||||
if(this.notes.length > 0) {
|
||||
this.notes.forEach((note) => { note.visible = true; })
|
||||
this.selectFirstNote();
|
||||
} else if(this.localDataLoaded) {
|
||||
this.createNewNote();
|
||||
} else if(syncManager.initialDataLoaded()) {
|
||||
if(!tag.isSmartTag()) {
|
||||
this.createNewNote();
|
||||
} else {
|
||||
if(this.selectedNote && !this.notes.includes(this.selectedNote)) {
|
||||
this.selectNote(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -357,6 +381,7 @@ angular.module('app')
|
||||
|
||||
this.selectNote = async function(note, viaClick = false) {
|
||||
if(!note) {
|
||||
this.selectionMade()(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -371,7 +396,7 @@ angular.module('app')
|
||||
this.selectedNote = note;
|
||||
if(note.content.conflict_of) {
|
||||
note.content.conflict_of = null; // clear conflict
|
||||
note.setDirty(true, true);
|
||||
modelManager.setItemDirty(note, true);
|
||||
syncManager.sync();
|
||||
}
|
||||
this.selectionMade()(note);
|
||||
|
||||
@@ -30,28 +30,30 @@ angular.module('app')
|
||||
|| syncEvent == "local-data-incremental-load") {
|
||||
this.tags = modelManager.tags;
|
||||
this.smartTags = modelManager.getSmartTags();
|
||||
if(this.noteCountsNeedReload) {
|
||||
this.noteCountsNeedReload = false;
|
||||
this.reloadNoteCounts();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
modelManager.addItemSyncObserver("note-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => {
|
||||
// recompute note counts
|
||||
let tags = [];
|
||||
if(this.tags) {
|
||||
tags = tags.concat(this.tags);
|
||||
}
|
||||
if(this.smartTags) {
|
||||
tags = tags.concat(this.smartTags);
|
||||
}
|
||||
modelManager.addItemSyncObserver("tags-list", "*", (allItems, validItems, deletedItems, source, sourceKey) => {
|
||||
this.noteCountsNeedReload = true;
|
||||
});
|
||||
|
||||
for(let tag of tags) {
|
||||
var validNotes = SNNote.filterDummyNotes(tag.notes).filter(function(note){
|
||||
this.reloadNoteCounts = function() {
|
||||
let allTags = [];
|
||||
if(this.tags) { allTags = allTags.concat(this.tags);}
|
||||
if(this.smartTags) { allTags = allTags.concat(this.smartTags);}
|
||||
|
||||
for(let tag of allTags) {
|
||||
var validNotes = SNNote.filterDummyNotes(tag.notes).filter((note) => {
|
||||
return !note.archived && !note.content.trashed;
|
||||
});
|
||||
|
||||
tag.cachedNoteCount = validNotes.length;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.panelController = {};
|
||||
|
||||
@@ -108,8 +110,8 @@ angular.module('app')
|
||||
}
|
||||
this.selectedTag = tag;
|
||||
if(tag.content.conflict_of) {
|
||||
tag.content.conflict_of = null; // clear conflict
|
||||
tag.setDirty(true, true);
|
||||
tag.content.conflict_of = null;
|
||||
modelManager.setItemDirty(tag, true);
|
||||
syncManager.sync();
|
||||
}
|
||||
this.selectionMade()(tag);
|
||||
|
||||
@@ -301,7 +301,7 @@ class AccountMenu {
|
||||
if(item.content_type == "SN|Component") { item.active = false; }
|
||||
}
|
||||
|
||||
syncManager.sync({additionalFields: ["created_at", "updated_at"]}).then((response) => {
|
||||
syncManager.sync().then((response) => {
|
||||
// Response can be null if syncing offline
|
||||
callback(response, errorCount);
|
||||
});
|
||||
|
||||
@@ -31,8 +31,13 @@ class ActionsMenu {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
action.running = true;
|
||||
actionsManager.executeAction(action, extension, $scope.item, function(response){
|
||||
actionsManager.executeAction(action, extension, $scope.item, (response, error) => {
|
||||
if(error) {
|
||||
return;
|
||||
}
|
||||
|
||||
action.running = false;
|
||||
$scope.handleActionResponse(action, response);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ class ComponentView {
|
||||
this.templateUrl = "directives/component-view.html";
|
||||
this.scope = {
|
||||
component: "=",
|
||||
onLoad: "=",
|
||||
manualDealloc: "="
|
||||
};
|
||||
|
||||
@@ -118,6 +119,7 @@ class ComponentView {
|
||||
$timeout(() => {
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = desktopError; /* Typically we'd just set this to false at this point, but we now account for desktopError */
|
||||
$scope.onLoad && $scope.onLoad($scope.component);
|
||||
}, 7)
|
||||
})
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ class EditorMenu {
|
||||
};
|
||||
}
|
||||
|
||||
controller($scope, componentManager, syncManager, $timeout) {
|
||||
controller($scope, componentManager, syncManager, modelManager, $timeout) {
|
||||
'ngInject';
|
||||
|
||||
$scope.formData = {};
|
||||
@@ -27,7 +27,7 @@ class EditorMenu {
|
||||
if(component) {
|
||||
if(component.content.conflict_of) {
|
||||
component.content.conflict_of = null; // clear conflict if applicable
|
||||
component.setDirty(true, true);
|
||||
modelManager.setItemDirty(component, true);
|
||||
syncManager.sync();
|
||||
}
|
||||
}
|
||||
@@ -52,11 +52,11 @@ class EditorMenu {
|
||||
var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0];
|
||||
if(currentDefault) {
|
||||
currentDefault.setAppDataItem("defaultEditor", false);
|
||||
currentDefault.setDirty(true);
|
||||
modelManager.setItemDirty(currentDefault, true);
|
||||
}
|
||||
|
||||
component.setAppDataItem("defaultEditor", true);
|
||||
component.setDirty(true);
|
||||
modelManager.setItemDirty(component, true);
|
||||
syncManager.sync();
|
||||
|
||||
$scope.defaultEditor = component;
|
||||
@@ -64,7 +64,7 @@ class EditorMenu {
|
||||
|
||||
$scope.removeEditorDefault = function(component) {
|
||||
component.setAppDataItem("defaultEditor", false);
|
||||
component.setDirty(true);
|
||||
modelManager.setItemDirty(component, true);
|
||||
syncManager.sync();
|
||||
|
||||
$scope.defaultEditor = null;
|
||||
|
||||
@@ -75,10 +75,11 @@ class RevisionPreviewModal {
|
||||
var uuid = $scope.uuid;
|
||||
item = modelManager.findItem(uuid);
|
||||
item.content = Object.assign({}, $scope.content);
|
||||
// mapResponseItemsToLocalModels is async, but we don't need to wait here.
|
||||
modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
|
||||
}
|
||||
|
||||
item.setDirty(true);
|
||||
modelManager.setItemDirty(item, true);
|
||||
syncManager.sync();
|
||||
|
||||
$scope.dismiss();
|
||||
|
||||
@@ -54,10 +54,10 @@ class ActionsManager {
|
||||
|
||||
async executeAction(action, extension, item, callback) {
|
||||
|
||||
var customCallback = (response) => {
|
||||
var customCallback = (response, error) => {
|
||||
action.running = false;
|
||||
this.$timeout(() => {
|
||||
callback(response);
|
||||
callback(response, error);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -74,14 +74,14 @@ class ActionsManager {
|
||||
|
||||
if(!item.errorDecrypting) {
|
||||
if(merge) {
|
||||
var items = this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
|
||||
var items = await this.modelManager.mapResponseItemsToLocalModels([item], SFModelManager.MappingSourceRemoteActionRetrieved);
|
||||
for(var mappedItem of items) {
|
||||
mappedItem.setDirty(true);
|
||||
this.modelManager.setItemDirty(mappedItem, true);
|
||||
}
|
||||
this.syncManager.sync();
|
||||
customCallback({item: item});
|
||||
} else {
|
||||
item = this.modelManager.createItem(item, true /* Dont notify observers */);
|
||||
item = this.modelManager.createItem(item);
|
||||
customCallback({item: item});
|
||||
}
|
||||
return true;
|
||||
@@ -127,11 +127,10 @@ class ActionsManager {
|
||||
action.error = false;
|
||||
handleResponseDecryption(response, await this.authManager.keys(), true);
|
||||
}, (response) => {
|
||||
if(response && response.error) {
|
||||
alert("An issue occurred while processing this action. Please try again.");
|
||||
}
|
||||
let error = (response && response.error) || {message: "An issue occurred while processing this action. Please try again."}
|
||||
alert(error.message);
|
||||
action.error = true;
|
||||
customCallback(null);
|
||||
customCallback(null, error);
|
||||
})
|
||||
}
|
||||
break;
|
||||
@@ -142,8 +141,10 @@ class ActionsManager {
|
||||
action.error = false;
|
||||
handleResponseDecryption(response, await this.authManager.keys(), false);
|
||||
}, (response) => {
|
||||
let error = (response && response.error) || {message: "An issue occurred while processing this action. Please try again."}
|
||||
alert(error.message);
|
||||
action.error = true;
|
||||
customCallback(null);
|
||||
customCallback(null, error);
|
||||
})
|
||||
|
||||
break;
|
||||
|
||||
@@ -145,7 +145,7 @@ class AuthManager extends SFAuthManager {
|
||||
// Safe to create. Create and return object.
|
||||
var prefs = new SFItem({content_type: prefsContentType});
|
||||
this.modelManager.addItem(prefs);
|
||||
prefs.setDirty(true);
|
||||
this.modelManager.setItemDirty(prefs, true);
|
||||
this.$rootScope.sync();
|
||||
valueCallback(prefs);
|
||||
});
|
||||
@@ -157,7 +157,7 @@ class AuthManager extends SFAuthManager {
|
||||
|
||||
syncUserPreferences() {
|
||||
if(this.userPreferences) {
|
||||
this.userPreferences.setDirty(true);
|
||||
this.modelManager.setItemDirty(this.userPreferences, true);
|
||||
this.$rootScope.sync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ class ComponentManager extends SNComponentManager {
|
||||
platform: getPlatformString()
|
||||
});
|
||||
|
||||
// this.loggingEnabled = true;
|
||||
|
||||
this.$compile = $compile;
|
||||
this.$rootScope = $rootScope;
|
||||
}
|
||||
|
||||
@@ -70,9 +70,16 @@ class DesktopManager {
|
||||
if(!this.isDesktop) {
|
||||
return;
|
||||
}
|
||||
this.lastSearchedText = text;
|
||||
this.searchHandler(text);
|
||||
}
|
||||
|
||||
redoSearch() {
|
||||
if(this.lastSearchedText) {
|
||||
this.searchText(this.lastSearchedText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
deregisterUpdateObserver(observer) {
|
||||
_.pull(this.updateObservers, observer);
|
||||
@@ -112,7 +119,8 @@ class DesktopManager {
|
||||
this.modelManager.notifySyncObserversOfModels([component], SFModelManager.MappingSourceDesktopInstalled);
|
||||
component.setAppDataItem("installError", null);
|
||||
}
|
||||
component.setDirty(true);
|
||||
|
||||
this.modelManager.setItemDirty(component, true);
|
||||
this.syncManager.sync();
|
||||
|
||||
this.timeout(() => {
|
||||
|
||||
@@ -38,8 +38,8 @@ class MigrationManager extends SFMigrationManager {
|
||||
}
|
||||
})
|
||||
component.setAppDataItem("data", editor.data);
|
||||
component.setDirty(true);
|
||||
this.modelManager.addItem(component);
|
||||
this.modelManager.setItemDirty(component, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class MigrationManager extends SFMigrationManager {
|
||||
if(clientData) {
|
||||
note.setDomainDataItem(component.uuid, clientData, ComponentManager.ClientDataDomain);
|
||||
note.setDomainDataItem(component.hosted_url, null, ComponentManager.ClientDataDomain);
|
||||
note.setDirty(true, true); // dont update client date
|
||||
this.modelManager.setItemDirty(note, true);
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
@@ -126,14 +126,14 @@ class MigrationManager extends SFMigrationManager {
|
||||
let tag = this.modelManager.findItem(reference.uuid);
|
||||
if(tag && !tag.hasRelationshipWithItem(note)) {
|
||||
tag.addItemAsRelationship(note);
|
||||
tag.setDirty(true, true);
|
||||
this.modelManager.setItemDirty(tag, true);
|
||||
dirtyCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if(newReferences.length != references.length) {
|
||||
note.content.references = newReferences;
|
||||
note.setDirty(true, true);
|
||||
this.modelManager.setItemDirty(note, true);
|
||||
dirtyCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ class ModelManager extends SFModelManager {
|
||||
var tag = _.find(this.tags, {title: title})
|
||||
if(!tag) {
|
||||
tag = this.createItem({content_type: "Tag", content: {title: title}});
|
||||
tag.setDirty(true);
|
||||
this.addItem(tag);
|
||||
this.setItemDirty(tag, true);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
@@ -49,13 +49,13 @@ class NativeExtManager {
|
||||
}
|
||||
|
||||
if(needsSync) {
|
||||
resolvedSingleton.setDirty(true);
|
||||
this.modelManager.setItemDirty(resolvedSingleton, true);
|
||||
this.syncManager.sync();
|
||||
}
|
||||
}, (valueCallback) => {
|
||||
// Safe to create. Create and return object.
|
||||
let url = window._extensions_manager_location;
|
||||
console.log("Installing Extensions Manager from URL", url);
|
||||
// console.log("Installing Extensions Manager from URL", url);
|
||||
if(!url) {
|
||||
console.error("window._extensions_manager_location must be set.");
|
||||
return;
|
||||
@@ -93,7 +93,7 @@ class NativeExtManager {
|
||||
var component = this.modelManager.createItem(item);
|
||||
this.modelManager.addItem(component);
|
||||
|
||||
component.setDirty(true);
|
||||
this.modelManager.setItemDirty(component, true);
|
||||
this.syncManager.sync();
|
||||
|
||||
this.systemExtensions.push(component.uuid);
|
||||
@@ -125,13 +125,13 @@ class NativeExtManager {
|
||||
}
|
||||
|
||||
if(needsSync) {
|
||||
resolvedSingleton.setDirty(true);
|
||||
this.modelManager.setItemDirty(resolvedSingleton, true);
|
||||
this.syncManager.sync();
|
||||
}
|
||||
}, (valueCallback) => {
|
||||
// Safe to create. Create and return object.
|
||||
let url = window._batch_manager_location;
|
||||
console.log("Installing Batch Manager from URL", url);
|
||||
// console.log("Installing Batch Manager from URL", url);
|
||||
if(!url) {
|
||||
console.error("window._batch_manager_location must be set.");
|
||||
return;
|
||||
@@ -171,7 +171,7 @@ class NativeExtManager {
|
||||
var component = this.modelManager.createItem(item);
|
||||
this.modelManager.addItem(component);
|
||||
|
||||
component.setDirty(true);
|
||||
this.modelManager.setItemDirty(component, true);
|
||||
this.syncManager.sync();
|
||||
|
||||
this.systemExtensions.push(component.uuid);
|
||||
|
||||
@@ -5,6 +5,8 @@ class SyncManager extends SFSyncManager {
|
||||
this.$rootScope = $rootScope;
|
||||
this.$compile = $compile;
|
||||
|
||||
// this.loggingEnabled = true;
|
||||
|
||||
// Content types appearing first are always mapped first
|
||||
this.contentTypeLoadPriority = [
|
||||
"SN|UserPreferences", "SN|Privileges",
|
||||
|
||||
@@ -6,12 +6,13 @@ $heading-height: 75px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
.section.editor {
|
||||
flex: 1 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: hidden;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
background-color: var(--sn-stylekit-editor-background-color);
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
}
|
||||
|
||||
#editor-title-bar {
|
||||
@@ -28,12 +29,15 @@ $heading-height: 75px;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
|
||||
$title-width: 70%;
|
||||
$save-status-width: 30%;
|
||||
|
||||
> .title {
|
||||
font-size: var(--sn-stylekit-font-size-h1);
|
||||
font-weight: bold;
|
||||
padding-top: 0px;
|
||||
width: 100%;
|
||||
padding-right: 120px; /* make room for save status */
|
||||
width: $title-width;
|
||||
padding-right: 20px; /* make room for save status */
|
||||
|
||||
> .input {
|
||||
float: left;
|
||||
@@ -43,17 +47,17 @@ $heading-height: 75px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
|
||||
&:disabled {
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#save-status {
|
||||
width: 20%;
|
||||
width: $save-status-width;
|
||||
float: right;
|
||||
position: absolute;
|
||||
|
||||
@@ -63,6 +67,7 @@ $heading-height: 75px;
|
||||
font-weight: normal;
|
||||
margin-top: 4px;
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
|
||||
.desc, .message:not(.warning):not(.danger) {
|
||||
// color: var(--sn-stylekit-editor-foreground-color);
|
||||
@@ -122,8 +127,8 @@ $heading-height: 75px;
|
||||
font-family: monospace;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
background-color: var(--sn-stylekit-editor-background-color);
|
||||
color: var(--sn-stylekit-editor-foreground-color);
|
||||
|
||||
border: none;
|
||||
outline: none;
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
.ion, .ionicons,
|
||||
.ion-locked:before,
|
||||
.ion-plus:before,
|
||||
.ion-arrow-return-left:before,
|
||||
.ion-arrow-return-right: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;
|
||||
}
|
||||
@@ -25,8 +23,4 @@
|
||||
|
||||
.ion-plus:before { content: "\f218"; }
|
||||
|
||||
.ion-arrow-return-left:before { content: "\f265"; }
|
||||
|
||||
.ion-arrow-return-right:before { content: "\f266"; }
|
||||
|
||||
/*# sourceMappingURL=ionicons.css.map */
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
font-size: var(--sn-stylekit-font-size-h1);
|
||||
|
||||
.section-title-bar-header .title {
|
||||
color: var(--sn-stylekit-neutral-color);
|
||||
font-size: var(--sn-stylekit-font-size-h3);
|
||||
font-weight: 600;
|
||||
width: calc(90% - 45px);
|
||||
}
|
||||
}
|
||||
@@ -33,21 +34,24 @@
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
#notes-options-menu {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
clear: left;
|
||||
height: 29px;
|
||||
height: 28px;
|
||||
margin-top: 14px;
|
||||
position: relative;
|
||||
|
||||
.filter-bar {
|
||||
background-color: var(--sn-stylekit-contrast-background-color);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--sn-stylekit-general-border-radius);
|
||||
height: 100%;
|
||||
color: #909090;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: var(--sn-stylekit-font-size-h3);
|
||||
line-height: 35px;
|
||||
|
||||
border: none;
|
||||
width: 100%;
|
||||
@@ -155,7 +159,7 @@
|
||||
padding: 4px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
border-radius: 2px;
|
||||
border-radius: var(--sn-stylekit-general-border-radius);
|
||||
margin-right: 4px;
|
||||
|
||||
&.info {
|
||||
|
||||
@@ -51,6 +51,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sk-menu-panel-header {
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#session-history-menu {
|
||||
.sk-menu-panel .sk-menu-panel-row .sk-sublabel.opaque {
|
||||
opacity: 1.0
|
||||
|
||||
@@ -103,10 +103,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
|
||||
%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', 'spellcheck' => 'false'}
|
||||
%input.sk-input.contrast{:placeholder => 'Email', "sn-autofocus" => 'true',
|
||||
"should-focus" => "true", :name => 'email', :required => true, :type => 'email',
|
||||
'ng-model' => 'formData.email', 'spellcheck' => 'false', "ng-model-options"=> "{allowInvalid: true}"}
|
||||
%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
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
.sn-component#session-history-menu
|
||||
.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
|
||||
.sk-menu-panel-header-title {{history.entries.length || 'No'}} revisions
|
||||
%a.sk-a.info.sk-h5{"ng-click" => "showOptions = !showOptions; $event.stopPropagation();"} Options
|
||||
|
||||
%div{"ng-if" => "showOptions"}
|
||||
%menu-row{"label" => "'Clear note local history'", "action" => "clearItemHistory()"}
|
||||
|
||||
@@ -75,11 +75,12 @@
|
||||
|
||||
%panel-resizer.left{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish","control" => "ctrl.leftResizeControl", "min-width" => 300, "property" => "'left'", "hoverable" => "true"}
|
||||
|
||||
%component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor"}
|
||||
%component-view.component-view{"ng-if" => "ctrl.selectedEditor", "component" => "ctrl.selectedEditor", "on-load" => "ctrl.onEditorLoad"}
|
||||
|
||||
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.selectedEditor", "ng-model" => "ctrl.note.text", "ng-readonly" => "ctrl.note.locked",
|
||||
"ng-change" => "ctrl.contentChanged()", "ng-trim" => "false", "ng-click" => "ctrl.clickedTextArea()",
|
||||
"ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}", "ng-model-options"=>"{ debounce: 300 }"}
|
||||
"ng-focus" => "ctrl.onContentFocus()", "dir" => "auto", "ng-attr-spellcheck" => "{{ctrl.spellcheck}}",
|
||||
"ng-model-options"=>"{ debounce: ctrl.EditorNgDebounce }"}
|
||||
{{ctrl.onSystemEditorLoad()}}
|
||||
|
||||
%panel-resizer{"ng-if" => "ctrl.marginResizersEnabled", "panel-id" => "'editor-content'", "on-resize-finish" => "ctrl.onPanelResizeFinish", "control" => "ctrl.rightResizeControl", "min-width" => 300, "hoverable" => "true", "property" => "'right'"}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.sn-component
|
||||
#footer-bar.sk-app-bar.no-edges
|
||||
#footer-bar.sk-app-bar.no-edges.no-bottom-edge
|
||||
.left
|
||||
|
||||
.sk-app-bar-item{"ng-click" => "ctrl.accountMenuPressed()", "click-outside" => "ctrl.clickOutsideAccountMenu()", "is-open" => "ctrl.showAccountMenu"}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
%lock-screen{"ng-if" => "needsUnlock", "on-success" => "onSuccessfulUnlock"}
|
||||
|
||||
.app#app{"ng-if" => "!needsUnlock", "ng-class" => "appClass"}
|
||||
%tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade",
|
||||
"tags" => "tags", "remove-tag" => "removeTag"}
|
||||
%tags-section{"save" => "tagsSave", "add-new" => "tagsAddNew", "selection-made" => "tagsSelectionMade", "remove-tag" => "removeTag"}
|
||||
%notes-section{"add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", "tag" => "selectedTag"}
|
||||
%editor-section{"note" => "selectedNote", "remove" => "deleteNote", "update-tags" => "updateTagsForNote"}
|
||||
|
||||
|
||||
@@ -22,12 +22,11 @@
|
||||
.sk-app-bar-item-column
|
||||
.sk-sublabel {{ctrl.optionsSubtitle()}}
|
||||
|
||||
.sk-menu-panel.dropdown-menu{"ng-show" => "ctrl.showMenu"}
|
||||
.sk-menu-panel.dropdown-menu#notes-options-menu{"ng-show" => "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 }"}
|
||||
%a.info.sk-h5{"ng-click" => "ctrl.toggleReverseSort()"}
|
||||
{{ctrl.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}}
|
||||
|
||||
%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'"}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
|
||||
.count {{tag.cachedNoteCount}}
|
||||
|
||||
.red.small-text.bold{"ng-show" => "tag.content.conflict_of"} Conflicted Copy
|
||||
.red.small-text.bold{"ng-show" => "tag.errorDecrypting"} Missing Keys
|
||||
.danger.small-text.bold{"ng-show" => "tag.content.conflict_of"} Conflicted Copy
|
||||
.danger.small-text.bold{"ng-show" => "tag.errorDecrypting"} Missing Keys
|
||||
|
||||
.menu{"ng-show" => "ctrl.selectedTag == tag"}
|
||||
%a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-show" => "!ctrl.editingTag"} Rename
|
||||
|
||||
19
circle.yml
19
circle.yml
@@ -1,19 +0,0 @@
|
||||
machine:
|
||||
ruby:
|
||||
version:
|
||||
2.3.1
|
||||
node:
|
||||
version:
|
||||
6.1.0
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g grunt
|
||||
override:
|
||||
- bundle install
|
||||
- npm install
|
||||
- grunt
|
||||
|
||||
test:
|
||||
override:
|
||||
- npm test
|
||||
36
index.html
Normal file
36
index.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<!-- <!DOCTYPE html> -->
|
||||
<html ng-app="app">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||
|
||||
<link href="public/favicon/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"></link>
|
||||
<link href="public/favicon/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"></link>
|
||||
<link href="public/favicon/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"></link>
|
||||
<link href="public/favicon/site.webmanifest" rel="manifest"></link>
|
||||
|
||||
<link color="#5bbad5" href="favicon/safari-pinned-tab.svg" rel="mask-icon"></link>
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<meta ng-bind="title" content="Standard Notes" name="apple-mobile-web-app-title"/>
|
||||
<meta ng-bind="title" content="Standard Notes" name="application-name"/>
|
||||
|
||||
<title>Notes · Standard Notes</title>
|
||||
|
||||
<script>
|
||||
window._default_sf_server = "https://sync.standardnotes.org";
|
||||
window._extensions_manager_location = "public/extensions/extensions-manager/dist/index.html";
|
||||
window._batch_manager_location = "public/extensions/batch-manager/dist/index.min.html";
|
||||
</script>
|
||||
|
||||
<script src="dist/javascripts/compiled.js"></script>
|
||||
<link rel="stylesheet" media="all" href="dist/stylesheets/app.css" debug="false" />
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div ng-controller="HomeCtrl" ng-include="'home.html'"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
28
package-lock.json
generated
28
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.0.8",
|
||||
"version": "3.0.10",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -2337,7 +2337,7 @@
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "2.13.2",
|
||||
"nan": "2.14.0",
|
||||
"node-pre-gyp": "0.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -4273,9 +4273,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz",
|
||||
"integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==",
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
|
||||
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -5601,9 +5601,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"sn-stylekit": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-2.0.14.tgz",
|
||||
"integrity": "sha512-48RqEhxLbvDrEmY+SgiQg8IbeAlf0bjtHk/RAhMNwGc9ZrUgBoxMyxJJgUZeKiyx+dm8POfaxpsgzHBG9Qix+A==",
|
||||
"version": "2.0.15",
|
||||
"resolved": "https://registry.npmjs.org/sn-stylekit/-/sn-stylekit-2.0.15.tgz",
|
||||
"integrity": "sha512-QY8MtnHphyGJ0ne05DEhiPQuacOHT1owxl1UVsCNRaY8EtwH2KByu9MAtWBpMiNqGQEz1NK68VOl1lRfCK25gQ==",
|
||||
"dev": true
|
||||
},
|
||||
"snake-case": {
|
||||
@@ -5724,9 +5724,9 @@
|
||||
}
|
||||
},
|
||||
"snjs": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/snjs/-/snjs-0.2.1.tgz",
|
||||
"integrity": "sha512-Kmzsb3ktBFXNDc62HODbsh+5EL5VklYf8gF/BiwKIorJTfkVqJx0fOKnAs0F4CuADfzGgxHWBV6rFeWPJHsPJg==",
|
||||
"version": "0.2.8",
|
||||
"resolved": "https://registry.npmjs.org/snjs/-/snjs-0.2.8.tgz",
|
||||
"integrity": "sha512-qUPy+9DAQnF/MHnGt9lWndBpaC2EdSgZVf0HnRtMx5pRZ7vpcGeyuvyZwPGnRCwFJZcQDdidRp33/Xewy8wsww==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
@@ -5817,9 +5817,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"standard-file-js": {
|
||||
"version": "0.3.58",
|
||||
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.58.tgz",
|
||||
"integrity": "sha512-xkoC+lDK12WPL2+ckD2Ki8WfTsy8TphvwqLS9OASuY06MHdVh9qGGooYvewjXSQkSQTBDrs7f+0mzEX0Rn2AZw==",
|
||||
"version": "0.3.63",
|
||||
"resolved": "https://registry.npmjs.org/standard-file-js/-/standard-file-js-0.3.63.tgz",
|
||||
"integrity": "sha512-E/+hzccZui5JB6vX4Lk1TAdr73H9Ct39pqeS+cQHlKEoybQ5PBK6j503JuxubNrb968NeW2sbsywh4g3syARHg==",
|
||||
"dev": true
|
||||
},
|
||||
"static-extend": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.0.9",
|
||||
"version": "3.0.10",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,8 +38,8 @@
|
||||
"grunt-shell": "^2.1.0",
|
||||
"mocha": "^5.2.0",
|
||||
"serve-static": "^1.13.2",
|
||||
"sn-stylekit": "2.0.14",
|
||||
"snjs": "0.2.1",
|
||||
"standard-file-js": "0.3.58"
|
||||
"sn-stylekit": "2.0.15",
|
||||
"snjs": "0.2.8",
|
||||
"standard-file-js": "0.3.63"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ export default class Factory {
|
||||
return new SFModelManager();
|
||||
}
|
||||
|
||||
static yesterday() {
|
||||
return new Date(new Date().setDate(new Date().getDate() - 1));
|
||||
}
|
||||
|
||||
static createItemParams() {
|
||||
var params = {
|
||||
uuid: SFJS.crypto.generateUUIDSync(),
|
||||
|
||||
@@ -255,13 +255,7 @@ describe("notes and tags", () => {
|
||||
let note = modelManager.allItemsMatchingTypes(["Note"])[0];
|
||||
let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
|
||||
|
||||
// Usually content_type will be provided by a server response
|
||||
var duplicateParams = _.merge({content_type: "Tag"}, tag);
|
||||
duplicateParams.uuid = null;
|
||||
|
||||
expect(duplicateParams.content_type).to.equal("Tag");
|
||||
var duplicateTag = await modelManager.createConflictedItem(duplicateParams);
|
||||
modelManager.addConflictedItem(duplicateTag, tag);
|
||||
var duplicateTag = await modelManager.duplicateItemAndAddAsConflict(tag);
|
||||
|
||||
expect(tag.uuid).to.not.equal(duplicateTag.uuid);
|
||||
|
||||
@@ -293,15 +287,9 @@ describe("notes and tags", () => {
|
||||
let note = modelManager.allItemsMatchingTypes(["Note"])[0];
|
||||
let tag = modelManager.allItemsMatchingTypes(["Tag"])[0];
|
||||
|
||||
// Usually content_type will be provided by a server response
|
||||
var duplicateParams = _.merge({content_type: "Note"}, note);
|
||||
duplicateParams.uuid = null;
|
||||
|
||||
var duplicateNote = await modelManager.createConflictedItem(duplicateParams);
|
||||
modelManager.addConflictedItem(duplicateNote, note);
|
||||
var duplicateNote = await modelManager.duplicateItemAndAddAsConflict(note);
|
||||
|
||||
expect(note.uuid).to.not.equal(duplicateNote.uuid);
|
||||
|
||||
expect(duplicateNote.tags.length).to.equal(note.tags.length);
|
||||
});
|
||||
|
||||
@@ -579,6 +567,7 @@ describe("syncing", () => {
|
||||
})
|
||||
|
||||
it('duplicating a tag should maintian its relationships', async () => {
|
||||
await syncManager.loadLocalItems();
|
||||
modelManager.handleSignout();
|
||||
let pair = createRelatedNoteTagPair();
|
||||
let noteParams = pair[0];
|
||||
@@ -597,6 +586,7 @@ describe("syncing", () => {
|
||||
expect(modelManager.allItems.length).to.equal(2);
|
||||
|
||||
tag.title = `${Math.random()}`
|
||||
tag.updated_at = Factory.yesterday();
|
||||
tag.setDirty(true);
|
||||
|
||||
expect(note.referencingObjects.length).to.equal(1);
|
||||
|
||||
Reference in New Issue
Block a user