Skip to content
This repository has been archived by the owner on Feb 12, 2022. It is now read-only.

Stack input on note #112

Merged
merged 11 commits into from
Dec 19, 2018
Empty file added src/assets/.gitkeep
Empty file.
2 changes: 1 addition & 1 deletion src/browser/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class AppComponent implements OnInit {
);

readonly noSelectedNote: Observable<boolean> = this.collection
.getSelectedNote(false)
.getSelectedNote()
.pipe(map(selectedNote => selectedNote === null));

constructor(
Expand Down
15 changes: 14 additions & 1 deletion src/browser/note/note-collection/note-collection.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export enum NoteCollectionActionTypes {
UPDATE_CONTRIBUTION_FAIL = '[NoteCollection] Update contribution fail',
CHANGE_NOTE_TITLE = '[NoteCollection] Change note title',
DELETE_NOTE = '[NoteCollection] Delete note',
CHANGE_NOTE_STACKS = '[NoteCollection] Change note stacks',
}


Expand Down Expand Up @@ -142,6 +143,17 @@ export class DeleteNoteAction implements Action {
}


export class ChangeNoteStacksAction implements Action {
readonly type = NoteCollectionActionTypes.CHANGE_NOTE_STACKS;

constructor(public readonly payload: {
note: NoteItem,
stacks: string[],
}) {
}
}


export type NoteCollectionAction =
LoadNoteCollectionAction
| LoadNoteCollectionCompleteAction
Expand All @@ -157,4 +169,5 @@ export type NoteCollectionAction =
| UpdateNoteContributionAction
| UpdateNoteContributionFailAction
| ChangeNoteTitleAction
| DeleteNoteAction;
| DeleteNoteAction
| ChangeNoteStacksAction;
2 changes: 2 additions & 0 deletions src/browser/note/note-collection/note-collection.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DatePipe } from '@angular/common';
import { NgModule } from '@angular/core';
import { SharedModule } from '../../shared';
import { StackSharedModule } from '../../stack/stack-shared';
import { UiModule } from '../../ui/ui.module';
import { NoteSharedModule } from '../note-shared';
import { CreateNewNoteDialogComponent } from './create-new-note-dialog/create-new-note-dialog.component';
Expand All @@ -20,6 +21,7 @@ import { NoteListToolsComponent } from './note-list-tools/note-list-tools.compon
UiModule,
SharedModule,
NoteSharedModule,
StackSharedModule,
],
declarations: [
NoteCalendarComponent,
Expand Down
48 changes: 48 additions & 0 deletions src/browser/note/note-collection/note-collection.reducer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { datetime, DateUnits } from '../../../libs/datetime';
import { NoteItemDummy, prepareForFilteringNotes, prepareForSortingNotes } from './dummies';
import {
AddNoteAction,
ChangeNoteStacksAction,
ChangeNoteTitleAction,
ChangeSortDirectionAction,
ChangeSortOrderAction,
Expand Down Expand Up @@ -524,4 +525,51 @@ describe('browser.note.noteCollection.noteCollectionReducer', () => {
expect(result.selectedNote).toBeNull();
});
});

describe('CHANGE_NOTE_STACKS', () => {
const dummy = new NoteItemDummy();
let notes: NoteItem[];
let beforeState: NoteCollectionState;

beforeEach(() => {
notes = createDummies(dummy, 10);
beforeState = noteCollectionReducer(
undefined,
new LoadNoteCollectionCompleteAction({ notes }),
);
});

it('should change note stack ids at index.', () => {
const targetNote = notes[3];
const result = noteCollectionReducer(
beforeState,
new ChangeNoteStacksAction({
note: targetNote,
stacks: ['a', 'b', 'c'],
}),
);

expect(result.notes[3].stackIds).toEqual(['a', 'b', 'c']);
});

it('should change note stack ids at index and selected note if index of note '
+ 'is currently selected.', () => {
const targetNote = notes[7];
beforeState = noteCollectionReducer(
beforeState,
new SelectNoteAction({ note: targetNote }),
);

const result = noteCollectionReducer(
beforeState,
new ChangeNoteStacksAction({
note: targetNote,
stacks: ['a', 'b', 'c'],
}),
);

expect(result.selectedNote.stackIds).toEqual(['a', 'b', 'c']);
expect(result.notes[7].stackIds).toEqual(['a', 'b', 'c']);
});
});
});
7 changes: 7 additions & 0 deletions src/browser/note/note-collection/note-collection.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,13 @@ export function noteCollectionReducer(
withNoteDelete(state, getIndexOfNote(state, action.payload.note)),
);

case NoteCollectionActionTypes.CHANGE_NOTE_STACKS:
return withFilteredAndSortedNotes(withNoteUpdate(
state,
getIndexOfNote(state, action.payload.note),
{ stackIds: action.payload.stacks },
));

default:
return state;
}
Expand Down
49 changes: 28 additions & 21 deletions src/browser/note/note-collection/note-collection.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { NoteStateWithRoot } from '../note.state';
import { NoteDummy, NoteItemDummy } from './dummies';
import {
AddNoteAction,
ChangeNoteStacksAction,
ChangeNoteTitleAction,
DeleteNoteAction,
DeselectNoteAction,
Expand Down Expand Up @@ -318,13 +319,17 @@ describe('browser.note.noteCollection.NoteCollectionService', () => {

const callback = jasmine.createSpy('change note title spy');
collection.changeNoteTitle(note, newTitle).then(callback).catch(err => error = err);
flush();

mockFs
.expect<boolean>({
methodName: 'isPathExists',
args: [newContentFilePath],
methodName: 'renameFile',
args: [
note.contentFilePath, // Old Path
newContentFilePath, // New Path
],
})
.flush(true);
.error(new Error());

expect(error instanceof NoteContentFileAlreadyExistsError).toBe(true);
expect((error as NoteError).code).toEqual(NoteErrorCodes.CONTENT_FILE_ALREADY_EXISTS);
Expand All @@ -345,31 +350,17 @@ describe('browser.note.noteCollection.NoteCollectionService', () => {

const callback = jasmine.createSpy('change note title spy');
collection.changeNoteTitle(note, newTitle).then(callback);
flush();

// Pass file duplication.
mockFs
.expect<boolean>({
methodName: 'isPathExists',
args: [newContentFilePath],
})
.flush(false);

// 2 stubs
const stubs = mockFs.expectMany([
{
methodName: 'writeJsonFile',
args: [note.filePath, FsMatchLiterals.ANY],
},
{
.expect<void>({
methodName: 'renameFile',
args: [
note.contentFilePath, // Old Path
newContentFilePath, // New Path
],
},
]);

stubs.forEach(stub => stub.flush());
})
.flush();

expect(store.dispatch).toHaveBeenCalledWith(new ChangeNoteTitleAction({
note,
Expand All @@ -380,6 +371,22 @@ describe('browser.note.noteCollection.NoteCollectionService', () => {
}));
});

describe('changeNoteStacks', () => {
it('should dispatch \"CHANGE_NOTE_STACKS\' action.', () => {
spyOn(store, 'dispatch');

const note = noteItemDummy.create();
const stacks = ['a', 'b', 'c'];

collection.changeNoteStacks(note, stacks);

expect(store.dispatch).toHaveBeenCalledWith(new ChangeNoteStacksAction({
note,
stacks,
}));
});
});

describe('getNoteVcsFileChangeStatus', () => {
it('should return status of note.', () => {
const vcsFileChanges = new Subject<VcsFileChange[]>();
Expand Down
68 changes: 38 additions & 30 deletions src/browser/note/note-collection/note-collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { select, Store } from '@ngrx/store';
import { shell } from 'electron';
import * as path from 'path';
import { Observable, Subject, Subscription, zip } from 'rxjs';
import { filter, map, switchMap, take } from 'rxjs/operators';
import { map, switchMap, take } from 'rxjs/operators';
import { makeNoteContentFileName, Note, NoteSnippetTypes } from '../../../core/note';
import { VcsFileChange, VcsFileChangeStatusTypes } from '../../../core/vcs';
import { isOutsidePath } from '../../../libs/path';
Expand All @@ -16,7 +16,9 @@ import { convertToNoteSnippets, NoteParser } from '../note-shared';
import { NoteStateWithRoot } from '../note.state';
import {
AddNoteAction,
ChangeNoteTitleAction, DeleteNoteAction,
ChangeNoteStacksAction,
ChangeNoteTitleAction,
DeleteNoteAction,
DeselectNoteAction,
LoadNoteCollectionAction,
LoadNoteCollectionCompleteAction,
Expand Down Expand Up @@ -97,19 +99,15 @@ export class NoteCollectionService implements OnDestroy {
}));
}

getFilteredAndSortedNoteList(waitForInitial: boolean = true): Observable<NoteItem[]> {
getFilteredAndSortedNoteList(): Observable<NoteItem[]> {
return this.store.pipe(
select(state => state.note.collection),
filter(state => waitForInitial ? state.loaded : true),
select(state => state.filteredAndSortedNotes),
select(state => state.note.collection.filteredAndSortedNotes),
);
}

getSelectedNote(waitForInitial: boolean = true): Observable<NoteItem | null> {
getSelectedNote(): Observable<NoteItem | null> {
return this.store.pipe(
select(state => state.note.collection),
filter(state => waitForInitial ? state.loaded : true),
select(state => state.selectedNote),
select(state => state.note.collection.selectedNote),
);
}

Expand Down Expand Up @@ -215,33 +213,39 @@ export class NoteCollectionService implements OnDestroy {

async changeNoteTitle(noteItem: NoteItem, newTitle: string): Promise<void> {
const dirName = path.dirname(noteItem.contentFilePath);
const newContentFileName = makeNoteContentFileName(noteItem.createdDatetime, newTitle);
const newContentFilePath = path.resolve(dirName, newContentFileName);
let newContentFileName: string;
let newContentFilePath: string;

const allNotes = await toPromise(this.store.pipe(
select(state => state.note.collection.notes),
take(1),
));
const allNoteContentFilePaths = allNotes.map(item => item.contentFilePath);

let index = 0;
const isNoteTitleDuplicated = title => allNoteContentFilePaths.includes(title);

// Check title duplication.
do {
const title = index === 0 ? newTitle : `${newTitle}(${index})`;

newContentFileName = makeNoteContentFileName(noteItem.createdDatetime, title);
newContentFilePath = path.resolve(dirName, newContentFileName);
index++;
} while (isNoteTitleDuplicated(newContentFilePath));

// If content file name is same, just ignore.
if (newContentFileName === noteItem.contentFileName) {
return;
}

if (await toPromise(this.fs.isPathExists(newContentFilePath))) {
// Rename file.
try {
await toPromise(this.fs.renameFile(noteItem.contentFilePath, newContentFilePath));
} catch (error) {
throw new NoteContentFileAlreadyExistsError();
}

const note: Note = {
id: noteItem.id,
title: newTitle,
snippets: noteItem.snippets,
stackIds: noteItem.stackIds,
contentFileName: newContentFileName,
contentFilePath: newContentFilePath,
createdDatetime: noteItem.createdDatetime,
};

await toPromise(zip(
this.fs.writeJsonFile<Note>(noteItem.filePath, note),
this.fs.renameFile(noteItem.contentFilePath, newContentFilePath),
));

this.store.dispatch(new ChangeNoteTitleAction({
note: noteItem,
title: newTitle,
Expand All @@ -250,6 +254,10 @@ export class NoteCollectionService implements OnDestroy {
}));
}

changeNoteStacks(noteItem: NoteItem, stacks: string[]): void {
this.store.dispatch(new ChangeNoteStacksAction({ note: noteItem, stacks }));
}

deleteNote(noteItem: NoteItem): void {
const allRemoved = shell.moveItemToTrash(noteItem.filePath)
&& shell.moveItemToTrash(noteItem.contentFilePath);
Expand All @@ -259,7 +267,7 @@ export class NoteCollectionService implements OnDestroy {
return;
}

this.getSelectedNote(false).pipe(take(1)).subscribe((selectedNote: NoteItem) => {
this.getSelectedNote().pipe(take(1)).subscribe((selectedNote: NoteItem) => {
if (selectedNote && selectedNote.id === noteItem.id) {
this.store.dispatch(new DeselectNoteAction());
}
Expand All @@ -272,7 +280,7 @@ export class NoteCollectionService implements OnDestroy {
this.toggleNoteSelectionSubscription =
this._toggleNoteSelection.asObservable().pipe(
switchMap(note =>
this.getSelectedNote(false).pipe(
this.getSelectedNote().pipe(
take(1),
map(selectedNote => ([selectedNote, note])),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<div class="NoteList">
<div fxLayout fxLayoutAlign="center center" *ngIf="isEmpty" class="NoteList__emptyState">
<div fxLayout fxLayoutAlign="center center"
[class.NoteList__emptyState--hide]="!isEmpty"
class="NoteList__emptyState">
No Notes
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@
min-height: 1px;

&__emptyState {
visibility: visible;
padding: $spacing-triple $spacing;
font: {
size: $font-size-md;
weight: $font-weight-semiBold;
};

&--hide {
display: none !important;
visibility: hidden;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class NoteListComponent implements OnInit, OnDestroy, AfterViewInit {

ngOnInit(): void {
this.selectedNoteSubscription = this.collection
.getSelectedNote(true)
.getSelectedNote()
.subscribe(selectedNote => this._selectedNote = selectedNote);
}

Expand Down
Loading