Merges master
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,4 +35,4 @@ dump.rdb
|
||||
/public/uploads/*
|
||||
!/public/uploads/.keep
|
||||
|
||||
.vscode
|
||||
.vscode
|
||||
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3001",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
README.md
10
README.md
@@ -11,7 +11,7 @@ Standard Notes is a simple and private notes app available on most platforms, in
|
||||
- Fast and encrypted cross-platform sync
|
||||
- Free sync on unlimited devices
|
||||
- Extensible with editors (such as Markdown and Code), themes, and components (like Folders and Autocomplete Tags). Learn more about [Extended](https://standardnotes.org/extensions).
|
||||
- Open-source and the option to self-host your notes server. You can [host your own Standard Server](https://docs.standardnotes.org/self-hosting.html) in a few easy steps.
|
||||
- Open-source and the option to self-host your notes server. You can [host your own Standard Server](https://docs.standardnotes.org/self-hosting/getting-started) in a few easy steps.
|
||||
- A strong focus on longevity and sustainability. [Learn more](https://standardnotes.org/longevity).
|
||||
|
||||
### Creating your private notes account
|
||||
@@ -60,16 +60,16 @@ This repo contains the core code used in the web app, as well as the Electron-ba
|
||||
**Instructions:**
|
||||
|
||||
1. Clone the repo
|
||||
1. `npm run build`
|
||||
1. `rails s`
|
||||
2. `npm install`
|
||||
3. `npm start`
|
||||
|
||||
Open your browser to http://localhost:3000.
|
||||
Then open your browser to `http://localhost:3000`.
|
||||
|
||||
---
|
||||
|
||||
**Extensions Manager and Batch Manager:**
|
||||
|
||||
The web app makes use of two optional native extensions, which can be configured to work as follows:
|
||||
The web app makes use of two optional native extensions, which, when running the app with Rails, can be configured to work as follows:
|
||||
|
||||
1. `git submodule update --init --force --remote` (will load the submodules in the `public/extensions` folder)
|
||||
1. Set the following environment variables in the .env file:
|
||||
|
||||
@@ -39,7 +39,7 @@ class LockScreenCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
async submitPasscodeForm($event) {
|
||||
async submitPasscodeForm() {
|
||||
if (
|
||||
!this.formData.passcode ||
|
||||
this.formData.passcode.length === 0
|
||||
@@ -78,4 +78,4 @@ export class LockScreen {
|
||||
puppet: '='
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,8 +133,7 @@ class NotesCtrl extends PureCtrl {
|
||||
const selectedNote = this.state.selectedNote;
|
||||
if (selectedNote) {
|
||||
const discarded = selectedNote.deleted || selectedNote.content.trashed;
|
||||
const notIncluded = !this.state.notes.includes(selectedNote);
|
||||
if (notIncluded || discarded) {
|
||||
if (discarded) {
|
||||
this.selectNextOrCreateNew();
|
||||
}
|
||||
} else {
|
||||
@@ -165,7 +164,7 @@ class NotesCtrl extends PureCtrl {
|
||||
});
|
||||
this.resetScrollPosition();
|
||||
this.setShowMenuFalse();
|
||||
this.setNoteFilterText('');
|
||||
await this.setNoteFilterText('');
|
||||
this.desktopManager.searchText();
|
||||
this.resetPagination();
|
||||
|
||||
@@ -193,9 +192,9 @@ class NotesCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @template
|
||||
* @internal
|
||||
* @internal
|
||||
*/
|
||||
async selectNote(note) {
|
||||
this.appState.setSelectedNote(note);
|
||||
@@ -219,7 +218,7 @@ class NotesCtrl extends PureCtrl {
|
||||
selectedTag: this.state.tag,
|
||||
showArchived: this.state.showArchived,
|
||||
hidePinned: this.state.hidePinned,
|
||||
filterText: this.state.noteFilter.text,
|
||||
filterText: this.state.noteFilter.text.toLowerCase(),
|
||||
sortBy: this.state.sortBy,
|
||||
reverse: this.state.sortReverse
|
||||
});
|
||||
@@ -514,10 +513,16 @@ class NotesCtrl extends PureCtrl {
|
||||
if (!selectedTag) {
|
||||
throw 'Attempting to create note with no selected tag';
|
||||
}
|
||||
if (this.state.selectedNote && this.state.selectedNote.dummy) {
|
||||
let title;
|
||||
let isDummyNote = true;
|
||||
if (this.isFiltering()) {
|
||||
title = this.state.noteFilter.text;
|
||||
isDummyNote = false;
|
||||
} else if (this.state.selectedNote && this.state.selectedNote.dummy) {
|
||||
return;
|
||||
} else {
|
||||
title = `Note ${this.state.notes.length + 1}`;
|
||||
}
|
||||
const title = "Note" + (this.state.notes ? (" " + (this.state.notes.length + 1)) : "");
|
||||
const newNote = await this.application.createItem({
|
||||
contentType: ContentTypes.Note,
|
||||
content: {
|
||||
@@ -527,7 +532,7 @@ class NotesCtrl extends PureCtrl {
|
||||
add: true
|
||||
});
|
||||
newNote.client_updated_at = new Date();
|
||||
newNote.dummy = true;
|
||||
newNote.dummy = isDummyNote;
|
||||
this.application.setItemNeedsSync({ item: newNote });
|
||||
if (!selectedTag.isSmartTag()) {
|
||||
selectedTag.addItemAsRelationship(newNote);
|
||||
@@ -562,9 +567,6 @@ class NotesCtrl extends PureCtrl {
|
||||
this.searchSubmitted = false;
|
||||
}
|
||||
await this.reloadNotes();
|
||||
if (!this.state.notes.includes(this.state.selectedNote)) {
|
||||
this.selectFirstNote();
|
||||
}
|
||||
}
|
||||
|
||||
onFilterEnter() {
|
||||
|
||||
@@ -254,40 +254,29 @@ class RootCtrl extends PureCtrl {
|
||||
}, false);
|
||||
}
|
||||
|
||||
handleAutoSignInFromParams() {
|
||||
const urlParam = (key) => {
|
||||
return this.$location.search()[key];
|
||||
};
|
||||
async handleAutoSignInFromParams() {
|
||||
const params = this.$location.search();
|
||||
const server = params.server;
|
||||
const email = params.email;
|
||||
const password = params.pw;
|
||||
if (!server || !email || !password) return;
|
||||
|
||||
const autoSignInFromParams = async () => {
|
||||
const server = urlParam('server');
|
||||
const email = urlParam('email');
|
||||
const pw = urlParam('pw');
|
||||
if (!this.application.getUser()) {
|
||||
if (
|
||||
await this.application.getHost() === server
|
||||
&& this.application.getUser().email === email
|
||||
) {
|
||||
/** Already signed in, return */
|
||||
// eslint-disable-next-line no-useless-return
|
||||
return;
|
||||
} else {
|
||||
/** Sign out */
|
||||
await this.application.signOut();
|
||||
await this.application.restart();
|
||||
}
|
||||
const user = this.application.getUser();
|
||||
if (user) {
|
||||
if (user.email === email && await this.application.getHost() === server) {
|
||||
/** Already signed in, return */
|
||||
return;
|
||||
} else {
|
||||
await this.application.setHost(server);
|
||||
this.application.signIn({
|
||||
email: email,
|
||||
password: pw,
|
||||
});
|
||||
/** Sign out */
|
||||
await this.application.signOut();
|
||||
await this.application.restart();
|
||||
}
|
||||
};
|
||||
|
||||
if (urlParam('server')) {
|
||||
autoSignInFromParams();
|
||||
}
|
||||
await this.application.setHost(server);
|
||||
this.application.signIn({
|
||||
email: email,
|
||||
password: password,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,6 +199,7 @@ class AccountMenuCtrl extends PureCtrl {
|
||||
}
|
||||
await this.setFormDataState({
|
||||
status: null,
|
||||
user_password: null
|
||||
});
|
||||
const error = response
|
||||
? response.error
|
||||
@@ -462,6 +463,19 @@ class AccountMenuCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
hidePasswordForm() {
|
||||
this.setFormDataState({
|
||||
showLogin: false,
|
||||
showRegister: false,
|
||||
user_password: null,
|
||||
password_conf: null
|
||||
});
|
||||
}
|
||||
|
||||
hasPasscode() {
|
||||
return this.passcodeManager.hasPasscode();
|
||||
}
|
||||
|
||||
addPasscodeClicked() {
|
||||
this.setFormDataState({
|
||||
showPasscodeForm: true
|
||||
|
||||
@@ -42,7 +42,7 @@ class EditorMenuCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
toggleDefaultForEditor(editor) {
|
||||
if(this.defaultEditor === editor) {
|
||||
if(this.state.defaultEditor === editor) {
|
||||
this.removeEditorDefault(editor);
|
||||
} else {
|
||||
this.makeEditorDefault(editor);
|
||||
|
||||
@@ -338,10 +338,7 @@
|
||||
.sk-panel-row
|
||||
.sk-p.left.neutral.faded {{self.state.appVersion}}
|
||||
a.sk-a.right(
|
||||
ng-click=`
|
||||
self.state.formData.showLogin = false;
|
||||
self.state.formData.showRegister = false;
|
||||
`,
|
||||
ng-click='self.hidePasswordForm()',
|
||||
ng-if='self.state.formData.showLogin || self.state.formData.showRegister'
|
||||
)
|
||||
| Cancel
|
||||
|
||||
@@ -5,31 +5,30 @@
|
||||
.section-title-bar-header
|
||||
.title {{self.state.panelTitle}}
|
||||
.sk-button.contrast.wide(
|
||||
ng-click='self.createNewNote()',
|
||||
ng-click='self.createNewNote()',
|
||||
title='Create a new note in the selected tag'
|
||||
)
|
||||
.sk-label
|
||||
i.icon.ion-plus.add-button
|
||||
.filter-section(role='search')
|
||||
input#search-bar.filter-bar(
|
||||
lowercase='true',
|
||||
ng-blur='self.onFilterEnter()',
|
||||
ng-change='self.filterTextChanged()',
|
||||
ng-keyup='$event.keyCode == 13 && self.onFilterEnter();',
|
||||
ng-blur='self.onFilterEnter()',
|
||||
ng-change='self.filterTextChanged()',
|
||||
ng-keyup='$event.keyCode == 13 && self.onFilterEnter();',
|
||||
ng-model='self.state.noteFilter.text',
|
||||
placeholder='Search',
|
||||
select-on-click='true',
|
||||
placeholder='Search',
|
||||
select-on-click='true',
|
||||
title='Searches notes in the currently selected tag'
|
||||
)
|
||||
#search-clear-button(
|
||||
ng-click='self.clearFilterText();',
|
||||
ng-click='self.clearFilterText();',
|
||||
ng-show='self.state.noteFilter.text'
|
||||
) ✕
|
||||
#notes-menu-bar.sn-component
|
||||
.sk-app-bar.no-edges
|
||||
.left
|
||||
.sk-app-bar-item(
|
||||
ng-class="{'selected' : self.state.mutable.showMenu}",
|
||||
ng-class="{'selected' : self.state.mutable.showMenu}",
|
||||
ng-click='self.state.mutable.showMenu = !self.state.mutable.showMenu'
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
@@ -45,67 +44,67 @@
|
||||
a.info.sk-h5(ng-click='self.toggleReverseSort()')
|
||||
| {{self.state.sortReverse === true ? 'Disable Reverse Sort' : 'Enable Reverse Sort'}}
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.selectedSortByCreated()"
|
||||
circle="self.state.sortBy == 'created_at' && 'success'"
|
||||
desc="'Sort notes by newest first'"
|
||||
action="self.selectedMenuItem(); self.selectedSortByCreated()"
|
||||
circle="self.state.sortBy == 'created_at' && 'success'"
|
||||
desc="'Sort notes by newest first'"
|
||||
label="'Date Added'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.selectedSortByUpdated()"
|
||||
circle="self.state.sortBy == 'client_updated_at' && 'success'"
|
||||
desc="'Sort notes with the most recently updated first'"
|
||||
action="self.selectedMenuItem(); self.selectedSortByUpdated()"
|
||||
circle="self.state.sortBy == 'client_updated_at' && 'success'"
|
||||
desc="'Sort notes with the most recently updated first'"
|
||||
label="'Date Modified'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.selectedSortByTitle()"
|
||||
circle="self.state.sortBy == 'title' && 'success'"
|
||||
desc="'Sort notes alphabetically by their title'"
|
||||
action="self.selectedMenuItem(); self.selectedSortByTitle()"
|
||||
circle="self.state.sortBy == 'title' && 'success'"
|
||||
desc="'Sort notes alphabetically by their title'"
|
||||
label="'Title'"
|
||||
)
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Display
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('showArchived')"
|
||||
circle="self.state.showArchived ? 'success' : 'danger'"
|
||||
desc=`'Archived notes are usually hidden.
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('showArchived')"
|
||||
circle="self.state.showArchived ? 'success' : 'danger'"
|
||||
desc=`'Archived notes are usually hidden.
|
||||
You can explicitly show them with this option.'`
|
||||
faded="!self.state.showArchived"
|
||||
faded="!self.state.showArchived"
|
||||
label="'Archived Notes'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hidePinned')"
|
||||
circle="self.state.hidePinned ? 'danger' : 'success'"
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hidePinned')"
|
||||
circle="self.state.hidePinned ? 'danger' : 'success'"
|
||||
desc=`'Pinned notes always appear on top. You can hide them temporarily
|
||||
with this option so you can focus on other notes in the list.'`
|
||||
faded="self.state.hidePinned"
|
||||
faded="self.state.hidePinned"
|
||||
label="'Pinned Notes'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideNotePreview')"
|
||||
circle="self.state.hideNotePreview ? 'danger' : 'success'"
|
||||
desc="'Hide the note preview for a more condensed list of notes'"
|
||||
faded="self.state.hideNotePreview"
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideNotePreview')"
|
||||
circle="self.state.hideNotePreview ? 'danger' : 'success'"
|
||||
desc="'Hide the note preview for a more condensed list of notes'"
|
||||
faded="self.state.hideNotePreview"
|
||||
label="'Note Preview'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideDate')"
|
||||
circle="self.state.hideDate ? 'danger' : 'success'"
|
||||
desc="'Hide the date displayed in each row'"
|
||||
faded="self.state.hideDate"
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideDate')"
|
||||
circle="self.state.hideDate ? 'danger' : 'success'"
|
||||
desc="'Hide the date displayed in each row'"
|
||||
faded="self.state.hideDate"
|
||||
label="'Date'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideTags')"
|
||||
circle="self.state.hideTags ? 'danger' : 'success'"
|
||||
desc="'Hide the list of tags associated with each note'"
|
||||
faded="self.state.hideTags"
|
||||
action="self.selectedMenuItem(); self.togglePrefKey('hideTags')"
|
||||
circle="self.state.hideTags ? 'danger' : 'success'"
|
||||
desc="'Hide the list of tags associated with each note'"
|
||||
faded="self.state.hideTags"
|
||||
label="'Tags'"
|
||||
)
|
||||
.scrollable
|
||||
#notes-scrollable.infinite-scroll(
|
||||
can-load='true',
|
||||
infinite-scroll='self.paginate()',
|
||||
can-load='true',
|
||||
infinite-scroll='self.paginate()',
|
||||
threshold='200'
|
||||
)
|
||||
.note(
|
||||
@@ -120,12 +119,12 @@
|
||||
| {{note.title}}
|
||||
.note-preview(
|
||||
ng-if=`
|
||||
!self.state.hideNotePreview &&
|
||||
!note.content.hidePreview &&
|
||||
!self.state.hideNotePreview &&
|
||||
!note.content.hidePreview &&
|
||||
!note.content.protected`
|
||||
)
|
||||
.html-preview(
|
||||
ng-bind-html='note.content.preview_html',
|
||||
ng-bind-html='note.content.preview_html',
|
||||
ng-show='note.content.preview_html'
|
||||
)
|
||||
.plain-preview(
|
||||
@@ -135,9 +134,9 @@
|
||||
ng-show='!note.content.preview_html && !note.content.preview_plain'
|
||||
) {{note.text}}
|
||||
.date.faded(ng-show='!self.state.hideDate')
|
||||
span(ng-show="self.state.sortBy == 'client_updated_at'")
|
||||
span(ng-show="self.state.sortBy == 'client_updated_at'")
|
||||
| Modified {{note.cachedUpdatedAtString || 'Now'}}
|
||||
span(ng-show="self.state.sortBy != 'client_updated_at'")
|
||||
span(ng-show="self.state.sortBy != 'client_updated_at'")
|
||||
| {{note.cachedCreatedAtString || 'Now'}}
|
||||
.tags-string(ng-show='note.shouldShowTags')
|
||||
.faded {{note.savedTagsString || note.tagsString()}}
|
||||
|
||||
87030
dist/javascripts/app.js
vendored
87030
dist/javascripts/app.js
vendored
File diff suppressed because one or more lines are too long
2
dist/javascripts/app.js.map
vendored
2
dist/javascripts/app.js.map
vendored
File diff suppressed because one or more lines are too long
2655
dist/stylesheets/app.css
vendored
2655
dist/stylesheets/app.css
vendored
File diff suppressed because one or more lines are too long
2
dist/stylesheets/app.css.map
vendored
2
dist/stylesheets/app.css.map
vendored
File diff suppressed because one or more lines are too long
1331
package-lock.json
generated
1331
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"url": "https://github.com/standardnotes/web"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack --mode development -w",
|
||||
"start": "webpack-dev-server --progress",
|
||||
"bundle": "webpack --mode production",
|
||||
"build": "bundle install && npm install && npm run bundle",
|
||||
"submodules": "git submodule update --init --force --remote",
|
||||
@@ -47,10 +47,11 @@
|
||||
"pug-loader": "^2.4.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"serve-static": "^1.14.1",
|
||||
"sn-stylekit": "2.0.20",
|
||||
"snjs": "github:standardnotes/snjs#ea2f182ba4b53642516714396bbef264f25e373b",
|
||||
"sn-stylekit": "2.0.22",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-cli": "^3.3.10"
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.10.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
|
||||
25
public/manifest.json
Normal file
25
public/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"app": {
|
||||
"launch": {
|
||||
"urls": [
|
||||
"https://app.standardnotes.org/"
|
||||
],
|
||||
"web_url": "https://app.standardnotes.org",
|
||||
"container": "tab"
|
||||
}
|
||||
},
|
||||
"offline_enabled": true,
|
||||
"permissions": [],
|
||||
"requirements": {
|
||||
"3D": {
|
||||
"features": []
|
||||
}
|
||||
},
|
||||
"icons": {
|
||||
"128": "favicon/android-chrome-192x192.png"
|
||||
},
|
||||
"name": "Standard Notes",
|
||||
"description": "A Simple And Private Notes App",
|
||||
"version": "1.0",
|
||||
"manifest_version": 2
|
||||
}
|
||||
@@ -7,6 +7,15 @@ module.exports = {
|
||||
output: {
|
||||
filename: './javascripts/app.js'
|
||||
},
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/extensions': {
|
||||
target: 'http://localhost:3000',
|
||||
pathRewrite: { '^/extensions': '/public/extensions' }
|
||||
}
|
||||
},
|
||||
port: 3000
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
__VERSION__: JSON.stringify(require('./package.json').version)
|
||||
|
||||
Reference in New Issue
Block a user