Skip to content

Commit

Permalink
Merge pull request #358 from MicroPad/357-readonly-help
Browse files Browse the repository at this point in the history
Make Help Readonly
  • Loading branch information
NickGeek authored Jun 18, 2021
2 parents bf60fa8 + 60cc800 commit de42d43
Show file tree
Hide file tree
Showing 25 changed files with 185 additions and 50 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "micropad",
"version": "3.29.1",
"version": "3.30.0",
"private": true,
"scripts": {
"preinstall": "python3 ../libs/build-libs.py; ./get_precache_files.py > src/extraPrecacheFiles.ts",
Expand Down
10 changes: 10 additions & 0 deletions app/src/app/ReadOnly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { isDev } from './util';

export function isReadOnlyNotebook(title: string) {
// All notebooks are editable in dev mode
if (isDev()) {
return false;
}

return title === 'Help';
}
25 changes: 23 additions & 2 deletions app/src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,11 @@ export const actions = {
imagePasted: actionCreator.async<string, void, Error>('IMAGE_PASTED'),
exportAll: actionCreator.async<void, Blob, Error>('EXPORT_ALL_NOTEPADS'),
exportToMarkdown: actionCreator.async<void, Blob, Error>('EXPORT_ALL_NOTEPADS_TO_MD'),
clearOldData: actionCreator.async<void, void, Error>('CLEAR_OLD_DATA'),
clearOldData: actionCreator.async<{ silent: boolean }, void, Error>('CLEAR_OLD_DATA'),
getHelp: actionCreator.async<void, void, Error>('GET_HELP'),
getDueDates: actionCreator.async<string[], DueItem[], Error>('GET_DUE_DATES'),
moveObjAcrossNotepads: actionCreator.async<MoveAcrossNotepadsAction, void, Error>('CROSS_NOTEPAD_MOVE'),

started: actionCreator<void>('APP_STARTED'),
restoreJsonNotepad: actionCreator<string>('PARSE_JSON_NOTEPAD'),
restoreJsonNotepadAndLoadNote: actionCreator<RestoreJsonNotepadAndLoadNoteAction>('PARSE_JSON_NOTEPAD_AND_LOAD_NOTE'),
newNotepad: actionCreator<FlatNotepad>('NEW_NOTEPAD'),
Expand Down Expand Up @@ -120,3 +119,25 @@ export const actions = {
closeNotepad: actionCreator<void>('CLOSE_NOTEPAD'),
importMarkdown: actionCreator<Translators.Markdown.MarkdownImport[]>('IMPORT_FROM_MARKDOWN')
};

export const READ_ONLY_ACTIONS: ReadonlySet<string> = new Set<string>([
actions.quickNote.started.type,
actions.quickNote.done.type,
actions.quickNote.failed.type,

actions.imagePasted.started.type,
actions.imagePasted.done.type,
actions.imagePasted.failed.type,

actions.updateElement.type,
actions.quickMarkdownInsert.type,
actions.insertElement.type,
actions.toggleInsertMenu.type,
actions.openEditor.type,
actions.renameNotepadObject.type,
actions.newSection.type,
actions.newNote.type,
actions.moveNotepadObject.type,
actions.deleteElement.type,
actions.deleteNotepadObject.type
]);
14 changes: 12 additions & 2 deletions app/src/app/assets/Help.npx

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion app/src/app/components/explorer/NotepadExplorerComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export default class NotepadExplorerComponent extends React.Component<ConnectedP
<div>
<strong style={{ display: 'inline-flex' }}
onContextMenu={NotepadExplorerComponent.handleRightClick}>
<span style={{ paddingRight: '5px' }}>{notepad.title}</span>
<span style={{ paddingRight: '5px' }}>
{notepad.title}
{this.props.isReadOnly && <em style={{ paddingLeft: '5px' }}>(Read-Only)</em>}
</span>
<ExplorerOptionsComponent objToEdit={notepad} type="notepad"/>
</strong>

Expand Down
3 changes: 2 additions & 1 deletion app/src/app/components/explorer/NotepadExplorerContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export function mapStateToProps({ notepads, explorer, app, currentNote }: IStore
openSections: explorer.openSections,
isFullScreen: app.isFullScreen,
openNote: note,
theme: ThemeValues[app.theme]
theme: ThemeValues[app.theme],
isReadOnly: !!notepads?.notepad?.isReadOnly
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { actions } from '../../../actions';
import AppSettingsComponent from './AppSettingsComponent';

export const appSettingsContainer = connect(() => ({}), dispatch => ({
clearOldData: () => dispatch(actions.clearOldData.started())
clearOldData: () => dispatch(actions.clearOldData.started({ silent: false }))
}));

export default appSettingsContainer(AppSettingsComponent);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import { FormEvent } from 'react';
import { Button, Col, Icon, Input, Modal, Row } from 'react-materialize';
import { Notepad } from 'upad-parse/dist';
import { NPXObject } from 'upad-parse/dist/NPXObject';
Expand Down Expand Up @@ -88,7 +89,8 @@ export default class ExplorerOptionsComponent extends React.Component<Props> {
);
}

private rename = () => {
private rename = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const { objToEdit, type, renameNotepad, renameNotepadObject } = this.props;
const value = this.titleInput.state.value;

Expand All @@ -107,6 +109,8 @@ export default class ExplorerOptionsComponent extends React.Component<Props> {
default:
break;
}

return false;
}

private delete = async () => {
Expand Down
1 change: 1 addition & 0 deletions app/src/app/components/header/NotepadDropdownComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export default class NotepadDropdownComponent extends React.Component<INotepadDr

private createNotepad = async () => {
const title = await Dialog.prompt('Notebook/Notepad Title:');
if (!title) return;

let notepad = new FlatNotepad(title);
let section = FlatNotepad.makeFlatSection('Unorganised Notes');
Expand Down
5 changes: 3 additions & 2 deletions app/src/app/components/note-viewer/NoteViewerComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface INoteViewerComponentProps {
downloadAsset?: (filename: string, uuid: string) => void;
updateElement?: (id: string, changes: NoteElement, newAsset?: Blob) => void;
toggleInsertMenu?: (opts: Partial<IInsertElementState>) => void;
hideInsert?: () => void;
insert?: (element: NoteElement) => void;
deleteElement?: (id: string) => void;
deleteNotepad?: () => void;
Expand Down Expand Up @@ -141,14 +142,14 @@ export default class NoteViewerComponent extends React.Component<INoteViewerComp
}

componentDidMount() {
const { edit, toggleInsertMenu } = this.props;
const { edit, hideInsert } = this.props;

this.escapeHit$.subscribe(() => edit!(''));

setInterval(() => {
if (this.scrolling) {
this.scrolling = false;
toggleInsertMenu!({ enabled: false });
hideInsert?.();
}
}, 250);
}
Expand Down
8 changes: 7 additions & 1 deletion app/src/app/containers/NoteViewerContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import { Note } from 'upad-parse/dist';
let noteRef: string = '';
let note: Note | null;
let notepadTitle: string = '';
let isInsertMenuOpen: boolean = false;

export function mapStateToProps({ notepads, currentNote, app }: IStoreState) {
noteRef = currentNote.ref;
isInsertMenuOpen = currentNote.insertElement.enabled;

if (currentNote.ref.length !== 0) {
note = notepads.notepad!.item!.notes[currentNote.ref];
Expand Down Expand Up @@ -62,7 +64,11 @@ export function mapDispatchToProps(dispatch: Dispatch<Action>): Partial<INoteVie
})),
makeQuickNotepad: () => dispatch(actions.quickNotepad(undefined)),
makeQuickNote: () => dispatch(actions.quickNote.started(undefined)),
deleteNotepad: () => dispatch(actions.deleteNotepad(notepadTitle))
deleteNotepad: () => dispatch(actions.deleteNotepad(notepadTitle)),
hideInsert: () => {
if (!isInsertMenuOpen) return;
return dispatch(actions.toggleInsertMenu({ enabled: false }));
}
};
}

Expand Down
14 changes: 12 additions & 2 deletions app/src/app/epics/HelpEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,25 @@ import { filterTruthy } from '../util';
import { actions, MicroPadAction } from '../actions';
import { Dialog } from '../services/dialogs';

export const getHelp$ = (action$: Observable<MicroPadAction>, store: EpicStore) =>
const HELP_READONLY_DATE = new Date('2021-06-18T14:34:30.958+12:00');

export const getHelp$ = (action$: Observable<MicroPadAction>, store: EpicStore, { getStorage }: EpicDeps) =>
action$.pipe(
ofType<MicroPadAction>(actions.getHelp.started.type),
concatMap(() =>
from((async () => {
const notepadList = store.getState().notepads.savedNotepadTitles;
if (!notepadList || !notepadList.includes('Help')) return true;

return Dialog.confirm(`You have already imported the Help notebook. It can be accessed from the notebooks dropdown. If you continue you will lose any changes made to the notebook.`);
const helpLastModified: string | null = await getStorage().notepadStorage.getItem<string>('Help')
.then(np => np ? JSON.parse(np).lastModified : null)
.catch(err => { console.error(err); return null; });

if (!helpLastModified || new Date(helpLastModified).getTime() < HELP_READONLY_DATE.getTime()) {
return Dialog.confirm(`You have already imported the Help notebook. It can be accessed from the notebooks dropdown. If you continue you will lose any changes made to the notebook.`);
}

return true;
})())
),
filterTruthy(),
Expand Down
4 changes: 2 additions & 2 deletions app/src/app/epics/NoteEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ const loadNote$ = (action$: Observable<MicroPadAction>, store: EpicStore) =>
];
}

const error = new Error(`MicroPad couldn't load the current note`);
console.warn(error);
const error = new Error(`MicroPad couldn't load the current note (handled in loadNote$)`);
console.error(error);
return [actions.loadNote.failed({ params: ref, error })];
})
);
Expand Down
29 changes: 26 additions & 3 deletions app/src/app/epics/NotepadEpics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { actions, MicroPadAction } from '../actions';
import { actions, MicroPadAction, READ_ONLY_ACTIONS } from '../actions';
import {
catchError,
combineLatest,
Expand Down Expand Up @@ -43,8 +43,9 @@ import { Dispatch } from 'redux';
import { format } from 'date-fns';
import { NotepadShell } from 'upad-parse/dist/interfaces';
import { fromShell } from '../services/CryptoService';
import { ASSET_STORAGE, NOTEPAD_STORAGE } from '../root';
import { ASSET_STORAGE, NOTEPAD_STORAGE, store as STORE } from '../root';
import { EpicDeps, EpicStore } from './index';
import * as Materialize from 'materialize-css/dist/js/materialize';

const parseQueue: string[] = [];

Expand Down Expand Up @@ -519,6 +520,27 @@ const moveObjAcrossNotepadsFailure$ = (actions$: Observable<MicroPadAction>) =>
noEmit()
);

const warnOnReadOnlyEdit$ = (actions$: Observable<MicroPadAction>, store: EpicStore, { getToastEventHandler }: EpicDeps) =>
actions$.pipe(
filter(() => !!store.getState().notepads.notepad?.isReadOnly),
filter(action => READ_ONLY_ACTIONS.has(action.type)),
tap(() => {
Materialize.Toast.removeAll();
const guid = getToastEventHandler().register(async () => {
const newTitle = await Dialog.prompt('New Title:');
if (!newTitle) return;

STORE.dispatch(actions.renameNotepad.started(newTitle));
})

Materialize.toast(`This notepad is read-only. Changes will not be saved.<br />` +
`Please create a notebook or open another one using the notebooks dropdown if you want to edit a notebook.<br />` +
`If you have made changes to this notebook, you can make it editable by renaming it.<br />` +
`<a class="btn-flat amber-text" style="font-weight: 500;" href="#!" onclick="window.toastEvent('${guid}');">RENAME</a>`, 10_000);
}),
noEmit()
)

export const notepadEpics$ = combineEpics<MicroPadAction, Dispatch, EpicDeps>(
parseNpx$,
syncOnNotepadParsed$ as any,
Expand All @@ -543,7 +565,8 @@ export const notepadEpics$ = combineEpics<MicroPadAction, Dispatch, EpicDeps>(
quickNotepad$,
autoFillNewNotepads$,
moveObjAcrossNotepads$,
moveObjAcrossNotepadsFailure$
moveObjAcrossNotepadsFailure$,
warnOnReadOnlyEdit$
);

interface IExportedNotepad {
Expand Down
34 changes: 21 additions & 13 deletions app/src/app/epics/StorageEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
concatMap,
debounceTime,
distinctUntilChanged,
distinctUntilKeyChanged,
filter,
map,
mergeMap,
Expand All @@ -27,6 +28,7 @@ import { NotepadShell } from 'upad-parse/dist/interfaces';
import { ASSET_STORAGE, NOTEPAD_STORAGE } from '../root';
import { ICurrentNoteState } from '../reducers/NoteReducer';
import { EpicDeps, EpicStore } from './index';
import { isReadOnlyNotebook } from '../ReadOnly';

let currentNotepadTitle = '';

Expand Down Expand Up @@ -55,13 +57,14 @@ const saveOnChanges$ = (action$: Observable<MicroPadAction>, store: EpicStore) =
map((notepadState: INotepadStoreState) => notepadState.item),
filterTruthy(),
debounceTime(1000),
distinctUntilChanged(),
distinctUntilKeyChanged('lastModified'),
filter((notepad: FlatNotepad) => {
const condition = notepad.title === currentNotepadTitle;
currentNotepadTitle = notepad.title;

return condition;
}),
filter(notepad => !isReadOnlyNotebook(notepad.title)),
map((notepad: FlatNotepad) => notepad.toNotepad()),
mergeMap((notepad: Notepad) => {
const actionsToReturn: Action<any>[] = [];
Expand Down Expand Up @@ -157,7 +160,7 @@ const deleteNotepad$ = (action$: Observable<MicroPadAction>) =>
ofType<MicroPadAction, Action<string>>(actions.deleteNotepad.type),
map((action: Action<string>) => action.payload),
tap((notepadTitle: string) => from(NOTEPAD_STORAGE.removeItem(notepadTitle))),
noEmit()
map(() => actions.clearOldData.started({ silent: true }))
);

export type LastOpenedNotepad = { notepadTitle: string, noteRef?: string };
Expand Down Expand Up @@ -229,26 +232,26 @@ const clearLastOpenedNotepad$ = (action$: Observable<MicroPadAction>) =>

const clearOldData$ = (action$: Observable<MicroPadAction>, store: EpicStore) =>
action$.pipe(
ofType<MicroPadAction>(actions.clearOldData.started.type),
concatMap(() =>
from(cleanHangingAssets(NOTEPAD_STORAGE, ASSET_STORAGE, store.getState())).pipe(
ofType<MicroPadAction, Action<{ silent: boolean }>>(actions.clearOldData.started.type),
concatMap(action =>
from(cleanHangingAssets(NOTEPAD_STORAGE, ASSET_STORAGE, store.getState(), action.payload.silent)).pipe(
mergeMap((addPasskeyActions: Action<AddCryptoPasskeyAction>[]) => [
actions.clearOldData.done({ params: undefined, result: undefined }),
actions.clearOldData.done({ params: action.payload, result: undefined }),
...addPasskeyActions
]),
catchError(error => {
Dialog.alert('There was an error clearing old data');
console.error(error);
return of(actions.clearOldData.failed({ params: undefined, error }));
return of(actions.clearOldData.failed({ params: action.payload, error }));
})
)
)
);

const notifyOnClearOldDataSuccess$ = (action$: Observable<Action<Success<void, void>>>) =>
action$.pipe(
ofType<MicroPadAction>(actions.clearOldData.done.type),
tap(() => Dialog.alert('The spring cleaning has been done!')),
ofType<MicroPadAction, Action<Success<{ silent: boolean }, void>>>(actions.clearOldData.done.type),
tap(action => !action.payload.params.silent && Dialog.alert('The spring cleaning has been done!')),
noEmit()
);

Expand All @@ -271,13 +274,18 @@ export const storageEpics$ = combineEpics(
/**
* Clean up all the assets that aren't in any notepads yet
*/
async function cleanHangingAssets(notepadStorage: LocalForage, assetStorage: LocalForage, state: IStoreState): Promise<Action<AddCryptoPasskeyAction>[]> {
async function cleanHangingAssets(notepadStorage: LocalForage, assetStorage: LocalForage, state: IStoreState, silent): Promise<Action<AddCryptoPasskeyAction>[]> {
const cryptoPasskeys: Action<AddCryptoPasskeyAction>[] = [];

const notepads: Promise<EncryptNotepadAction>[] = [];
const notepads: Promise<EncryptNotepadAction | Error>[] = [];
await notepadStorage.iterate((json: string) => {
const shell: NotepadShell = JSON.parse(json);
notepads.push(fromShell(shell, state.notepadPasskeys[shell.title]));
let passkey = state.notepadPasskeys[shell.title];
if (!passkey && silent) {
passkey = '';
}

notepads.push(fromShell(shell, passkey).catch(err => err));

return;
});
Expand All @@ -290,7 +298,7 @@ async function cleanHangingAssets(notepadStorage: LocalForage, assetStorage: Loc

const areNotepadsStillEncrypted = !!resolvedNotepadsOrErrors.find(res => res instanceof Error);

const resolvedNotepads = resolvedNotepadsOrErrors.filter(res => !(res instanceof Error)).map((cryptoInfo: EncryptNotepadAction) => {
const resolvedNotepads = resolvedNotepadsOrErrors.filter((res): res is EncryptNotepadAction => !(res instanceof Error)).map((cryptoInfo: EncryptNotepadAction) => {
cryptoPasskeys.push(actions.addCryptoPasskey({ notepadTitle: cryptoInfo.notepad.title, passkey: cryptoInfo.passkey }));
return cryptoInfo.notepad;
});
Expand Down
6 changes: 3 additions & 3 deletions app/src/app/epics/SyncEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from 'rxjs/operators';
import { Action, Success } from 'redux-typescript-actions';
import { AssetList, ISyncedNotepad, SyncLoginRequest, SyncUser } from '../types/SyncTypes';
import { ASSET_STORAGE, store as STORE, SYNC_STORAGE, TOAST_HANDLER } from '../root';
import { ASSET_STORAGE, store as STORE, SYNC_STORAGE } from '../root';
import * as DifferenceEngine from '../services/DifferenceEngine';
import { Dialog } from '../services/dialogs';
import { IStoreState, SYNC_NAME } from '../types';
Expand Down Expand Up @@ -103,11 +103,11 @@ export const sync$ = (action$: Observable<MicroPadAction>) =>
filterTruthy()
);

export const requestDownload$ = (action$: Observable<MicroPadAction>) =>
export const requestDownload$ = (action$: Observable<MicroPadAction>, _, { getToastEventHandler }: EpicDeps) =>
action$.pipe(
ofType<MicroPadAction, Action<string>>(actions.requestSyncDownload.type),
tap((action: Action<string>) => {
const guid = TOAST_HANDLER.register(() => STORE.dispatch(actions.syncDownload.started(action.payload)));
const guid = getToastEventHandler().register(() => STORE.dispatch(actions.syncDownload.started(action.payload)));
Materialize.toast(`A newer copy of your notepad is online <a class="btn-flat amber-text" style="font-weight: 500;" href="#!" onclick="window.toastEvent('${guid}');">DOWNLOAD</a>`);
}),
noEmit()
Expand Down
Loading

0 comments on commit de42d43

Please sign in to comment.