Editor TypeScript

This commit is contained in:
Mo Bitar
2020-04-11 19:42:50 -05:00
parent 16bde8d1d4
commit 2bc3658f1a
15 changed files with 794 additions and 614 deletions

View File

@@ -43,6 +43,10 @@ export class PureCtrl {
this.deinit();
}
public get appState() {
return this.application!.getAppState();
}
/** @private */
async resetState() {
this.state = this.getInitialState();
@@ -66,6 +70,10 @@ export class PureCtrl {
});
}
async updateUI(func: () => void) {
this.$timeout(func);
}
initProps(props: CtrlProps) {
if (Object.keys(this.props).length > 0) {
throw 'Already init-ed props.';

View File

@@ -157,10 +157,10 @@ class FooterCtrl extends PureCtrl {
streamItems() {
this.application.streamItems({
contentType: ContentTypes.Component,
contentType: ContentType.Component,
stream: async () => {
this.rooms = this.application.getItems({
contentType: ContentTypes.Component
contentType: ContentType.Component
}).filter((candidate) => {
return candidate.area === 'rooms' && !candidate.deleted;
});
@@ -175,7 +175,7 @@ class FooterCtrl extends PureCtrl {
contentType: 'SN|Theme',
stream: async () => {
const themes = this.application.getDisplayableItems({
contentType: ContentTypes.Theme
contentType: ContentType.Theme
}).filter((candidate) => {
return (
!candidate.deleted &&

View File

@@ -3,7 +3,7 @@ import template from '%/notes.pug';
import { ApplicationEvent, ContentTypes, removeFromArray } from 'snjs';
import { PureCtrl } from '@Controllers';
import { AppStateEvent } from '@/services/state';
import { KeyboardModifiers, KeyboardKeys } from '@/services/keyboardManager';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import {
PrefKeys
} from '@/services/preferencesManager';
@@ -155,7 +155,7 @@ class NotesCtrl extends PureCtrl {
streamNotesAndTags() {
this.application.streamItems({
contentType: [ContentTypes.Note, ContentTypes.Tag],
contentType: [ContentType.Note, ContentType.Tag],
stream: async ({ items }) => {
await this.reloadNotes();
const selectedNote = this.state.selectedNote;
@@ -169,7 +169,7 @@ class NotesCtrl extends PureCtrl {
}
/** Note has changed values, reset its flags */
const notes = items.filter((item) => item.content_type === ContentTypes.Note);
const notes = items.filter((item) => item.content_type === ContentType.Note);
for (const note of notes) {
if(note.deleted) {
continue;
@@ -202,7 +202,7 @@ class NotesCtrl extends PureCtrl {
title = `Note ${this.state.notes.length + 1}`;
}
const newNote = await this.application.createManagedItem({
contentType: ContentTypes.Note,
contentType: ContentType.Note,
content: {
text: '',
title: title
@@ -680,8 +680,8 @@ class NotesCtrl extends PureCtrl {
this.newNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: 'n',
modifiers: [
KeyboardModifiers.Meta,
KeyboardModifiers.Ctrl
KeyboardModifier.Meta,
KeyboardModifier.Ctrl
],
onKeyDown: (event) => {
event.preventDefault();
@@ -690,7 +690,7 @@ class NotesCtrl extends PureCtrl {
});
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Down,
key: KeyboardKey.Down,
elements: [
document.body,
this.getSearchBar()
@@ -705,7 +705,7 @@ class NotesCtrl extends PureCtrl {
});
this.nextNoteKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: KeyboardKeys.Up,
key: KeyboardKey.Up,
element: document.body,
onKeyDown: (event) => {
this.selectPreviousNote();
@@ -715,8 +715,8 @@ class NotesCtrl extends PureCtrl {
this.searchKeyObserver = this.application.getKeyboardService().addKeyObserver({
key: "f",
modifiers: [
KeyboardModifiers.Meta,
KeyboardModifiers.Shift
KeyboardModifier.Meta,
KeyboardModifier.Shift
],
onKeyDown: (event) => {
const searchBar = this.getSearchBar();

View File

@@ -1,21 +1,40 @@
import { WebDirective, PanelPuppet } from './../types';
import { WebApplication } from './../application';
import {
SNNote,
SNSmartTag,
ContentTypes,
SNTag,
ContentType,
ApplicationEvent,
ComponentActions
ComponentAction,
SNSmartTag,
ComponentArea,
SNComponent
} from 'snjs';
import template from '%/tags.pug';
import { AppStateEvent } from '@/services/state';
import { PANEL_NAME_TAGS } from '@/controllers/constants';
import { PrefKeys } from '@/services/preferencesManager';
import { STRING_DELETE_TAG } from '@/strings';
import { PureCtrl } from '@Controllers';
import { PureCtrl } from '@Controllers/abstract/pure_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 {
/** Passed through template */
readonly application!: WebApplication
private readonly panelPuppet: PanelPuppet
private unregisterComponent?: any
component?: SNComponent
private editingOriginalName?: string
formData: { tagTitle?: string } = {}
titles: Partial<Record<UuidString, string>> = {}
/* @ngInject */
constructor(
$timeout,
$timeout: ng.ITimeoutService,
) {
super($timeout);
this.panelPuppet = {
@@ -25,7 +44,7 @@ class TagsPanelCtrl extends PureCtrl {
deinit() {
this.unregisterComponent();
this.unregisterComponent = null;
this.unregisterComponent = undefined;
super.deinit();
}
@@ -37,12 +56,12 @@ class TagsPanelCtrl extends PureCtrl {
};
}
onAppStart() {
async onAppStart() {
super.onAppStart();
this.registerComponentHandler();
}
onAppLaunch() {
async onAppLaunch() {
super.onAppLaunch();
this.loadPreferences();
this.beginStreamingItems();
@@ -64,20 +83,21 @@ class TagsPanelCtrl extends PureCtrl {
* @access private
*/
getMappedTags() {
const tags = this.application.getItems({ contentType: ContentTypes.Tag });
const tags = this.application.getItems(ContentType.Tag) as SNTag[];
return tags.sort((a, b) => {
return a.content.title < b.content.title ? -1 : 1;
return a.title < b.title ? -1 : 1;
});
}
beginStreamingItems() {
this.application.streamItems({
contentType: ContentTypes.Tag,
stream: async ({ items }) => {
this.application.streamItems(
ContentType.Tag,
async (items) => {
await this.setState({
tags: this.getMappedTags(),
smartTags: this.application.getSmartTags(),
});
this.reloadTitles(items as SNTag[]);
this.reloadNoteCounts();
if (this.state.selectedTag) {
/** If the selected tag has been deleted, revert to All view. */
@@ -89,11 +109,17 @@ class TagsPanelCtrl extends PureCtrl {
}
}
}
});
);
}
reloadTitles(tags: Array<SNTag | SNSmartTag>) {
for(const tag of tags) {
this.titles[tag.uuid] = tag.title;
}
}
/** @override */
onAppStateEvent(eventName, data) {
onAppStateEvent(eventName: AppStateEvent, data?: any) {
if (eventName === AppStateEvent.PreferencesChanged) {
this.loadPreferences();
} else if (eventName === AppStateEvent.TagChanged) {
@@ -105,7 +131,7 @@ class TagsPanelCtrl extends PureCtrl {
/** @override */
async onAppEvent(eventName) {
async onAppEvent(eventName: ApplicationEvent) {
super.onAppEvent(eventName);
if (eventName === ApplicationEvent.LocalDataIncrementalLoad) {
this.reloadNoteCounts();
@@ -119,19 +145,25 @@ class TagsPanelCtrl extends PureCtrl {
}
reloadNoteCounts() {
let allTags = [];
let allTags: Array<SNTag | SNSmartTag> = [];
if (this.state.tags) {
allTags = allTags.concat(this.state.tags);
}
if (this.state.smartTags) {
allTags = allTags.concat(this.state.smartTags);
}
const noteCounts = {};
const noteCounts: NoteCounts = {};
for (const tag of allTags) {
const validNotes = SNNote.filterDummyNotes(tag.notes).filter((note) => {
return !note.archived && !note.content.trashed;
});
noteCounts[tag.uuid] = validNotes.length;
if (tag.isSmartTag()) {
const notes = this.application.notesMatchingSmartTag(tag as SNSmartTag);
noteCounts[tag.uuid] = notes.length;
} else {
const notes = this.application.referencesForItem(tag, ContentType.Note)
.filter((note) => {
return !note.archived && !note.trashed;
})
noteCounts[tag.uuid] = notes.length;
}
}
this.setState({
noteCounts: noteCounts
@@ -144,17 +176,22 @@ class TagsPanelCtrl extends PureCtrl {
}
const width = this.application.getPrefsService().getValue(PrefKeys.TagsPanelWidth);
if (width) {
this.panelPuppet.setWidth(width);
if (this.panelPuppet.isCollapsed()) {
this.panelPuppet.setWidth!(width);
if (this.panelPuppet.isCollapsed!()) {
this.application.getAppState().panelDidResize(
PANEL_NAME_TAGS,
this.panelPuppet.isCollapsed()
this.panelPuppet.isCollapsed!()
);
}
}
}
onPanelResize = (newWidth, lastLeft, isAtMaxWidth, isCollapsed) => {
onPanelResize = (
newWidth: number,
lastLeft: number,
isAtMaxWidth: boolean,
isCollapsed: boolean
) => {
this.application.getPrefsService().setUserPrefValue(
PrefKeys.TagsPanelWidth,
newWidth,
@@ -167,50 +204,49 @@ class TagsPanelCtrl extends PureCtrl {
}
registerComponentHandler() {
this.unregisterComponent = this.application.componentManager.registerHandler({
this.unregisterComponent = this.application.componentManager!.registerHandler({
identifier: 'tags',
areas: ['tags-list'],
areas: [ComponentArea.TagsList],
activationHandler: (component) => {
this.component = component;
},
contextRequestHandler: (component) => {
return null;
contextRequestHandler: () => {
return undefined;
},
actionHandler: (_, action, data) => {
if (action === ComponentActions.SelectItem) {
if (data.item.content_type === ContentTypes.Tag) {
const tag = this.application.findItem({ uuid: data.item.uuid });
if (action === ComponentAction.SelectItem) {
if (data.item.content_type === ContentType.Tag) {
const tag = this.application.findItem(data.item.uuid);
if (tag) {
this.selectTag(tag);
this.selectTag(tag as SNTag);
}
} else if (data.item.content_type === ContentTypes.SmartTag) {
this.application.createTemplateItem({
contentType: ContentTypes.SmartTag,
content: data.item.content
}).then(smartTag => {
this.selectTag(smartTag);
} else if (data.item.content_type === ContentType.SmartTag) {
this.application.createTemplateItem(
ContentType.SmartTag,
data.item.content
).then(smartTag => {
this.selectTag(smartTag as SNSmartTag);
});
}
} else if (action === ComponentActions.ClearSelection) {
} else if (action === ComponentAction.ClearSelection) {
this.selectTag(this.state.smartTags[0]);
}
}
});
}
async selectTag(tag) {
async selectTag(tag: SNTag) {
if (tag.isSmartTag()) {
Object.defineProperty(tag, 'notes', {
get: () => {
return this.application.getNotesMatchingSmartTag({
smartTag: tag
});
return this.application.notesMatchingSmartTag(tag as SNSmartTag);
}
});
}
if (tag.content.conflict_of) {
tag.content.conflict_of = null;
this.application.saveItem({ item: tag });
if (tag.conflictOf) {
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
mutator.conflictOf = undefined;
})
}
this.application.getAppState().setSelectedTag(tag);
}
@@ -219,9 +255,9 @@ class TagsPanelCtrl extends PureCtrl {
if (this.state.editingTag) {
return;
}
const newTag = await this.application.createTemplateItem({
contentType: ContentTypes.Tag
});
const newTag = await this.application.createTemplateItem(
ContentType.Tag
);
this.setState({
tags: [newTag].concat(this.state.tags),
previousTag: this.state.selectedTag,
@@ -231,14 +267,14 @@ class TagsPanelCtrl extends PureCtrl {
});
}
tagTitleDidChange(tag) {
onTagTitleChange(tag: SNTag | SNSmartTag) {
this.setState({
editingTag: tag
});
}
async saveTag($event, tag) {
$event.target.blur();
async saveTag($event: Event, tag: SNTag) {
($event.target! as HTMLInputElement).blur();
await this.setState({
editingTag: null,
});
@@ -246,8 +282,8 @@ class TagsPanelCtrl extends PureCtrl {
if (!tag.title || tag.title.length === 0) {
let newSelectedTag = this.state.selectedTag;
if (this.state.editingTag) {
tag.title = this.editingOriginalName;
this.editingOriginalName = null;
this.titles[tag.uuid] = this.editingOriginalName;
this.editingOriginalName = undefined;
} else if (this.state.newTag) {
newSelectedTag = this.state.previousTag;
}
@@ -259,14 +295,14 @@ class TagsPanelCtrl extends PureCtrl {
return;
}
this.editingOriginalName = null;
this.editingOriginalName = undefined;
const matchingTag = this.application.findTag({ title: tag.title });
const matchingTag = this.application.findTag(tag.title);
const alreadyExists = matchingTag && matchingTag !== tag;
if (this.state.newTag === tag && alreadyExists) {
this.application.alertService.alert({
text: "A tag with this name already exists."
});
this.application.alertService!.alert(
"A tag with this name already exists."
);
this.setState({
newTag: null,
tags: this.getMappedTags(),
@@ -274,40 +310,48 @@ class TagsPanelCtrl extends PureCtrl {
});
return;
}
this.application.saveItem({ item: tag });
this.application.changeAndSaveItem(tag.uuid, (mutator) => {
const tagMutator = mutator as TagMutator;
tagMutator.title = this.titles[tag.uuid]!;
});
this.selectTag(tag);
this.setState({
newTag: null
});
}
async selectedRenameTag($event, tag) {
async selectedRenameTag(tag: SNTag) {
this.editingOriginalName = tag.title;
await this.setState({
editingTag: tag
});
document.getElementById('tag-' + tag.uuid).focus();
document.getElementById('tag-' + tag.uuid)!.focus();
}
selectedDeleteTag(tag) {
selectedDeleteTag(tag: SNTag) {
this.removeTag(tag);
}
removeTag(tag) {
this.application.alertService.confirm({
text: STRING_DELETE_TAG,
destructive: true,
onConfirm: () => {
this.application.deleteItem({ item: tag });
removeTag(tag: SNTag) {
this.application.alertService!.confirm(
STRING_DELETE_TAG,
undefined,
undefined,
undefined,
() => {
/* On confirm */
this.application.deleteItem(tag);
this.selectTag(this.state.smartTags[0]);
}
});
},
undefined,
true,
);
}
}
export class TagsPanel {
export class TagsPanel extends WebDirective {
constructor() {
super();
this.restrict = 'E';
this.scope = {
application: '='

View File

@@ -27,7 +27,7 @@ class RevisionPreviewModalCtrl {
async configure() {
this.note = await this.application.createTemplateItem({
contentType: ContentTypes.Note,
contentType: ContentType.Note,
content: this.content
});
@@ -45,7 +45,7 @@ class RevisionPreviewModalCtrl {
* editor object has non-copyable properties like .window, which cannot be transfered
*/
const editorCopy = await this.application.createTemplateItem({
contentType: ContentTypes.Component,
contentType: ContentType.Component,
content: editorForNote.content
});
this.application.component.setReadonlyStateForComponent(editorCopy, true, true);

View File

@@ -1,125 +0,0 @@
/** @public */
export const KeyboardKeys = {
Tab: "Tab",
Backspace: "Backspace",
Up: "ArrowUp",
Down: "ArrowDown",
};
/** @public */
export const KeyboardModifiers = {
Shift: "Shift",
Ctrl: "Control",
/** ⌘ key on Mac, ⊞ key on Windows */
Meta: "Meta",
Alt: "Alt",
};
/** @private */
const KeyboardKeyEvents = {
Down: "KeyEventDown",
Up: "KeyEventUp"
};
export class KeyboardManager {
constructor() {
this.observers = [];
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
/** @access public */
deinit() {
this.observers.length = 0;
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
this.handleKeyDown = null;
this.handleKeyUp = null;
}
modifiersForEvent(event) {
const allModifiers = Object.keys(KeyboardModifiers).map((key) => KeyboardModifiers[key]);
const eventModifiers = allModifiers.filter((modifier) => {
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
const matches = (
((event.ctrlKey || event.key === KeyboardModifiers.Ctrl) && modifier === KeyboardModifiers.Ctrl) ||
((event.metaKey || event.key === KeyboardModifiers.Meta) && modifier === KeyboardModifiers.Meta) ||
((event.altKey || event.key === KeyboardModifiers.Alt) && modifier === KeyboardModifiers.Alt) ||
((event.shiftKey || event.key === KeyboardModifiers.Shift) && modifier === KeyboardModifiers.Shift)
);
return matches;
});
return eventModifiers;
}
eventMatchesKeyAndModifiers(event, key, modifiers = []) {
const eventModifiers = this.modifiersForEvent(event);
if (eventModifiers.length !== modifiers.length) {
return false;
}
for (const modifier of modifiers) {
if (!eventModifiers.includes(modifier)) {
return false;
}
}
// Modifers match, check key
if (!key) {
return true;
}
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
// In our case we don't differentiate between the two.
return key.toLowerCase() === event.key.toLowerCase();
}
notifyObserver(event, keyEventType) {
for (const observer of this.observers) {
if (observer.element && event.target !== observer.element) {
continue;
}
if (observer.elements && !observer.elements.includes(event.target)) {
continue;
}
if (observer.notElement && observer.notElement === event.target) {
continue;
}
if (observer.notElementIds && observer.notElementIds.includes(event.target.id)) {
continue;
}
if (this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
const callback = keyEventType === KeyboardKeyEvents.Down ? observer.onKeyDown : observer.onKeyUp;
if (callback) {
callback(event);
}
}
}
}
handleKeyDown(event) {
this.notifyObserver(event, KeyboardKeyEvents.Down);
}
handleKeyUp(event) {
this.notifyObserver(event, KeyboardKeyEvents.Up);
}
addKeyObserver({ key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds }) {
const observer = { key, modifiers, onKeyDown, onKeyUp, element, elements, notElement, notElementIds };
this.observers.push(observer);
return observer;
}
removeKeyObserver(observer) {
this.observers.splice(this.observers.indexOf(observer), 1);
}
}

View File

@@ -0,0 +1,147 @@
import { removeFromArray } from 'snjs';
export enum KeyboardKey {
Tab = "Tab",
Backspace = "Backspace",
Up = "ArrowUp",
Down = "ArrowDown",
};
export enum KeyboardModifier {
Shift = "Shift",
Ctrl = "Control",
/** ⌘ key on Mac, ⊞ key on Windows */
Meta = "Meta",
Alt = "Alt",
};
enum KeyboardKeyEvent {
Down = "KeyEventDown",
Up = "KeyEventUp"
};
type KeyboardObserver = {
key?: KeyboardKey
modifiers?: KeyboardModifier[]
onKeyDown?: (event: KeyboardEvent) => void
onKeyUp?: (event: KeyboardEvent) => void
element?: HTMLElement
elements?: HTMLElement[]
notElement?: HTMLElement
notElementIds?: string[]
}
export class KeyboardManager {
private observers: KeyboardObserver[] = []
private handleKeyDown: any
private handleKeyUp: any
constructor() {
this.handleKeyDown = (event: KeyboardEvent) => {
this.notifyObserver(event, KeyboardKeyEvent.Down);
}
this.handleKeyUp = (event: KeyboardEvent) => {
this.notifyObserver(event, KeyboardKeyEvent.Up);
}
window.addEventListener('keydown', this.handleKeyDown);
window.addEventListener('keyup', this.handleKeyUp);
}
public deinit() {
this.observers.length = 0;
window.removeEventListener('keydown', this.handleKeyDown);
window.removeEventListener('keyup', this.handleKeyUp);
this.handleKeyDown = undefined;
this.handleKeyUp = undefined;
}
modifiersForEvent(event: KeyboardEvent) {
const allModifiers = Object.values(KeyboardModifier);
const eventModifiers = allModifiers.filter((modifier) => {
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
const matches = (
(
(event.ctrlKey || event.key === KeyboardModifier.Ctrl)
&& modifier === KeyboardModifier.Ctrl
) ||
(
(event.metaKey || event.key === KeyboardModifier.Meta)
&& modifier === KeyboardModifier.Meta
) ||
(
(event.altKey || event.key === KeyboardModifier.Alt)
&& modifier === KeyboardModifier.Alt
) ||
(
(event.shiftKey || event.key === KeyboardModifier.Shift)
&& modifier === KeyboardModifier.Shift
)
);
return matches;
});
return eventModifiers;
}
eventMatchesKeyAndModifiers(
event: KeyboardEvent,
key: KeyboardKey,
modifiers: KeyboardModifier[] = []
) {
const eventModifiers = this.modifiersForEvent(event);
if (eventModifiers.length !== modifiers.length) {
return false;
}
for (const modifier of modifiers) {
if (!eventModifiers.includes(modifier)) {
return false;
}
}
// Modifers match, check key
if (!key) {
return true;
}
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
// In our case we don't differentiate between the two.
return key.toLowerCase() === event.key.toLowerCase();
}
notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent) {
const target = event.target as HTMLElement;
for (const observer of this.observers) {
if (observer.element && event.target !== observer.element) {
continue;
}
if (observer.elements && !observer.elements.includes(target)) {
continue;
}
if (observer.notElement && observer.notElement === event.target) {
continue;
}
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
continue;
}
if (this.eventMatchesKeyAndModifiers(event, observer.key!, observer.modifiers)) {
const callback = keyEvent === KeyboardKeyEvent.Down
? observer.onKeyDown
: observer.onKeyUp;
if (callback) {
callback(event);
}
}
}
}
addKeyObserver(observer: KeyboardObserver) {
this.observers.push(observer);
return () => {
removeFromArray(this.observers, observer);
};
}
}

View File

@@ -26,7 +26,7 @@ export class NativeExtManager extends ApplicationService {
get extManagerPred() {
const extManagerId = 'org.standardnotes.extensions-manager';
return SNPredicate.CompoundPredicate([
new SNPredicate('content_type', '=', ContentTypes.Component),
new SNPredicate('content_type', '=', ContentType.Component),
new SNPredicate('package_info.identifier', '=', extManagerId)
]);
}
@@ -34,7 +34,7 @@ export class NativeExtManager extends ApplicationService {
get batchManagerPred() {
const batchMgrId = 'org.standardnotes.batch-manager';
return SNPredicate.CompoundPredicate([
new SNPredicate('content_type', '=', ContentTypes.Component),
new SNPredicate('content_type', '=', ContentType.Component),
new SNPredicate('package_info.identifier', '=', batchMgrId)
]);
}
@@ -65,8 +65,8 @@ export class NativeExtManager extends ApplicationService {
}
// Handle addition of SN|ExtensionRepo permission
const permission = extensionsManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION);
if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) {
permission.content_types.push(ContentTypes.ExtensionRepo);
if (!permission.content_types.includes(ContentType.ExtensionRepo)) {
permission.content_types.push(ContentType.ExtensionRepo);
needsSync = true;
}
if (needsSync) {
@@ -92,13 +92,13 @@ export class NativeExtManager extends ApplicationService {
{
name: STREAM_ITEMS_PERMISSION,
content_types: [
ContentTypes.Component,
ContentTypes.Theme,
ContentTypes.ServerExtension,
ContentTypes.ActionsExtension,
ContentTypes.Mfa,
ContentTypes.Editor,
ContentTypes.ExtensionRepo
ContentType.Component,
ContentType.Theme,
ContentType.ServerExtension,
ContentType.ActionsExtension,
ContentType.Mfa,
ContentType.Editor,
ContentType.ExtensionRepo
]
}
]
@@ -110,7 +110,7 @@ export class NativeExtManager extends ApplicationService {
}
const payload = CreateMaxPayloadFromAnyObject({
object: {
content_type: ContentTypes.Component,
content_type: ContentType.Component,
content: content
}
});
@@ -146,7 +146,7 @@ export class NativeExtManager extends ApplicationService {
}
const payload = CreateMaxPayloadFromAnyObject({
object: {
content_type: ContentTypes.Component,
content_type: ContentType.Component,
content: content
}
});
@@ -172,8 +172,8 @@ export class NativeExtManager extends ApplicationService {
}
// Handle addition of SN|ExtensionRepo permission
const permission = batchManager.content.permissions.find((p) => p.name === STREAM_ITEMS_PERMISSION);
if (!permission.content_types.includes(ContentTypes.ExtensionRepo)) {
permission.content_types.push(ContentTypes.ExtensionRepo);
if (!permission.content_types.includes(ContentType.ExtensionRepo)) {
permission.content_types.push(ContentType.ExtensionRepo);
needsSync = true;
}
if (needsSync) {

View File

@@ -33,7 +33,7 @@ export class PreferencesManager extends ApplicationService {
streamPreferences() {
this.application.streamItems({
contentType: ContentTypes.UserPrefs,
contentType: ContentType.UserPrefs,
stream: () => {
this.loadSingleton();
}
@@ -41,7 +41,7 @@ export class PreferencesManager extends ApplicationService {
}
async loadSingleton() {
const contentType = ContentTypes.UserPrefs;
const contentType = ContentType.UserPrefs;
const predicate = new SNPredicate('content_type', '=', contentType);
this.userPreferences = await this.application.singletonManager.findOrCreateSingleton({
predicate: predicate,

View File

@@ -140,7 +140,7 @@ export class AppState {
);
}
async setSelectedNote(note: SNNote) {
async setSelectedNote(note?: SNNote) {
const run = async () => {
const previousNote = this.selectedNote;
this.selectedNote = note;
@@ -166,6 +166,12 @@ export class AppState {
}
}
getNoteTags(note: SNNote) {
return this.application.referencesForItem(note).filter((ref) => {
return ref.content_type === note.content_type;
}) as SNTag[]
}
getSelectedTag() {
return this.selectedTag;
}

View File

@@ -3,6 +3,7 @@ export class WebDirective implements ng.IDirective {
controllerAs?: string;
bindToController?: boolean | { [boundProperty: string]: string };
restrict?: string;
replace?: boolean
scope?: boolean | { [boundProperty: string]: string };
template?: string | ((tElement: any, tAttrs: any) => string);
}
@@ -15,4 +16,13 @@ export enum PasswordWizardType {
export interface PasswordWizardScope extends Partial<ng.IScope> {
type: PasswordWizardType,
application: any
}
export type PanelPuppet = {
onReady: () => void
ready?: boolean
setWidth?: (width: number) => void
setLeft?: (left: number) => void
isCollapsed?: () => boolean
flash?: () => void
}

View File

@@ -17,12 +17,12 @@
)
.title
input#note-title-editor.input(
ng-blur='self.onNameBlur()',
ng-blur='self.onTitleBlur()',
ng-change='self.onTitleChange()',
ng-disabled='self.noteLocked',
ng-focus='self.onNameFocus()',
ng-focus='self.onTitleFocus()',
ng-keyup='$event.keyCode == 13 && self.onTitleEnter($event)',
ng-model='self.state.note.title',
ng-model='self.editorValues.title',
select-on-click='true',
spellcheck='false')
#save-status
@@ -39,11 +39,11 @@
application='self.application'
)
input.tags-input(
ng-blur='self.saveTags()',
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.state.mutable.tagsString',
ng-model='self.editorValues.tagsInputValue',
placeholder='#tags',
spellcheck='false',
type='text'
@@ -85,7 +85,7 @@
)
menu-row(
action='self.selectedMenuItem(true); self.toggleNotePreview()',
circle="self.state.note.content.hidePreview ? 'danger' : 'success'",
circle="self.state.note.hidePreview ? 'danger' : 'success'",
circle-align="'right'",
desc="'Hide or unhide the note preview from the list of notes'",
label="'Preview'"
@@ -130,7 +130,7 @@
.sk-menu-panel-header
.sk-menu-panel-header-title Global Display
menu-row(
action="self.selectedMenuItem(true); self.toggleKey(self.prefKeyMonospace)",
action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeyMonospace)",
circle="self.state.monospaceEnabled ? 'success' : 'neutral'",
desc="'Toggles the font style for the default editor'",
disabled='self.state.selectedEditor',
@@ -138,7 +138,7 @@
subtitle="self.state.selectedEditor ? 'Not available with editor extensions' : null"
)
menu-row(
action="self.selectedMenuItem(true); self.toggleKey(self.prefKeySpellcheck)",
action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeySpellcheck)",
circle="self.state.spellcheck ? 'success' : 'neutral'",
desc="'Toggles spellcheck for the default editor'",
disabled='self.state.selectedEditor',
@@ -149,7 +149,7 @@
: (self.state.isDesktop ? 'May degrade editor performance' : null)
`)
menu-row(
action="self.selectedMenuItem(true); self.toggleKey(self.prefKeyMarginResizers)",
action="self.selectedMenuItem(true); self.togglePrefKey(self.prefKeyMarginResizers)",
circle="self.state.marginResizersEnabled ? 'success' : 'neutral'",
desc="'Allows for editor left and right margins to be resized'",
faded='!self.state.marginResizersEnabled',
@@ -217,7 +217,7 @@
ng-click='self.clickedTextArea()',
ng-focus='self.onContentFocus()',
ng-if='!self.state.selectedEditor',
ng-model='self.state.note.text',
ng-model='self.editorValues.text',
ng-model-options='{ debounce: self.state.editorDebounce}',
ng-readonly='self.noteLocked',
ng-trim='false'

View File

@@ -120,8 +120,8 @@
.note-preview(
ng-if=`
!self.state.hideNotePreview &&
!note.content.hidePreview &&
!note.content.protected`
!note.hidePreview &&
!note.protected`
)
.html-preview(
ng-bind-html='note.content.preview_html',

View File

@@ -23,7 +23,11 @@
ng-repeat='tag in self.state.smartTags'
)
.tag-info
input.title(ng-disabled='true', ng-model='tag.title')
input.title(
ng-disabled='true',
ng-change='self.onTagTitleChange(tag)'
ng-model='self.titles[tag.uuid]'
)
.count(ng-show='tag.content.isAllTag') {{self.state.noteCounts[tag.uuid]}}
.tags-title-section.section-title-bar
.section-title-bar-header
@@ -38,12 +42,12 @@
.tag-icon #
input.title(
ng-attr-id='tag-{{tag.uuid}}',
ng-blur='self.saveTag($event, tag)',
ng-change='self.tagTitleDidChange(tag)',
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()',
ng-model='tag.title',
should-focus='self.state.newTag || self.state.editingTag == tag',
sn-autofocus='true',
spellcheck='false'
@@ -53,8 +57,8 @@
.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($event, 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.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.