Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v4.5 #456

Merged
merged 4 commits into from
Dec 24, 2023
Merged

v4.5 #456

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading