22
Gruntfile.js
@@ -70,7 +70,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
app: {
|
||||
src: [
|
||||
'node_modules/sn-models/dist/sn-models.js',
|
||||
'node_modules/snjs/dist/snjs.js',
|
||||
'app/assets/javascripts/app/*.js',
|
||||
'app/assets/javascripts/app/models/**/*.js',
|
||||
'app/assets/javascripts/app/controllers/**/*.js',
|
||||
@@ -136,7 +136,7 @@ module.exports = function(grunt) {
|
||||
singleQuotes: true,
|
||||
},
|
||||
|
||||
neeto: {
|
||||
sn: {
|
||||
files: {
|
||||
'dist/javascripts/compiled.js': 'dist/javascripts/compiled.js',
|
||||
},
|
||||
@@ -149,6 +149,19 @@ module.exports = function(grunt) {
|
||||
dest: 'dist/javascripts/compiled.min.js'
|
||||
}
|
||||
},
|
||||
|
||||
ngconstant: {
|
||||
options: {
|
||||
name: 'app',
|
||||
dest: 'app/assets/javascripts/app/constants.js',
|
||||
deps: false,
|
||||
constants: {
|
||||
appVersion: grunt.file.readJSON('package.json').version
|
||||
}
|
||||
},
|
||||
build: {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-newer');
|
||||
@@ -162,10 +175,13 @@ module.exports = function(grunt) {
|
||||
grunt.loadNpmTasks('grunt-contrib-uglify');
|
||||
grunt.loadNpmTasks('grunt-babel');
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-ng-constant');
|
||||
|
||||
grunt.registerTask('default', ['haml', 'ngtemplates', 'sass', 'concat:app', 'babel', 'browserify',
|
||||
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
|
||||
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify', 'ngconstant:build']);
|
||||
|
||||
grunt.registerTask('vendor', ['concat:app', 'sass', 'babel', 'browserify',
|
||||
'concat:lib', 'concat:dist', 'ngAnnotate', 'concat:css', 'uglify']);
|
||||
|
||||
grunt.registerTask('constants', ['ngconstant:build'])
|
||||
};
|
||||
|
||||
@@ -55,11 +55,6 @@ Array.prototype.containsPrimitiveSubset = function(array) {
|
||||
return !array.some(val => this.indexOf(val) === -1);
|
||||
}
|
||||
|
||||
/* Use with numbers and strings, not objects */
|
||||
Array.prototype.containsObjectSubset = function(array) {
|
||||
return !array.some(val => !_.find(this, val));
|
||||
}
|
||||
|
||||
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
|
||||
if (!Array.prototype.includes) {
|
||||
Object.defineProperty(Array.prototype, 'includes', {
|
||||
|
||||
5
app/assets/javascripts/app/constants.js
Normal file
@@ -0,0 +1,5 @@
|
||||
angular.module('app')
|
||||
|
||||
.constant('appVersion', '3.0.9')
|
||||
|
||||
;
|
||||
@@ -23,7 +23,8 @@ angular.module('app')
|
||||
}
|
||||
})
|
||||
.controller('EditorCtrl', function ($sce, $timeout, authManager, $rootScope, actionsManager,
|
||||
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory, privilegesManager) {
|
||||
syncManager, modelManager, themeManager, componentManager, storageManager, sessionHistory,
|
||||
privilegesManager, keyboardManager) {
|
||||
|
||||
this.spellcheck = true;
|
||||
this.componentManager = componentManager;
|
||||
@@ -121,6 +122,9 @@ angular.module('app')
|
||||
this.showExtensions = false;
|
||||
this.showMenu = false;
|
||||
this.noteStatus = null;
|
||||
// When setting alt key down and deleting note, an alert will come up and block the key up event when alt is released.
|
||||
// We reset it on set note so that the alt menu restores to default.
|
||||
this.altKeyDown = false;
|
||||
this.loadTagsString();
|
||||
|
||||
let onReady = () => {
|
||||
@@ -796,10 +800,41 @@ angular.module('app')
|
||||
syncManager.sync();
|
||||
}
|
||||
|
||||
this.altKeyObserver = keyboardManager.addKeyObserver({
|
||||
modifiers: [KeyboardManager.KeyModifierAlt],
|
||||
onKeyDown: () => {
|
||||
$timeout(() => {
|
||||
this.altKeyDown = true;
|
||||
})
|
||||
},
|
||||
onKeyUp: () => {
|
||||
$timeout(() => {
|
||||
this.altKeyDown = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
this.trashKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: KeyboardManager.KeyBackspace,
|
||||
notElementIds: ["note-text-editor", "note-title-editor"],
|
||||
modifiers: [KeyboardManager.KeyModifierMeta],
|
||||
onKeyDown: () => {
|
||||
$timeout(() => {
|
||||
this.deleteNote();
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
|
||||
this.deleteKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: KeyboardManager.KeyBackspace,
|
||||
modifiers: [KeyboardManager.KeyModifierMeta, KeyboardManager.KeyModifierShift, KeyboardManager.KeyModifierAlt],
|
||||
onKeyDown: (event) => {
|
||||
event.preventDefault();
|
||||
$timeout(() => {
|
||||
this.deleteNote(true);
|
||||
});
|
||||
},
|
||||
})
|
||||
|
||||
/*
|
||||
Editor Customization
|
||||
@@ -810,49 +845,59 @@ angular.module('app')
|
||||
return;
|
||||
}
|
||||
this.loadedTabListener = true;
|
||||
|
||||
/**
|
||||
* Insert 4 spaces when a tab key is pressed,
|
||||
* only used when inside of the text editor.
|
||||
* If the shift key is pressed first, this event is
|
||||
* not fired.
|
||||
*/
|
||||
var parent = this;
|
||||
var handleTab = function (event) {
|
||||
if (!event.shiftKey && event.which == 9) {
|
||||
if(parent.note.locked) {
|
||||
|
||||
const editor = document.getElementById("note-text-editor");
|
||||
this.tabObserver = keyboardManager.addKeyObserver({
|
||||
element: editor,
|
||||
key: KeyboardManager.KeyTab,
|
||||
onKeyDown: (event) => {
|
||||
if(event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.note.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// Using document.execCommand gives us undo support
|
||||
if(!document.execCommand("insertText", false, "\t")) {
|
||||
let insertSuccessful = document.execCommand("insertText", false, "\t");
|
||||
if(!insertSuccessful) {
|
||||
// document.execCommand works great on Chrome/Safari but not Firefox
|
||||
var start = this.selectionStart;
|
||||
var end = this.selectionEnd;
|
||||
var start = editor.selectionStart;
|
||||
var end = editor.selectionEnd;
|
||||
var spaces = " ";
|
||||
|
||||
// Insert 4 spaces
|
||||
this.value = this.value.substring(0, start)
|
||||
+ spaces + this.value.substring(end);
|
||||
editor.value = editor.value.substring(0, start)
|
||||
+ spaces + editor.value.substring(end);
|
||||
|
||||
// Place cursor 4 spaces away from where
|
||||
// the tab key was pressed
|
||||
this.selectionStart = this.selectionEnd = start + 4;
|
||||
editor.selectionStart = editor.selectionEnd = start + 4;
|
||||
}
|
||||
|
||||
parent.note.text = this.value;
|
||||
parent.changesMade();
|
||||
$timeout(() => {
|
||||
this.note.text = editor.value;
|
||||
this.changesMade({bypassDebouncer: true});
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
// This handles when the editor itself is destroyed, and not when our controller is destroyed.
|
||||
angular.element(editor).on('$destroy', function(){
|
||||
if(this.tabObserver) {
|
||||
keyboardManager.removeKeyObserver(this.tabObserver);
|
||||
this.loadedTabListener = false;
|
||||
}
|
||||
}
|
||||
|
||||
var element = document.getElementById("note-text-editor");
|
||||
element.addEventListener('keydown', handleTab);
|
||||
|
||||
angular.element(element).on('$destroy', function(){
|
||||
window.removeEventListener('keydown', handleTab);
|
||||
this.loadedTabListener = false;
|
||||
}.bind(this))
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,13 +23,14 @@ angular.module('app')
|
||||
}
|
||||
})
|
||||
.controller('NotesCtrl', function (authManager, $timeout, $rootScope, modelManager,
|
||||
syncManager, storageManager, desktopManager, privilegesManager) {
|
||||
syncManager, storageManager, desktopManager, privilegesManager, keyboardManager) {
|
||||
|
||||
this.panelController = {};
|
||||
this.searchSubmitted = false;
|
||||
|
||||
$rootScope.$on("user-preferences-changed", () => {
|
||||
this.loadPreferences();
|
||||
this.reloadNotes();
|
||||
});
|
||||
|
||||
authManager.addEventHandler((event) => {
|
||||
@@ -335,6 +336,25 @@ angular.module('app')
|
||||
}
|
||||
}
|
||||
|
||||
this.selectNextNote = function() {
|
||||
var visibleNotes = this.visibleNotes();
|
||||
let currentIndex = visibleNotes.indexOf(this.selectedNote);
|
||||
if(currentIndex + 1 < visibleNotes.length) {
|
||||
this.selectNote(visibleNotes[currentIndex + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
this.selectPreviousNote = function() {
|
||||
var visibleNotes = this.visibleNotes();
|
||||
let currentIndex = visibleNotes.indexOf(this.selectedNote);
|
||||
if(currentIndex - 1 >= 0) {
|
||||
this.selectNote(visibleNotes[currentIndex - 1]);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this.selectNote = async function(note, viaClick = false) {
|
||||
if(!note) {
|
||||
return;
|
||||
@@ -398,7 +418,6 @@ angular.module('app')
|
||||
|
||||
this.noteFilter = {text : ''};
|
||||
|
||||
|
||||
this.onFilterEnter = function() {
|
||||
// For Desktop, performing a search right away causes input to lose focus.
|
||||
// We wait until user explicity hits enter before highlighting desktop search results.
|
||||
@@ -566,4 +585,59 @@ angular.module('app')
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Keyboard Shortcuts
|
||||
*/
|
||||
|
||||
// In the browser we're not allowed to override cmd/ctrl + n, so we have to use Control modifier as well.
|
||||
// These rules don't apply to desktop, but probably better to be consistent.
|
||||
this.newNoteKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: "n",
|
||||
modifiers: [KeyboardManager.KeyModifierMeta, KeyboardManager.KeyModifierCtrl],
|
||||
onKeyDown: (event) => {
|
||||
event.preventDefault();
|
||||
$timeout(() => {
|
||||
this.createNewNote();
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
this.getSearchBar = function() {
|
||||
return document.getElementById("search-bar");
|
||||
}
|
||||
|
||||
this.nextNoteKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: KeyboardManager.KeyDown,
|
||||
elements: [document.body, this.getSearchBar()],
|
||||
onKeyDown: (event) => {
|
||||
let searchBar = this.getSearchBar();
|
||||
if(searchBar == document.activeElement) {
|
||||
searchBar.blur()
|
||||
}
|
||||
$timeout(() => {
|
||||
this.selectNextNote();
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
this.nextNoteKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: KeyboardManager.KeyUp,
|
||||
element: document.body,
|
||||
onKeyDown: (event) => {
|
||||
$timeout(() => {
|
||||
this.selectPreviousNote();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.searchKeyObserver = keyboardManager.addKeyObserver({
|
||||
key: "f",
|
||||
modifiers: [KeyboardManager.KeyModifierMeta, KeyboardManager.KeyModifierShift],
|
||||
onKeyDown: (event) => {
|
||||
let searchBar = this.getSearchBar();
|
||||
if(searchBar) {searchBar.focus()};
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
@@ -10,9 +10,10 @@ class AccountMenu {
|
||||
}
|
||||
|
||||
controller($scope, $rootScope, authManager, modelManager, syncManager, storageManager, dbManager, passcodeManager,
|
||||
$timeout, $compile, archiveManager, privilegesManager) {
|
||||
$timeout, $compile, archiveManager, privilegesManager, appVersion) {
|
||||
'ngInject';
|
||||
|
||||
$scope.appVersion = "v" + appVersion;
|
||||
$scope.formData = {mergeLocal: true, ephemeral: false};
|
||||
|
||||
$scope.user = authManager.user;
|
||||
|
||||
@@ -111,15 +111,16 @@ class ComponentView {
|
||||
}
|
||||
|
||||
$timeout.cancel($scope.loadTimeout);
|
||||
componentManager.registerComponentWindow(component, iframe.contentWindow);
|
||||
componentManager.registerComponentWindow(component, iframe.contentWindow).then(() => {
|
||||
// Add small timeout to, as $scope.loading controls loading overlay,
|
||||
// which is used to avoid flicker when enabling extensions while having an enabled theme
|
||||
// we don't use ng-show because it causes problems with rendering iframes after timeout, for some reason.
|
||||
$timeout(() => {
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = desktopError; /* Typically we'd just set this to false at this point, but we now account for desktopError */
|
||||
}, 7)
|
||||
})
|
||||
|
||||
// Add small timeout to, as $scope.loading controls loading overlay,
|
||||
// which is used to avoid flicker when enabling extensions while having an enabled theme
|
||||
// we don't use ng-show because it causes problems with rendering iframes after timeout, for some reason.
|
||||
$timeout(() => {
|
||||
$scope.loading = false;
|
||||
$scope.issueLoading = desktopError; /* Typically we'd just set this to false at this point, but we now account for desktopError */
|
||||
}, 7)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -160,7 +161,7 @@ class ComponentView {
|
||||
$scope.componentValid = false;
|
||||
componentManager.reloadComponent($scope.component).then(() => {
|
||||
$scope.reloadStatus();
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
$scope.reloadStatus = function(doManualReload = true) {
|
||||
|
||||
@@ -6,7 +6,7 @@ class PermissionsModal {
|
||||
this.scope = {
|
||||
show: "=",
|
||||
component: "=",
|
||||
permissions: "=",
|
||||
permissionsString: "=",
|
||||
callback: "="
|
||||
};
|
||||
}
|
||||
@@ -31,68 +31,6 @@ class PermissionsModal {
|
||||
controller($scope, modelManager) {
|
||||
'ngInject';
|
||||
|
||||
$scope.permissionsString = function() {
|
||||
var finalString = "";
|
||||
let permissionsCount = $scope.permissions.length;
|
||||
|
||||
let addSeparator = (index, length) => {
|
||||
if(index > 0) {
|
||||
if(index == length - 1) {
|
||||
if(length == 2) {
|
||||
return " and ";
|
||||
} else {
|
||||
return ", and "
|
||||
}
|
||||
} else {
|
||||
return ", ";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
$scope.permissions.forEach((permission, index) => {
|
||||
|
||||
if(permission.name === "stream-items") {
|
||||
var types = permission.content_types.map(function(type){
|
||||
var desc = modelManager.humanReadableDisplayForContentType(type);
|
||||
if(desc) {
|
||||
return desc + "s";
|
||||
} else {
|
||||
return "items of type " + type;
|
||||
}
|
||||
})
|
||||
var typesString = "";
|
||||
|
||||
for(var i = 0;i < types.length;i++) {
|
||||
var type = types[i];
|
||||
typesString += addSeparator(i, types.length + permissionsCount - index - 1);
|
||||
typesString += type;
|
||||
}
|
||||
|
||||
finalString += addSeparator(index, permissionsCount);
|
||||
|
||||
finalString += typesString;
|
||||
|
||||
if(types.length >= 2 && index < permissionsCount - 1) {
|
||||
// If you have a list of types, and still an additional root-level permission coming up, add a comma
|
||||
finalString += ", ";
|
||||
}
|
||||
} else if(permission.name === "stream-context-item") {
|
||||
var mapping = {
|
||||
"editor-stack" : "working note",
|
||||
"note-tags" : "working note",
|
||||
"editor-editor": "working note"
|
||||
}
|
||||
|
||||
finalString += addSeparator(index, permissionsCount, true);
|
||||
|
||||
finalString += mapping[$scope.component.area];
|
||||
}
|
||||
})
|
||||
|
||||
return finalString + ".";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
117
app/assets/javascripts/app/services/keyboardManager.js
Normal file
@@ -0,0 +1,117 @@
|
||||
class KeyboardManager {
|
||||
|
||||
constructor() {
|
||||
this.observers = [];
|
||||
|
||||
KeyboardManager.KeyTab = "Tab";
|
||||
KeyboardManager.KeyBackspace = "Backspace";
|
||||
KeyboardManager.KeyUp = "ArrowUp";
|
||||
KeyboardManager.KeyDown = "ArrowDown";
|
||||
|
||||
KeyboardManager.KeyModifierShift = "Shift";
|
||||
KeyboardManager.KeyModifierCtrl = "Control";
|
||||
// ⌘ key on Mac, ⊞ key on Windows
|
||||
KeyboardManager.KeyModifierMeta = "Meta";
|
||||
KeyboardManager.KeyModifierAlt = "Alt";
|
||||
|
||||
KeyboardManager.KeyEventDown = "KeyEventDown";
|
||||
KeyboardManager.KeyEventUp = "KeyEventUp";
|
||||
|
||||
KeyboardManager.AllModifiers = [
|
||||
KeyboardManager.KeyModifierShift,
|
||||
KeyboardManager.KeyModifierCtrl,
|
||||
KeyboardManager.KeyModifierMeta,
|
||||
KeyboardManager.KeyModifierAlt
|
||||
]
|
||||
|
||||
window.addEventListener('keydown', this.handleKeyDown.bind(this));
|
||||
window.addEventListener('keyup', this.handleKeyUp.bind(this));
|
||||
}
|
||||
|
||||
modifiersForEvent(event) {
|
||||
let eventModifiers = KeyboardManager.AllModifiers.filter((modifier) => {
|
||||
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
||||
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
||||
let matches = (
|
||||
((event.ctrlKey || event.key == KeyboardManager.KeyModifierCtrl) && modifier === KeyboardManager.KeyModifierCtrl) ||
|
||||
((event.metaKey || event.key == KeyboardManager.KeyModifierMeta) && modifier === KeyboardManager.KeyModifierMeta) ||
|
||||
((event.altKey || event.key == KeyboardManager.KeyModifierAlt) && modifier === KeyboardManager.KeyModifierAlt) ||
|
||||
((event.shiftKey || event.key == KeyboardManager.KeyModifierShift) && modifier === KeyboardManager.KeyModifierShift)
|
||||
)
|
||||
|
||||
return matches;
|
||||
})
|
||||
|
||||
return eventModifiers;
|
||||
}
|
||||
|
||||
eventMatchesKeyAndModifiers(event, key, modifiers = []) {
|
||||
let eventModifiers = this.modifiersForEvent(event);
|
||||
|
||||
if(eventModifiers.length != modifiers.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(let modifier of modifiers) {
|
||||
if(!eventModifiers.includes(modifier)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Modifers match, check key
|
||||
if(!key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
|
||||
// In our case we don't differentiate between the two.
|
||||
return key.toLowerCase() == event.key.toLowerCase();
|
||||
}
|
||||
|
||||
notifyObserver(event, keyEventType) {
|
||||
for(let observer of this.observers) {
|
||||
if(observer.element && event.target != observer.element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(observer.elements && !observer.elements.includes(event.target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(observer.notElement && observer.notElement == event.target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(observer.notElementIds && observer.notElementIds.includes(event.target.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
|
||||
let callback = keyEventType == KeyboardManager.KeyEventDown ? observer.onKeyDown : observer.onKeyUp;
|
||||
if(callback) {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(event) {
|
||||
this.notifyObserver(event, KeyboardManager.KeyEventDown);
|
||||
}
|
||||
|
||||
handleKeyUp(event) {
|
||||
this.notifyObserver(event, KeyboardManager.KeyEventUp);
|
||||
}
|
||||
|
||||
addKeyObserver({key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds}) {
|
||||
let observer = {key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds};
|
||||
this.observers.push(observer);
|
||||
return observer;
|
||||
}
|
||||
|
||||
removeKeyObserver(observer) {
|
||||
this.observers.splice(this.observers.indexOf(observer), 1);
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('app').service('keyboardManager', KeyboardManager);
|
||||
@@ -41,6 +41,13 @@ class NativeExtManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle addition of SN|ExtensionRepo permission
|
||||
let permission = resolvedSingleton.content.permissions.find((p) => p.name == "stream-items");
|
||||
if(!permission.content_types.includes("SN|ExtensionRepo")) {
|
||||
permission.content_types.push("SN|ExtensionRepo");
|
||||
needsSync = true;
|
||||
}
|
||||
|
||||
if(needsSync) {
|
||||
resolvedSingleton.setDirty(true);
|
||||
this.syncManager.sync();
|
||||
@@ -68,7 +75,10 @@ class NativeExtManager {
|
||||
permissions: [
|
||||
{
|
||||
name: "stream-items",
|
||||
content_types: ["SN|Component", "SN|Theme", "SF|Extension", "Extension", "SF|MFA", "SN|Editor"]
|
||||
content_types: [
|
||||
"SN|Component", "SN|Theme", "SF|Extension",
|
||||
"Extension", "SF|MFA", "SN|Editor", "SN|ExtensionRepo"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -141,7 +151,12 @@ class NativeExtManager {
|
||||
permissions: [
|
||||
{
|
||||
name: "stream-items",
|
||||
content_types: ["Note", "Tag", "SN|Component", "SN|Theme", "SF|Extension", "Extension", "SF|MFA", "SN|Editor", "SN|UserPreferences"]
|
||||
content_types: [
|
||||
"Note", "Tag", "SN|SmartTag",
|
||||
"SN|Component", "SN|Theme", "SN|UserPreferences",
|
||||
"SF|Extension", "Extension", "SF|MFA", "SN|Editor",
|
||||
"SN|FileSafe|Credentials", "SN|FileSafe|FileMetadata", "SN|FileSafe|Integration"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ $heading-height: 75px;
|
||||
overflow-y: hidden;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
tab-size: 2;
|
||||
background-color: var(--sn-stylekit-background-color);
|
||||
|
||||
position: relative;
|
||||
@@ -135,6 +136,8 @@ $heading-height: 75px;
|
||||
|
||||
#editor-pane-component-stack {
|
||||
width: 100%;
|
||||
// To keep glued to bottom when editor is in loading state and not visible.
|
||||
margin-top: auto;
|
||||
|
||||
// When two component stack items are expired and eat up full screen, this is required to scroll them.
|
||||
// overflow: auto;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#notes-title-bar {
|
||||
padding-top: 16px;
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
font-size: var(--sn-stylekit-font-size-h1);
|
||||
|
||||
.section-title-bar-header .title {
|
||||
color: var(--sn-stylekit-neutral-color);
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
.filter-section {
|
||||
clear: left;
|
||||
height: 32px;
|
||||
height: 29px;
|
||||
margin-top: 14px;
|
||||
position: relative;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
color: #909090;
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
font-size: var(--sn-stylekit-font-size-h3);
|
||||
line-height: 35px;
|
||||
|
||||
border: none;
|
||||
@@ -120,8 +120,9 @@
|
||||
}
|
||||
|
||||
.note-preview {
|
||||
font-size: 15px;
|
||||
margin-top: 1px;
|
||||
font-size: var(--sn-stylekit-font-size-h3);
|
||||
margin-top: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -140,12 +141,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.preview-hidden {
|
||||
font-size: 14px;
|
||||
margin-top: 2px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.note-flag {
|
||||
color: var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
@@ -53,12 +53,22 @@
|
||||
|
||||
> .tag-info {
|
||||
height: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
> .tag-icon {
|
||||
width: 10px;
|
||||
opacity: 0.2;
|
||||
font-size: var(--sn-stylekit-font-size-h2);
|
||||
font-weight: bold;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
> .title {
|
||||
width: 80%;
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
float: left;
|
||||
color: var(--sn-stylekit-secondary-foreground-color);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -194,7 +194,9 @@
|
||||
.sk-panel-row
|
||||
.sk-spinner.small.info{"ng-if" => "importData.loading"}
|
||||
.sk-panel-footer
|
||||
%a.sk-a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"}
|
||||
Cancel
|
||||
%a.sk-a.right.danger{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"}
|
||||
{{ user ? "Sign out and clear local data" : "Clear all local data" }}
|
||||
.sk-panel-row
|
||||
.sk-p.left.neutral.faded {{appVersion}}
|
||||
%a.sk-a.right{"ng-if" => "formData.showLogin || formData.showRegister", "ng-click" => "formData.showLogin = false; formData.showRegister = false;"}
|
||||
Cancel
|
||||
%a.sk-a.right.danger{"ng-if" => "!formData.showLogin && !formData.showRegister", "ng-click" => "destroyLocalData()"}
|
||||
{{ user ? "Sign out and clear local data" : "Clear all local data" }}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
.sk-h2
|
||||
%strong {{component.name}}
|
||||
would like to interact with your
|
||||
{{permissionsString()}}
|
||||
{{permissionsString}}
|
||||
|
||||
.sk-panel-row
|
||||
%p.sk-p
|
||||
|
||||
@@ -38,13 +38,13 @@
|
||||
%menu-row{"label" => "ctrl.note.locked ? 'Unlock' : 'Lock'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleLockNote()", "desc" => "'Locking notes prevents unintentional editing'"}
|
||||
%menu-row{"label" => "ctrl.note.content.protected ? 'Unprotect' : 'Protect'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleProtectNote()", "desc" => "'Protecting a note will require credentials to view it (Manage Privileges via Account menu)'"}
|
||||
%menu-row{"label" => "'Preview'", "circle" => "ctrl.note.content.hidePreview ? 'danger' : 'success'", "circle-align" => "'right'", "action" => "ctrl.selectedMenuItem(true); ctrl.toggleNotePreview()", "desc" => "'Hide or unhide the note preview from the list of notes'"}
|
||||
%menu-row{"ng-show" => "!ctrl.note.content.trashed && !ctrl.note.errorDecrypting", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "stylekit-class" => "'warning'", "desc" => "'Send this note to the trash'"}
|
||||
%menu-row{"ng-show" => "!ctrl.altKeyDown && !ctrl.note.content.trashed && !ctrl.note.errorDecrypting", "label" => "'Move to Trash'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNote()", "stylekit-class" => "'warning'", "desc" => "'Send this note to the trash'"}
|
||||
%menu-row{"ng-show" => "!ctrl.note.content.trashed && ctrl.note.errorDecrypting", "label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
||||
|
||||
%div{"ng-if" => "ctrl.note.content.trashed"}
|
||||
%menu-row{"label" => "'Restore'", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "stylekit-class" => "'info'", "desc" => "'Undelete this note and restore it back into your notes'"}
|
||||
%div{"ng-if" => "ctrl.note.content.trashed || ctrl.altKeyDown"}
|
||||
%menu-row{"label" => "'Restore'", "ng-show" => "ctrl.note.content.trashed", "action" => "ctrl.selectedMenuItem(true); ctrl.restoreTrashedNote()", "stylekit-class" => "'info'", "desc" => "'Undelete this note and restore it back into your notes'"}
|
||||
%menu-row{"label" => "'Delete Permanently'", "action" => "ctrl.selectedMenuItem(true); ctrl.deleteNotePermanantely()", "stylekit-class" => "'danger'", "desc" => "'Delete this note permanently from all your devices'"}
|
||||
%menu-row{"label" => "'Empty Trash'", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "stylekit-class" => "'danger'", "desc" => "'Permanently delete all notes in the trash'"}
|
||||
%menu-row{"label" => "'Empty Trash'", "ng-show" => "ctrl.note.content.trashed || !ctrl.altKeyDown", "subtitle" => "ctrl.getTrashCount() + ' notes in trash'", "action" => "ctrl.selectedMenuItem(true); ctrl.emptyTrash()", "stylekit-class" => "'danger'", "desc" => "'Permanently delete all notes in the trash'"}
|
||||
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
.sk-label
|
||||
%i.icon.ion-plus.add-button
|
||||
.filter-section{"role" => "search"}
|
||||
%input.filter-bar#search-bar.mousetrap{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search",
|
||||
%input.filter-bar#search-bar{"select-on-click" => "true", "ng-model" => "ctrl.noteFilter.text", "placeholder" => "Search",
|
||||
"ng-change" => "ctrl.filterTextChanged()", "lowercase" => "true", "ng-blur" => "ctrl.onFilterEnter()", "ng-keyup" => "$event.keyCode == 13 && ctrl.onFilterEnter();",
|
||||
"title" => "Searches notes in the currently selected tag"}
|
||||
#search-clear-button{"ng-show" => "ctrl.noteFilter.text", "ng-click" => "ctrl.clearFilterText();"} ✕
|
||||
@@ -56,7 +56,8 @@
|
||||
.name{"ng-show" => "note.title"}
|
||||
{{note.title}}
|
||||
|
||||
.note-preview{"ng-show" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected"}
|
||||
-# Use ng-if and not ng-show here. We don't want previews in the dom at all if protected.
|
||||
.note-preview{"ng-if" => "!ctrl.hideNotePreview && !note.content.hidePreview && !note.content.protected"}
|
||||
.html-preview{"ng-show" => "note.content.preview_html", "ng-bind-html" => "note.content.preview_html"}
|
||||
.plain-preview{"ng-show" => "!note.content.preview_html && note.content.preview_plain"} {{note.content.preview_plain}}
|
||||
.default-preview{"ng-show" => "!note.content.preview_html && !note.content.preview_plain"} {{note.text}}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
%span.sk-bold Tags
|
||||
.tag{"ng-repeat" => "tag in ctrl.tags track by tag.uuid", "ng-click" => "ctrl.selectTag(tag)", "ng-class" => "{'selected' : ctrl.selectedTag == tag}"}
|
||||
.tag-info
|
||||
.tag-icon #
|
||||
%input.title{"ng-class" => "{'editing' : ctrl.editingTag == tag}", "ng-attr-id" => "tag-{{tag.uuid}}", "ng-click" => "ctrl.selectTag(tag)", "ng-model" => "tag.title",
|
||||
"ng-keyup" => "$event.keyCode == 13 && $event.target.blur()", "sn-autofocus" => "true", "should-focus" => "ctrl.newTag || ctrl.editingTag == tag",
|
||||
"ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<link href="favicon/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180"></link>
|
||||
<link href="favicon/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"></link>
|
||||
<link href="favicon/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"></link>
|
||||
<link href="favicon/manifest.json" rel="manifest"></link>
|
||||
<link href="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">
|
||||
@@ -17,7 +17,7 @@
|
||||
<meta ng-bind="title" content="Standard Notes" name="application-name"/>
|
||||
<base href="/"></base>
|
||||
|
||||
<title>Notes — Standard Notes</title>
|
||||
<title>Notes · Standard Notes</title>
|
||||
<meta name="description" content="Standard Notes is a basic notes app that delivers only the essentials in note taking. Because of its simplicity and resistance to growth, users can count on durability and convenience."/>
|
||||
|
||||
<meta name="twitter:title" content="Standard Notes, a private and secure notes app."/>
|
||||
|
||||
@@ -14,7 +14,7 @@ require "rails/test_unit/railtie"
|
||||
# you've limited to :test, :development, or :production.
|
||||
Bundler.require(*Rails.groups)
|
||||
|
||||
module Neeto
|
||||
module StandardNotes
|
||||
class Application < Rails::Application
|
||||
# Cross-Origin Resource Sharing (CORS) for Rack compatible web applications.
|
||||
config.middleware.insert_before 0, Rack::Cors do
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
default: &default
|
||||
key_path: /path/to/key.pem
|
||||
repo_url: https://github.com/neeto-project/neeto-web-client.git
|
||||
repo_url: https://github.com/sn-project/sn-web-client.git
|
||||
user: ssh_username
|
||||
|
||||
staging:
|
||||
<<: *default
|
||||
server: staging.yourdomain.com
|
||||
branch: staging
|
||||
deploy_to: ~/neeto-staging-client
|
||||
deploy_to: ~/sn-staging-client
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
server: yourdomain.com
|
||||
deploy_to: ~/neeto-prod-client
|
||||
deploy_to: ~/sn-prod-client
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CAP_CONFIG = YAML.load_file("config/cap.yml")
|
||||
|
||||
set :application, 'neeto'
|
||||
set :application, 'standard-notes'
|
||||
set :repo_url, CAP_CONFIG["default"]["repo_url"]
|
||||
|
||||
# Default branch is :master
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Rails.application.config.session_store :cookie_store, key: '_neeto_session'
|
||||
Rails.application.config.session_store :cookie_store, key: '_sn_session'
|
||||
|
||||
2804
package-lock.json
generated
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "standard-notes-web",
|
||||
"version": "3.0.8",
|
||||
"version": "3.0.9",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,11 +34,12 @@
|
||||
"grunt-haml2html": "^0.3.1",
|
||||
"grunt-newer": "^1.2.0",
|
||||
"grunt-ng-annotate": "^3.0.0",
|
||||
"grunt-ng-constant": "^2.0.2",
|
||||
"grunt-shell": "^2.1.0",
|
||||
"mocha": "^5.2.0",
|
||||
"serve-static": "^1.13.2",
|
||||
"sn-models": "0.1.14",
|
||||
"sn-stylekit": "2.0.13",
|
||||
"standard-file-js": "0.3.57",
|
||||
"grunt-shell": "^2.1.0"
|
||||
"sn-stylekit": "2.0.14",
|
||||
"snjs": "0.2.1",
|
||||
"standard-file-js": "0.3.58"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
BIN
public/favicon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -2,7 +2,7 @@
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<square150x150logo src="favicon/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
|
||||
|
Before Width: | Height: | Size: 556 B After Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 677 B After Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,16.000000) scale(0.003200,-0.003200)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M230 2490 l0 -2270 2270 0 2270 0 0 2270 0 2270 -2270 0 -2270 0 0
|
||||
-2270z m4340 0 l0 -2070 -2070 0 -2070 0 0 2070 0 2070 2070 0 2070 0 0 -2070z"/>
|
||||
<path d="M2368 3475 c-2 -1 -23 -5 -48 -8 -298 -37 -515 -205 -603 -467 -14
|
||||
-41 -29 -91 -33 -110 -4 -19 -9 -328 -11 -687 l-4 -651 168 1 168 2 2 606 c2
|
||||
648 4 683 54 782 82 162 231 240 449 234 255 -7 412 -140 454 -387 3 -14 5
|
||||
-298 5 -632 l1 -606 168 1 167 2 0 645 c-1 634 -1 646 -23 730 -81 306 -261
|
||||
468 -597 535 -31 7 -311 15 -317 10z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1005 B |
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"name": "Standard Notes",
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon/android-chrome-192x192.png",
|
||||
"src": "favicon/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicon/android-chrome-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"src": "favicon/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||