Merge pull request #295 from standardnotes/sfjs-0.3.60

3.0.10
This commit is contained in:
Mo Bitar
2019-06-05 14:30:42 -05:00
committed by GitHub
39 changed files with 365 additions and 306 deletions

View File

@@ -1,3 +0,0 @@
{
"allow_root": true
}

1
.gitignore vendored
View File

@@ -20,6 +20,7 @@
/app/assets/templates/generated/
/node_modules
/dist
/dist/javascripts/app.js
/dist/javascripts/compiled.js
/dist/javascripts/transpiled.js

View File

@@ -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

View File

@@ -1,5 +1,5 @@
angular.module('app')
.constant('appVersion', '3.0.9')
.constant('appVersion', '3.0.10')
;

View File

@@ -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});
})
},
})

View File

@@ -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)

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);
});

View File

@@ -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);

View File

@@ -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)
})

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -12,6 +12,8 @@ class ComponentManager extends SNComponentManager {
platform: getPlatformString()
});
// this.loggingEnabled = true;
this.$compile = $compile;
this.$rootScope = $rootScope;
}

View File

@@ -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(() => {

View File

@@ -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++;
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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",

View File

@@ -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;

View File

@@ -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 */

View File

@@ -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 {

View File

@@ -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

View File

@@ -103,10 +103,6 @@ $screen-md-max: ($screen-lg-min - 1) !default;
padding: 10px !important;
}
.red {
color: red !important;
}
.bold {
font-weight: bold !important;
}

View File

@@ -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

View File

@@ -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()"}

View File

@@ -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'"}

View File

@@ -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"}

View File

@@ -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"}

View File

@@ -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'"}

View File

@@ -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

View File

@@ -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
View 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
View File

@@ -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": {

View File

@@ -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"
}
}

View File

@@ -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(),

View File

@@ -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);