Merge pull request #165 from standardnotes/editor-components
Editor components
This commit is contained in:
@@ -20,32 +20,14 @@ angular.module('app.frontend')
|
|||||||
ctrl.noteDidChange(note, oldNote);
|
ctrl.noteDidChange(note, oldNote);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
scope.$watch('ctrl.note.text', function(newText){
|
|
||||||
if(!ctrl.note) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore this change if it originated from here
|
|
||||||
if(ctrl.changingTextFromEditor) {
|
|
||||||
ctrl.changingTextFromEditor = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl.postNoteToExternalEditor(ctrl.note);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager, editorManager, themeManager, componentManager, storageManager) {
|
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, extensionManager, syncManager, modelManager, themeManager, componentManager, storageManager) {
|
||||||
|
|
||||||
this.componentManager = componentManager;
|
this.componentManager = componentManager;
|
||||||
this.componentStack = [];
|
this.componentStack = [];
|
||||||
|
|
||||||
$rootScope.$on("theme-changed", function(){
|
|
||||||
this.postThemeToExternalEditor();
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
$rootScope.$on("sync:taking-too-long", function(){
|
$rootScope.$on("sync:taking-too-long", function(){
|
||||||
this.syncTakingTooLong = true;
|
this.syncTakingTooLong = true;
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -58,140 +40,30 @@ angular.module('app.frontend')
|
|||||||
this.loadTagsString();
|
this.loadTagsString();
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack"], activationHandler: function(component){
|
|
||||||
|
|
||||||
if(!component.active) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(component.area === "note-tags") {
|
|
||||||
this.tagsComponent = component;
|
|
||||||
} else {
|
|
||||||
// stack
|
|
||||||
if(!_.find(this.componentStack, component)) {
|
|
||||||
this.componentStack.push(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$timeout(function(){
|
|
||||||
var iframe = componentManager.iframeForComponent(component);
|
|
||||||
if(iframe) {
|
|
||||||
iframe.onload = function() {
|
|
||||||
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
|
||||||
}.bind(this);
|
|
||||||
}
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
}.bind(this), contextRequestHandler: function(component){
|
|
||||||
return this.note;
|
|
||||||
}.bind(this), actionHandler: function(component, action, data){
|
|
||||||
if(action === "set-size") {
|
|
||||||
var setSize = function(element, size) {
|
|
||||||
var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`;
|
|
||||||
var heightString = typeof size.height === 'string' ? size.height : `${data.height}px`;
|
|
||||||
element.setAttribute("style", `width:${widthString}; height:${heightString}; `);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data.type === "content") {
|
|
||||||
var iframe = componentManager.iframeForComponent(component);
|
|
||||||
var width = data.width;
|
|
||||||
var height = data.height;
|
|
||||||
iframe.width = width;
|
|
||||||
iframe.height = height;
|
|
||||||
|
|
||||||
setSize(iframe, data);
|
|
||||||
} else {
|
|
||||||
if(component.area == "note-tags") {
|
|
||||||
var container = document.getElementById("note-tags-component-container");
|
|
||||||
setSize(container, data);
|
|
||||||
} else {
|
|
||||||
var container = document.getElementById("component-" + component.uuid);
|
|
||||||
setSize(container, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if(action === "associate-item") {
|
|
||||||
if(data.item.content_type == "Tag") {
|
|
||||||
var tag = modelManager.findItem(data.item.uuid);
|
|
||||||
this.addTag(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if(action === "deassociate-item") {
|
|
||||||
var tag = modelManager.findItem(data.item.uuid);
|
|
||||||
this.removeTag(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
}.bind(this)});
|
|
||||||
|
|
||||||
window.addEventListener("message", function(event){
|
|
||||||
if(event.data.status) {
|
|
||||||
this.postNoteToExternalEditor();
|
|
||||||
} else if(!event.data.api) {
|
|
||||||
// console.log("Received message", event.data);
|
|
||||||
var id = event.data.id;
|
|
||||||
var text = event.data.text;
|
|
||||||
var data = event.data.data;
|
|
||||||
|
|
||||||
if(this.note.uuid === id) {
|
|
||||||
// to ignore $watch events
|
|
||||||
this.changingTextFromEditor = true;
|
|
||||||
this.note.text = text;
|
|
||||||
if(data) {
|
|
||||||
var changesMade = this.editor.setData(id, data);
|
|
||||||
if(changesMade) {
|
|
||||||
this.editor.setDirty(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.changesMade();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.bind(this), false);
|
|
||||||
|
|
||||||
this.noteDidChange = function(note, oldNote) {
|
this.noteDidChange = function(note, oldNote) {
|
||||||
this.setNote(note, oldNote);
|
this.setNote(note, oldNote);
|
||||||
for(var component of this.componentStack) {
|
this.reloadComponentContext();
|
||||||
componentManager.setEventFlowForComponent(component, component.isActiveForItem(this.note));
|
|
||||||
}
|
|
||||||
componentManager.contextItemDidChangeInArea("note-tags");
|
|
||||||
componentManager.contextItemDidChangeInArea("editor-stack");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setNote = function(note, oldNote) {
|
this.setNote = function(note, oldNote) {
|
||||||
var currentEditor = this.editor;
|
|
||||||
this.editor = null;
|
|
||||||
this.showExtensions = false;
|
this.showExtensions = false;
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
this.loadTagsString();
|
this.loadTagsString();
|
||||||
|
|
||||||
var setEditor = function(editor) {
|
let associatedEditor = this.editorForNote(note);
|
||||||
this.editor = editor;
|
if(this.editorComponent && this.editorComponent != associatedEditor) {
|
||||||
this.postNoteToExternalEditor();
|
// Deactivate old editor
|
||||||
this.noteReady = true;
|
componentManager.deactivateComponent(this.editorComponent);
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
var editor = this.editorForNote(note);
|
|
||||||
if(editor && !editor.systemEditor) {
|
|
||||||
// setting note to not ready will remove the editor from view in a flash,
|
|
||||||
// so we only want to do this if switching between external editors
|
|
||||||
this.noteReady = false;
|
|
||||||
}
|
|
||||||
if(editor) {
|
|
||||||
if(currentEditor !== editor) {
|
|
||||||
// switch after timeout, so that note data isnt posted to current editor
|
|
||||||
$timeout(function(){
|
|
||||||
setEditor(editor);
|
|
||||||
}.bind(this));
|
|
||||||
} else {
|
|
||||||
// switch immediately
|
|
||||||
setEditor(editor);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.editor = null;
|
|
||||||
this.noteReady = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Activate new editor if it's different from the one currently activated
|
||||||
|
if(associatedEditor && associatedEditor != this.editorComponent) {
|
||||||
|
this.enableComponent(associatedEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editorComponent = associatedEditor;
|
||||||
|
|
||||||
|
this.noteReady = true;
|
||||||
|
|
||||||
if(note.safeText().length == 0 && note.dummy) {
|
if(note.safeText().length == 0 && note.dummy) {
|
||||||
this.focusTitle(100);
|
this.focusTitle(100);
|
||||||
@@ -206,62 +78,44 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedEditor = function(editor) {
|
|
||||||
this.showEditorMenu = false;
|
|
||||||
|
|
||||||
if(this.editor && editor !== this.editor) {
|
|
||||||
this.editor.removeItemAsRelationship(this.note);
|
|
||||||
this.editor.setDirty(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.addItemAsRelationship(this.note);
|
|
||||||
editor.setDirty(true);
|
|
||||||
|
|
||||||
syncManager.sync();
|
|
||||||
|
|
||||||
this.editor = editor;
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
this.editorForNote = function(note) {
|
this.editorForNote = function(note) {
|
||||||
var editors = modelManager.itemsForContentType("SN|Editor");
|
let editors = componentManager.componentsForArea("editor-editor");
|
||||||
for(var editor of editors) {
|
for(var editor of editors) {
|
||||||
if(_.includes(editor.notes, note)) {
|
if(editor.isActiveForItem(note)) {
|
||||||
return editor;
|
return editor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _.find(editors, {default: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.postDataToExternalEditor = function(data) {
|
// No editor found for note. Use default editor, if note does not prefer system editor
|
||||||
var externalEditorElement = document.getElementById("editor-iframe");
|
if(!note.getAppDataItem("prefersPlainEditor")) {
|
||||||
if(externalEditorElement) {
|
return editors.filter((e) => {return e.isDefaultEditor()})[0];
|
||||||
externalEditorElement.contentWindow.postMessage(data, '*');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function themeData() {
|
this.selectedEditor = function(editorComponent) {
|
||||||
return {
|
this.showEditorMenu = false;
|
||||||
themes: [themeManager.currentTheme ? themeManager.currentTheme.url : null]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.postThemeToExternalEditor = function() {
|
if(this.editorComponent && this.editorComponent !== editorComponent) {
|
||||||
this.postDataToExternalEditor(themeData())
|
// This disassociates the editor from the note, but the component itself still needs to be deactivated
|
||||||
}
|
this.disableComponentForCurrentItem(this.editorComponent);
|
||||||
|
// Now deactivate the component
|
||||||
this.postNoteToExternalEditor = function() {
|
componentManager.deactivateComponent(this.editorComponent);
|
||||||
if(!this.editor) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = {
|
if(editorComponent) {
|
||||||
text: this.note.text,
|
this.note.setAppDataItem("prefersPlainEditor", false);
|
||||||
data: this.editor.dataForKey(this.note.uuid),
|
this.note.setDirty(true);
|
||||||
id: this.note.uuid,
|
this.enableComponent(editorComponent);
|
||||||
|
this.associateComponentWithCurrentItem(editorComponent);
|
||||||
|
} else {
|
||||||
|
// Note prefers plain editor
|
||||||
|
this.note.setAppDataItem("prefersPlainEditor", true);
|
||||||
|
this.note.setDirty(true);
|
||||||
|
syncManager.sync();
|
||||||
}
|
}
|
||||||
_.merge(data, themeData());
|
|
||||||
this.postDataToExternalEditor(data);
|
this.editorComponent = editorComponent;
|
||||||
}
|
}.bind(this)
|
||||||
|
|
||||||
this.hasAvailableExtensions = function() {
|
this.hasAvailableExtensions = function() {
|
||||||
return extensionManager.extensionsInContextOfItem(this.note).length > 0;
|
return extensionManager.extensionsInContextOfItem(this.note).length > 0;
|
||||||
@@ -295,20 +149,16 @@ angular.module('app.frontend')
|
|||||||
if(success) {
|
if(success) {
|
||||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||||
statusTimeout = $timeout(function(){
|
statusTimeout = $timeout(function(){
|
||||||
var status = "All changes saved";
|
|
||||||
if(authManager.offline()) {
|
|
||||||
status += " (offline)";
|
|
||||||
}
|
|
||||||
this.saveError = false;
|
this.saveError = false;
|
||||||
this.syncTakingTooLong = false;
|
this.syncTakingTooLong = false;
|
||||||
this.noteStatus = $sce.trustAsHtml(status);
|
this.showAllChangesSavedStatus();
|
||||||
}.bind(this), 200)
|
}.bind(this), 200)
|
||||||
} else {
|
} else {
|
||||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||||
statusTimeout = $timeout(function(){
|
statusTimeout = $timeout(function(){
|
||||||
this.saveError = true;
|
this.saveError = true;
|
||||||
this.syncTakingTooLong = false;
|
this.syncTakingTooLong = false;
|
||||||
this.noteStatus = $sce.trustAsHtml("Error syncing<br>(changes saved offline)")
|
this.showErrorStatus();
|
||||||
}.bind(this), 200)
|
}.bind(this), 200)
|
||||||
}
|
}
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
@@ -328,11 +178,26 @@ angular.module('app.frontend')
|
|||||||
if(saveTimeout) $timeout.cancel(saveTimeout);
|
if(saveTimeout) $timeout.cancel(saveTimeout);
|
||||||
if(statusTimeout) $timeout.cancel(statusTimeout);
|
if(statusTimeout) $timeout.cancel(statusTimeout);
|
||||||
saveTimeout = $timeout(function(){
|
saveTimeout = $timeout(function(){
|
||||||
this.noteStatus = $sce.trustAsHtml("Saving...");
|
this.showSavingStatus();
|
||||||
this.saveNote();
|
this.saveNote();
|
||||||
}.bind(this), 275)
|
}.bind(this), 275)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.showSavingStatus = function() {
|
||||||
|
this.noteStatus = $sce.trustAsHtml("Saving...");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showAllChangesSavedStatus = function() {
|
||||||
|
var status = "All changes saved";
|
||||||
|
if(authManager.offline()) {
|
||||||
|
status += " (offline)";
|
||||||
|
}
|
||||||
|
this.noteStatus = $sce.trustAsHtml(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showErrorStatus = function() {
|
||||||
|
this.noteStatus = $sce.trustAsHtml("Error syncing<br>(changes saved offline)")
|
||||||
|
}
|
||||||
|
|
||||||
this.contentChanged = function() {
|
this.contentChanged = function() {
|
||||||
this.changesMade();
|
this.changesMade();
|
||||||
@@ -388,7 +253,6 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.clickedEditNote = function() {
|
this.clickedEditNote = function() {
|
||||||
this.editorMode = 'edit';
|
|
||||||
this.focusEditor(100);
|
this.focusEditor(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,18 +318,131 @@ angular.module('app.frontend')
|
|||||||
Components
|
Components
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let alertKey = "displayed-component-disable-alert";
|
componentManager.registerHandler({identifier: "editor", areas: ["note-tags", "editor-stack", "editor-editor"], activationHandler: function(component){
|
||||||
|
|
||||||
this.disableComponent = function(component) {
|
if(component.area === "note-tags") {
|
||||||
componentManager.disableComponentForItem(component, this.note);
|
// Autocomplete Tags
|
||||||
componentManager.setEventFlowForComponent(component, false);
|
this.tagsComponent = component.active ? component : null;
|
||||||
if(!storageManager.getItem(alertKey)) {
|
} else if(component.area == "editor-stack") {
|
||||||
|
// Stack
|
||||||
|
if(component.active) {
|
||||||
|
if(!_.find(this.componentStack, component)) {
|
||||||
|
this.componentStack.push(component);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_.pull(this.componentStack, component);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Editor
|
||||||
|
if(component.active && this.note && component.isActiveForItem(this.note)) {
|
||||||
|
this.editorComponent = component;
|
||||||
|
} else {
|
||||||
|
this.editorComponent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(component.active) {
|
||||||
|
$timeout(function(){
|
||||||
|
var iframe = componentManager.iframeForComponent(component);
|
||||||
|
if(iframe) {
|
||||||
|
iframe.onload = function() {
|
||||||
|
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
||||||
|
}.bind(this);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
}.bind(this), contextRequestHandler: function(component){
|
||||||
|
return this.note;
|
||||||
|
}.bind(this), actionHandler: function(component, action, data){
|
||||||
|
if(action === "set-size") {
|
||||||
|
var setSize = function(element, size) {
|
||||||
|
var widthString = typeof size.width === 'string' ? size.width : `${data.width}px`;
|
||||||
|
var heightString = typeof size.height === 'string' ? size.height : `${data.height}px`;
|
||||||
|
element.setAttribute("style", `width:${widthString}; height:${heightString}; `);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.type === "content") {
|
||||||
|
var iframe = componentManager.iframeForComponent(component);
|
||||||
|
var width = data.width;
|
||||||
|
var height = data.height;
|
||||||
|
iframe.width = width;
|
||||||
|
iframe.height = height;
|
||||||
|
|
||||||
|
setSize(iframe, data);
|
||||||
|
} else {
|
||||||
|
if(component.area == "note-tags") {
|
||||||
|
var container = document.getElementById("note-tags-component-container");
|
||||||
|
setSize(container, data);
|
||||||
|
} else {
|
||||||
|
var container = document.getElementById("component-" + component.uuid);
|
||||||
|
setSize(container, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(action === "associate-item") {
|
||||||
|
if(data.item.content_type == "Tag") {
|
||||||
|
var tag = modelManager.findItem(data.item.uuid);
|
||||||
|
this.addTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(action === "deassociate-item") {
|
||||||
|
var tag = modelManager.findItem(data.item.uuid);
|
||||||
|
this.removeTag(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(action === "save-items" || action === "save-success" || action == "save-error") {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.bind(this)});
|
||||||
|
|
||||||
|
this.reloadComponentContext = function() {
|
||||||
|
for(var component of this.componentStack) {
|
||||||
|
componentManager.setEventFlowForComponent(component, component.isActiveForItem(this.note));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentManager.contextItemDidChangeInArea("note-tags");
|
||||||
|
componentManager.contextItemDidChangeInArea("editor-stack");
|
||||||
|
componentManager.contextItemDidChangeInArea("editor-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enableComponent = function(component) {
|
||||||
|
componentManager.activateComponent(component);
|
||||||
|
componentManager.setEventFlowForComponent(component, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.associateComponentWithCurrentItem = function(component) {
|
||||||
|
componentManager.associateComponentWithItem(component, this.note);
|
||||||
|
}
|
||||||
|
|
||||||
|
let alertKey = "displayed-component-disable-alert";
|
||||||
|
this.disableComponentForCurrentItem = function(component, showAlert) {
|
||||||
|
componentManager.disassociateComponentWithItem(component, this.note);
|
||||||
|
componentManager.setEventFlowForComponent(component, 0);
|
||||||
|
if(showAlert && !storageManager.getItem(alertKey)) {
|
||||||
alert("This component will be disabled for this note. You can re-enable this component in the 'Menu' of the editor pane.");
|
alert("This component will be disabled for this note. You can re-enable this component in the 'Menu' of the editor pane.");
|
||||||
storageManager.setItem(alertKey, true);
|
storageManager.setItem(alertKey, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasDisabledComponents = function() {
|
this.hasDisabledStackComponents = function() {
|
||||||
for(var component of this.componentStack) {
|
for(var component of this.componentStack) {
|
||||||
if(component.ignoreEvents) {
|
if(component.ignoreEvents) {
|
||||||
return true;
|
return true;
|
||||||
@@ -475,7 +452,7 @@ angular.module('app.frontend')
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.restoreDisabledComponents = function() {
|
this.restoreDisabledStackComponents = function() {
|
||||||
var relevantComponents = this.componentStack.filter(function(component){
|
var relevantComponents = this.componentStack.filter(function(component){
|
||||||
return component.ignoreEvents;
|
return component.ignoreEvents;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
angular.module('app.frontend')
|
angular.module('app.frontend')
|
||||||
.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
|
.controller('HomeCtrl', function ($scope, $location, $rootScope, $timeout, modelManager,
|
||||||
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager) {
|
dbManager, syncManager, authManager, themeManager, passcodeManager, storageManager, migrationManager) {
|
||||||
|
|
||||||
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
|
storageManager.initialize(passcodeManager.hasPasscode(), authManager.isEphemeralSession());
|
||||||
|
|
||||||
|
|||||||
@@ -49,9 +49,11 @@ angular.module('app.frontend')
|
|||||||
this.selectFirstNote(false);
|
this.selectFirstNote(false);
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
|
|
||||||
this.notesToDisplay = 20;
|
this.DefaultNotesToDisplayValue = 20;
|
||||||
|
|
||||||
|
this.notesToDisplay = this.DefaultNotesToDisplayValue;
|
||||||
this.paginate = function() {
|
this.paginate = function() {
|
||||||
this.notesToDisplay += 20
|
this.notesToDisplay += this.DefaultNotesToDisplayValue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.optionsSubtitle = function() {
|
this.optionsSubtitle = function() {
|
||||||
@@ -77,6 +79,14 @@ angular.module('app.frontend')
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.tagDidChange = function(tag, oldTag) {
|
this.tagDidChange = function(tag, oldTag) {
|
||||||
|
var scrollable = document.getElementById("notes-scrollable");
|
||||||
|
if(scrollable) {
|
||||||
|
scrollable.scrollTop = 0;
|
||||||
|
scrollable.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.notesToDisplay = this.DefaultNotesToDisplayValue;
|
||||||
|
|
||||||
this.showMenu = false;
|
this.showMenu = false;
|
||||||
|
|
||||||
if(this.selectedNote && this.selectedNote.dummy) {
|
if(this.selectedNote && this.selectedNote.dummy) {
|
||||||
|
|||||||
@@ -151,17 +151,17 @@ class Item {
|
|||||||
App Data
|
App Data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setAppDataItem(key, value) {
|
setDomainDataItem(key, value, domain) {
|
||||||
var data = this.appData[AppDomain];
|
var data = this.appData[domain];
|
||||||
if(!data) {
|
if(!data) {
|
||||||
data = {}
|
data = {}
|
||||||
}
|
}
|
||||||
data[key] = value;
|
data[key] = value;
|
||||||
this.appData[AppDomain] = data;
|
this.appData[domain] = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppDataItem(key) {
|
getDomainDataItem(key, domain) {
|
||||||
var data = this.appData[AppDomain];
|
var data = this.appData[domain];
|
||||||
if(data) {
|
if(data) {
|
||||||
return data[key];
|
return data[key];
|
||||||
} else {
|
} else {
|
||||||
@@ -169,6 +169,14 @@ class Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setAppDataItem(key, value) {
|
||||||
|
this.setDomainDataItem(key, value, AppDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAppDataItem(key) {
|
||||||
|
return this.getDomainDataItem(key, AppDomain);
|
||||||
|
}
|
||||||
|
|
||||||
get pinned() {
|
get pinned() {
|
||||||
return this.getAppDataItem("pinned");
|
return this.getAppDataItem("pinned");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ class Component extends Item {
|
|||||||
if(!this.disassociatedItemIds) {
|
if(!this.disassociatedItemIds) {
|
||||||
this.disassociatedItemIds = [];
|
this.disassociatedItemIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!this.associatedItemIds) {
|
||||||
|
this.associatedItemIds = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapContentToLocalProperties(content) {
|
mapContentToLocalProperties(content) {
|
||||||
@@ -28,6 +32,9 @@ class Component extends Item {
|
|||||||
|
|
||||||
// items that have requested a component to be disabled in its context
|
// items that have requested a component to be disabled in its context
|
||||||
this.disassociatedItemIds = content.disassociatedItemIds || [];
|
this.disassociatedItemIds = content.disassociatedItemIds || [];
|
||||||
|
|
||||||
|
// items that have requested a component to be enabled in its context
|
||||||
|
this.associatedItemIds = content.associatedItemIds || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
structureParams() {
|
structureParams() {
|
||||||
@@ -38,7 +45,8 @@ class Component extends Item {
|
|||||||
permissions: this.permissions,
|
permissions: this.permissions,
|
||||||
active: this.active,
|
active: this.active,
|
||||||
componentData: this.componentData,
|
componentData: this.componentData,
|
||||||
disassociatedItemIds: this.disassociatedItemIds
|
disassociatedItemIds: this.disassociatedItemIds,
|
||||||
|
associatedItemIds: this.associatedItemIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
_.merge(params, super.structureParams());
|
_.merge(params, super.structureParams());
|
||||||
@@ -53,7 +61,36 @@ class Component extends Item {
|
|||||||
return "SN|Component";
|
return "SN|Component";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEditor() {
|
||||||
|
return this.area == "editor-editor";
|
||||||
|
}
|
||||||
|
|
||||||
|
isDefaultEditor() {
|
||||||
|
return this.getAppDataItem("defaultEditor") == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
An associative component depends on being explicitly activated for a given item, compared to a dissaciative component,
|
||||||
|
which is enabled by default in areas unrelated to a certain item.
|
||||||
|
*/
|
||||||
|
static associativeAreas() {
|
||||||
|
return ["editor-editor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
isAssociative() {
|
||||||
|
return Component.associativeAreas().includes(this.area);
|
||||||
|
}
|
||||||
|
|
||||||
|
associateWithItem(item) {
|
||||||
|
this.associatedItemIds.push(item.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
isActiveForItem(item) {
|
isActiveForItem(item) {
|
||||||
return this.disassociatedItemIds.indexOf(item.uuid) === -1;
|
if(this.isAssociative()) {
|
||||||
|
return this.associatedItemIds.indexOf(item.uuid) !== -1;
|
||||||
|
} else {
|
||||||
|
return this.disassociatedItemIds.indexOf(item.uuid) === -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/* This domain will be used to save context item client data */
|
||||||
|
let ClientDataDomain = "org.standardnotes.sn.components";
|
||||||
|
|
||||||
class ComponentManager {
|
class ComponentManager {
|
||||||
|
|
||||||
constructor($rootScope, modelManager, syncManager, themeManager, $timeout, $compile) {
|
constructor($rootScope, modelManager, syncManager, themeManager, $timeout, $compile) {
|
||||||
@@ -28,7 +31,15 @@ class ComponentManager {
|
|||||||
this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data);
|
this.handleMessage(this.componentForSessionKey(event.data.sessionKey), event.data);
|
||||||
}.bind(this), false);
|
}.bind(this), false);
|
||||||
|
|
||||||
this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems) {
|
this.modelManager.addItemSyncObserver("component-manager", "*", function(allItems, validItems, deletedItems, source) {
|
||||||
|
|
||||||
|
/* If the source of these new or updated items is from a Component itself saving items, we don't need to notify
|
||||||
|
components again of the same item. Regarding notifying other components than the issuing component, other mapping sources
|
||||||
|
will take care of that, like ModelManager.MappingSourceRemoteSaved
|
||||||
|
*/
|
||||||
|
if(source == ModelManager.MappingSourceComponentRetrieved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" });
|
var syncedComponents = allItems.filter(function(item){return item.content_type === "SN|Component" });
|
||||||
for(var component of syncedComponents) {
|
for(var component of syncedComponents) {
|
||||||
@@ -62,23 +73,23 @@ class ComponentManager {
|
|||||||
name: "stream-context-item"
|
name: "stream-context-item"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
for(let observer of this.contextStreamObservers) {
|
for(let observer of this.contextStreamObservers) {
|
||||||
this.runWithPermissions(observer.component, requiredContextPermissions, observer.originalMessage.permissions, function(){
|
this.runWithPermissions(observer.component, requiredContextPermissions, observer.originalMessage.permissions, function(){
|
||||||
for(let handler of this.handlers) {
|
for(let handler of this.handlers) {
|
||||||
if(handler.areas.includes(observer.component.area) === false) {
|
if(!handler.areas.includes(observer.component.area)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var itemInContext = handler.contextRequestHandler(observer.component);
|
var itemInContext = handler.contextRequestHandler(observer.component);
|
||||||
if(itemInContext) {
|
if(itemInContext) {
|
||||||
var matchingItem = _.find(allItems, {uuid: itemInContext.uuid});
|
var matchingItem = _.find(allItems, {uuid: itemInContext.uuid});
|
||||||
if(matchingItem) {
|
if(matchingItem) {
|
||||||
this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage);
|
this.sendContextItemInReply(observer.component, matchingItem, observer.originalMessage, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,24 +126,37 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonForItem(item) {
|
jsonForItem(item, component, source) {
|
||||||
var params = {uuid: item.uuid, content_type: item.content_type, created_at: item.created_at, updated_at: item.updated_at, deleted: item.deleted};
|
var params = {uuid: item.uuid, content_type: item.content_type, created_at: item.created_at, updated_at: item.updated_at, deleted: item.deleted};
|
||||||
params.content = item.createContentJSONFromProperties();
|
params.content = item.createContentJSONFromProperties();
|
||||||
|
params.clientData = item.getDomainDataItem(component.url, ClientDataDomain) || {};
|
||||||
|
|
||||||
|
/* This means the this function is being triggered through a remote Saving response, which should not update
|
||||||
|
actual local content values. The reason is, Save responses may be delayed, and a user may have changed some values
|
||||||
|
in between the Save was initiated, and the time it completes. So we only want to update actual content values (and not just metadata)
|
||||||
|
when its another source, like ModelManager.MappingSourceRemoteRetrieved.
|
||||||
|
*/
|
||||||
|
if(source && source == ModelManager.MappingSourceRemoteSaved) {
|
||||||
|
params.isMetadataUpdate = true;
|
||||||
|
}
|
||||||
|
this.removePrivatePropertiesFromResponseItems([params]);
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendItemsInReply(component, items, message) {
|
sendItemsInReply(component, items, message, source) {
|
||||||
|
if(this.loggingEnabled) {console.log("Web|componentManager|sendItemsInReply", component, items, message)};
|
||||||
var response = {items: {}};
|
var response = {items: {}};
|
||||||
var mapped = items.map(function(item) {
|
var mapped = items.map(function(item) {
|
||||||
return this.jsonForItem(item);
|
return this.jsonForItem(item, component, source);
|
||||||
}.bind(this));
|
}.bind(this));
|
||||||
|
|
||||||
response.items = mapped;
|
response.items = mapped;
|
||||||
this.replyToMessage(component, message, response);
|
this.replyToMessage(component, message, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendContextItemInReply(component, item, originalMessage) {
|
sendContextItemInReply(component, item, originalMessage, source) {
|
||||||
var response = {item: this.jsonForItem(item)};
|
if(this.loggingEnabled) {console.log("Web|componentManager|sendContextItemInReply", component, item, originalMessage)};
|
||||||
|
var response = {item: this.jsonForItem(item, component, source)};
|
||||||
this.replyToMessage(component, originalMessage, response);
|
this.replyToMessage(component, originalMessage, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +164,18 @@ class ComponentManager {
|
|||||||
return this.modelManager.itemsForContentType("SN|Component");
|
return this.modelManager.itemsForContentType("SN|Component");
|
||||||
}
|
}
|
||||||
|
|
||||||
componentsForStack(stack) {
|
componentsForArea(area) {
|
||||||
return this.components.filter(function(component){
|
return this.components.filter(function(component){
|
||||||
return component.area === stack;
|
return component.area === area;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentForUrl(url) {
|
||||||
|
return this.components.filter(function(component){
|
||||||
|
return component.url === url;
|
||||||
|
})[0];
|
||||||
|
}
|
||||||
|
|
||||||
componentForSessionKey(key) {
|
componentForSessionKey(key) {
|
||||||
return _.find(this.components, {sessionKey: key});
|
return _.find(this.components, {sessionKey: key});
|
||||||
}
|
}
|
||||||
@@ -172,6 +202,8 @@ class ComponentManager {
|
|||||||
create-item
|
create-item
|
||||||
delete-items
|
delete-items
|
||||||
set-component-data
|
set-component-data
|
||||||
|
save-context-client-data
|
||||||
|
get-context-client-data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if(message.action === "stream-items") {
|
if(message.action === "stream-items") {
|
||||||
@@ -202,24 +234,44 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
else if(message.action === "create-item") {
|
else if(message.action === "create-item") {
|
||||||
var item = this.modelManager.createItem(message.data.item);
|
var responseItem = message.data.item;
|
||||||
|
this.removePrivatePropertiesFromResponseItems([responseItem]);
|
||||||
|
var item = this.modelManager.createItem(responseItem);
|
||||||
|
if(responseItem.clientData) {
|
||||||
|
item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain);
|
||||||
|
}
|
||||||
this.modelManager.addItem(item);
|
this.modelManager.addItem(item);
|
||||||
this.modelManager.resolveReferencesForItem(item);
|
this.modelManager.resolveReferencesForItem(item);
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
this.syncManager.sync();
|
this.syncManager.sync();
|
||||||
this.replyToMessage(component, message, {item: this.jsonForItem(item)})
|
this.replyToMessage(component, message, {item: this.jsonForItem(item, component)})
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(message.action === "save-items") {
|
else if(message.action === "save-items") {
|
||||||
var responseItems = message.data.items;
|
var responseItems = message.data.items;
|
||||||
var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems);
|
|
||||||
|
this.removePrivatePropertiesFromResponseItems(responseItems);
|
||||||
|
|
||||||
|
/*
|
||||||
|
We map the items here because modelManager is what updates the UI. If you were to instead get the items directly,
|
||||||
|
this would update them server side via sync, but would never make its way back to the UI.
|
||||||
|
*/
|
||||||
|
var localItems = this.modelManager.mapResponseItemsToLocalModels(responseItems, ModelManager.MappingSourceComponentRetrieved);
|
||||||
|
|
||||||
for(var item of localItems) {
|
for(var item of localItems) {
|
||||||
var responseItem = _.find(responseItems, {uuid: item.uuid});
|
var responseItem = _.find(responseItems, {uuid: item.uuid});
|
||||||
_.merge(item.content, responseItem.content);
|
_.merge(item.content, responseItem.content);
|
||||||
|
if(responseItem.clientData) {
|
||||||
|
item.setDomainDataItem(component.url, responseItem.clientData, ClientDataDomain);
|
||||||
|
}
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
}
|
}
|
||||||
this.syncManager.sync();
|
this.syncManager.sync((response) => {
|
||||||
|
// Allow handlers to be notified when a save begins and ends, to update the UI
|
||||||
|
var saveMessage = Object.assign({}, message);
|
||||||
|
saveMessage.action = response && response.error ? "save-error" : "save-success";
|
||||||
|
this.handleMessage(component, saveMessage);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let handler of this.handlers) {
|
for(let handler of this.handlers) {
|
||||||
@@ -231,6 +283,21 @@ class ComponentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removePrivatePropertiesFromResponseItems(responseItems) {
|
||||||
|
// Don't allow component to overwrite these properties.
|
||||||
|
let privateProperties = ["appData"];
|
||||||
|
for(var responseItem of responseItems) {
|
||||||
|
|
||||||
|
// Do not pass in actual items here, otherwise that would be destructive.
|
||||||
|
// Instead, generic JS/JSON objects should be passed.
|
||||||
|
console.assert(typeof responseItem.setDirty !== 'function');
|
||||||
|
|
||||||
|
for(var prop of privateProperties) {
|
||||||
|
delete responseItem[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleStreamItemsMessage(component, message) {
|
handleStreamItemsMessage(component, message) {
|
||||||
var requiredPermissions = [
|
var requiredPermissions = [
|
||||||
{
|
{
|
||||||
@@ -378,10 +445,13 @@ class ComponentManager {
|
|||||||
sendMessageToComponent(component, message) {
|
sendMessageToComponent(component, message) {
|
||||||
if(component.ignoreEvents && message.action !== "component-registered") {
|
if(component.ignoreEvents && message.action !== "component-registered") {
|
||||||
if(this.loggingEnabled) {
|
if(this.loggingEnabled) {
|
||||||
console.log("Component disabled for current item, not sending any messages.");
|
console.log("Component disabled for current item, not sending any messages.", component.name);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if(this.loggingEnabled) {
|
||||||
|
console.log("Web|sendMessageToComponent", component, message);
|
||||||
|
}
|
||||||
component.window.postMessage(message, "*");
|
component.window.postMessage(message, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -415,7 +485,9 @@ class ComponentManager {
|
|||||||
this.syncManager.sync();
|
this.syncManager.sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeComponents.push(component);
|
if(!this.activeComponents.includes(component)) {
|
||||||
|
this.activeComponents.push(component);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerHandler(handler) {
|
registerHandler(handler) {
|
||||||
@@ -424,6 +496,15 @@ class ComponentManager {
|
|||||||
|
|
||||||
// Called by other views when the iframe is ready
|
// Called by other views when the iframe is ready
|
||||||
registerComponentWindow(component, componentWindow) {
|
registerComponentWindow(component, componentWindow) {
|
||||||
|
if(component.window === componentWindow) {
|
||||||
|
if(this.loggingEnabled) {
|
||||||
|
console.log("Web|componentManager", "attempting to re-register same component window.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.loggingEnabled) {
|
||||||
|
console.log("Web|componentManager|registerComponentWindow", component);
|
||||||
|
}
|
||||||
component.window = componentWindow;
|
component.window = componentWindow;
|
||||||
component.sessionKey = Neeto.crypto.generateUUID();
|
component.sessionKey = Neeto.crypto.generateUUID();
|
||||||
this.sendMessageToComponent(component, {action: "component-registered", sessionKey: component.sessionKey, componentData: component.componentData});
|
this.sendMessageToComponent(component, {action: "component-registered", sessionKey: component.sessionKey, componentData: component.componentData});
|
||||||
@@ -466,11 +547,28 @@ class ComponentManager {
|
|||||||
return component.active;
|
return component.active;
|
||||||
}
|
}
|
||||||
|
|
||||||
disableComponentForItem(component, item) {
|
disassociateComponentWithItem(component, item) {
|
||||||
|
_.pull(component.associatedItemIds, item.uuid);
|
||||||
|
|
||||||
if(component.disassociatedItemIds.indexOf(item.uuid) !== -1) {
|
if(component.disassociatedItemIds.indexOf(item.uuid) !== -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
component.disassociatedItemIds.push(item.uuid);
|
component.disassociatedItemIds.push(item.uuid);
|
||||||
|
|
||||||
|
component.setDirty(true);
|
||||||
|
this.syncManager.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
associateComponentWithItem(component, item) {
|
||||||
|
_.pull(component.disassociatedItemIds, item.uuid);
|
||||||
|
|
||||||
|
if(component.associatedItemIds.includes(item.uuid)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.associatedItemIds.push(item.uuid);
|
||||||
|
|
||||||
component.setDirty(true);
|
component.setDirty(true);
|
||||||
this.syncManager.sync();
|
this.syncManager.sync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ class AccountMenu {
|
|||||||
|
|
||||||
$scope.importJSONData = function(data, password, callback) {
|
$scope.importJSONData = function(data, password, callback) {
|
||||||
var onDataReady = function(errorCount) {
|
var onDataReady = function(errorCount) {
|
||||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
var items = modelManager.mapResponseItemsToLocalModels(data.items, ModelManager.MappingSourceFileImport);
|
||||||
items.forEach(function(item){
|
items.forEach(function(item){
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
item.deleted = false;
|
item.deleted = false;
|
||||||
|
|||||||
@@ -9,14 +9,17 @@ class EditorMenu {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
controller($scope, editorManager) {
|
controller($scope, componentManager) {
|
||||||
'ngInject';
|
'ngInject';
|
||||||
|
|
||||||
$scope.formData = {};
|
$scope.formData = {};
|
||||||
$scope.editorManager = editorManager;
|
|
||||||
|
$scope.editors = componentManager.componentsForArea("editor-editor");
|
||||||
|
|
||||||
$scope.selectEditor = function($event, editor) {
|
$scope.selectEditor = function($event, editor) {
|
||||||
editor.conflict_of = null; // clear conflict if applicable
|
if(editor) {
|
||||||
|
editor.conflict_of = null; // clear conflict if applicable
|
||||||
|
}
|
||||||
$scope.callback()(editor);
|
$scope.callback()(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ class GlobalExtensionsMenu {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
controller($scope, extensionManager, syncManager, modelManager, themeManager, editorManager, componentManager) {
|
controller($scope, extensionManager, syncManager, modelManager, themeManager, componentManager) {
|
||||||
'ngInject';
|
'ngInject';
|
||||||
|
|
||||||
$scope.formData = {};
|
$scope.formData = {};
|
||||||
|
|
||||||
$scope.extensionManager = extensionManager;
|
$scope.extensionManager = extensionManager;
|
||||||
$scope.themeManager = themeManager;
|
$scope.themeManager = themeManager;
|
||||||
$scope.editorManager = editorManager;
|
|
||||||
$scope.componentManager = componentManager;
|
$scope.componentManager = componentManager;
|
||||||
|
|
||||||
$scope.serverExtensions = modelManager.itemsForContentType("SF|Extension");
|
$scope.serverExtensions = modelManager.itemsForContentType("SF|Extension");
|
||||||
@@ -120,23 +119,6 @@ class GlobalExtensionsMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Editors
|
|
||||||
|
|
||||||
$scope.deleteEditor = function(editor) {
|
|
||||||
if(confirm("Are you sure you want to delete this editor?")) {
|
|
||||||
editorManager.deleteEditor(editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.setDefaultEditor = function(editor) {
|
|
||||||
editorManager.setDefaultEditor(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.removeDefaultEditor = function(editor) {
|
|
||||||
editorManager.removeDefaultEditor(editor);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
||||||
$scope.revokePermissions = function(component) {
|
$scope.revokePermissions = function(component) {
|
||||||
@@ -151,6 +133,23 @@ class GlobalExtensionsMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$scope.makeEditorDefault = function(component) {
|
||||||
|
var currentDefault = componentManager.componentsForArea("editor-editor").filter((e) => {return e.isDefaultEditor()})[0];
|
||||||
|
if(currentDefault) {
|
||||||
|
currentDefault.setAppDataItem("defaultEditor", false);
|
||||||
|
currentDefault.setDirty(true);
|
||||||
|
}
|
||||||
|
component.setAppDataItem("defaultEditor", true);
|
||||||
|
component.setDirty(true);
|
||||||
|
syncManager.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.removeEditorDefault = function(component) {
|
||||||
|
component.setAppDataItem("defaultEditor", false);
|
||||||
|
component.setDirty(true);
|
||||||
|
syncManager.sync();
|
||||||
|
}
|
||||||
|
|
||||||
// Installation
|
// Installation
|
||||||
|
|
||||||
$scope.submitInstallLink = function() {
|
$scope.submitInstallLink = function() {
|
||||||
@@ -219,11 +218,6 @@ class GlobalExtensionsMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.handleEditorLink = function(link, completion) {
|
|
||||||
editorManager.addNewEditorFromURL(link);
|
|
||||||
completion();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ class PermissionsModal {
|
|||||||
} else if(permission.name === "stream-context-item") {
|
} else if(permission.name === "stream-context-item") {
|
||||||
var mapping = {
|
var mapping = {
|
||||||
"editor-stack" : "working note",
|
"editor-stack" : "working note",
|
||||||
"note-tags" : "working note"
|
"note-tags" : "working note",
|
||||||
|
"editor-editor": "working note"
|
||||||
}
|
}
|
||||||
return "Access to " + mapping[$scope.component.area];
|
return "Access to " + mapping[$scope.component.area];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
class EditorManager {
|
|
||||||
|
|
||||||
constructor($rootScope, modelManager, syncManager) {
|
|
||||||
this.syncManager = syncManager;
|
|
||||||
this.modelManager = modelManager;
|
|
||||||
|
|
||||||
this.editorType = "SN|Editor";
|
|
||||||
this._systemEditor = {
|
|
||||||
systemEditor: true,
|
|
||||||
name: "Plain"
|
|
||||||
}
|
|
||||||
|
|
||||||
$rootScope.$on("sync:completed", function(){
|
|
||||||
// we want to wait for sync completion before creating a syncable system editor
|
|
||||||
// we need to sync the system editor so that we can assign note preferences to it
|
|
||||||
// that is, when a user selects Plain for a note, we need to remember that
|
|
||||||
if(this.systemEditor.uuid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var liveSysEditor = _.find(this.allEditors, {systemEditor: true});
|
|
||||||
if(liveSysEditor) {
|
|
||||||
this._systemEditor = liveSysEditor;
|
|
||||||
} else {
|
|
||||||
this._systemEditor = modelManager.createItem({
|
|
||||||
content_type: this.editorType,
|
|
||||||
systemEditor: true,
|
|
||||||
name: "Plain"
|
|
||||||
})
|
|
||||||
modelManager.addItem(this._systemEditor);
|
|
||||||
this._systemEditor.setDirty(true);
|
|
||||||
syncManager.sync();
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
get allEditors() {
|
|
||||||
return this.modelManager.itemsForContentType(this.editorType);
|
|
||||||
}
|
|
||||||
|
|
||||||
get externalEditors() {
|
|
||||||
return this.allEditors.filter(function(editor){
|
|
||||||
return !editor.systemEditor;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
get systemEditors() {
|
|
||||||
return [this.systemEditor];
|
|
||||||
}
|
|
||||||
|
|
||||||
get systemEditor() {
|
|
||||||
return this._systemEditor;
|
|
||||||
}
|
|
||||||
|
|
||||||
get defaultEditor() {
|
|
||||||
return _.find(this.externalEditors, {default: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
editorForUrl(url) {
|
|
||||||
return this.externalEditors.filter(function(editor){return editor.url == url})[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
setDefaultEditor(editor) {
|
|
||||||
var defaultEditor = this.defaultEditor;
|
|
||||||
if(defaultEditor) {
|
|
||||||
defaultEditor.default = false;
|
|
||||||
defaultEditor.setDirty(true);
|
|
||||||
}
|
|
||||||
editor.default = true;
|
|
||||||
editor.setDirty(true);
|
|
||||||
this.syncManager.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeDefaultEditor(editor) {
|
|
||||||
editor.default = false;
|
|
||||||
editor.setDirty(true);
|
|
||||||
this.syncManager.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
addNewEditorFromURL(url) {
|
|
||||||
var name = getParameterByName("name", url);
|
|
||||||
var editor = this.modelManager.createItem({
|
|
||||||
content_type: this.editorType,
|
|
||||||
url: url,
|
|
||||||
name: name
|
|
||||||
})
|
|
||||||
|
|
||||||
this.modelManager.addItem(editor);
|
|
||||||
editor.setDirty(true);
|
|
||||||
this.syncManager.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteEditor(editor) {
|
|
||||||
this.modelManager.setItemToBeDeleted(editor);
|
|
||||||
this.syncManager.sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
angular.module('app.frontend').service('editorManager', EditorManager);
|
|
||||||
@@ -161,7 +161,7 @@ class ExtensionManager {
|
|||||||
action.error = false;
|
action.error = false;
|
||||||
var items = response.items || [response.item];
|
var items = response.items || [response.item];
|
||||||
EncryptionHelper.decryptMultipleItems(items, this.authManager.keys());
|
EncryptionHelper.decryptMultipleItems(items, this.authManager.keys());
|
||||||
items = this.modelManager.mapResponseItemsToLocalModels(items);
|
items = this.modelManager.mapResponseItemsToLocalModels(items, ModelManager.MappingSourceRemoteActionRetrieved);
|
||||||
for(var item of items) {
|
for(var item of items) {
|
||||||
item.setDirty(true);
|
item.setDirty(true);
|
||||||
}
|
}
|
||||||
|
|||||||
61
app/assets/javascripts/app/services/migrationManager.js
Normal file
61
app/assets/javascripts/app/services/migrationManager.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
class MigrationManager {
|
||||||
|
|
||||||
|
constructor($rootScope, modelManager, syncManager, componentManager) {
|
||||||
|
this.$rootScope = $rootScope;
|
||||||
|
this.modelManager = modelManager;
|
||||||
|
this.syncManager = syncManager;
|
||||||
|
this.componentManager = componentManager;
|
||||||
|
|
||||||
|
this.migrators = [];
|
||||||
|
|
||||||
|
this.addEditorToComponentMigrator();
|
||||||
|
|
||||||
|
this.modelManager.addItemSyncObserver("migration-manager", "*", (allItems, validItems, deletedItems) => {
|
||||||
|
for(var migrator of this.migrators) {
|
||||||
|
var items = allItems.filter((item) => {return item.content_type == migrator.content_type});
|
||||||
|
if(items.length > 0) {
|
||||||
|
migrator.handler(items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Migrate SN|Editor to SN|Component. Editors are deprecated as of November 2017. Editors using old APIs must
|
||||||
|
convert to using the new component API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
addEditorToComponentMigrator() {
|
||||||
|
this.migrators.push({
|
||||||
|
content_type: "SN|Editor",
|
||||||
|
|
||||||
|
handler: (editors) => {
|
||||||
|
// Convert editors to components
|
||||||
|
for(var editor of editors) {
|
||||||
|
// If there's already a component for this url, then skip this editor
|
||||||
|
if(editor.url && !this.componentManager.componentForUrl(editor.url)) {
|
||||||
|
var component = this.modelManager.createItem({
|
||||||
|
content_type: "SN|Component",
|
||||||
|
url: editor.url,
|
||||||
|
name: editor.name,
|
||||||
|
area: "editor-editor"
|
||||||
|
})
|
||||||
|
component.setAppDataItem("data", editor.data);
|
||||||
|
component.setDirty(true);
|
||||||
|
this.modelManager.addItem(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let editor of editors) {
|
||||||
|
this.modelManager.setItemToBeDeleted(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.syncManager.sync();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
angular.module('app.frontend').service('migrationManager', MigrationManager);
|
||||||
@@ -1,6 +1,13 @@
|
|||||||
class ModelManager {
|
class ModelManager {
|
||||||
|
|
||||||
constructor(storageManager) {
|
constructor(storageManager) {
|
||||||
|
ModelManager.MappingSourceRemoteRetrieved = "MappingSourceRemoteRetrieved";
|
||||||
|
ModelManager.MappingSourceRemoteSaved = "MappingSourceRemoteSaved";
|
||||||
|
ModelManager.MappingSourceLocalRetrieved = "MappingSourceLocalRetrieved";
|
||||||
|
ModelManager.MappingSourceComponentRetrieved = "MappingSourceComponentRetrieved";
|
||||||
|
ModelManager.MappingSourceRemoteActionRetrieved = "MappingSourceRemoteActionRetrieved"; /* aciton-based Extensions like note history */
|
||||||
|
ModelManager.MappingSourceFileImport = "MappingSourceFileImport";
|
||||||
|
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
this.notes = [];
|
this.notes = [];
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
@@ -96,11 +103,11 @@ class ModelManager {
|
|||||||
return tag;
|
return tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapResponseItemsToLocalModels(items) {
|
mapResponseItemsToLocalModels(items, source) {
|
||||||
return this.mapResponseItemsToLocalModelsOmittingFields(items, null);
|
return this.mapResponseItemsToLocalModelsOmittingFields(items, null, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapResponseItemsToLocalModelsOmittingFields(items, omitFields) {
|
mapResponseItemsToLocalModelsOmittingFields(items, omitFields, source) {
|
||||||
var models = [], processedObjects = [], modelsToNotifyObserversOf = [];
|
var models = [], processedObjects = [], modelsToNotifyObserversOf = [];
|
||||||
|
|
||||||
// first loop should add and process items
|
// first loop should add and process items
|
||||||
@@ -151,12 +158,12 @@ class ModelManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notifySyncObserversOfModels(modelsToNotifyObserversOf);
|
this.notifySyncObserversOfModels(modelsToNotifyObserversOf, source);
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
notifySyncObserversOfModels(models) {
|
notifySyncObserversOfModels(models, source) {
|
||||||
for(var observer of this.itemSyncObservers) {
|
for(var observer of this.itemSyncObservers) {
|
||||||
var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
var allRelevantItems = models.filter(function(item){return item.content_type == observer.type || observer.type == "*"});
|
||||||
var validItems = [], deletedItems = [];
|
var validItems = [], deletedItems = [];
|
||||||
@@ -169,7 +176,7 @@ class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(allRelevantItems.length > 0) {
|
if(allRelevantItems.length > 0) {
|
||||||
observer.callback(allRelevantItems, validItems, deletedItems);
|
observer.callback(allRelevantItems, validItems, deletedItems, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ class SyncManager {
|
|||||||
|
|
||||||
loadLocalItems(callback) {
|
loadLocalItems(callback) {
|
||||||
var params = this.storageManager.getAllModels(function(items){
|
var params = this.storageManager.getAllModels(function(items){
|
||||||
var items = this.handleItemsResponse(items, null);
|
var items = this.handleItemsResponse(items, null, ModelManager.MappingSourceLocalRetrieved);
|
||||||
Item.sortItemsByDate(items);
|
Item.sortItemsByDate(items);
|
||||||
callback(items);
|
callback(items);
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
@@ -267,7 +267,7 @@ class SyncManager {
|
|||||||
|
|
||||||
// Map retrieved items to local data
|
// Map retrieved items to local data
|
||||||
var retrieved
|
var retrieved
|
||||||
= this.handleItemsResponse(response.retrieved_items, null);
|
= this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved);
|
||||||
|
|
||||||
// Append items to master list of retrieved items for this ongoing sync operation
|
// Append items to master list of retrieved items for this ongoing sync operation
|
||||||
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
|
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
|
||||||
@@ -279,7 +279,7 @@ class SyncManager {
|
|||||||
|
|
||||||
// Map saved items to local data
|
// Map saved items to local data
|
||||||
var saved =
|
var saved =
|
||||||
this.handleItemsResponse(response.saved_items, omitFields);
|
this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved);
|
||||||
|
|
||||||
// Create copies of items or alternate their uuids if neccessary
|
// Create copies of items or alternate their uuids if neccessary
|
||||||
var unsaved = response.unsaved;
|
var unsaved = response.unsaved;
|
||||||
@@ -355,10 +355,10 @@ class SyncManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleItemsResponse(responseItems, omitFields) {
|
handleItemsResponse(responseItems, omitFields, source) {
|
||||||
var keys = this.authManager.keys() || this.passcodeManager.keys();
|
var keys = this.authManager.keys() || this.passcodeManager.keys();
|
||||||
EncryptionHelper.decryptMultipleItems(responseItems, keys);
|
EncryptionHelper.decryptMultipleItems(responseItems, keys);
|
||||||
var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
|
var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields, source);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ h2 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
background-color: #d8d7d9;
|
background-color: #f1f1f1;
|
||||||
|
border-top: 1px solid rgba(black, 0.04);
|
||||||
height: $footer-height;
|
height: $footer-height;
|
||||||
max-height: $footer-height;
|
max-height: $footer-height;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
%ul.dropdown-menu.sectioned-menu
|
%ul.dropdown-menu.sectioned-menu
|
||||||
.header
|
.header
|
||||||
.title System Editors
|
.title System Editor
|
||||||
%ul
|
%ul
|
||||||
%li.menu-item{"ng-repeat" => "editor in editorManager.systemEditors", "ng-click" => "selectEditor($event, editor)"}
|
%li.menu-item{"ng-click" => "selectEditor($event, null)"}
|
||||||
%span.pull-left.mr-10{"ng-if" => "selectedEditor === editor"} ✓
|
%span.pull-left.mr-10{"ng-if" => "selectedEditor == null"} ✓
|
||||||
%label.menu-item-title.pull-left {{editor.name}}
|
%label.menu-item-title.pull-left Plain
|
||||||
|
|
||||||
%div{"ng-if" => "editorManager.externalEditors.length > 0"}
|
%div{"ng-if" => "editors.length > 0"}
|
||||||
.header
|
.header
|
||||||
.title External Editors
|
.title External Editors
|
||||||
.subtitle Can access your current note decrypted.
|
.subtitle Can access your current note decrypted.
|
||||||
%ul
|
%ul
|
||||||
%li.menu-item{"ng-repeat" => "editor in editorManager.externalEditors", "ng-click" => "selectEditor($event, editor)"}
|
%li.menu-item{"ng-repeat" => "editor in editors", "ng-click" => "selectEditor($event, editor)"}
|
||||||
%strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy
|
%strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy
|
||||||
%label.menu-item-title
|
%label.menu-item-title
|
||||||
|
%span.inline.tinted.mr-10{"ng-if" => "selectedEditor === editor"} ✓
|
||||||
{{editor.name}}
|
{{editor.name}}
|
||||||
%span.inline.tinted{"style" => "margin-left: 8px;", "ng-if" => "selectedEditor === editor"} ✓
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
.float-group.h20
|
.float-group.h20
|
||||||
%h1.tinted.pull-left Extensions
|
%h1.tinted.pull-left Extensions
|
||||||
%a.block.pull-right.dashboard-link{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} Open Dashboard
|
%a.block.pull-right.dashboard-link{"href" => "https://dashboard.standardnotes.org", "target" => "_blank"} Open Dashboard
|
||||||
%div.clear{"ng-if" => "!extensionManager.extensions.length && !themeManager.themes.length && !editorManager.externalEditors.length"}
|
%div.clear{"ng-if" => "!extensionManager.extensions.length && !themeManager.themes.length && !componentManager.components.length"}
|
||||||
%p Customize your experience with editors, themes, and actions.
|
%p Customize your experience with editors, themes, and actions.
|
||||||
.tinted-box.mt-10
|
.tinted-box.mt-10
|
||||||
%h3 Available as part of the Extended subscription.
|
%h3 Available as part of the Extended subscription.
|
||||||
@@ -34,25 +34,6 @@
|
|||||||
%p.small.selectable.wrap{"ng-if" => "theme.showLink"}
|
%p.small.selectable.wrap{"ng-if" => "theme.showLink"}
|
||||||
{{theme.url}}
|
{{theme.url}}
|
||||||
|
|
||||||
%div{"ng-if" => "editorManager.externalEditors.length > 0"}
|
|
||||||
.header.container.section-margin
|
|
||||||
%h2 Editors
|
|
||||||
%p{"style" => "margin-top: 3px;"} Choose "Editor" in the note menu to use an editor for a specific note.
|
|
||||||
%ul
|
|
||||||
%li{"ng-repeat" => "editor in editorManager.externalEditors | orderBy: 'name'", "ng-click" => "clickedExtension(editor)"}
|
|
||||||
.container
|
|
||||||
%strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy
|
|
||||||
%h3
|
|
||||||
%input.bold{"ng-if" => "editor.rename", "ng-model" => "editor.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(editor);", "mb-autofocus" => "true", "should-focus" => "true"}
|
|
||||||
%span{"ng-if" => "!editor.rename"} {{editor.name}}
|
|
||||||
%div.mt-5{"ng-if" => "editor.showDetails"}
|
|
||||||
.link-group
|
|
||||||
%a{"ng-if" => "!editor.default", "ng-click" => "setDefaultEditor(editor); $event.stopPropagation();"} Make Default
|
|
||||||
%a.tinted{"ng-if" => "editor.default", "ng-click" => "removeDefaultEditor(editor); $event.stopPropagation();"} Remove as Default
|
|
||||||
%a{"ng-click" => "renameExtension(editor); $event.stopPropagation();"} Rename
|
|
||||||
%a{"ng-click" => "editor.showUrl = !editor.showUrl; $event.stopPropagation();"} Show Link
|
|
||||||
%a.red{ "ng-click" => "deleteEditor(editor); $event.stopPropagation();"} Delete
|
|
||||||
.wrap.mt-5.selectable{"ng-if" => "editor.showUrl"} {{editor.url}}
|
|
||||||
|
|
||||||
%div{"ng-if" => "extensionManager.extensions.length"}
|
%div{"ng-if" => "extensionManager.extensions.length"}
|
||||||
.header.container.section-margin
|
.header.container.section-margin
|
||||||
@@ -118,8 +99,13 @@
|
|||||||
%h3
|
%h3
|
||||||
%input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "mb-autofocus" => "true", "should-focus" => "true"}
|
%input.bold{"ng-if" => "component.rename", "ng-model" => "component.tempName", "ng-keyup" => "$event.keyCode == 13 && submitExtensionRename(component);", "mb-autofocus" => "true", "should-focus" => "true"}
|
||||||
%span{"ng-if" => "!component.rename"} {{component.name}}
|
%span{"ng-if" => "!component.rename"} {{component.name}}
|
||||||
%a{"ng-if" => "!componentManager.isComponentActive(component)", "ng-click" => "componentManager.activateComponent(component); $event.stopPropagation();"} Activate
|
|
||||||
%a{"ng-if" => "componentManager.isComponentActive(component)", "ng-click" => "componentManager.deactivateComponent(component); $event.stopPropagation();"} Deactivate
|
%div{"ng-if" => "component.isEditor()"}
|
||||||
|
%a{"ng-if" => "!component.isDefaultEditor()", "ng-click" => "makeEditorDefault(component); $event.stopPropagation();"} Make Default
|
||||||
|
%a{"ng-if" => "component.isDefaultEditor()", "ng-click" => "removeEditorDefault(component); $event.stopPropagation();"} Remove Default
|
||||||
|
%div{"ng-if" => "!component.isEditor()"}
|
||||||
|
%a{"ng-if" => "!componentManager.isComponentActive(component)", "ng-click" => "componentManager.activateComponent(component); $event.stopPropagation();"} Activate
|
||||||
|
%a{"ng-if" => "componentManager.isComponentActive(component)", "ng-click" => "componentManager.deactivateComponent(component); $event.stopPropagation();"} Deactivate
|
||||||
.mt-3{"ng-if" => "component.showDetails"}
|
.mt-3{"ng-if" => "component.showDetails"}
|
||||||
.link-group
|
.link-group
|
||||||
%a{"ng-click" => "renameExtension(component); $event.stopPropagation();"} Rename
|
%a{"ng-click" => "renameExtension(component); $event.stopPropagation();"} Rename
|
||||||
|
|||||||
@@ -34,21 +34,21 @@
|
|||||||
%i.icon.ion-arrow-expand
|
%i.icon.ion-arrow-expand
|
||||||
Toggle Fullscreen
|
Toggle Fullscreen
|
||||||
|
|
||||||
%li{"ng-if" => "ctrl.hasDisabledComponents()"}
|
%li{"ng-if" => "ctrl.hasDisabledStackComponents()"}
|
||||||
%label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledComponents()"} Restore Disabled Components
|
%label{"ng-click" => "ctrl.selectedMenuItem($event); ctrl.restoreDisabledStackComponents()"} Restore Disabled Components
|
||||||
|
|
||||||
%li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
|
%li{"ng-class" => "{'selected' : ctrl.showEditorMenu}", "click-outside" => "ctrl.showEditorMenu = false;", "is-open" => "ctrl.showEditorMenu"}
|
||||||
%label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor
|
%label{"ng-click" => "ctrl.showEditorMenu = !ctrl.showEditorMenu; ctrl.showMenu = false; ctrl.showExtensions = false;"} Editor
|
||||||
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editor"}
|
%editor-menu{"ng-if" => "ctrl.showEditorMenu", "callback" => "ctrl.selectedEditor", "selected-editor" => "ctrl.editorComponent"}
|
||||||
|
|
||||||
%li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
|
%li{"ng-class" => "{'selected' : ctrl.showExtensions}", "ng-if" => "ctrl.hasAvailableExtensions()", "click-outside" => "ctrl.showExtensions = false;", "is-open" => "ctrl.showExtensions"}
|
||||||
%label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions
|
%label{"ng-click" => "ctrl.showExtensions = !ctrl.showExtensions; ctrl.showMenu = false; ctrl.showEditorMenu = false;"} Actions
|
||||||
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
|
%contextual-extensions-menu{"ng-if" => "ctrl.showExtensions", "item" => "ctrl.note"}
|
||||||
|
|
||||||
.editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
|
.editor-content{"ng-if" => "ctrl.noteReady && !ctrl.note.errorDecrypting", "ng-class" => "{'fullscreen' : ctrl.fullscreen }"}
|
||||||
%iframe#editor-iframe{"ng-if" => "ctrl.editor && !ctrl.editor.systemEditor", "ng-src" => "{{ctrl.editor.url | trusted}}", "frameBorder" => "0", "style" => "width: 100%;"}
|
%iframe#editor-iframe{"ng-if" => "ctrl.editorComponent && ctrl.editorComponent.active", "ng-src" => "{{ctrl.editorComponent.url | trusted}}", "data-component-id" => "{{ctrl.editorComponent.uuid}}", "frameBorder" => "0", "style" => "width: 100%;"}
|
||||||
Loading
|
Loading
|
||||||
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.editor || ctrl.editor.systemEditor", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text",
|
%textarea.editable#note-text-editor{"ng-if" => "!ctrl.editorComponent", "ng-class" => "{'fullscreen' : ctrl.fullscreen }", "ng-model" => "ctrl.note.text",
|
||||||
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"}
|
"ng-change" => "ctrl.contentChanged()", "ng-click" => "ctrl.clickedTextArea()", "ng-focus" => "ctrl.onContentFocus()", "dir" => "auto"}
|
||||||
{{ctrl.onSystemEditorLoad()}}
|
{{ctrl.onSystemEditorLoad()}}
|
||||||
|
|
||||||
@@ -58,5 +58,5 @@
|
|||||||
|
|
||||||
#editor-pane-component-stack
|
#editor-pane-component-stack
|
||||||
.component.component-stack-border{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.ignoreEvents", "id" => "{{'component-' + component.uuid}}", "ng-mouseover" => "component.showExit = true", "ng-mouseleave" => "component.showExit = false"}
|
.component.component-stack-border{"ng-repeat" => "component in ctrl.componentStack", "ng-if" => "component.active", "ng-show" => "!component.ignoreEvents", "id" => "{{'component-' + component.uuid}}", "ng-mouseover" => "component.showExit = true", "ng-mouseleave" => "component.showExit = false"}
|
||||||
.exit-button.body-text-color{"ng-if" => "component.showExit", "ng-click" => "ctrl.disableComponent(component)"} ×
|
.exit-button.body-text-color{"ng-if" => "component.showExit", "ng-click" => "ctrl.disableComponentForCurrentItem(component, true)"} ×
|
||||||
%iframe#note-tags-iframe{"ng-src" => "{{component.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"}
|
%iframe#note-tags-iframe{"ng-src" => "{{component.url | trusted}}", "frameBorder" => "0", "sandbox" => "allow-scripts allow-top-navigation-by-user-activation allow-popups allow-popups-to-escape-sandbox allow-modals", "data-component-id" => "{{component.uuid}}"}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
Show archived notes
|
Show archived notes
|
||||||
|
|
||||||
.scrollable
|
.scrollable
|
||||||
.infinite-scroll{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
.infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"}
|
||||||
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy| limitTo:ctrl.notesToDisplay)) track by note.uuid",
|
.note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy| limitTo:ctrl.notesToDisplay)) track by note.uuid",
|
||||||
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
"ng-click" => "ctrl.selectNote(note)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"}
|
||||||
%strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy
|
%strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy
|
||||||
|
|||||||
Reference in New Issue
Block a user