Remove dummy concept in favor of editor group and editors
This commit is contained in:
@@ -5,18 +5,17 @@ declare const __VERSION__: string
|
||||
import angular from 'angular';
|
||||
import { configRoutes } from './routes';
|
||||
|
||||
import {
|
||||
ApplicationManager
|
||||
} from './applicationManager';
|
||||
import { ApplicationGroup } from './ui_models/application_group';
|
||||
|
||||
import {
|
||||
Root,
|
||||
ApplicationGroupView,
|
||||
ApplicationView,
|
||||
TagsPanel,
|
||||
NotesPanel,
|
||||
EditorPanel,
|
||||
Footer
|
||||
} from './controllers';
|
||||
EditorGroupView,
|
||||
EditorView,
|
||||
TagsView,
|
||||
NotesView,
|
||||
FooterView
|
||||
} from '@/views';
|
||||
|
||||
import {
|
||||
autofocus,
|
||||
@@ -62,13 +61,13 @@ angular
|
||||
// Controllers
|
||||
angular
|
||||
.module('app')
|
||||
.directive('root', () => new Root())
|
||||
.directive('applicationGroupView', () => new ApplicationGroupView())
|
||||
.directive('applicationView', () => new ApplicationView())
|
||||
.directive('tagsPanel', () => new TagsPanel())
|
||||
.directive('notesPanel', () => new NotesPanel())
|
||||
.directive('editorPanel', () => new EditorPanel())
|
||||
.directive('footer', () => new Footer())
|
||||
// .directive('lockScreen', () => new LockScreen());
|
||||
.directive('editorGroupView', () => new EditorGroupView())
|
||||
.directive('editorView', () => new EditorView())
|
||||
.directive('tagsView', () => new TagsView())
|
||||
.directive('notesView', () => new NotesView())
|
||||
.directive('footerView', () => new FooterView())
|
||||
|
||||
// Directives - Functional
|
||||
angular
|
||||
@@ -78,9 +77,7 @@ angular
|
||||
.directive('delayHide', delayHide)
|
||||
.directive('elemReady', elemReady)
|
||||
.directive('fileChange', fileChange)
|
||||
.directive('infiniteScroll', [
|
||||
infiniteScroll
|
||||
])
|
||||
.directive('infiniteScroll', [infiniteScroll])
|
||||
.directive('lowercase', lowercase)
|
||||
.directive('selectOnClick', ['$window', selectOnClick])
|
||||
.directive('snEnter', snEnter);
|
||||
@@ -93,11 +90,6 @@ angular
|
||||
.directive('challengeModal', () => new ChallengeModal())
|
||||
.directive('componentModal', () => new ComponentModal())
|
||||
.directive('componentView', () => new ComponentView())
|
||||
// .directive(
|
||||
// 'componentView',
|
||||
// ($rootScope, componentManager, desktopManager, $timeout) =>
|
||||
// new ComponentView($rootScope, componentManager, desktopManager, $timeout)
|
||||
// )
|
||||
.directive('editorMenu', () => new EditorMenu())
|
||||
.directive('inputModal', () => new InputModal())
|
||||
.directive('menuRow', () => new MenuRow())
|
||||
@@ -116,4 +108,4 @@ angular
|
||||
.filter('trusted', ['$sce', trusted]);
|
||||
|
||||
// Services
|
||||
angular.module('app').service('applicationManager', ApplicationManager);
|
||||
angular.module('app').service('mainApplicationGroup', ApplicationGroup);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export { PureCtrl } from './abstract/pure_ctrl';
|
||||
export { EditorPanel } from './editor';
|
||||
export { Footer } from './footer';
|
||||
export { NotesPanel } from './notes/notes';
|
||||
export { TagsPanel } from './tags';
|
||||
export { Root } from './root';
|
||||
export { ApplicationView } from './applicationView';
|
||||
@@ -1,37 +0,0 @@
|
||||
import { ApplicationManager } from './../applicationManager';
|
||||
import { WebDirective } from './../types';
|
||||
import template from '%/root.pug';
|
||||
import { WebApplication } from '@/application';
|
||||
|
||||
class RootCtrl {
|
||||
|
||||
private $timeout: ng.ITimeoutService
|
||||
private applicationManager: ApplicationManager
|
||||
public applications: WebApplication[] = []
|
||||
|
||||
/* @ngInject */
|
||||
constructor($timeout: ng.ITimeoutService, applicationManager: ApplicationManager) {
|
||||
this.$timeout = $timeout;
|
||||
this.applicationManager = applicationManager;
|
||||
this.applicationManager.addApplicationChangeObserver(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.$timeout(() => {
|
||||
this.applications = this.applicationManager.getApplications();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class Root extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.template = template;
|
||||
this.controller = RootCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import { WebApplication } from './application';
|
||||
import { SNAlertService } from "../../../../snjs/dist/@types";
|
||||
import { RawPayload } from '../../../../snjs/dist/@types/protocol/payloads/generator';
|
||||
|
||||
const DB_NAME = 'standardnotes';
|
||||
const STORE_NAME = 'items';
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import { debounce } from '@/utils';
|
||||
/* @ngInject */
|
||||
export function infiniteScroll() {
|
||||
return {
|
||||
link: function (scope: ng.IScope, elem: JQLite, attrs: any) {
|
||||
const scopeAny = scope as any;
|
||||
const offset = parseInt(attrs.threshold) || 0;
|
||||
const e = elem[0];
|
||||
const element = elem[0];
|
||||
scopeAny.paginate = debounce(() => {
|
||||
scope.$apply(attrs.infiniteScroll);
|
||||
}, 100);
|
||||
scopeAny.onScroll = () => {
|
||||
if (
|
||||
scope.$eval(attrs.canLoad) &&
|
||||
e.scrollTop + e.offsetHeight >= e.scrollHeight - offset
|
||||
element.scrollTop + element.offsetHeight >= element.scrollHeight - offset
|
||||
) {
|
||||
scope.$apply(attrs.infiniteScroll);
|
||||
scopeAny.paginate();
|
||||
}
|
||||
};
|
||||
elem.on('scroll', scopeAny.onScroll);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { WebDirective } from './../../types';
|
||||
import { isDesktopApplication, isNullOrUndefined } from '@/utils';
|
||||
import template from '%/directives/account-menu.pug';
|
||||
import { ProtectedAction, ContentType } from 'snjs';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import {
|
||||
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
|
||||
STRING_SIGN_OUT_CONFIRMATION,
|
||||
@@ -59,7 +59,7 @@ type AccountMenuState = {
|
||||
importData: any
|
||||
}
|
||||
|
||||
class AccountMenuCtrl extends PureCtrl {
|
||||
class AccountMenuCtrl extends PureViewCtrl {
|
||||
|
||||
public appVersion: string
|
||||
private syncStatus?: SyncOpStatus
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from '%/directives/actions-menu.pug';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { SNItem, Action, SNActionsExtension } from '@/../../../../snjs/dist/@types';
|
||||
import { ActionResponse } from '@/../../../../snjs/dist/@types/services/actions_service';
|
||||
|
||||
@@ -10,7 +10,7 @@ type ActionsMenuScope = {
|
||||
item: SNItem
|
||||
}
|
||||
|
||||
class ActionsMenuCtrl extends PureCtrl implements ActionsMenuScope {
|
||||
class ActionsMenuCtrl extends PureViewCtrl implements ActionsMenuScope {
|
||||
|
||||
application!: WebApplication
|
||||
item!: SNItem
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebDirective } from './../../types';
|
||||
import { WebApplication } from './../../application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import template from '%/directives/challenge-modal.pug';
|
||||
import {
|
||||
ChallengeType,
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
Challenge,
|
||||
ChallengeOrchestrator
|
||||
} from 'snjs';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
|
||||
type InputValue = {
|
||||
value: string
|
||||
@@ -28,7 +28,7 @@ type ChallengeModalState = {
|
||||
processing: boolean
|
||||
}
|
||||
|
||||
class ChallengeModalCtrl extends PureCtrl implements ChallengeModalScope {
|
||||
class ChallengeModalCtrl extends PureViewCtrl implements ChallengeModalScope {
|
||||
private $element: JQLite
|
||||
private processingTypes: ChallengeType[] = []
|
||||
application!: WebApplication
|
||||
@@ -159,7 +159,8 @@ export class ChallengeModal extends WebDirective {
|
||||
this.template = template;
|
||||
this.controller = ChallengeModalCtrl;
|
||||
this.controllerAs = 'ctrl';
|
||||
this.bindToController = {
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
challenge: '=',
|
||||
orchestrator: '=',
|
||||
application: '='
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from './../../application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { SNComponent } from 'snjs';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from '%/directives/component-modal.pug';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { SNComponent } from 'snjs';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from '%/directives/component-view.pug';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { WebDirective } from './../../types';
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { SNComponent, SNItem, ComponentArea } from 'snjs';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import template from '%/directives/editor-menu.pug';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
|
||||
|
||||
interface EditorMenuScope {
|
||||
@@ -13,7 +13,7 @@ interface EditorMenuScope {
|
||||
application: WebApplication
|
||||
}
|
||||
|
||||
class EditorMenuCtrl extends PureCtrl implements EditorMenuScope {
|
||||
class EditorMenuCtrl extends PureViewCtrl implements EditorMenuScope {
|
||||
|
||||
callback!: (component: SNComponent) => void
|
||||
selectedEditor!: SNComponent
|
||||
|
||||
@@ -84,7 +84,7 @@ class PanelResizerCtrl implements PanelResizerScope {
|
||||
this.$timeout = $timeout;
|
||||
|
||||
/** To allow for registering events */
|
||||
this.handleResize = this.handleResize.bind(this);
|
||||
this.handleResize = debounce(this.handleResize.bind(this), 250);
|
||||
this.onMouseMove = this.onMouseMove.bind(this);
|
||||
this.onMouseUp = this.onMouseUp.bind(this);
|
||||
this.onMouseDown = this.onMouseDown.bind(this);
|
||||
@@ -163,13 +163,11 @@ class PanelResizerCtrl implements PanelResizerScope {
|
||||
}
|
||||
|
||||
handleResize() {
|
||||
debounce(() => {
|
||||
this.reloadDefaultValues();
|
||||
this.handleWidthEvent();
|
||||
this.$timeout(() => {
|
||||
this.finishSettingWidth();
|
||||
});
|
||||
}, 250);
|
||||
this.reloadDefaultValues();
|
||||
this.handleWidthEvent();
|
||||
this.$timeout(() => {
|
||||
this.finishSettingWidth();
|
||||
});
|
||||
}
|
||||
|
||||
getParentRect() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { WebApplication } from './../../application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { PasswordWizardScope, PasswordWizardType, WebDirective } from './../../types';
|
||||
import template from '%/directives/password-wizard.pug';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
|
||||
const DEFAULT_CONTINUE_TITLE = "Continue";
|
||||
const Steps = {
|
||||
@@ -9,7 +9,7 @@ const Steps = {
|
||||
FinishStep: 2
|
||||
};
|
||||
|
||||
class PasswordWizardCtrl extends PureCtrl implements PasswordWizardScope {
|
||||
class PasswordWizardCtrl extends PureViewCtrl implements PasswordWizardScope {
|
||||
$element: JQLite
|
||||
application!: WebApplication
|
||||
type!: PasswordWizardType
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebDirective } from './../../types';
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { ProtectedAction, PrivilegeCredential, PrivilegeSessionLength } from 'snjs';
|
||||
import template from '%/directives/privileges-auth-modal.pug';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { WebDirective } from './../../types';
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import template from '%/directives/privileges-management-modal.pug';
|
||||
import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from 'snjs';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { PrivilegeMutator } from '@/../../../../snjs/dist/@types/models';
|
||||
|
||||
type DisplayInfo = {
|
||||
@@ -10,7 +10,7 @@ type DisplayInfo = {
|
||||
prompt: string
|
||||
}
|
||||
|
||||
class PrivilegesManagementModalCtrl extends PureCtrl {
|
||||
class PrivilegesManagementModalCtrl extends PureViewCtrl {
|
||||
|
||||
hasPasscode = false
|
||||
hasAccount = false
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { WebDirective } from './../../types';
|
||||
import {
|
||||
ContentType,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebDirective } from './../../types';
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import template from '%/directives/session-history-menu.pug';
|
||||
import { SNItem, ItemHistoryEntry, ItemHistory } from '@/../../../../snjs/dist/@types';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from '%/directives/sync-resolution-menu.pug';
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import { SKAlert } from 'sn-stylekit';
|
||||
|
||||
export class AlertService extends SNAlertService {
|
||||
async alert(
|
||||
title: string,
|
||||
text: string,
|
||||
title: string,
|
||||
closeButtonText = 'OK',
|
||||
onClose: () => void
|
||||
) {
|
||||
@@ -28,8 +28,8 @@ export class AlertService extends SNAlertService {
|
||||
}
|
||||
|
||||
async confirm(
|
||||
title: string,
|
||||
text: string,
|
||||
title: string,
|
||||
confirmButtonText = 'Confirm',
|
||||
cancelButtonText = 'Cancel',
|
||||
onConfirm: () => void,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote } from 'snjs';
|
||||
|
||||
export class ArchiveManager {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SNComponent, PurePayload, ComponentMutator, AppDataField } from 'snjs';
|
||||
/* eslint-disable camelcase */
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
// An interface used by the Desktop app to interact with SN
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from 'snjs';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from './../application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import {
|
||||
SNPredicate,
|
||||
ContentType,
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { WebApplication } from './../application';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import pull from 'lodash/pull';
|
||||
import { ProtectedAction, ApplicationEvent, SNTag, SNNote, SNUserPrefs, ContentType, SNSmartTag } from 'snjs';
|
||||
import {
|
||||
ProtectedAction,
|
||||
ApplicationEvent,
|
||||
SNTag,
|
||||
SNNote,
|
||||
SNUserPrefs,
|
||||
ContentType,
|
||||
SNSmartTag
|
||||
} from 'snjs';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { Editor } from '@/ui_models/editor';
|
||||
|
||||
export enum AppStateEvent {
|
||||
TagChanged = 1,
|
||||
NoteChanged = 2,
|
||||
ActiveEditorChanged = 2,
|
||||
PreferencesChanged = 3,
|
||||
PanelResized = 4,
|
||||
EditorFocused = 5,
|
||||
@@ -34,8 +43,8 @@ export class AppState {
|
||||
rootScopeCleanup2: any
|
||||
onVisibilityChange: any
|
||||
selectedTag?: SNTag
|
||||
selectedNote?: SNNote
|
||||
userPreferences?: SNUserPrefs
|
||||
multiEditorEnabled = false
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
@@ -74,16 +83,90 @@ export class AppState {
|
||||
this.onVisibilityChange = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new editor if one doesn't exist. If one does, we'll replace the
|
||||
* editor's note with an empty one.
|
||||
*/
|
||||
createEditor(title?: string) {
|
||||
const activeEditor = this.getActiveEditor();
|
||||
if (!activeEditor || this.multiEditorEnabled) {
|
||||
this.application.editorGroup.createEditor(title);
|
||||
} else {
|
||||
activeEditor.reset(title);
|
||||
}
|
||||
}
|
||||
|
||||
async openEditor(noteUuid: string) {
|
||||
const note = this.application.findItem(noteUuid) as SNNote;
|
||||
const run = async () => {
|
||||
const activeEditor = this.getActiveEditor();
|
||||
if (!activeEditor || this.multiEditorEnabled) {
|
||||
this.application.editorGroup.createEditor(noteUuid);
|
||||
} else {
|
||||
activeEditor.setNote(note);
|
||||
}
|
||||
await this.notifyEvent(AppStateEvent.ActiveEditorChanged);
|
||||
};
|
||||
if (note && note.safeContent.protected &&
|
||||
await this.application.privilegesService!.actionRequiresPrivilege(
|
||||
ProtectedAction.ViewProtectedNotes
|
||||
)) {
|
||||
return new Promise((resolve) => {
|
||||
this.application.presentPrivilegesModal(
|
||||
ProtectedAction.ViewProtectedNotes,
|
||||
() => {
|
||||
run().then(resolve);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return run();
|
||||
}
|
||||
}
|
||||
|
||||
getActiveEditor() {
|
||||
return this.application.editorGroup.editors[0];
|
||||
}
|
||||
|
||||
getEditors() {
|
||||
return this.application.editorGroup.editors;
|
||||
}
|
||||
|
||||
closeEditor(editor: Editor) {
|
||||
this.application.editorGroup.closeEditor(editor);
|
||||
}
|
||||
|
||||
closeActiveEditor() {
|
||||
this.application.editorGroup.closeActiveEditor();
|
||||
}
|
||||
|
||||
closeAllEditors() {
|
||||
this.application.editorGroup.closeAllEditors();
|
||||
}
|
||||
|
||||
editorForNote(note: SNNote) {
|
||||
for (const editor of this.getEditors()) {
|
||||
if (editor.note.uuid === note.uuid) {
|
||||
return editor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
streamNotesAndTags() {
|
||||
this.application!.streamItems(
|
||||
[ContentType.Note, ContentType.Tag],
|
||||
async (items) => {
|
||||
if(this.selectedNote) {
|
||||
const matchingNote = items.find((candidate) => candidate.uuid === this.selectedNote!.uuid);
|
||||
if(matchingNote) {
|
||||
this.selectedNote = matchingNote as SNNote;
|
||||
/** Close any editors for deleted notes */
|
||||
const notes = items.filter((candidate) => candidate.content_type === ContentType.Note) as SNNote[];
|
||||
for (const note of notes) {
|
||||
if (note.deleted) {
|
||||
const editor = this.editorForNote(note);
|
||||
if (editor) {
|
||||
this.closeEditor(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.selectedTag) {
|
||||
const matchingTag = items.find((candidate) => candidate.uuid === this.selectedTag!.uuid);
|
||||
if (matchingTag) {
|
||||
@@ -161,32 +244,6 @@ export class AppState {
|
||||
);
|
||||
}
|
||||
|
||||
async setSelectedNote(note?: SNNote) {
|
||||
const run = async () => {
|
||||
const previousNote = this.selectedNote;
|
||||
this.selectedNote = note;
|
||||
await this.notifyEvent(
|
||||
AppStateEvent.NoteChanged,
|
||||
{ previousNote: previousNote }
|
||||
);
|
||||
};
|
||||
if (note && note.safeContent.protected &&
|
||||
await this.application.privilegesService!.actionRequiresPrivilege(
|
||||
ProtectedAction.ViewProtectedNotes
|
||||
)) {
|
||||
return new Promise((resolve) => {
|
||||
this.application.presentPrivilegesModal(
|
||||
ProtectedAction.ViewProtectedNotes,
|
||||
() => {
|
||||
run().then(resolve);
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return run();
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the tags that are referncing this note */
|
||||
getNoteTags(note: SNNote) {
|
||||
return this.application.referencingForItem(note).filter((ref) => {
|
||||
@@ -196,11 +253,11 @@ export class AppState {
|
||||
|
||||
/** Returns the notes this tag references */
|
||||
getTagNotes(tag: SNTag) {
|
||||
if(tag.isSmartTag()) {
|
||||
if (tag.isSmartTag()) {
|
||||
return this.application.notesMatchingSmartTag(tag as SNSmartTag);
|
||||
} else {
|
||||
return this.application.referencesForItem(tag).filter((ref) => {
|
||||
return ref.content_type === tag.content_type;
|
||||
return ref.content_type === ContentType.Note;
|
||||
}) as SNNote[]
|
||||
}
|
||||
}
|
||||
@@ -209,10 +266,6 @@ export class AppState {
|
||||
return this.selectedTag;
|
||||
}
|
||||
|
||||
getSelectedNote() {
|
||||
return this.selectedNote;
|
||||
}
|
||||
|
||||
setUserPreferences(preferences: SNUserPrefs) {
|
||||
this.userPreferences = preferences;
|
||||
this.notifyEvent(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { removeFromArray } from 'snjs';
|
||||
import { FooterStatus } from './../types';
|
||||
import { FooterStatus } from '@/types';
|
||||
|
||||
type StatusCallback = (string: string) => void
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { WebApplication } from '@/application';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
StorageValueModes,
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
"paths": {
|
||||
"%/*": ["../templates/*"],
|
||||
"@/*": ["./*"],
|
||||
"@Controllers/*": ["./controllers/*"]
|
||||
"@Controllers/*": ["./controllers/*"],
|
||||
"@Views/*": ["./views/*"],
|
||||
"@Services/*": ["./services/*"],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { InputModalScope } from './directives/views/inputModal';
|
||||
import { PasswordWizardType, PasswordWizardScope } from './types';
|
||||
import { EditorGroup } from '@/ui_models/editor_group';
|
||||
import { InputModalScope } from '@/directives/views/inputModal';
|
||||
import { PasswordWizardType, PasswordWizardScope } from '@/types';
|
||||
import {
|
||||
Environment,
|
||||
SNApplication,
|
||||
@@ -23,7 +24,7 @@ import {
|
||||
ThemeManager,
|
||||
PreferencesManager,
|
||||
KeyboardManager
|
||||
} from './services';
|
||||
} from '@/services';
|
||||
|
||||
type WebServices = {
|
||||
appState: AppState
|
||||
@@ -44,6 +45,7 @@ export class WebApplication extends SNApplication {
|
||||
private onDeinit?: (app: WebApplication) => void
|
||||
private webServices!: WebServices
|
||||
private currentAuthenticationElement?: JQLite
|
||||
public editorGroup: EditorGroup
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
@@ -71,6 +73,7 @@ export class WebApplication extends SNApplication {
|
||||
this.scope = scope;
|
||||
this.onDeinit = onDeinit;
|
||||
deviceInterface.setApplication(this);
|
||||
this.editorGroup = new EditorGroup(this);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
@@ -86,6 +89,7 @@ export class WebApplication extends SNApplication {
|
||||
this.onDeinit!(this);
|
||||
this.onDeinit = undefined;
|
||||
this.$compile = undefined;
|
||||
this.editorGroup.deinit();
|
||||
(this.scope! as any).application = undefined;
|
||||
this.scope!.$destroy();
|
||||
this.scope = undefined;
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
StatusManager,
|
||||
ThemeManager,
|
||||
AppState
|
||||
} from './services';
|
||||
} from '@/services';
|
||||
|
||||
type AppManagerChangeCallback = () => void
|
||||
|
||||
export class ApplicationManager {
|
||||
export class ApplicationGroup {
|
||||
|
||||
$compile: ng.ICompileService
|
||||
$rootScope: ng.IRootScopeService
|
||||
104
app/assets/javascripts/ui_models/editor.ts
Normal file
104
app/assets/javascripts/ui_models/editor.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { SNNote, ContentType, PayloadSource } from 'snjs';
|
||||
import { WebApplication } from './application';
|
||||
|
||||
export class Editor {
|
||||
|
||||
public note!: SNNote
|
||||
private application: WebApplication
|
||||
private _onNoteChange?: () => void
|
||||
private _onNoteValueChange?: (note: SNNote, source?: PayloadSource) => void
|
||||
private removeStreamObserver: () => void
|
||||
public isTemplateNote = true
|
||||
|
||||
constructor(
|
||||
application: WebApplication,
|
||||
noteUuid?: string,
|
||||
noteTitle?: string
|
||||
) {
|
||||
this.application = application;
|
||||
if (noteUuid) {
|
||||
this.note = application.findItem(noteUuid) as SNNote;
|
||||
} else {
|
||||
this.reset(noteTitle);
|
||||
}
|
||||
|
||||
this.removeStreamObserver = this.application.streamItems(
|
||||
ContentType.Note,
|
||||
async (items, source) => {
|
||||
await this.handleNoteStream(items as SNNote[], source);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private async handleNoteStream(notes: SNNote[], source?: PayloadSource) {
|
||||
/** Update our note object reference whenever it changes */
|
||||
const matchingNote = notes.find((item) => {
|
||||
return item.uuid === this.note.uuid;
|
||||
}) as SNNote;
|
||||
if (matchingNote) {
|
||||
this.isTemplateNote = false;
|
||||
this.note = matchingNote;
|
||||
this._onNoteValueChange!(matchingNote, source);
|
||||
}
|
||||
}
|
||||
|
||||
async insertTemplatedNote() {
|
||||
return this.application.insertItem(this.note);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the editor to a blank state, removing any existing note from view,
|
||||
* and creating a placeholder note.
|
||||
*/
|
||||
async reset(noteTitle?: string) {
|
||||
const note = await this.application.createTemplateItem(
|
||||
ContentType.Note,
|
||||
{
|
||||
text: '',
|
||||
title: noteTitle || '',
|
||||
references: []
|
||||
}
|
||||
);
|
||||
this.isTemplateNote = true;
|
||||
this.setNote(note as SNNote);
|
||||
}
|
||||
|
||||
deinit() {
|
||||
this.removeStreamObserver();
|
||||
(this.removeStreamObserver as any) = undefined;
|
||||
this._onNoteChange = undefined;
|
||||
(this.application as any) = undefined;
|
||||
this._onNoteChange = undefined;
|
||||
this._onNoteValueChange = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register to be notified when the editor's note changes.
|
||||
*/
|
||||
public onNoteChange(onNoteChange: () => void) {
|
||||
this._onNoteChange = onNoteChange;
|
||||
if (this.note) {
|
||||
onNoteChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register to be notified when the editor's note's values change
|
||||
* (and thus a new object reference is created)
|
||||
*/
|
||||
public onNoteValueChange(
|
||||
onNoteValueChange: (note: SNNote, source?: PayloadSource) => void
|
||||
) {
|
||||
this._onNoteValueChange = onNoteValueChange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the editor contents by setting its note.
|
||||
*/
|
||||
public setNote(note: SNNote) {
|
||||
this.note = note;
|
||||
if (this._onNoteChange) {
|
||||
this._onNoteChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
69
app/assets/javascripts/ui_models/editor_group.ts
Normal file
69
app/assets/javascripts/ui_models/editor_group.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { removeFromArray } from 'snjs';
|
||||
import { Editor } from './editor';
|
||||
import { WebApplication } from './application';
|
||||
|
||||
type EditorGroupChangeCallback = () => void
|
||||
|
||||
export class EditorGroup {
|
||||
|
||||
public editors: Editor[] = []
|
||||
private application: WebApplication
|
||||
changeObservers: EditorGroupChangeCallback[] = []
|
||||
|
||||
constructor(application: WebApplication) {
|
||||
this.application = application;
|
||||
}
|
||||
|
||||
public deinit() {
|
||||
(this.application as any) = undefined;
|
||||
for (const editor of this.editors) {
|
||||
this.deleteEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
createEditor(noteUuid?: string, noteTitle?: string) {
|
||||
const editor = new Editor(this.application, noteUuid, noteTitle);
|
||||
this.editors.push(editor);
|
||||
this.notifyObservers();
|
||||
}
|
||||
|
||||
deleteEditor(editor: Editor) {
|
||||
editor.deinit();
|
||||
removeFromArray(this.editors, editor);
|
||||
}
|
||||
|
||||
closeEditor(editor: Editor) {
|
||||
this.deleteEditor(editor);
|
||||
this.notifyObservers();
|
||||
}
|
||||
|
||||
closeActiveEditor() {
|
||||
this.deleteEditor(this.editors[0]);
|
||||
}
|
||||
|
||||
closeAllEditors() {
|
||||
for(const editor of this.editors) {
|
||||
this.deleteEditor(editor);
|
||||
}
|
||||
}
|
||||
|
||||
get activeEditor() {
|
||||
return this.editors[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies observer when the active editor has changed.
|
||||
*/
|
||||
public addChangeObserver(callback: EditorGroupChangeCallback) {
|
||||
this.changeObservers.push(callback);
|
||||
if (this.activeEditor) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
private notifyObservers() {
|
||||
for (const observer of this.changeObservers) {
|
||||
observer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
import { WebApplication } from './../../application';
|
||||
import { ApplicationEvent } from 'snjs';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
|
||||
export type CtrlState = Partial<Record<string, any>>
|
||||
export type CtrlProps = Partial<Record<string, any>>
|
||||
|
||||
export class PureCtrl {
|
||||
export class PureViewCtrl {
|
||||
$timeout: ng.ITimeoutService
|
||||
/** Passed through templates */
|
||||
application?: WebApplication
|
||||
@@ -0,0 +1,17 @@
|
||||
.main-ui-view(
|
||||
ng-class='self.platformString'
|
||||
)
|
||||
#app.app(
|
||||
ng-class='self.state.appClass',
|
||||
ng-if='!self.state.needsUnlock && self.state.ready'
|
||||
)
|
||||
tags-view(application='self.application')
|
||||
notes-view(application='self.application')
|
||||
editor-group-view(
|
||||
application='self.application'
|
||||
)
|
||||
|
||||
footer-view(
|
||||
ng-if='!self.state.needsUnlock && self.state.ready'
|
||||
application='self.application'
|
||||
)
|
||||
@@ -1,21 +1,21 @@
|
||||
import { PanelPuppet, WebDirective, PermissionsModalScope, ModalComponentScope } from './../types';
|
||||
import { WebDirective, PermissionsModalScope, ModalComponentScope } from '@/types';
|
||||
import { getPlatformString } from '@/utils';
|
||||
import template from '%/application-view.pug';
|
||||
import template from './application-view.pug';
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
import { ApplicationEvent, SNComponent } from 'snjs';
|
||||
import angular from 'angular';
|
||||
import {
|
||||
PANEL_NAME_NOTES,
|
||||
PANEL_NAME_TAGS
|
||||
} from '@/controllers/constants';
|
||||
} from '@/views/constants';
|
||||
import {
|
||||
STRING_SESSION_EXPIRED,
|
||||
STRING_DEFAULT_FILE_ERROR
|
||||
} from '@/strings';
|
||||
import { PureCtrl } from './abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { PermissionDialog } from '@/../../../../snjs/dist/@types/services/component_manager';
|
||||
|
||||
class ApplicationViewCtrl extends PureCtrl {
|
||||
class ApplicationViewCtrl extends PureViewCtrl {
|
||||
private $compile?: ng.ICompileService
|
||||
private $location?: ng.ILocationService
|
||||
private $rootScope?: ng.IRootScopeService
|
||||
@@ -47,7 +47,7 @@ class ApplicationViewCtrl extends PureCtrl {
|
||||
this.presentPermissionsDialog = this.presentPermissionsDialog.bind(this);
|
||||
this.addDragDropHandlers();
|
||||
}
|
||||
|
||||
|
||||
deinit() {
|
||||
this.$location = undefined;
|
||||
this.$rootScope = undefined;
|
||||
@@ -61,7 +61,7 @@ class ApplicationViewCtrl extends PureCtrl {
|
||||
(this.presentPermissionsDialog as any) = undefined;
|
||||
super.deinit();
|
||||
}
|
||||
|
||||
|
||||
$onInit() {
|
||||
super.$onInit();
|
||||
this.loadApplication();
|
||||
@@ -226,7 +226,7 @@ class ApplicationViewCtrl extends PureCtrl {
|
||||
scope.component = dialog.component;
|
||||
scope.callback = dialog.callback;
|
||||
const el = this.$compile!(
|
||||
"<permissions-modal component='component' permissions-string='permissionsString'"
|
||||
"<permissions-modal component='component' permissions-string='permissionsString'"
|
||||
+ " callback='callback' class='sk-modal'></permissions-modal>"
|
||||
)(scope as any);
|
||||
angular.element(document.body).append(el);
|
||||
@@ -310,7 +310,8 @@ export class ApplicationView extends WebDirective {
|
||||
this.controller = ApplicationViewCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = {
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
application: '='
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
application-view(
|
||||
ng-repeat='application in self.applications',
|
||||
application='application'
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ApplicationGroup } from '@/ui_models/application_group';
|
||||
import { WebDirective } from '@/types';
|
||||
import template from './application-group-view.pug';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
|
||||
class ApplicationGroupViewCtrl {
|
||||
|
||||
private $timeout: ng.ITimeoutService
|
||||
private applicationGroup: ApplicationGroup
|
||||
public applications: WebApplication[] = []
|
||||
|
||||
/* @ngInject */
|
||||
constructor(
|
||||
$timeout: ng.ITimeoutService,
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
) {
|
||||
this.$timeout = $timeout;
|
||||
this.applicationGroup = mainApplicationGroup;
|
||||
this.applicationGroup.addApplicationChangeObserver(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
reload() {
|
||||
this.$timeout(() => {
|
||||
this.applications = this.applicationGroup.getApplications();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ApplicationGroupView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.template = template;
|
||||
this.controller = ApplicationGroupViewCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
}
|
||||
}
|
||||
259
app/assets/javascripts/views/editor/editor-view.pug
Normal file
259
app/assets/javascripts/views/editor/editor-view.pug
Normal file
@@ -0,0 +1,259 @@
|
||||
#editor-column.section.editor.sn-component(aria-label='Note')
|
||||
.sn-component
|
||||
.sk-app-bar.no-edges(
|
||||
ng-if='self.noteLocked',
|
||||
ng-init="self.lockText = 'Note Locked'",
|
||||
ng-mouseleave="self.lockText = 'Note Locked'",
|
||||
ng-mouseover="self.lockText = 'Unlock'"
|
||||
)
|
||||
.left
|
||||
.sk-app-bar-item(ng-click='self.toggleLockNote()')
|
||||
.sk-label.warning
|
||||
i.icon.ion-locked
|
||||
| {{self.lockText}}
|
||||
#editor-title-bar.section-title-bar(
|
||||
ng-class="{'locked' : self.noteLocked}",
|
||||
ng-show='self.note && !self.note.errorDecrypting'
|
||||
)
|
||||
.title
|
||||
input#note-title-editor.input(
|
||||
ng-blur='self.onTitleBlur()',
|
||||
ng-change='self.onTitleChange()',
|
||||
ng-disabled='self.noteLocked',
|
||||
ng-focus='self.onTitleFocus()',
|
||||
ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)',
|
||||
ng-model='self.editorValues.title',
|
||||
select-on-click='true',
|
||||
spellcheck='false')
|
||||
#save-status
|
||||
.message(
|
||||
ng-class="{'warning sk-bold': self.state.syncTakingTooLong, 'danger sk-bold': self.state.saveError}"
|
||||
) {{self.state.noteStatus.message}}
|
||||
.desc(ng-show='self.state.noteStatus.desc') {{self.state.noteStatus.desc}}
|
||||
.editor-tags
|
||||
#note-tags-component-container(ng-if='self.state.tagsComponent')
|
||||
component-view.component-view(
|
||||
component='self.state.tagsComponent',
|
||||
ng-class="{'locked' : self.noteLocked}",
|
||||
ng-style="self.noteLocked && {'pointer-events' : 'none'}",
|
||||
application='self.application'
|
||||
)
|
||||
input.tags-input(
|
||||
ng-blur='self.saveTagsFromStrings()',
|
||||
ng-disabled='self.noteLocked',
|
||||
ng-if='!(self.state.tagsComponent && self.state.tagsComponent.active)',
|
||||
ng-keyup='$event.keyCode == 13 && $event.target.blur();',
|
||||
ng-model='self.editorValues.tagsInputValue',
|
||||
placeholder='#tags',
|
||||
spellcheck='false',
|
||||
type='text'
|
||||
)
|
||||
.sn-component(ng-if='self.note')
|
||||
#editor-menu-bar.sk-app-bar.no-edges
|
||||
.left
|
||||
.sk-app-bar-item(
|
||||
click-outside=`self.setMenuState('showOptionsMenu', false)`,
|
||||
is-open='self.state.showOptionsMenu',
|
||||
ng-class="{'selected' : self.state.showOptionsMenu}",
|
||||
ng-click="self.toggleMenu('showOptionsMenu')"
|
||||
)
|
||||
.sk-label Options
|
||||
.sk-menu-panel.dropdown-menu(ng-if='self.state.showOptionsMenu')
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Note Options
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.togglePin()',
|
||||
desc="'Pin or unpin a note from the top of your list'",
|
||||
label="self.note.pinned ? 'Unpin' : 'Pin'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.toggleArchiveNote()',
|
||||
desc="'Archive or unarchive a note from your Archived system tag'",
|
||||
label="self.note.archived ? 'Unarchive' : 'Archive'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.toggleLockNote()',
|
||||
desc="'Locking notes prevents unintentional editing'",
|
||||
label="self.noteLocked ? 'Unlock' : 'Lock'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.toggleProtectNote()',
|
||||
desc=`'Protecting a note will require credentials to view
|
||||
it (Manage Privileges via Account menu)'`,
|
||||
label="self.note.protected ? 'Unprotect' : 'Protect'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.toggleNotePreview()',
|
||||
circle="self.note.hidePreview ? 'danger' : 'success'",
|
||||
circle-align="'right'",
|
||||
desc="'Hide or unhide the note preview from the list of notes'",
|
||||
label="'Preview'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(); self.deleteNote()',
|
||||
desc="'Send this note to the trash'",
|
||||
label="'Move to Trash'",
|
||||
ng-show='!self.state.altKeyDown && !self.note.trashed && !self.note.errorDecrypting',
|
||||
stylekit-class="'warning'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(); self.deleteNotePermanantely()',
|
||||
desc="'Delete this note permanently from all your devices'",
|
||||
label="'Delete Permanently'",
|
||||
ng-show='!self.note.trashed && self.note.errorDecrypting',
|
||||
stylekit-class="'danger'"
|
||||
)
|
||||
div(ng-if='self.note.trashed || self.state.altKeyDown')
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.restoreTrashedNote()',
|
||||
desc="'Undelete this note and restore it back into your notes'",
|
||||
label="'Restore'",
|
||||
ng-show='self.note.trashed',
|
||||
stylekit-class="'info'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.deleteNotePermanantely()',
|
||||
desc="'Delete this note permanently from all your devices'",
|
||||
label="'Delete Permanently'",
|
||||
stylekit-class="'danger'"
|
||||
)
|
||||
menu-row(
|
||||
action='self.selectedMenuItem(true); self.emptyTrash()',
|
||||
desc="'Permanently delete all notes in the trash'",
|
||||
label="'Empty Trash'",
|
||||
ng-show='self.note.trashed || !self.state.altKeyDown',
|
||||
stylekit-class="'danger'",
|
||||
subtitle="self.getTrashCount() + ' notes in trash'"
|
||||
)
|
||||
.sk-menu-panel-section
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Global Display
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeyMonospace)",
|
||||
circle="self.state.monospaceEnabled ? 'success' : 'neutral'",
|
||||
desc="'Toggles the font style for the default editor'",
|
||||
disabled='self.state.selectedEditor',
|
||||
label="'Monospace Font'",
|
||||
subtitle="self.state.selectedEditor ? 'Not available with editor extensions' : null"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeySpellcheck)",
|
||||
circle="self.state.spellcheck ? 'success' : 'neutral'",
|
||||
desc="'Toggles spellcheck for the default editor'",
|
||||
disabled='self.state.selectedEditor',
|
||||
label="'Spellcheck'",
|
||||
subtitle=`
|
||||
self.state.selectedEditor
|
||||
? 'Not available with editor extensions'
|
||||
: (self.state.isDesktop ? 'May degrade editor performance' : null)
|
||||
`)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(true); self.toggleWebPrefKey(self.prefKeyMarginResizers)",
|
||||
circle="self.state.marginResizersEnabled ? 'success' : 'neutral'",
|
||||
desc="'Allows for editor left and right margins to be resized'",
|
||||
faded='!self.state.marginResizersEnabled',
|
||||
label="'Margin Resizers'"
|
||||
)
|
||||
.sk-app-bar-item(
|
||||
click-outside=`self.setMenuState('showEditorMenu', false)`
|
||||
is-open='self.state.showEditorMenu',
|
||||
ng-class="{'selected' : self.state.showEditorMenu}",
|
||||
ng-click="self.toggleMenu('showEditorMenu')"
|
||||
)
|
||||
.sk-label Editor
|
||||
editor-menu(
|
||||
callback='self.editorMenuOnSelect()',
|
||||
current-item='self.note',
|
||||
ng-if='self.state.showEditorMenu',
|
||||
selected-editor='self.state.selectedEditor',
|
||||
application='self.application'
|
||||
)
|
||||
.sk-app-bar-item(
|
||||
click-outside=`self.setMenuState('showExtensions', false)`,
|
||||
is-open='self.state.showExtensions',
|
||||
ng-class="{'selected' : self.state.showExtensions}",
|
||||
ng-click="self.toggleMenu('showExtensions')"
|
||||
)
|
||||
.sk-label Actions
|
||||
actions-menu(
|
||||
item='self.note',
|
||||
ng-if='self.state.showExtensions',
|
||||
application='self.application'
|
||||
)
|
||||
.sk-app-bar-item(
|
||||
click-outside=`self.setMenuState('showSessionHistory', false)`,
|
||||
is-open='self.state.showSessionHistory',
|
||||
ng-click="self.toggleMenu('showSessionHistory')"
|
||||
)
|
||||
.sk-label Session History
|
||||
session-history-menu(
|
||||
item='self.note',
|
||||
ng-if='self.state.showSessionHistory',
|
||||
application='self.application'
|
||||
)
|
||||
#editor-content.editor-content(
|
||||
ng-if='self.state.noteReady && !self.note.errorDecrypting'
|
||||
)
|
||||
panel-resizer.left(
|
||||
control='self.leftPanelPuppet',
|
||||
hoverable='true',
|
||||
min-width='300',
|
||||
ng-if='self.state.marginResizersEnabled',
|
||||
on-resize-finish='self.onPanelResizeFinish()',
|
||||
panel-id="'editor-content'",
|
||||
property="'left'"
|
||||
)
|
||||
component-view.component-view(
|
||||
component='self.state.selectedEditor',
|
||||
ng-if='self.state.selectedEditor',
|
||||
on-load='self.onEditorLoad',
|
||||
application='self.application'
|
||||
)
|
||||
textarea#note-text-editor.editable(
|
||||
dir='auto',
|
||||
ng-attr-spellcheck='{{self.state.spellcheck}}',
|
||||
ng-change='self.contentChanged()',
|
||||
ng-click='self.clickedTextArea()',
|
||||
ng-focus='self.onContentFocus()',
|
||||
ng-if='!self.state.selectedEditor',
|
||||
ng-model='self.editorValues.text',
|
||||
ng-model-options='{ debounce: self.state.editorDebounce}',
|
||||
ng-readonly='self.noteLocked',
|
||||
ng-trim='false'
|
||||
)
|
||||
| {{self.onSystemEditorLoad()}}
|
||||
panel-resizer(
|
||||
control='self.rightPanelPuppet',
|
||||
hoverable='true', min-width='300',
|
||||
ng-if='self.state.marginResizersEnabled',
|
||||
on-resize-finish='self.onPanelResizeFinish()',
|
||||
panel-id="'editor-content'",
|
||||
property="'right'"
|
||||
)
|
||||
.section(ng-show='self.note.errorDecrypting')
|
||||
p.medium-padding(style='padding-top: 0 !important;')
|
||||
| There was an error decrypting this item. Ensure you are running the
|
||||
| latest version of this app, then sign out and sign back in to try again.
|
||||
#editor-pane-component-stack(ng-show='self.note')
|
||||
#component-stack-menu-bar.sk-app-bar.no-edges(ng-if='self.state.componentStack.length')
|
||||
.left
|
||||
.sk-app-bar-item(
|
||||
ng-click='self.toggleStackComponentForCurrentItem(component)',
|
||||
ng-repeat='component in self.state.componentStack track by component.uuid'
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.small(
|
||||
ng-class="{'info' : !component.hidden && component.active, 'neutral' : component.hidden || !component.active}"
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-label {{component.name}}
|
||||
.sn-component
|
||||
component-view.component-view.component-stack-item(
|
||||
component='component',
|
||||
manual-dealloc='true',
|
||||
ng-if='component.active',
|
||||
ng-repeat='component in self.state.componentStack track by component.uuid',
|
||||
ng-show='!component.hidden',
|
||||
application='self.application'
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
import { WebApplication } from './../application';
|
||||
import { PanelPuppet, WebDirective } from './../types';
|
||||
import { Editor } from '@/ui_models/editor';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { PanelPuppet, WebDirective } from '@/types';
|
||||
import angular from 'angular';
|
||||
import {
|
||||
ApplicationEvent,
|
||||
@@ -19,8 +20,8 @@ import {
|
||||
import find from 'lodash/find';
|
||||
import { isDesktopApplication } from '@/utils';
|
||||
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
|
||||
import template from '%/editor.pug';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import template from './editor-view.pug';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { AppStateEvent, EventSource } from '@/services/state';
|
||||
import {
|
||||
STRING_DELETED_NOTE,
|
||||
@@ -39,12 +40,6 @@ const SAVE_TIMEOUT_DEBOUNCE = 350;
|
||||
const SAVE_TIMEOUT_NO_DEBOUNCE = 100;
|
||||
const EDITOR_DEBOUNCE = 200;
|
||||
|
||||
const AppDataKeys = {
|
||||
Pinned: 'pinned',
|
||||
Locked: 'locked',
|
||||
Archived: 'archived',
|
||||
PrefersPlainEditor: 'prefersPlainEditor'
|
||||
};
|
||||
const ElementIds = {
|
||||
NoteTextEditor: 'note-text-editor',
|
||||
NoteTitleEditor: 'note-title-editor',
|
||||
@@ -63,9 +58,8 @@ type NoteStatus = {
|
||||
}
|
||||
|
||||
type EditorState = {
|
||||
note: SNNote
|
||||
saveError?: any
|
||||
selectedEditor?: SNComponent
|
||||
editorComponent?: SNComponent
|
||||
noteStatus?: NoteStatus
|
||||
tagsAsStrings?: string
|
||||
marginResizersEnabled?: boolean
|
||||
@@ -73,6 +67,12 @@ type EditorState = {
|
||||
isDesktop?: boolean
|
||||
tagsComponent?: SNComponent
|
||||
componentStack?: SNComponent[]
|
||||
syncTakingTooLong: boolean
|
||||
showExtensions: boolean
|
||||
noteReady: boolean
|
||||
showOptionsMenu: boolean
|
||||
altKeyDown: boolean
|
||||
spellcheck: boolean
|
||||
/** Fields that can be directly mutated by the template */
|
||||
mutable: {}
|
||||
}
|
||||
@@ -83,9 +83,16 @@ type EditorValues = {
|
||||
tagsInputValue?: string
|
||||
}
|
||||
|
||||
class EditorCtrl extends PureCtrl {
|
||||
interface EditorViewScope {
|
||||
application: WebApplication
|
||||
editor: Editor
|
||||
}
|
||||
|
||||
class EditorViewCtrl extends PureViewCtrl implements EditorViewScope {
|
||||
/** Passed through template */
|
||||
readonly application!: WebApplication
|
||||
readonly editor!: Editor
|
||||
|
||||
private leftPanelPuppet?: PanelPuppet
|
||||
private rightPanelPuppet?: PanelPuppet
|
||||
private unregisterComponent: any
|
||||
@@ -150,9 +157,23 @@ class EditorCtrl extends PureCtrl {
|
||||
return this.state as EditorState;
|
||||
}
|
||||
|
||||
get note() {
|
||||
return this.editor.note;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
super.$onInit();
|
||||
this.registerKeyboardShortcuts();
|
||||
this.editor.onNoteChange(() => {
|
||||
this.handleEditorNoteChange();
|
||||
})
|
||||
this.editor.onNoteValueChange((note, source) => {
|
||||
if (isPayloadSourceRetrieved(source!)) {
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
this.reloadTagsString();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/** @override */
|
||||
@@ -168,6 +189,10 @@ class EditorCtrl extends PureCtrl {
|
||||
};
|
||||
}
|
||||
|
||||
async setEditorState(state: Partial<EditorState>) {
|
||||
return this.setState(state);
|
||||
}
|
||||
|
||||
async onAppLaunch() {
|
||||
await super.onAppLaunch();
|
||||
this.streamItems();
|
||||
@@ -176,29 +201,21 @@ class EditorCtrl extends PureCtrl {
|
||||
|
||||
/** @override */
|
||||
onAppStateEvent(eventName: AppStateEvent, data: any) {
|
||||
if (eventName === AppStateEvent.NoteChanged) {
|
||||
this.handleNoteSelectionChange(
|
||||
this.application.getAppState().getSelectedNote()!,
|
||||
data.previousNote
|
||||
);
|
||||
} else if (eventName === AppStateEvent.PreferencesChanged) {
|
||||
if (eventName === AppStateEvent.PreferencesChanged) {
|
||||
this.reloadPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
/** @override */
|
||||
onAppEvent(eventName: ApplicationEvent) {
|
||||
if (!this.getState().note) {
|
||||
return;
|
||||
}
|
||||
if (eventName === ApplicationEvent.HighLatencySync) {
|
||||
this.setState({ syncTakingTooLong: true });
|
||||
this.setEditorState({ syncTakingTooLong: true });
|
||||
} else if (eventName === ApplicationEvent.CompletedSync) {
|
||||
this.setState({ syncTakingTooLong: false });
|
||||
if (this.getState().note.dirty) {
|
||||
this.setEditorState({ syncTakingTooLong: false });
|
||||
if (this.note.dirty) {
|
||||
/** if we're still dirty, don't change status, a sync is likely upcoming. */
|
||||
} else {
|
||||
const saved = this.getState().note.lastSyncEnd! > this.getState().note.lastSyncBegan!;
|
||||
const saved = this.note.lastSyncEnd! > this.note.lastSyncBegan!;
|
||||
const isInErrorState = this.getState().saveError;
|
||||
if (isInErrorState || saved) {
|
||||
this.showAllChangesSavedStatus();
|
||||
@@ -210,7 +227,7 @@ class EditorCtrl extends PureCtrl {
|
||||
* Otherwise, it means the originating sync came from somewhere else
|
||||
* and we don't want to display an error here.
|
||||
*/
|
||||
if (this.getState().note.dirty) {
|
||||
if (this.note.dirty) {
|
||||
this.showErrorStatus();
|
||||
}
|
||||
} else if (eventName === ApplicationEvent.LocalDatabaseWriteError) {
|
||||
@@ -221,6 +238,50 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
async handleEditorNoteChange() {
|
||||
const note = this.editor.note;
|
||||
await this.setEditorState({
|
||||
showExtensions: false,
|
||||
showOptionsMenu: false,
|
||||
altKeyDown: false,
|
||||
noteStatus: undefined
|
||||
});
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
if (!note) {
|
||||
this.setEditorState({
|
||||
noteReady: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
const associatedEditor = this.editorForNote(note);
|
||||
if (associatedEditor && associatedEditor !== this.getState().editorComponent) {
|
||||
/**
|
||||
* 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.setEditorState({
|
||||
noteReady: false,
|
||||
editorComponent: associatedEditor
|
||||
});
|
||||
} else if (!associatedEditor) {
|
||||
/** No editor */
|
||||
this.setEditorState({
|
||||
editorComponent: undefined
|
||||
});
|
||||
}
|
||||
await this.setEditorState({
|
||||
noteReady: true,
|
||||
});
|
||||
this.reloadTagsString();
|
||||
this.reloadPreferences();
|
||||
if (note.safeText().length === 0) {
|
||||
this.focusTitle();
|
||||
}
|
||||
this.reloadComponentContext();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Because note.locked accesses note.content.appData,
|
||||
* we do not want to expose the template to direct access to note.locked,
|
||||
@@ -230,57 +291,24 @@ class EditorCtrl extends PureCtrl {
|
||||
* on a deleted note.
|
||||
*/
|
||||
get noteLocked() {
|
||||
if (!this.getState().note || this.getState().note.deleted) {
|
||||
if (!this.note || this.note.deleted) {
|
||||
return false;
|
||||
}
|
||||
return this.getState().note.locked;
|
||||
return this.note.locked;
|
||||
}
|
||||
|
||||
streamItems() {
|
||||
this.application.streamItems(
|
||||
ContentType.Note,
|
||||
async (items, source) => {
|
||||
const currentNote = this.getState().note;
|
||||
if (!currentNote) {
|
||||
return;
|
||||
}
|
||||
const matchingNote = items.find((item) => {
|
||||
return item.uuid === currentNote.uuid;
|
||||
}) as SNNote;
|
||||
if (!matchingNote) {
|
||||
return;
|
||||
}
|
||||
if (matchingNote?.deleted) {
|
||||
await this.setState({
|
||||
note: undefined,
|
||||
noteReady: false
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
await this.setState({
|
||||
note: matchingNote
|
||||
});
|
||||
}
|
||||
if (!isPayloadSourceRetrieved(source!)) {
|
||||
return;
|
||||
}
|
||||
this.editorValues.title = matchingNote.title;
|
||||
this.editorValues.text = matchingNote.text;
|
||||
this.reloadTagsString();
|
||||
}
|
||||
);
|
||||
|
||||
this.application.streamItems(
|
||||
ContentType.Tag,
|
||||
(items) => {
|
||||
if (!this.getState().note) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
for (const tag of items) {
|
||||
if (
|
||||
!this.editorValues.tagsInputValue ||
|
||||
tag.deleted ||
|
||||
tag.hasRelationshipWithItem(this.getState().note)
|
||||
tag.hasRelationshipWithItem(this.note)
|
||||
) {
|
||||
this.reloadTagsString();
|
||||
break;
|
||||
@@ -293,7 +321,7 @@ class EditorCtrl extends PureCtrl {
|
||||
ContentType.Component,
|
||||
async (items) => {
|
||||
const components = items as SNComponent[];
|
||||
if (!this.getState().note) {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
/** Reload componentStack in case new ones were added or removed */
|
||||
@@ -306,9 +334,9 @@ class EditorCtrl extends PureCtrl {
|
||||
return;
|
||||
}
|
||||
/** Find the most recent editor for note */
|
||||
const editor = this.editorForNote(this.getState().note);
|
||||
this.setState({
|
||||
selectedEditor: editor
|
||||
const editor = this.editorForNote(this.note);
|
||||
this.setEditorState({
|
||||
editorComponent: editor
|
||||
});
|
||||
if (!editor) {
|
||||
this.reloadFont();
|
||||
@@ -317,62 +345,12 @@ class EditorCtrl extends PureCtrl {
|
||||
);
|
||||
}
|
||||
|
||||
async handleNoteSelectionChange(note: SNNote, previousNote?: SNNote) {
|
||||
await this.setState({
|
||||
note: note,
|
||||
showExtensions: false,
|
||||
showOptionsMenu: false,
|
||||
altKeyDown: false,
|
||||
noteStatus: null
|
||||
});
|
||||
this.editorValues.title = note.title;
|
||||
this.editorValues.text = note.text;
|
||||
if (!note) {
|
||||
this.setState({
|
||||
noteReady: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
const associatedEditor = this.editorForNote(note);
|
||||
if (associatedEditor && associatedEditor !== this.getState().selectedEditor) {
|
||||
/**
|
||||
* 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.setState({
|
||||
noteReady: false,
|
||||
selectedEditor: associatedEditor
|
||||
});
|
||||
} else if (!associatedEditor) {
|
||||
/** No editor */
|
||||
this.setState({
|
||||
selectedEditor: null
|
||||
});
|
||||
}
|
||||
await this.setState({
|
||||
noteReady: true,
|
||||
});
|
||||
this.reloadTagsString();
|
||||
this.reloadPreferences();
|
||||
|
||||
if (note.dummy) {
|
||||
this.focusTitle();
|
||||
}
|
||||
if (previousNote && previousNote !== note) {
|
||||
if (previousNote.dummy) {
|
||||
this.performNoteDeletion(previousNote);
|
||||
}
|
||||
}
|
||||
|
||||
this.reloadComponentContext();
|
||||
}
|
||||
|
||||
editorForNote(note: SNNote) {
|
||||
return this.application.componentManager!.editorForNote(note);
|
||||
}
|
||||
|
||||
setMenuState(menu: string, state: boolean) {
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
[menu]: state
|
||||
});
|
||||
this.closeAllMenus(menu);
|
||||
@@ -395,7 +373,7 @@ class EditorCtrl extends PureCtrl {
|
||||
menuState[candidate] = false;
|
||||
}
|
||||
}
|
||||
this.setState(menuState);
|
||||
this.setEditorState(menuState);
|
||||
}
|
||||
|
||||
editorMenuOnSelect(component: SNComponent) {
|
||||
@@ -403,10 +381,10 @@ class EditorCtrl extends PureCtrl {
|
||||
/** If plain editor or other editor */
|
||||
this.setMenuState('showEditorMenu', false);
|
||||
const editor = component;
|
||||
if (this.getState().selectedEditor && editor !== this.getState().selectedEditor) {
|
||||
this.disassociateComponentWithCurrentNote(this.getState().selectedEditor!);
|
||||
if (this.getState().editorComponent && editor !== this.getState().editorComponent) {
|
||||
this.disassociateComponentWithCurrentNote(this.getState().editorComponent!);
|
||||
}
|
||||
const note = this.getState().note;
|
||||
const note = this.note;
|
||||
if (editor) {
|
||||
const prefersPlain = note.prefersPlainEditor;
|
||||
if (prefersPlain) {
|
||||
@@ -427,8 +405,8 @@ class EditorCtrl extends PureCtrl {
|
||||
this.reloadFont();
|
||||
}
|
||||
|
||||
this.setState({
|
||||
selectedEditor: editor
|
||||
this.setEditorState({
|
||||
editorComponent: editor
|
||||
});
|
||||
} else if (component.area === 'editor-stack') {
|
||||
this.toggleStackComponentForCurrentItem(component);
|
||||
@@ -440,7 +418,7 @@ class EditorCtrl extends PureCtrl {
|
||||
|
||||
hasAvailableExtensions() {
|
||||
return this.application.actionsManager!.
|
||||
extensionsInContextOfItem(this.getState().note).length > 0;
|
||||
extensionsInContextOfItem(this.note).length > 0;
|
||||
}
|
||||
|
||||
performFirefoxPinnedTabFix() {
|
||||
@@ -454,14 +432,14 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
saveNote(
|
||||
async saveNote(
|
||||
bypassDebouncer = false,
|
||||
isUserModified = false,
|
||||
dontUpdatePreviews = false,
|
||||
customMutate?: (mutator: NoteMutator) => void
|
||||
) {
|
||||
this.performFirefoxPinnedTabFix();
|
||||
const note = this.getState().note;
|
||||
const note = this.note;
|
||||
|
||||
if (note.deleted) {
|
||||
this.application.alertService!.alert(
|
||||
@@ -469,15 +447,16 @@ class EditorCtrl extends PureCtrl {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.editor.isTemplateNote) {
|
||||
await this.editor.insertTemplatedNote();
|
||||
}
|
||||
if (!this.application.findItem(note.uuid)) {
|
||||
this.application.alertService!.alert(
|
||||
STRING_INVALID_NOTE
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.showSavingStatus();
|
||||
|
||||
this.application.changeItem(note.uuid, (mutator) => {
|
||||
const noteMutator = mutator as NoteMutator;
|
||||
if (customMutate) {
|
||||
@@ -516,7 +495,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
showAllChangesSavedStatus() {
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
saveError: false,
|
||||
syncTakingTooLong: false
|
||||
});
|
||||
@@ -532,7 +511,7 @@ class EditorCtrl extends PureCtrl {
|
||||
desc: "Changes saved offline"
|
||||
};
|
||||
}
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
saveError: true,
|
||||
syncTakingTooLong: false
|
||||
});
|
||||
@@ -541,11 +520,11 @@ class EditorCtrl extends PureCtrl {
|
||||
|
||||
setStatus(status: { message: string, date?: Date }, wait = true) {
|
||||
let waitForMs;
|
||||
if (!this.getState().noteStatus || !this.getState().noteStatus!.date) {
|
||||
if (!this.state.noteStatus || !this.state.noteStatus!.date) {
|
||||
waitForMs = 0;
|
||||
} else {
|
||||
waitForMs = MINIMUM_STATUS_DURATION - (
|
||||
new Date().getTime() - this.getState().noteStatus!.date!.getTime()
|
||||
new Date().getTime() - this.state.noteStatus!.date!.getTime()
|
||||
);
|
||||
}
|
||||
if (!wait || waitForMs < 0) {
|
||||
@@ -556,7 +535,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
this.statusTimeout = this.$timeout(() => {
|
||||
status.date = new Date();
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
noteStatus: status
|
||||
});
|
||||
}, waitForMs);
|
||||
@@ -619,21 +598,21 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
async deleteNote(permanently: boolean) {
|
||||
if (this.getState().note.dummy) {
|
||||
if (this.editor.isTemplateNote) {
|
||||
this.application.alertService!.alert(
|
||||
STRING_DELETE_PLACEHOLDER_ATTEMPT
|
||||
);
|
||||
return;
|
||||
}
|
||||
const run = () => {
|
||||
if (this.getState().note.locked) {
|
||||
if (this.note.locked) {
|
||||
this.application.alertService!.alert(
|
||||
STRING_DELETE_LOCKED_ATTEMPT
|
||||
);
|
||||
return;
|
||||
}
|
||||
const title = this.getState().note.safeTitle().length
|
||||
? `'${this.getState().note.title}'`
|
||||
const title = this.note.safeTitle().length
|
||||
? `'${this.note.title}'`
|
||||
: "this note";
|
||||
const text = StringDeleteNote(
|
||||
title,
|
||||
@@ -646,7 +625,7 @@ class EditorCtrl extends PureCtrl {
|
||||
undefined,
|
||||
() => {
|
||||
if (permanently) {
|
||||
this.performNoteDeletion(this.getState().note);
|
||||
this.performNoteDeletion(this.note);
|
||||
} else {
|
||||
this.saveNote(
|
||||
true,
|
||||
@@ -657,8 +636,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
);
|
||||
}
|
||||
this.application.getAppState().setSelectedNote(undefined);
|
||||
this.setMenuState('showOptionsMenu', false);
|
||||
this.appState.closeEditor(this.editor);
|
||||
},
|
||||
undefined,
|
||||
true,
|
||||
@@ -681,16 +659,6 @@ class EditorCtrl extends PureCtrl {
|
||||
|
||||
performNoteDeletion(note: SNNote) {
|
||||
this.application.deleteItem(note);
|
||||
if (note === this.getState().note) {
|
||||
this.setState({
|
||||
note: null
|
||||
});
|
||||
}
|
||||
if (note.dummy) {
|
||||
this.application.deleteItemLocally(note);
|
||||
return;
|
||||
}
|
||||
this.application.sync();
|
||||
}
|
||||
|
||||
restoreTrashedNote() {
|
||||
@@ -702,7 +670,7 @@ class EditorCtrl extends PureCtrl {
|
||||
mutator.trashed = false;
|
||||
}
|
||||
);
|
||||
this.application.getAppState().setSelectedNote(undefined);
|
||||
this.appState.closeEditor(this.editor);
|
||||
}
|
||||
|
||||
deleteNotePermanantely() {
|
||||
@@ -735,7 +703,7 @@ class EditorCtrl extends PureCtrl {
|
||||
false,
|
||||
true,
|
||||
(mutator) => {
|
||||
mutator.pinned = !this.getState().note.pinned
|
||||
mutator.pinned = !this.note.pinned
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -746,7 +714,7 @@ class EditorCtrl extends PureCtrl {
|
||||
false,
|
||||
true,
|
||||
(mutator) => {
|
||||
mutator.locked = !this.getState().note.locked
|
||||
mutator.locked = !this.note.locked
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -757,7 +725,7 @@ class EditorCtrl extends PureCtrl {
|
||||
false,
|
||||
true,
|
||||
(mutator) => {
|
||||
mutator.protected = !this.getState().note.protected
|
||||
mutator.protected = !this.note.protected
|
||||
}
|
||||
);
|
||||
/** Show privileges manager if protection is not yet set up */
|
||||
@@ -776,7 +744,7 @@ class EditorCtrl extends PureCtrl {
|
||||
false,
|
||||
true,
|
||||
(mutator) => {
|
||||
mutator.hidePreview = !this.getState().note.hidePreview
|
||||
mutator.hidePreview = !this.note.hidePreview
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -787,21 +755,21 @@ class EditorCtrl extends PureCtrl {
|
||||
false,
|
||||
true,
|
||||
(mutator) => {
|
||||
mutator.archived = !this.getState().note.archived
|
||||
mutator.archived = !this.note.archived
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
reloadTagsString() {
|
||||
const tags = this.appState.getNoteTags(this.getState().note);
|
||||
const tags = this.appState.getNoteTags(this.note);
|
||||
const string = SNTag.arrayToDisplayString(tags);
|
||||
this.updateUI(() => {
|
||||
this.editorValues.tagsInputValue = string;
|
||||
})
|
||||
}
|
||||
|
||||
addTag(tag: SNTag) {
|
||||
const tags = this.appState.getNoteTags(this.getState().note);
|
||||
private addTag(tag: SNTag) {
|
||||
const tags = this.appState.getNoteTags(this.note);
|
||||
const strings = tags.map((currentTag) => {
|
||||
return currentTag.title;
|
||||
});
|
||||
@@ -810,7 +778,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
removeTag(tag: SNTag) {
|
||||
const tags = this.appState.getNoteTags(this.getState().note);
|
||||
const tags = this.appState.getNoteTags(this.note);
|
||||
const strings = tags.map((currentTag) => {
|
||||
return currentTag.title;
|
||||
}).filter((title) => {
|
||||
@@ -819,7 +787,7 @@ class EditorCtrl extends PureCtrl {
|
||||
this.saveTagsFromStrings(strings);
|
||||
}
|
||||
|
||||
async saveTagsFromStrings(strings?: string[]) {
|
||||
public async saveTagsFromStrings(strings?: string[]) {
|
||||
if (
|
||||
!strings
|
||||
&& this.editorValues.tagsInputValue === this.getState().tagsAsStrings
|
||||
@@ -836,10 +804,8 @@ class EditorCtrl extends PureCtrl {
|
||||
return string.trim();
|
||||
});
|
||||
}
|
||||
|
||||
const note = this.getState().note;
|
||||
const note = this.note;
|
||||
const currentTags = this.appState.getNoteTags(note);
|
||||
|
||||
const removeTags = [];
|
||||
for (const tag of currentTags) {
|
||||
if (strings.indexOf(tag.title) === -1) {
|
||||
@@ -907,7 +873,7 @@ class EditorCtrl extends PureCtrl {
|
||||
WebPrefKey.EditorResizersEnabled,
|
||||
true
|
||||
);
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
monospaceEnabled,
|
||||
spellcheck,
|
||||
marginResizersEnabled
|
||||
@@ -973,10 +939,10 @@ class EditorCtrl extends PureCtrl {
|
||||
|
||||
if (key === WebPrefKey.EditorSpellcheck) {
|
||||
/** Allows textarea to reload */
|
||||
await this.setState({
|
||||
await this.setEditorState({
|
||||
noteReady: false
|
||||
});
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
noteReady: true
|
||||
});
|
||||
this.reloadFont();
|
||||
@@ -1000,42 +966,42 @@ class EditorCtrl extends PureCtrl {
|
||||
],
|
||||
activationHandler: (component) => {
|
||||
if (component.area === 'note-tags') {
|
||||
this.setState({
|
||||
tagsComponent: component.active ? component : null
|
||||
this.setEditorState({
|
||||
tagsComponent: component.active ? component : undefined
|
||||
});
|
||||
} else if (component.area === 'editor-editor') {
|
||||
if (
|
||||
component === this.getState().selectedEditor &&
|
||||
component === this.getState().editorComponent &&
|
||||
!component.active
|
||||
) {
|
||||
this.setState({ selectedEditor: null });
|
||||
this.setEditorState({ editorComponent: undefined });
|
||||
}
|
||||
else if (this.getState().selectedEditor) {
|
||||
if (this.getState().selectedEditor!.active && this.getState().note) {
|
||||
else if (this.getState().editorComponent) {
|
||||
if (this.getState().editorComponent!.active && this.note) {
|
||||
if (
|
||||
component.isExplicitlyEnabledForItem(this.getState().note)
|
||||
&& !this.getState().selectedEditor!.isExplicitlyEnabledForItem(this.getState().note)
|
||||
component.isExplicitlyEnabledForItem(this.note)
|
||||
&& !this.getState().editorComponent!.isExplicitlyEnabledForItem(this.note)
|
||||
) {
|
||||
this.setState({ selectedEditor: component });
|
||||
this.setEditorState({ editorComponent: component });
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (this.getState().note) {
|
||||
else if (this.note) {
|
||||
const enableable = (
|
||||
component.isExplicitlyEnabledForItem(this.getState().note)
|
||||
component.isExplicitlyEnabledForItem(this.note)
|
||||
|| component.isDefaultEditor()
|
||||
);
|
||||
if (
|
||||
component.active
|
||||
&& enableable
|
||||
) {
|
||||
this.setState({ selectedEditor: component });
|
||||
this.setEditorState({ editorComponent: component });
|
||||
} else {
|
||||
/**
|
||||
* Not a candidate, and no qualified editor.
|
||||
* Disable the current editor.
|
||||
*/
|
||||
this.setState({ selectedEditor: null });
|
||||
this.setEditorState({ editorComponent: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,11 +1011,11 @@ class EditorCtrl extends PureCtrl {
|
||||
},
|
||||
contextRequestHandler: (component) => {
|
||||
if (
|
||||
component === this.getState().selectedEditor ||
|
||||
component === this.getState().editorComponent ||
|
||||
component === this.getState().tagsComponent ||
|
||||
this.getState().componentStack!.includes(component)
|
||||
) {
|
||||
return this.getState().note;
|
||||
return this.note;
|
||||
}
|
||||
},
|
||||
focusHandler: (component, focused) => {
|
||||
@@ -1093,7 +1059,7 @@ class EditorCtrl extends PureCtrl {
|
||||
else if (action === ComponentAction.SaveItems) {
|
||||
const includesNote = data.items.map((item: RawPayload) => {
|
||||
return item.uuid;
|
||||
}).includes(this.getState().note.uuid);
|
||||
}).includes(this.note.uuid);
|
||||
if (includesNote) {
|
||||
this.showSavingStatus();
|
||||
}
|
||||
@@ -1109,19 +1075,19 @@ class EditorCtrl extends PureCtrl {
|
||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||
});
|
||||
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
componentStack: components
|
||||
});
|
||||
}
|
||||
|
||||
reloadComponentContext() {
|
||||
this.reloadComponentStackArray();
|
||||
if (this.getState().note) {
|
||||
if (this.note) {
|
||||
for (const component of this.getState().componentStack!) {
|
||||
if (component.active) {
|
||||
this.application.componentManager!.setComponentHidden(
|
||||
component,
|
||||
!component.isExplicitlyEnabledForItem(this.getState().note)
|
||||
!component.isExplicitlyEnabledForItem(this.note)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1148,7 +1114,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
disassociateComponentWithCurrentNote(component: SNComponent) {
|
||||
const note = this.getState().note;
|
||||
const note = this.note;
|
||||
this.application.changeAndSaveItem(component.uuid, (m) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeAssociatedItemId(note.uuid);
|
||||
@@ -1157,7 +1123,7 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
associateComponentWithCurrentNote(component: SNComponent) {
|
||||
const note = this.getState().note;
|
||||
const note = this.note;
|
||||
this.application.changeAndSaveItem(component.uuid, (m) => {
|
||||
const mutator = m as ComponentMutator;
|
||||
mutator.removeDisassociatedItemId(note.uuid);
|
||||
@@ -1171,12 +1137,12 @@ class EditorCtrl extends PureCtrl {
|
||||
KeyboardModifier.Alt
|
||||
],
|
||||
onKeyDown: () => {
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
altKeyDown: true
|
||||
});
|
||||
},
|
||||
onKeyUp: () => {
|
||||
this.setState({
|
||||
this.setEditorState({
|
||||
altKeyDown: false
|
||||
});
|
||||
}
|
||||
@@ -1223,7 +1189,7 @@ class EditorCtrl extends PureCtrl {
|
||||
element: editor,
|
||||
key: KeyboardKey.Tab,
|
||||
onKeyDown: (event) => {
|
||||
if (this.getState().note.locked || event.shiftKey) {
|
||||
if (this.note.locked || event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
@@ -1260,16 +1226,17 @@ class EditorCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorPanel extends WebDirective {
|
||||
export class EditorView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.restrict = 'E';
|
||||
this.scope = {
|
||||
editor: '=',
|
||||
application: '='
|
||||
};
|
||||
this.template = template;
|
||||
this.replace = true;
|
||||
this.controller = EditorCtrl;
|
||||
this.controller = EditorViewCtrl;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
editor-view(
|
||||
ng-repeat='editor in self.editors'
|
||||
application='self.application'
|
||||
editor='editor'
|
||||
)
|
||||
@@ -0,0 +1,35 @@
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import { WebDirective } from './../../types';
|
||||
import template from './editor-group-view.pug';
|
||||
import { Editor } from '@/ui_models/editor';
|
||||
|
||||
class EditorGroupViewCtrl {
|
||||
|
||||
private application!: WebApplication
|
||||
public editors: Editor[] = []
|
||||
|
||||
/* @ngInject */
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.application.editorGroup.addChangeObserver(() => {
|
||||
this.editors = this.application.editorGroup.editors;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorGroupView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.template = template;
|
||||
this.controller = EditorGroupViewCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
application: '='
|
||||
};
|
||||
}
|
||||
}
|
||||
97
app/assets/javascripts/views/footer/footer-view.pug
Normal file
97
app/assets/javascripts/views/footer/footer-view.pug
Normal file
@@ -0,0 +1,97 @@
|
||||
.sn-component
|
||||
#footer-bar.sk-app-bar.no-edges.no-bottom-edge
|
||||
.left
|
||||
.sk-app-bar-item(
|
||||
click-outside='ctrl.clickOutsideAccountMenu()',
|
||||
is-open='ctrl.showAccountMenu',
|
||||
ng-click='ctrl.accountMenuPressed()'
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-circle.small(
|
||||
ng-class="ctrl.hasError ? 'danger' : (ctrl.user ? 'info' : 'neutral')"
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-label.title(ng-class='{red: ctrl.hasError}') Account
|
||||
account-menu(
|
||||
close-function='ctrl.closeAccountMenu()',
|
||||
ng-click='$event.stopPropagation()',
|
||||
ng-if='ctrl.showAccountMenu',
|
||||
application='ctrl.application'
|
||||
)
|
||||
.sk-app-bar-item
|
||||
a.no-decoration.sk-label.title(
|
||||
href='https://standardnotes.org/help',
|
||||
rel='noopener',
|
||||
target='_blank'
|
||||
)
|
||||
| Help
|
||||
.sk-app-bar-item.border
|
||||
.sk-app-bar-item(ng-repeat='room in ctrl.rooms track by room.uuid')
|
||||
.sk-app-bar-item-column(ng-click='ctrl.selectRoom(room)')
|
||||
.sk-label {{room.name}}
|
||||
component-modal(
|
||||
component='room',
|
||||
ng-if='ctrl.roomShowState[room.uuid]',
|
||||
on-dismiss='ctrl.onRoomDismiss()',
|
||||
application='ctrl.application'
|
||||
)
|
||||
.center
|
||||
.sk-app-bar-item(ng-if='ctrl.arbitraryStatusMessage')
|
||||
.sk-app-bar-item-column
|
||||
span.neutral.sk-label {{ctrl.arbitraryStatusMessage}}
|
||||
.right
|
||||
.sk-app-bar-item(
|
||||
ng-click='ctrl.openSecurityUpdate()',
|
||||
ng-if='ctrl.state.dataUpgradeAvailable'
|
||||
)
|
||||
span.success.sk-label Encryption upgrade available.
|
||||
.sk-app-bar-item(
|
||||
ng-click='ctrl.clickedNewUpdateAnnouncement()',
|
||||
ng-if='ctrl.newUpdateAvailable == true'
|
||||
)
|
||||
span.info.sk-label New update available.
|
||||
.sk-app-bar-item.no-pointer(
|
||||
ng-if='ctrl.lastSyncDate && !ctrl.isRefreshing'
|
||||
)
|
||||
.sk-label.subtle
|
||||
| Last refreshed {{ctrl.lastSyncDate}}
|
||||
.sk-app-bar-item(
|
||||
ng-click='ctrl.toggleSyncResolutionMenu()',
|
||||
ng-if='(ctrl.state.outOfSync && !ctrl.isRefreshing) || ctrl.showSyncResolution'
|
||||
)
|
||||
.sk-label.warning(ng-if='ctrl.state.outOfSync') Potentially Out of Sync
|
||||
sync-resolution-menu(
|
||||
close-function='ctrl.toggleSyncResolutionMenu()',
|
||||
ng-click='$event.stopPropagation();',
|
||||
ng-if='ctrl.showSyncResolution',
|
||||
application='ctrl.application'
|
||||
)
|
||||
.sk-app-bar-item(ng-if='ctrl.lastSyncDate && ctrl.isRefreshing')
|
||||
.sk-spinner.small
|
||||
.sk-app-bar-item(ng-if='ctrl.offline')
|
||||
.sk-label Offline
|
||||
.sk-app-bar-item(ng-click='ctrl.refreshData()', ng-if='!ctrl.offline')
|
||||
.sk-label Refresh
|
||||
.sk-app-bar-item.border(ng-if='ctrl.dockShortcuts.length > 0')
|
||||
.sk-app-bar-item.dock-shortcut(ng-repeat='shortcut in ctrl.dockShortcuts')
|
||||
.sk-app-bar-item-column(
|
||||
ng-class="{'underline': shortcut.component.active}",
|
||||
ng-click='ctrl.selectShortcut(shortcut)'
|
||||
)
|
||||
.div(ng-if="shortcut.icon.type == 'circle'", title='{{shortcut.name}}')
|
||||
.sk-circle.small(
|
||||
ng-style="{'background-color': shortcut.icon.background_color, 'border-color': shortcut.icon.border_color}"
|
||||
)
|
||||
.div(ng-if="shortcut.icon.type == 'svg'", title='{{shortcut.name}}')
|
||||
.svg-item(
|
||||
elem-ready='ctrl.initSvgForShortcut(shortcut)',
|
||||
ng-attr-id='dock-svg-{{shortcut.component.uuid}}'
|
||||
)
|
||||
.sk-app-bar-item.border(ng-if='ctrl.state.hasPasscode')
|
||||
#lock-item.sk-app-bar-item(
|
||||
ng-click='ctrl.lockApp()',
|
||||
ng-if='ctrl.state.hasPasscode',
|
||||
title='Locks application and wipes unencrypted data from memory.'
|
||||
)
|
||||
.sk-label
|
||||
i#footer-lock-icon.icon.ion-locked
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FooterStatus, WebDirective } from './../types';
|
||||
import { FooterStatus, WebDirective } from '@/types';
|
||||
import { dateToLocalizedString } from '@/utils';
|
||||
import {
|
||||
ApplicationEvent,
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
ComponentArea,
|
||||
ComponentAction
|
||||
} from 'snjs';
|
||||
import template from '%/footer.pug';
|
||||
import template from './footer-view.pug';
|
||||
import { AppStateEvent, EventSource } from '@/services/state';
|
||||
import {
|
||||
STRING_GENERIC_SYNC_ERROR,
|
||||
STRING_NEW_UPDATE_READY
|
||||
} from '@/strings';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { ComponentMutator } from '@/../../../../snjs/dist/@types/models';
|
||||
|
||||
type DockShortcut = {
|
||||
@@ -29,7 +29,7 @@ type DockShortcut = {
|
||||
}
|
||||
}
|
||||
|
||||
class FooterCtrl extends PureCtrl {
|
||||
class FooterViewCtrl extends PureViewCtrl {
|
||||
|
||||
private $rootScope: ng.IRootScopeService
|
||||
private rooms: SNComponent[] = []
|
||||
@@ -427,15 +427,16 @@ class FooterCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export class Footer extends WebDirective {
|
||||
export class FooterView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.restrict = 'E';
|
||||
this.template = template;
|
||||
this.controller = FooterCtrl;
|
||||
this.controller = FooterViewCtrl;
|
||||
this.replace = true;
|
||||
this.controllerAs = 'ctrl';
|
||||
this.bindToController = {
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
application: '='
|
||||
};
|
||||
}
|
||||
11
app/assets/javascripts/views/index.ts
Normal file
11
app/assets/javascripts/views/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { PureViewCtrl } from './abstract/pure_view_ctrl';
|
||||
|
||||
export { ApplicationGroupView } from './application_group/application_group_view';
|
||||
export { ApplicationView } from './application/application_view';
|
||||
|
||||
export { EditorGroupView } from './editor_group/editor_group_view';
|
||||
export { EditorView } from './editor/editor_view';
|
||||
|
||||
export { FooterView } from './footer/footer_view';
|
||||
export { NotesView } from './notes/notes_view';
|
||||
export { TagsView } from './tags/tags_view';
|
||||
@@ -111,10 +111,8 @@ export function sortNotes(
|
||||
notes: SNNote[] = [],
|
||||
sortBy: string,
|
||||
reverse: boolean
|
||||
) {
|
||||
) {
|
||||
const sortValueFn = (a: SNNote, b: SNNote, pinCheck = false): number => {
|
||||
if (a.dummy) { return -1; }
|
||||
if (b.dummy) { return 1; }
|
||||
if (!pinCheck) {
|
||||
if (a.pinned && b.pinned) {
|
||||
return sortValueFn(a, b, true);
|
||||
@@ -123,7 +121,7 @@ export function sortNotes(
|
||||
if (b.pinned) { return 1; }
|
||||
}
|
||||
let aValue = (a as any)[sortBy] || '';
|
||||
let bValue = (a as any)[sortBy] || '';
|
||||
let bValue = (b as any)[sortBy] || '';
|
||||
let vector = 1;
|
||||
if (reverse) {
|
||||
vector *= -1;
|
||||
149
app/assets/javascripts/views/notes/notes-view.pug
Normal file
149
app/assets/javascripts/views/notes/notes-view.pug
Normal file
@@ -0,0 +1,149 @@
|
||||
#notes-column.sn-component.section.notes(aria-label='Notes')
|
||||
.content
|
||||
#notes-title-bar.section-title-bar
|
||||
.padded
|
||||
.section-title-bar-header
|
||||
.title {{self.state.panelTitle}}
|
||||
.sk-button.contrast.wide(
|
||||
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(
|
||||
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',
|
||||
title='Searches notes in the currently selected tag'
|
||||
)
|
||||
#search-clear-button(
|
||||
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-click='self.state.mutable.showMenu = !self.state.mutable.showMenu'
|
||||
)
|
||||
.sk-app-bar-item-column
|
||||
.sk-label
|
||||
| Options
|
||||
.sk-app-bar-item-column
|
||||
.sk-sublabel {{self.optionsSubtitle()}}
|
||||
#notes-options-menu.sk-menu-panel.dropdown-menu(
|
||||
ng-show='self.state.mutable.showMenu'
|
||||
)
|
||||
.sk-menu-panel-header
|
||||
.sk-menu-panel-header-title Sort By
|
||||
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'"
|
||||
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'"
|
||||
label="'Date Modified'"
|
||||
)
|
||||
menu-row(
|
||||
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.toggleWebPrefKey('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"
|
||||
label="'Archived Notes'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.toggleWebPrefKey('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"
|
||||
label="'Pinned Notes'"
|
||||
)
|
||||
menu-row(
|
||||
action="self.selectedMenuItem(); self.toggleWebPrefKey('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.toggleWebPrefKey('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.toggleWebPrefKey('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()',
|
||||
threshold='200'
|
||||
)
|
||||
.note(
|
||||
ng-repeat='note in self.state.renderedNotes track by note.uuid'
|
||||
ng-class="{'selected' : self.activeEditorNote == note}"
|
||||
ng-click='self.selectNote(note)'
|
||||
)
|
||||
.note-flags(ng-show='self.noteFlags[note.uuid].length > 0')
|
||||
.flag(ng-class='flag.class', ng-repeat='flag in self.noteFlags[note.uuid]')
|
||||
.label {{flag.text}}
|
||||
.name(ng-show='note.title')
|
||||
| {{note.title}}
|
||||
.note-preview(
|
||||
ng-if=`
|
||||
!self.state.hideNotePreview &&
|
||||
!note.hidePreview &&
|
||||
!note.protected`
|
||||
)
|
||||
.html-preview(
|
||||
ng-bind-html='note.preview_html',
|
||||
ng-show='note.preview_html'
|
||||
)
|
||||
.plain-preview(
|
||||
ng-show='!note.preview_html && note.preview_plain'
|
||||
) {{note.preview_plain}}
|
||||
.default-preview(
|
||||
ng-show='!note.preview_html && !note.preview_plain'
|
||||
) {{note.text}}
|
||||
.date.faded(ng-show='!self.state.hideDate')
|
||||
span(ng-show="self.state.sortBy == 'client_updated_at'")
|
||||
| Modified {{note.updatedAtString || 'Now'}}
|
||||
span(ng-show="self.state.sortBy != 'client_updated_at'")
|
||||
| {{note.createdAtString || 'Now'}}
|
||||
|
||||
panel-resizer(
|
||||
collapsable="true"
|
||||
control="self.panelPuppet"
|
||||
default-width="300"
|
||||
hoverable="true"
|
||||
on-resize-finish="self.onPanelResize()"
|
||||
panel-id="'notes-column'"
|
||||
)
|
||||
@@ -1,13 +1,20 @@
|
||||
import { PanelPuppet, WebDirective } from './../../types';
|
||||
import angular from 'angular';
|
||||
import template from '%/notes.pug';
|
||||
import { ApplicationEvent, ContentType, removeFromArray, SNNote, SNTag, WebPrefKey } from 'snjs';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import template from './notes-view.pug';
|
||||
import {
|
||||
ApplicationEvent,
|
||||
ContentType,
|
||||
removeFromArray,
|
||||
SNNote,
|
||||
SNTag,
|
||||
WebPrefKey
|
||||
} from 'snjs';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
|
||||
import {
|
||||
PANEL_NAME_NOTES
|
||||
} from '@/controllers/constants';
|
||||
} from '@/views/constants';
|
||||
import {
|
||||
NoteSortKey,
|
||||
filterAndSortNotes
|
||||
@@ -15,6 +22,7 @@ import {
|
||||
import { UuidString } from '@/../../../../snjs/dist/@types/types';
|
||||
|
||||
type NotesState = {
|
||||
panelTitle: string
|
||||
tag?: SNTag
|
||||
notes?: SNNote[]
|
||||
renderedNotes?: SNNote[]
|
||||
@@ -43,7 +51,7 @@ const DEFAULT_LIST_NUM_NOTES = 20;
|
||||
const ELEMENT_ID_SEARCH_BAR = 'search-bar';
|
||||
const ELEMENT_ID_SCROLL_CONTAINER = 'notes-scrollable';
|
||||
|
||||
class NotesCtrl extends PureCtrl {
|
||||
class NotesViewCtrl extends PureViewCtrl {
|
||||
|
||||
private panelPuppet?: PanelPuppet
|
||||
private reloadNotesPromise?: any
|
||||
@@ -101,13 +109,17 @@ class NotesCtrl extends PureCtrl {
|
||||
return this.state as NotesState;
|
||||
}
|
||||
|
||||
async setNotesState(state: Partial<NotesState>) {
|
||||
return this.setState(state);
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
notes: [],
|
||||
renderedNotes: [],
|
||||
mutable: { showMenu: false },
|
||||
noteFilter: { text: '' },
|
||||
} as NotesState;
|
||||
} as Partial<NotesState>;
|
||||
}
|
||||
|
||||
async onAppLaunch() {
|
||||
@@ -120,13 +132,10 @@ class NotesCtrl extends PureCtrl {
|
||||
onAppStateEvent(eventName: AppStateEvent, data?: any) {
|
||||
if (eventName === AppStateEvent.TagChanged) {
|
||||
this.handleTagChange(
|
||||
this.application!.getAppState().getSelectedTag()!,
|
||||
data.previousTag
|
||||
);
|
||||
} else if (eventName === AppStateEvent.NoteChanged) {
|
||||
this.handleNoteSelection(
|
||||
this.application!.getAppState().getSelectedNote()!
|
||||
this.application!.getAppState().getSelectedTag()!
|
||||
);
|
||||
} else if (eventName === AppStateEvent.ActiveEditorChanged) {
|
||||
this.handleEditorChange();
|
||||
} else if (eventName === AppStateEvent.PreferencesChanged) {
|
||||
this.reloadPreferences();
|
||||
this.reloadNotes();
|
||||
@@ -135,19 +144,19 @@ class NotesCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
get selectedNote() {
|
||||
return this.appState.getSelectedNote();
|
||||
get activeEditorNote() {
|
||||
const activeEditor = this.appState.getActiveEditor();
|
||||
return activeEditor && activeEditor.note;
|
||||
}
|
||||
|
||||
public get editorNotes() {
|
||||
return this.appState.getEditors().map((editor) => editor.note);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async onAppEvent(eventName: ApplicationEvent) {
|
||||
if (eventName === ApplicationEvent.SignedIn) {
|
||||
/** Delete dummy note if applicable */
|
||||
if (this.selectedNote && this.selectedNote!.dummy) {
|
||||
this.application!.deleteItemLocally(this.selectedNote!);
|
||||
await this.selectNote(undefined);
|
||||
await this.reloadNotes();
|
||||
}
|
||||
this.appState.closeAllEditors();
|
||||
} else if (eventName === ApplicationEvent.CompletedSync) {
|
||||
this.getMostValidNotes().then((notes) => {
|
||||
if (notes.length === 0) {
|
||||
@@ -196,9 +205,9 @@ class NotesCtrl extends PureCtrl {
|
||||
[ContentType.Note, ContentType.Tag],
|
||||
async (items) => {
|
||||
await this.reloadNotes();
|
||||
const selectedNote = this.selectedNote;
|
||||
if (selectedNote) {
|
||||
const discarded = selectedNote.deleted || selectedNote.trashed;
|
||||
const activeNote = this.activeEditorNote;
|
||||
if (activeNote) {
|
||||
const discarded = activeNote.deleted || activeNote.trashed;
|
||||
if (discarded) {
|
||||
this.selectNextOrCreateNew();
|
||||
}
|
||||
@@ -218,51 +227,20 @@ class NotesCtrl extends PureCtrl {
|
||||
);
|
||||
}
|
||||
|
||||
async selectNote(note?: SNNote) {
|
||||
return this.application!.getAppState().setSelectedNote(note);
|
||||
async selectNote(note: SNNote) {
|
||||
this.appState.openEditor(note.uuid);
|
||||
}
|
||||
|
||||
async createNewNote() {
|
||||
const selectedTag = this.application!.getAppState().getSelectedTag();
|
||||
if (!selectedTag) {
|
||||
throw 'Attempting to create note with no selected tag';
|
||||
}
|
||||
let title;
|
||||
let isDummyNote = true;
|
||||
let title = `Note ${this.getState().notes!.length + 1}`;
|
||||
if (this.isFiltering()) {
|
||||
title = this.getState().noteFilter.text;
|
||||
isDummyNote = false;
|
||||
} else if (this.selectedNote && this.selectedNote!.dummy) {
|
||||
return;
|
||||
} else {
|
||||
title = `Note ${this.getState().notes!.length + 1}`;
|
||||
}
|
||||
const newNote = await this.application!.createManagedItem(
|
||||
ContentType.Note,
|
||||
{
|
||||
text: '',
|
||||
title: title,
|
||||
references: []
|
||||
},
|
||||
true,
|
||||
{
|
||||
dummy: isDummyNote
|
||||
}
|
||||
) as SNNote;
|
||||
if (!selectedTag.isSmartTag()) {
|
||||
this.application!.changeItem(selectedTag.uuid, (mutator) => {
|
||||
mutator.addItemAsRelationship(newNote);
|
||||
});
|
||||
}
|
||||
this.selectNote(newNote);
|
||||
this.appState.createEditor(title);
|
||||
}
|
||||
|
||||
async handleTagChange(tag: SNTag, previousTag?: SNTag) {
|
||||
if (this.selectedNote && this.selectedNote!.dummy) {
|
||||
await this.application!.deleteItemLocally(this.selectedNote!);
|
||||
await this.selectNote(undefined);
|
||||
}
|
||||
await this.setState({ tag: tag });
|
||||
async handleTagChange(tag: SNTag) {
|
||||
await this.setNotesState({ tag });
|
||||
|
||||
this.resetScrollPosition();
|
||||
this.setShowMenuFalse();
|
||||
@@ -270,7 +248,8 @@ class NotesCtrl extends PureCtrl {
|
||||
this.application!.getDesktopService().searchText();
|
||||
this.resetPagination();
|
||||
|
||||
/* Capture db load state before beginning reloadNotes, since this status may change during reload */
|
||||
/* Capture db load state before beginning reloadNotes,
|
||||
since this status may change during reload */
|
||||
const dbLoaded = this.application!.isDatabaseLoaded();
|
||||
await this.reloadNotes();
|
||||
|
||||
@@ -280,10 +259,10 @@ class NotesCtrl extends PureCtrl {
|
||||
if (!tag.isSmartTag() || tag.isAllTag) {
|
||||
this.createPlaceholderNote();
|
||||
} else if (
|
||||
this.selectedNote &&
|
||||
!this.getState().notes!.includes(this.selectedNote!)
|
||||
this.activeEditorNote &&
|
||||
!this.getState().notes!.includes(this.activeEditorNote!)
|
||||
) {
|
||||
this.selectNote(undefined);
|
||||
this.appState.closeActiveEditor();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,7 +278,7 @@ class NotesCtrl extends PureCtrl {
|
||||
async removeNoteFromList(note: SNNote) {
|
||||
const notes = this.getState().notes!;
|
||||
removeFromArray(notes, note);
|
||||
await this.setState({
|
||||
await this.setNotesState({
|
||||
notes: notes,
|
||||
renderedNotes: notes.slice(0, this.notesToDisplay)
|
||||
});
|
||||
@@ -330,7 +309,7 @@ class NotesCtrl extends PureCtrl {
|
||||
this.loadFlagsForNote(note);
|
||||
}
|
||||
}
|
||||
await this.setState({
|
||||
await this.setNotesState({
|
||||
notes: notes,
|
||||
renderedNotes: notes.slice(0, this.notesToDisplay)
|
||||
});
|
||||
@@ -338,7 +317,7 @@ class NotesCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
setShowMenuFalse() {
|
||||
this.setState({
|
||||
this.setNotesState({
|
||||
mutable: {
|
||||
...this.getState().mutable,
|
||||
showMenu: false
|
||||
@@ -346,20 +325,10 @@ class NotesCtrl extends PureCtrl {
|
||||
});
|
||||
}
|
||||
|
||||
async handleNoteSelection(note: SNNote) {
|
||||
const previousNote = this.selectedNote;
|
||||
if (previousNote === note) {
|
||||
return;
|
||||
}
|
||||
if (previousNote && previousNote.dummy) {
|
||||
await this.application!.deleteItemLocally(previousNote);
|
||||
this.removeNoteFromList(previousNote);
|
||||
}
|
||||
if (!note) {
|
||||
return;
|
||||
}
|
||||
if (note.conflictOf) {
|
||||
this.application!.changeAndSaveItem(note.uuid, (mutator) => {
|
||||
async handleEditorChange() {
|
||||
const activeNote = this.appState.getActiveEditor().note;
|
||||
if (activeNote && activeNote.conflictOf) {
|
||||
this.application!.changeAndSaveItem(activeNote.uuid, (mutator) => {
|
||||
mutator.conflictOf = undefined;
|
||||
})
|
||||
}
|
||||
@@ -404,7 +373,7 @@ class NotesCtrl extends PureCtrl {
|
||||
WebPrefKey.NotesHideTags,
|
||||
false
|
||||
);
|
||||
this.setState({
|
||||
this.setNotesState({
|
||||
...viewOptions
|
||||
});
|
||||
if (prevSortValue && prevSortValue !== sortBy) {
|
||||
@@ -469,7 +438,7 @@ class NotesCtrl extends PureCtrl {
|
||||
} else if (this.getState().tag) {
|
||||
title = `${this.getState().tag!.title}`;
|
||||
}
|
||||
this.setState({
|
||||
this.setNotesState({
|
||||
panelTitle: title
|
||||
});
|
||||
}
|
||||
@@ -584,7 +553,7 @@ class NotesCtrl extends PureCtrl {
|
||||
selectNextNote() {
|
||||
const displayableNotes = this.displayableNotes();
|
||||
const currentIndex = displayableNotes.findIndex((candidate) => {
|
||||
return candidate.uuid === this.selectedNote!.uuid
|
||||
return candidate.uuid === this.activeEditorNote!.uuid
|
||||
});
|
||||
if (currentIndex + 1 < displayableNotes.length) {
|
||||
this.selectNote(displayableNotes[currentIndex + 1]);
|
||||
@@ -598,13 +567,13 @@ class NotesCtrl extends PureCtrl {
|
||||
} else if (!this.getState().tag || !this.getState().tag!.isSmartTag()) {
|
||||
this.createPlaceholderNote();
|
||||
} else {
|
||||
this.selectNote(undefined);
|
||||
this.appState.closeActiveEditor();
|
||||
}
|
||||
}
|
||||
|
||||
selectPreviousNote() {
|
||||
const displayableNotes = this.displayableNotes();
|
||||
const currentIndex = displayableNotes.indexOf(this.selectedNote!);
|
||||
const currentIndex = displayableNotes.indexOf(this.activeEditorNote!);
|
||||
if (currentIndex - 1 >= 0) {
|
||||
this.selectNote(displayableNotes[currentIndex - 1]);
|
||||
return true;
|
||||
@@ -619,7 +588,7 @@ class NotesCtrl extends PureCtrl {
|
||||
}
|
||||
|
||||
async setNoteFilterText(text: string) {
|
||||
await this.setState({
|
||||
await this.setNotesState({
|
||||
noteFilter: {
|
||||
...this.getState().noteFilter,
|
||||
text: text
|
||||
@@ -748,12 +717,12 @@ class NotesCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export class NotesPanel extends WebDirective {
|
||||
export class NotesView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.template = template;
|
||||
this.replace = true;
|
||||
this.controller = NotesCtrl;
|
||||
this.controller = NotesViewCtrl;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
this.scope = {
|
||||
72
app/assets/javascripts/views/tags/tags-view.pug
Normal file
72
app/assets/javascripts/views/tags/tags-view.pug
Normal file
@@ -0,0 +1,72 @@
|
||||
#tags-column.sn-component.section.tags(aria-label='Tags')
|
||||
.component-view-container(ng-if='self.component.active')
|
||||
component-view.component-view(
|
||||
component='self.component',
|
||||
application='self.application'
|
||||
)
|
||||
#tags-content.content(ng-if='!(self.component && self.component.active)')
|
||||
.tags-title-section.section-title-bar
|
||||
.section-title-bar-header
|
||||
.sk-h3.title
|
||||
span.sk-bold Views
|
||||
.sk-button.sk-secondary-contrast.wide(
|
||||
ng-click='self.clickedAddNewTag()',
|
||||
title='Create a new tag'
|
||||
)
|
||||
.sk-label
|
||||
i.icon.ion-plus.add-button
|
||||
.scrollable
|
||||
.infinite-scroll
|
||||
.tag(
|
||||
ng-class="{'selected' : self.state.selectedTag == tag, 'faded' : !tag.isAllTag}",
|
||||
ng-click='self.selectTag(tag)',
|
||||
ng-repeat='tag in self.state.smartTags track by tag.uuid'
|
||||
)
|
||||
.tag-info
|
||||
input.title(
|
||||
ng-disabled='true',
|
||||
ng-change='self.onTagTitleChange(tag)'
|
||||
ng-model='tag.title'
|
||||
)
|
||||
.count(ng-show='tag.isAllTag') {{self.state.noteCounts[tag.uuid]}}
|
||||
.tags-title-section.section-title-bar
|
||||
.section-title-bar-header
|
||||
.sk-h3.title
|
||||
span.sk-bold Tags
|
||||
.tag(
|
||||
ng-class="{'selected' : self.state.selectedTag == tag}",
|
||||
ng-click='self.selectTag(tag)',
|
||||
ng-repeat='tag in self.state.tags track by tag.uuid'
|
||||
)
|
||||
.tag-info
|
||||
.tag-icon #
|
||||
input.title(
|
||||
ng-attr-id='tag-{{tag.uuid}}',
|
||||
ng-blur='self.saveTag($event, tag)'
|
||||
ng-change='self.onTagTitleChange(tag)',
|
||||
ng-model='self.titles[tag.uuid]',
|
||||
ng-class="{'editing' : self.state.editingTag == tag}",
|
||||
ng-click='self.selectTag(tag)',
|
||||
ng-keyup='$event.keyCode == 13 && $event.target.blur()',
|
||||
should-focus='self.state.newTag || self.state.editingTag == tag',
|
||||
sn-autofocus='true',
|
||||
spellcheck='false'
|
||||
)
|
||||
.count {{self.state.noteCounts[tag.uuid]}}
|
||||
.danger.small-text.bold(ng-show='tag.conflictOf') Conflicted Copy
|
||||
.danger.small-text.bold(ng-show='tag.errorDecrypting && !tag.waitingForKey') Missing Keys
|
||||
.info.small-text.bold(ng-show='tag.errorDecrypting && tag.waitingForKey') Waiting For Keys
|
||||
.menu(ng-show='self.state.selectedTag == tag')
|
||||
a.item(ng-click='self.selectedRenameTag(tag)' ng-show='!self.state.editingTag') Rename
|
||||
a.item(ng-click='self.saveTag($event, tag)' ng-show='self.state.editingTag') Save
|
||||
a.item(ng-click='self.selectedDeleteTag(tag)') Delete
|
||||
.no-tags-placeholder(ng-show='self.state.tags.length == 0')
|
||||
| No tags. Create one using the add button above.
|
||||
panel-resizer(
|
||||
collapsable='true',
|
||||
control='self.panelPuppet',
|
||||
default-width='150',
|
||||
hoverable='true',
|
||||
on-resize-finish='self.onPanelResize()',
|
||||
panel-id="'tags-column'"
|
||||
)
|
||||
@@ -1,7 +1,6 @@
|
||||
import { WebDirective, PanelPuppet } from './../types';
|
||||
import { WebApplication } from './../application';
|
||||
import { WebDirective, PanelPuppet } from '@/types';
|
||||
import { WebApplication } from '@/ui_models/application';
|
||||
import {
|
||||
SNNote,
|
||||
SNTag,
|
||||
ContentType,
|
||||
ApplicationEvent,
|
||||
@@ -11,17 +10,17 @@ import {
|
||||
SNComponent,
|
||||
WebPrefKey
|
||||
} from 'snjs';
|
||||
import template from '%/tags.pug';
|
||||
import template from './tags-view.pug';
|
||||
import { AppStateEvent } from '@/services/state';
|
||||
import { PANEL_NAME_TAGS } from '@/controllers/constants';
|
||||
import { PANEL_NAME_TAGS } from '@/views/constants';
|
||||
import { STRING_DELETE_TAG } from '@/strings';
|
||||
import { PureCtrl } from '@Controllers/abstract/pure_ctrl';
|
||||
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
|
||||
import { UuidString } from '@/../../../../snjs/dist/@types/types';
|
||||
import { TagMutator } from '@/../../../../snjs/dist/@types/models/app/tag';
|
||||
|
||||
type NoteCounts = Partial<Record<string, number>>
|
||||
|
||||
class TagsPanelCtrl extends PureCtrl {
|
||||
class TagsViewCtrl extends PureViewCtrl {
|
||||
|
||||
/** Passed through template */
|
||||
readonly application!: WebApplication
|
||||
@@ -106,6 +105,10 @@ class TagsPanelCtrl extends PureCtrl {
|
||||
});
|
||||
if (!matchingTag || matchingTag.deleted) {
|
||||
this.selectTag(this.state.smartTags[0]);
|
||||
} else {
|
||||
this.setState({
|
||||
selectedTag: matchingTag
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +345,7 @@ class TagsPanelCtrl extends PureCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export class TagsPanel extends WebDirective {
|
||||
export class TagsView extends WebDirective {
|
||||
constructor() {
|
||||
super();
|
||||
this.restrict = 'E';
|
||||
@@ -351,7 +354,7 @@ export class TagsPanel extends WebDirective {
|
||||
};
|
||||
this.template = template;
|
||||
this.replace = true;
|
||||
this.controller = TagsPanelCtrl;
|
||||
this.controller = TagsViewCtrl;
|
||||
this.controllerAs = 'self';
|
||||
this.bindToController = true;
|
||||
}
|
||||
Reference in New Issue
Block a user