Skip to content

Commit

Permalink
Merge pull request #456 from MicroPad/next-dev
Browse files Browse the repository at this point in the history
v4.5
  • Loading branch information
NickGeek authored Dec 24, 2023
2 parents 039a6c8 + 9b675f7 commit af8c97d
Show file tree
Hide file tree
Showing 19 changed files with 192 additions and 24 deletions.
12 changes: 12 additions & 0 deletions app/.run/typecheck.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="typecheck" type="js.build_tools.npm" nameIsGenerated="true">
<package-json value="$PROJECT_DIR$/package.json" />
<command value="run" />
<scripts>
<script value="typecheck" />
</scripts>
<node-interpreter value="project" />
<envs />
<method v="2" />
</configuration>
</component>
Binary file modified app/bun.lockb
Binary file not shown.
5 changes: 2 additions & 3 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "micropad",
"version": "4.4.0",
"version": "4.5.0",
"private": true,
"scripts": {
"preinstall": "python3 ../libs/build-libs.py && ./get_precache_files.py > src/extraPrecacheFiles.ts",
Expand Down Expand Up @@ -188,8 +188,7 @@
"@types/react": "^17",
"react": "^17",
"redux": "^4",
"rxjs": "^7.5.7",
"playwright-core": "^1.27.1"
"rxjs": "^7.5.7"
},
"packageManager": "yarn@3.5.0"
}
1 change: 1 addition & 0 deletions app/src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const actions = {
filePasted: actionCreator<File>('FILE_PASTED'),
updateEncryptionStatus: actionCreator<EncryptionStatus>('UPDATE_CRYPTO_STATUS'),
hashtagSearchOrJump: actionCreator<string>('HASHTAG_SEARCH_OR_JUMP'),
setShowHistoricalDueDates: actionCreator<boolean>('SET_SHOW_HISTORICAL_DUE_DATES'),
};

export const READ_ONLY_ACTIONS: ReadonlySet<string> = new Set<string>([
Expand Down
15 changes: 11 additions & 4 deletions app/src/app/assets/Help.npx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.due-date-list > span > a {
padding-left: 5px;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import { formatDistanceStrict } from 'date-fns';
import { formatDistanceStrict, isAfter } from 'date-fns';
import { generateGuid } from '../../../util';
import { dueDateListConnector } from './DueDateListContainer';
import { ConnectedProps } from 'react-redux';
import DueDateOptionsComponent from './DueDateOptionsComponent';
import './DueDateListComponent.css';

type AllProps = ConnectedProps<typeof dueDateListConnector>;
const DueDateListComponent = (props: AllProps) => {
Expand All @@ -11,8 +13,9 @@ const DueDateListComponent = (props: AllProps) => {

return (
<div className="due-date-list">
<span>
<span onContextMenu={handleOptsRightClick}>
<strong>Upcoming due dates</strong>
<DueDateOptionsComponent />
{isLoading ? <em>(Recalculating…)</em> : <React.Fragment />}
</span>
<ol>
Expand All @@ -29,7 +32,7 @@ const DueDateListComponent = (props: AllProps) => {
textDecoration: 'underline'
}}>
{item.note.title}
</a> ({formatDistanceStrict(item.date, new Date())})
</a> ({computeDistanceMessage(item.date)})
</li>
)
}
Expand All @@ -38,4 +41,18 @@ const DueDateListComponent = (props: AllProps) => {
);
};

function computeDistanceMessage(due: Date, currentDate: Date = new Date()): string {
const baseMsg = formatDistanceStrict(due, currentDate);
if (isAfter(due, currentDate)) {
return baseMsg;
}
return baseMsg + ' ago';
}

function handleOptsRightClick(e: React.MouseEvent<HTMLElement, MouseEvent>): boolean {
e.preventDefault();
(e.target as Node).parentElement?.querySelector<HTMLAnchorElement>('.due-date-opts-trigger')?.click();
return false;
}

export default DueDateListComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.due-date-opts__content > * {
color: var(--mp-theme-explorerContent);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { connect, ConnectedProps } from 'react-redux';
import { Checkbox, Icon } from 'react-materialize';
import { DEFAULT_MODAL_OPTIONS } from '../../../util';
import React from 'react';
import SingletonModalComponent from '../../singleton-modal/SingletonModalContainer';
import { ModalId } from '../../../types/ModalIds';
import { IStoreState } from '../../../types';
import { ThemeValues } from '../../../ThemeValues';
import { actions } from '../../../actions';
import './DueDateOptionsComponent.css';

const MODAL_ID: ModalId = 'due-date-options-modal';

type Props = ConnectedProps<typeof dueDateOptionsConnector>
export const DueDateOptionsComponent = (props: Props) => {
return (<SingletonModalComponent
id={MODAL_ID}
key={MODAL_ID}
header={`Options for upcoming due dates`}
trigger={<a href="#!" className="due-date-opts-trigger" style={{ color: props.colour }} onContextMenu={e => {
e.preventDefault();
(e.target as Node).parentElement?.querySelector<HTMLAnchorElement>('.due-date-opts-trigger')?.click();
return false;
}}><Icon tiny={true} className="due-date-opts-trigger">settings</Icon></a>}
options={DEFAULT_MODAL_OPTIONS}>
<div className="due-date-opts__content">
<Checkbox
label="Show historical due dates"
value="1"
checked={props.showHistoricalDueDates}
onChange={() => props.setShowHistoricalDueDates(!props.showHistoricalDueDates)}
filledIn
/>
</div>
</SingletonModalComponent>);
}

export const dueDateOptionsConnector = connect(
(state: IStoreState) => ({
colour: ThemeValues[state.app.theme].explorerContent,
showHistoricalDueDates: state.dueDateSettings.showHistoricalDueDates ?? false,
}),
{
setShowHistoricalDueDates: actions.setShowHistoricalDueDates,
}
);
export default dueDateOptionsConnector(DueDateOptionsComponent);
34 changes: 31 additions & 3 deletions app/src/app/epics/DueDatesEpics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { combineEpics, ofType } from 'redux-observable';
import { forkJoin, from, Observable, of } from 'rxjs';
import { actions, MicroPadAction, MicroPadActions } from '../actions';
import { catchError, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { catchError, distinctUntilChanged, filter, map, switchMap, take, withLatestFrom } from 'rxjs/operators';
import { EpicDeps, EpicStore } from './index';
import { Translators } from 'upad-parse/dist';
import { NotepadShell } from 'upad-parse/dist/interfaces';
import { getDueDates, sortDueDates } from '../services/DueDates';
import { IStoreState } from '../types';
import { SettingsStorageKeys } from '../storage/settings-storage-keys';
import { filterTruthy, noEmit } from '../util';

export const getDueDatesOnInit$ = (action$: Observable<MicroPadAction>) =>
action$.pipe(
Expand Down Expand Up @@ -38,7 +40,7 @@ export const getDueDates$ = (action$: Observable<MicroPadAction>, state$: EpicSt
.filter(obj => !obj.crypto || !!state.notepadPasskeys[obj.title])
.map(obj =>
from(Translators.Json.toFlatNotepadFromNotepad(obj, state.notepadPasskeys[obj.title])).pipe(
map(notepad => getDueDates(notepad))
map(notepad => getDueDates(notepad, state.dueDateSettings))
)
);

Expand All @@ -57,8 +59,34 @@ export const getDueDates$ = (action$: Observable<MicroPadAction>, state$: EpicSt
)
);

export const reindexDueDatesOnSettingsChange$ = (action$: Observable<MicroPadAction>, state$: EpicStore) =>
action$.pipe(
ofType(actions.setShowHistoricalDueDates.type),
switchMap(() => state$.pipe(
take(1),
map(state => state.notepads.savedNotepadTitles),
filterTruthy(),
map(notepads => actions.getDueDates.started(notepads)))
));

export const persistDueDateOpts$ = (_action$: Observable<MicroPadAction>, state$: EpicStore, { getStorage }: EpicDeps) =>
state$.pipe(
map(state => state.dueDateSettings),
filter(opts => opts.showHistoricalDueDates !== null),
distinctUntilChanged(),
switchMap(dueDateSettings => from(
getStorage()
.settingsStorage
.setItem(SettingsStorageKeys.DUE_DATE_OPTS, dueDateSettings)
.catch(e => { console.error(e); })
)),
noEmit()
);

export const dueDatesEpics$ = combineEpics<MicroPadAction, MicroPadAction, IStoreState, EpicDeps>(
getDueDatesOnInit$,
getDueDatesOnSave$,
getDueDates$
getDueDates$,
reindexDueDatesOnSettingsChange$,
persistDueDateOpts$,
);
5 changes: 3 additions & 2 deletions app/src/app/epics/EditorEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { from, Observable } from 'rxjs';
import { distinctUntilChanged, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { noEmit } from '../util';
import { IStoreState } from '../types';
import { SettingsStorageKeys } from '../storage/settings-storage-keys';

export const persistSpellCheck$ = (action$: Observable<MicroPadAction>, state$: EpicStore, { getStorage }: EpicDeps) =>
action$.pipe(
Expand All @@ -15,7 +16,7 @@ export const persistSpellCheck$ = (action$: Observable<MicroPadAction>, state$:
switchMap(shouldSpellCheck => from(
getStorage()
.settingsStorage
.setItem('shouldSpellCheck', shouldSpellCheck)
.setItem(SettingsStorageKeys.SHOULD_SPELL_CHECK, shouldSpellCheck)
.catch(e => { console.error(e); })
)),
noEmit()
Expand All @@ -30,7 +31,7 @@ export const persistWordWrap$ = (action$: Observable<MicroPadAction>, state$: Ep
switchMap(shouldWordWrap => from(
getStorage()
.settingsStorage
.setItem('shouldWordWrap', shouldWordWrap)
.setItem(SettingsStorageKeys.SHOULD_WORD_WRAP, shouldWordWrap)
.catch(e => { console.error(e); })
)),
noEmit()
Expand Down
6 changes: 4 additions & 2 deletions app/src/app/reducers/BaseReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { MicroPadAction, READ_ONLY_ACTIONS } from '../actions';
import { Action, Reducer, ReducersMapObject } from 'redux';
import { isReadOnlyNotebook } from '../ReadOnly';
import deepFreeze from 'deep-freeze';
import { combineReducers } from '@reduxjs/toolkit';
import { appInfoSlice } from './AppInfoReducer';
import { editorSlice } from './EditorReducer';
import { combineReducers } from '@reduxjs/toolkit';
import { dueDateSettingsSlice } from './DueDateSettingsReducer';

interface ReduxReducer<S, A extends Action> {
reducer: Reducer<S, A>
Expand All @@ -35,7 +36,8 @@ const REDUCERS: Reducer<IStoreState, MicroPadAction> = (() => {
sync: new SyncReducer().reducer,
/* New reducers */
[editorSlice.name]: editorSlice.reducer,
[appInfoSlice.name]: appInfoSlice.reducer
[appInfoSlice.name]: appInfoSlice.reducer,
[dueDateSettingsSlice.name]: dueDateSettingsSlice.reducer,
};

return combineReducers(reducers);
Expand Down
19 changes: 19 additions & 0 deletions app/src/app/reducers/DueDateSettingsReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createSlice, SliceCaseReducers } from '@reduxjs/toolkit';
import { actions } from '../actions';

export type DueDateSettingsState = {
showHistoricalDueDates: boolean | null,
};

export const dueDateSettingsSlice = createSlice<DueDateSettingsState, SliceCaseReducers<DueDateSettingsState>, 'dueDateSettings'>({
name: 'dueDateSettings',
initialState: {
showHistoricalDueDates: null,
},
reducers: {},
extraReducers: builder => builder
.addCase(actions.setShowHistoricalDueDates, (state, action) => ({
...state,
showHistoricalDueDates: action.payload
}))
});
10 changes: 10 additions & 0 deletions app/src/app/root.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ strong {
border-bottom: 2px solid var(--mp-theme-accentContent);
}

/* Same as above but for modals */
.modal [type=checkbox].filled-in:checked+span:not(.lever):after, .vex [type=checkbox].filled-in:checked+span:not(.lever):after {
background-color: var(--mp-theme-accent);
border: 2px solid #5a5a5a;
}
.modal [type=checkbox].filled-in:checked+span:not(.lever):before, .vex [type=checkbox].filled-in:checked+span:not(.lever):before {
border-right: 2px solid var(--mp-theme-accentContent);
border-bottom: 2px solid var(--mp-theme-accentContent);
}

.btn:hover, .btn-flat:hover, .btn-large:hover, .btn-small:hover {
background-color: var(--mp-theme-accent);
color: var(--mp-theme-accentContent);
Expand Down
14 changes: 10 additions & 4 deletions app/src/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import InfoBannerComponent from './components/header/info-banner/InfoBannerConta
import { watchPastes } from './services/paste-watcher';
import { configureStore } from '@reduxjs/toolkit';
import { isDev } from './util';
import { DueDateSettingsState } from './reducers/DueDateSettingsReducer';
import { SettingsStorageKeys } from './storage/settings-storage-keys';

try {
document.domain = MICROPAD_URL.split('//')[1];
Expand Down Expand Up @@ -233,7 +235,11 @@ async function hydrateStoreFromLocalforage() {
const theme = await localforage.getItem<ThemeName>('theme');
if (!!theme) store.dispatch(actions.selectTheme(theme));

await restoreSavedPasswords$;
const dueDateOpts$ = SETTINGS_STORAGE.getItem<DueDateSettingsState>(SettingsStorageKeys.DUE_DATE_OPTS)
.then(opts => store.dispatch(actions.setShowHistoricalDueDates(opts?.showHistoricalDueDates ?? false)));
await dueDateOpts$;

await Promise.all([dueDateOpts$, restoreSavedPasswords$]);

// Reopen the last notebook + note
const lastOpenedNotepad = await localforage.getItem<string | LastOpenedNotepad>('last opened notepad');
Expand All @@ -251,18 +257,18 @@ async function hydrateStoreFromLocalforage() {

async function displayWhatsNew() {
// some clean up of an old item, can be removed in the future
localforage.removeItem('oldMinorVersion').catch(e => console.error(e));
localforage.removeItem(SettingsStorageKeys.OLD_MINOR_VERSION).catch(e => console.error(e));

const minorVersion = store.getState().app.version.minor;
const oldMinorVersion = await SETTINGS_STORAGE.getItem<number>('oldMinorVersion');
const oldMinorVersion = await SETTINGS_STORAGE.getItem<number>(SettingsStorageKeys.OLD_MINOR_VERSION);
if (minorVersion === oldMinorVersion) return;

// Open "What's New"
setTimeout(() => {
store.dispatch(actions.openModal('whats-new-modal'));
}, 0);

SETTINGS_STORAGE.setItem<number>('oldMinorVersion', minorVersion).catch(e => console.error(e));
SETTINGS_STORAGE.setItem<number>(SettingsStorageKeys.OLD_MINOR_VERSION, minorVersion).catch(e => console.error(e));
}

function notepadDownloadHandler() {
Expand Down
8 changes: 6 additions & 2 deletions app/src/app/services/DueDates.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { FlatNotepad, Note } from 'upad-parse/dist';
import { DueDateSettingsState } from '../reducers/DueDateSettingsReducer';

export type DueItem = {
date: Date,
note: Note,
notepadTitle: string
};

export function getDueDates(notepad: FlatNotepad): DueItem[] {
export function getDueDates(notepad: FlatNotepad, opts: DueDateSettingsState): DueItem[] {
return Object.values(notepad.notes)
.map(note => {
const earliestDueDate = note.elements
.map(element => element.args.dueDate)
.filter((a?: string): a is string => !!a)
.map(dueDate => parseInt(dueDate!, 10))
.filter(due => due >= new Date().getTime())
.filter(due => {
if (opts.showHistoricalDueDates) { return true; }
return due >= new Date().getTime();
})
.sort()[0];

return {
Expand Down
6 changes: 6 additions & 0 deletions app/src/app/storage/settings-storage-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const enum SettingsStorageKeys {
SHOULD_WORD_WRAP = 'shouldWordWrap',
SHOULD_SPELL_CHECK = 'shouldSpellCheck',
DUE_DATE_OPTS = 'dueDateOpts',
OLD_MINOR_VERSION = 'oldMinorVersion',
}
3 changes: 2 additions & 1 deletion app/src/app/types/ModalIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type ModalId =
| `bib-modal-${string}-${string}`
| `formatting-help-modal-${string}`
| `notepad-edit-object-modal-${string}`
| `np-title-${string}`;
| `np-title-${string}`
| 'due-date-options-modal';
2 changes: 2 additions & 0 deletions app/src/app/types/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IsExportingState } from '../reducers/IsExportingReducer';
import { NotepadPasskeysState } from '../reducers/NotepadPasskeysReducer';
import { EditorState } from '../reducers/EditorReducer';
import { AppInfoState } from '../reducers/AppInfoReducer';
import { DueDateSettingsState } from '../reducers/DueDateSettingsReducer';

export interface IStoreState {
readonly app: IAppStoreState;
Expand All @@ -23,6 +24,7 @@ export interface IStoreState {
readonly isExporting: IsExportingState;
readonly editor: EditorState;
readonly appInfo: AppInfoState;
readonly dueDateSettings: DueDateSettingsState;
}
export type PleaseWait = {
finish: () => void,
Expand Down

0 comments on commit af8c97d

Please sign in to comment.