Skip to content

Commit

Permalink
Fixing hashtags, asset handling, and more #391
Browse files Browse the repository at this point in the history
  • Loading branch information
NickGeek committed Feb 4, 2022
1 parent cb313ad commit 2898053
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 72 deletions.
25 changes: 21 additions & 4 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@fontsource/abeezee": "^4.4.5",
"@nick_webster/photon": "^0.3.1",
"@redux-devtools/extension": "^3.2.1",
"@sentry/integrations": "^6.6.0",
"@sentry/react": "^6.6.0",
"@sentry/tracing": "^6.6.0",
Expand All @@ -40,7 +41,6 @@
"react-select": "^5.2.1",
"react-treeview": "^0.4.7",
"redux": "^4.1.0",
"redux-devtools-extension": "^2.13.2",
"redux-observable": "^2.0.0",
"rxjs": "^7.5.1",
"save-as": "^0.1.8",
Expand Down Expand Up @@ -88,11 +88,28 @@
"key-spacing": "error",
"@typescript-eslint/prefer-ts-expect-error": "warn",
"no-debugger": "error",
"quotes": ["error", "single", { "allowTemplateLiterals": true }],
"jsx-quotes": ["error", "prefer-double"],
"quotes": [
"error",
"single",
{
"allowTemplateLiterals": true
}
],
"jsx-quotes": [
"error",
"prefer-double"
],
"no-script-url": "error",
"no-eval": "error",
"no-console": ["error", { "allow": ["warn", "error"] }]
"no-console": [
"error",
{
"allow": [
"warn",
"error"
]
}
]
}
}
]
Expand Down
1 change: 1 addition & 0 deletions app/src/app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export const actions = {
actWithSyncNotepad: actionCreator<NotepadToSyncNotepadAction>('ACT_WITH_SYNC_NOTEPAD'),
requestSyncDownload: actionCreator<string>('REQUEST_SYNC_DOWNLOAD'),
syncProError: actionCreator<void>('SYNC_PRO_ERROR'),
setSyncProStatus: actionCreator<boolean>('SET_SYNC_PRO_STATUS'),
setHelpPref: actionCreator<boolean>('SET_HELP_PREF'),
checkVersion: actionCreator<void>('CHECK_VERSION_ELECTRON'),
closeNote: actionCreator<void>('CLOSE_NOTE'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,9 @@ function configureShowdown(): Converter {
listeners: {
'hashHTMLBlocks.after': (evtName, text) => {
let i = 0;
return text.replaceAll(/(^|\s)(#[a-z\d-]+)/gi, match => {
return text.replaceAll(/(^|\s)(#[a-z\d-]+)/gi, (match, whitespace) => {
matches.push(`<a href="javascript:void(0);" onclick="searchHashtag('#${match.split('#')[1]}');">${match}</a>`);
return '%PLACEHOLDER3' + i++ + 'ENDPLACEHOLDER3%';
return whitespace + '%PLACEHOLDER3' + i++ + 'ENDPLACEHOLDER3%';
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import LoginComponent from '../../../containers/LoginContainer';
import { syncOptionsConnector } from './SyncOptionsContainer';
import { ConnectedProps } from 'react-redux';
import Button2 from '../../Button';
import { Icon } from 'react-materialize';

export default class SyncOptionsComponent extends React.Component<ConnectedProps<typeof syncOptionsConnector>> {
render() {
Expand All @@ -26,6 +27,8 @@ export default class SyncOptionsComponent extends React.Component<ConnectedProps
);
}

const shouldShowCollaborationOptions = syncState.sharedNotepadList?.[notepad.title]?.scribe || syncState.user.isPro;

return (
<React.Fragment>
<strong>{SYNC_NAME} Options for <em>{notepad.title}</em></strong>
Expand All @@ -46,7 +49,8 @@ export default class SyncOptionsComponent extends React.Component<ConnectedProps
<div>
<ul className="sync-settings-component__action-list">
<li>
Synced! (<a href="#!" style={{ textDecoration: 'underline' }} onClick={this.manualSync}>Refresh</a>)
Synced!
<Button2 flat style={{ lineHeight: 'initial' }} onClick={this.manualSync}><Icon>sync</Icon></Button2>
</li>

<li>
Expand All @@ -64,12 +68,12 @@ export default class SyncOptionsComponent extends React.Component<ConnectedProps
</Button2>
</li>

<li style={{ paddingTop: '1em' }}>
{shouldShowCollaborationOptions && <li style={{ paddingTop: '1em' }}>
<a target="_blank" rel="noopener noreferrer nofollow" href={`${MICROPAD_URL}/sync/manage`}>Collaboration/Sharing Options</a>
<ul className="sync-settings-component__action-list">
{!!syncState.sharedNotepadList![notepad.title] && <li>Scribe: <em>{syncState.sharedNotepadList![notepad.title].scribe}</em></li>}
</ul>
</li>
</li>}
</ul>
</div>
}
Expand Down
17 changes: 10 additions & 7 deletions app/src/app/epics/NoteEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,14 @@ export const noteEpics$ = combineEpics<MicroPadAction, MicroPadAction, IStoreSta
);

function getNoteAssets(elements: NoteElement[]): Promise<{ elements: NoteElement[], blobUrls: object }> {
const storageRequests: Promise<Blob>[] = [];
const storageRequests: Promise<Blob | null>[] = [];
const blobRefs: string[] = [];

elements = elements.map(element => {
// Is this a notebook before v2?
if (element.type !== 'markdown' && element.content !== 'AS') {
const asset = new Asset(dataURItoBlob(element.content));
storageRequests.push(ASSET_STORAGE.setItem(asset.uuid, asset.data));
storageRequests.push(ASSET_STORAGE.setItem<Blob>(asset.uuid, asset.data));
blobRefs.push(asset.uuid);

return { ...element, args: { ...element.args, ext: asset.uuid }, content: 'AS' };
Expand All @@ -240,9 +240,9 @@ function getNoteAssets(elements: NoteElement[]): Promise<{ elements: NoteElement
// Notebooks from v2 or higher
if (!!element.args.ext) {
storageRequests.push(
ASSET_STORAGE.getItem(element.args.ext)
.then((blobObj) => {
const blob = blobObj as Blob;
ASSET_STORAGE.getItem<Blob>(element.args.ext)
.then(blob => {
if (!blob) return null;

if (element.type === 'pdf') {
return blob.slice(0, blob.size, 'application/pdf');
Expand All @@ -260,9 +260,12 @@ function getNoteAssets(elements: NoteElement[]): Promise<{ elements: NoteElement

return new Promise(resolve =>
Promise.all(storageRequests)
.then((blobs: Blob[]) => {
.then((blobs: Array<Blob | null>) => {
const blobUrls = {};
blobs.filter(b => !!b).forEach((blob, i) => blobUrls[blobRefs[i]] = URL.createObjectURL(blob));
blobs.forEach((blob, i) => {
if (!blob) return;
blobUrls[blobRefs[i]] = URL.createObjectURL(blob);
});

resolve({
elements,
Expand Down
4 changes: 2 additions & 2 deletions app/src/app/epics/StorageEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ const openNotepadFromStorage$ = (action$: Observable<MicroPadAction>, state$: Ep
const cleanUnusedAssets$ = (action$: Observable<MicroPadAction>, state$: EpicStore) =>
action$
.pipe(
ofType(actions.parseNpx.done.type, actions.deleteElement.type),
ofType(actions.parseNpx.done.type, actions.deleteElement.type, actions.deleteNotepadObject.type),
map(() => state$.value),
map((state: IStoreState) => state.notepads.notepad),
filterTruthy(),
Expand Down Expand Up @@ -216,7 +216,7 @@ const persistLastOpenedNote$ = (action$: Observable<MicroPadAction>, state$: Epi

const clearLastOpenNoteOnClose$ = (action$: Observable<MicroPadAction>, state$: EpicStore, { getStorage }: EpicDeps) =>
action$.pipe(
ofType(actions.closeNote.type),
ofType(actions.closeNote.type, actions.deleteNotepadObject.type),
map(() => state$.value.notepads.notepad?.item?.title),
tap(currentNotepad => {
if (currentNotepad) {
Expand Down
93 changes: 50 additions & 43 deletions app/src/app/epics/SyncEpics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,47 +182,38 @@ export const upload$ = (action$: Observable<MicroPadAction>, state$: EpicStore,
map((action): [SyncAction, SyncUser] => [(action as MicroPadActions['syncUpload']['started']).payload, state$.value.sync.user!]),
filter(([payload, user]: [SyncAction, SyncUser]) => !!payload && !!user),
concatMap(([payload, user]: [SyncAction, SyncUser]) =>
DifferenceEngine.AccountService.isPro(user.username, user.token).pipe(
tap((isPro: boolean) => {
if (Object.keys(payload.notepad.assetHashList).length < 10 || isPro) return;
throw new Error('too many assets');
}),
concatMap(() =>
DifferenceEngine.SyncService.uploadNotepad(
user.username,
user.token,
payload.syncId,
payload.notepad,
state$.value.notepadPasskeys[payload.notepad.title]
)
.pipe(
concatMap((assetList: AssetList) => from((async () => {
const requests: UploadAssetAction[] = [];

const orderedAssetList = Object.entries(assetList);
const blobs: Array<Blob | null> = await optimiseAssets(
getStorage().assetStorage,
orderedAssetList.map(([uuid]) => uuid),
state$.value.notepads.notepad?.item!
);
orderedAssetList
.map(([, url]) => url)
.filter((url, i) => {
if (!blobs[i]) {
console.error('Asset was null, skipping ', url);
return false;
}
return true;
})
.forEach((url, i) => requests.push({ url, asset: blobs[i]! }));

return requests;
})())),
concatMap((requests: UploadAssetAction[]) => from(
Promise.all(requests.map(req => DifferenceEngine.uploadAsset(req.url, req.asset).toPromise()))
))
)
),
DifferenceEngine.SyncService.uploadNotepad(
user.username,
user.token,
payload.syncId,
payload.notepad,
state$.value.notepadPasskeys[payload.notepad.title]
).pipe(
concatMap((assetList: AssetList) => from((async () => {
const requests: UploadAssetAction[] = [];

const orderedAssetList = Object.entries(assetList);
const blobs: Array<Blob | null> = await optimiseAssets(
getStorage().assetStorage,
orderedAssetList.map(([uuid]) => uuid),
state$.value.notepads.notepad?.item!
);
orderedAssetList
.map(([, url]) => url)
.filter((url, i) => {
if (!blobs[i]) {
console.error('Asset was null, skipping ', url);
return false;
}
return true;
})
.forEach((url, i) => requests.push({ url, asset: blobs[i]! }));

return requests;
})())),
concatMap((requests: UploadAssetAction[]) => from(
Promise.all(requests.map(req => DifferenceEngine.uploadAsset(req.url, req.asset).toPromise()))
)),
switchMap(() =>
uploadCount$.pipe(
first(),
Expand All @@ -235,7 +226,7 @@ export const upload$ = (action$: Observable<MicroPadAction>, state$: EpicStore,
catchError((error): Observable<Action<any>> => {
uploadCount$.next(0);

if (error && error.message === 'too many assets') {
if (error?.response?.error === 'Too many assets on a non-pro notepad') {
return of(actions.syncProError());
}

Expand Down Expand Up @@ -381,6 +372,21 @@ export const syncOnRenameNotebook$ = (action$: Observable<MicroPadAction>, store
}))
);

export const getProStatus$ = (action$: Observable<MicroPadAction>) =>
action$.pipe(
ofType(actions.syncLogin.done.type),
map(action => (action as MicroPadActions['syncLogin']['done']).payload.result),
switchMap(user =>
DifferenceEngine.AccountService.isPro(user.username, user.token).pipe(
map(proStatus => actions.setSyncProStatus(proStatus)),
catchError(error => {
console.error(error);
return EMPTY;
})
)
)
);

export const syncEpics$ = combineEpics<MicroPadAction, MicroPadAction, IStoreState, EpicDeps>(
persistOnLogin$,
login$,
Expand All @@ -398,5 +404,6 @@ export const syncEpics$ = combineEpics<MicroPadAction, MicroPadAction, IStoreSta
refreshNotepadListOnAction$,
clearStorageOnLogout$,
openSyncProErrorModal$,
syncOnRenameNotebook$
syncOnRenameNotebook$,
getProStatus$
);
4 changes: 2 additions & 2 deletions app/src/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import { MICROPAD_URL } from './types';
import { applyMiddleware, compose, createStore } from 'redux';
import { BaseReducer } from './reducers/BaseReducer';
import { epicMiddleware } from './epics';
import { composeWithDevTools } from 'redux-devtools-extension';
import localforage from 'localforage';
import * as ReactDOM from 'react-dom';
import { actions } from './actions';
Expand All @@ -49,6 +48,7 @@ import InfoModalsComponent from './components/InfoModalsComponent';
import { rootEpic$ } from './epics/rootEpic';
import InfoBannerComponent from './components/header/info-banner/InfoBannerContainer';
import { watchPastes } from './services/paste-watcher';
import { composeWithDevTools } from '@redux-devtools/extension';

window.MicroPadGlobals = {};

Expand All @@ -63,7 +63,7 @@ export const store = createStore(
baseReducer.reducer,
baseReducer.initialState,
composeWithDevTools({
actionsBlacklist: ['MOUSE_MOVE']
actionsDenylist: ['MOUSE_MOVE']
})(compose(applyMiddleware(epicMiddleware), createSentryReduxEnhancer()))
);

Expand Down
11 changes: 9 additions & 2 deletions app/src/app/services/DifferenceEngine.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { from, fromEvent, lastValueFrom, Observable, of } from 'rxjs';
import { from, fromEvent, lastValueFrom, Observable, of, retryWhen } from 'rxjs';
import { MICROPAD_URL } from '../types';
import { concatMap, filter, map, retry, take } from 'rxjs/operators';
import { AssetList, INotepadSharingData, ISyncedNotepad, SyncedNotepadList } from '../types/SyncTypes';
Expand Down Expand Up @@ -159,7 +159,14 @@ function callApi<T>(parent: string, endpoint: string, resource: string, payload?
timeout: !!payload ? undefined : 10000 // 10 seconds
}).pipe(
map(res => res.response),
retry(2)
retryWhen(errors => errors.pipe(
map((error, i) => {
if (i > 2 || error?.response?.error === 'Too many assets on a non-pro notepad') {
throw error;
}
return error;
})
))
);
}

Expand Down
5 changes: 3 additions & 2 deletions app/src/app/types/SyncTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ export type SyncLoginRequest = {
};

export type SyncUser = {
username: string;
token: string;
username: string,
token: string,
isPro?: boolean
};

export type AssetList = { [uuid: string]: string };
Expand Down
19 changes: 14 additions & 5 deletions app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,13 @@
dependencies:
regenerator-runtime "^0.13.4"

"@babel/runtime@^7.16.7":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0"
integrity sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==
dependencies:
regenerator-runtime "^0.13.4"

"@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
Expand Down Expand Up @@ -1375,6 +1382,13 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@redux-devtools/extension@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@redux-devtools/extension/-/extension-3.2.1.tgz#8f1074477823752a43ff83ba5b7c7e836fb5ab40"
integrity sha512-be3BUZaRZmpj8VLnbkDsAnYI1B5HRb6QrfCsokK9k/85a4mPtmxYzgc2jWQ1J55kt0U5mcVO8kojHRuqbfsTsA==
dependencies:
"@babel/runtime" "^7.16.7"

"@rollup/plugin-babel@^5.2.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879"
Expand Down Expand Up @@ -5782,11 +5796,6 @@ readable-stream@~2.3.6:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"

redux-devtools-extension@^2.13.2:
version "2.13.9"
resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7"
integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A==

redux-observable@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/redux-observable/-/redux-observable-2.0.0.tgz#4358bef2e924723a8b1ad0e835ccebb1612a6b9a"
Expand Down

0 comments on commit 2898053

Please sign in to comment.