Skip to content

Commit de7135e

Browse files
committed
feat(pages): add ability to clear recent pages
1 parent da0a32b commit de7135e

File tree

4 files changed

+209
-119
lines changed

4 files changed

+209
-119
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { once } from 'lodash';
2+
import type { InferProcedureOpts } from 'src/trpc/helpers';
3+
import { authProcedure } from 'src/trpc/helpers';
4+
5+
const baseProcedure = authProcedure;
6+
7+
export const clearRecentPagesProcedure = once(() =>
8+
baseProcedure.mutation(clearRecentPages),
9+
);
10+
11+
export async function clearRecentPages({
12+
ctx,
13+
}: InferProcedureOpts<typeof baseProcedure>) {
14+
return await ctx.usingLocks([[`user-lock:${ctx.userId}`]], async () => {
15+
await ctx.dataAbstraction.patch('user', ctx.userId, {
16+
recent_page_ids: [],
17+
});
18+
});
19+
}

apps/app-server/src/trpc/api/users/pages/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { trpc } from 'src/trpc/server';
22

3+
import { clearRecentPagesProcedure } from './clear-recent-pages';
34
import { getCurrentPathProcedure } from './get-current-path';
45
import { getGroupIdsProcedure } from './get-group-ids';
56
import { getStartingPageIdProcedure } from './get-starting-page-id';
@@ -13,6 +14,8 @@ export const pagesRouter = trpc.router({
1314

1415
getStartingPageId: getStartingPageIdProcedure(),
1516
getCurrentPath: getCurrentPathProcedure(),
17+
18+
clearRecentPages: clearRecentPagesProcedure(),
1619
removeRecentPage: removeRecentPageProcedure(),
1720

1821
setEncryptedDefaultNote: setEncryptedDefaultNoteProcedure(),

apps/client/src/layouts/PagesLayout/LeftSidebar/RecentPages.vue

+61-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,35 @@
3333
</q-toolbar-title>
3434
</div>
3535
</DeepBtn>
36+
37+
<q-btn
38+
icon="mdi-menu"
39+
style="
40+
position: absolute;
41+
right: 4px;
42+
width: 32px;
43+
height: 32px;
44+
min-height: 0;
45+
"
46+
>
47+
<q-menu auto-close>
48+
<q-list>
49+
<q-item
50+
clickable
51+
@click="clearRecentPages"
52+
:disable="internals.pages.react.recentPageIds.length === 0"
53+
>
54+
<q-item-section avatar>
55+
<q-icon name="mdi-close" />
56+
</q-item-section>
57+
58+
<q-item-section>
59+
<q-item-label>Clear recent pages</q-item-label>
60+
</q-item-section>
61+
</q-item>
62+
</q-list>
63+
</q-menu>
64+
</q-btn>
3665
</q-toolbar>
3766

3867
<q-list
@@ -42,6 +71,16 @@
4271
flex: uiStore().recentPagesExpanded ? uiStore().recentPagesWeight : '0',
4372
}"
4473
>
74+
<q-item v-if="internals.pages.react.recentPageIds.length === 0">
75+
<q-item-section>
76+
<q-item-label
77+
style="color: rgba(255, 255, 255, 0.7); font-size: 13.5px"
78+
>
79+
No recent pages.
80+
</q-item-label>
81+
</q-item-section>
82+
</q-item>
83+
4584
<div
4685
v-for="pageId in internals.pages.react.recentPageIds"
4786
:key="pageId"
@@ -84,7 +123,7 @@
84123
<script setup lang="ts">
85124
import { listenPointerEvents, map, negateProp } from '@stdlib/misc';
86125
import { pull } from 'lodash';
87-
import { handleError } from 'src/code/utils/misc';
126+
import { asyncDialog, handleError } from 'src/code/utils/misc';
88127
import type { ComponentPublicInstance } from 'vue';
89128
90129
const listRef = ref<ComponentPublicInstance>();
@@ -113,6 +152,27 @@ function resizeHandlePointerDown(downEvent: PointerEvent) {
113152
});
114153
}
115154
155+
async function clearRecentPages() {
156+
try {
157+
await asyncDialog({
158+
title: 'Clear recent pages',
159+
message: 'Are you sure you want to clear recent pages?',
160+
161+
focus: 'cancel',
162+
163+
cancel: { label: 'No', flat: true, color: 'primary' },
164+
ok: { label: 'Yes', flat: true, color: 'negative' },
165+
});
166+
167+
await trpcClient.users.pages.clearRecentPages.mutate();
168+
169+
internals.pages.recentPageIdsKeepOverride = true;
170+
internals.pages.react.recentPageIdsOverride = [];
171+
} catch (error) {
172+
handleError(error);
173+
}
174+
}
175+
116176
async function removeRecentPage(pageId: string) {
117177
try {
118178
await trpcClient.users.pages.removeRecentPage.mutate({ pageId });

apps/client/src/layouts/PagesLayout/LeftSidebar/SelectedPages.vue

+126-118
Original file line numberDiff line numberDiff line change
@@ -141,140 +141,148 @@ import { negateProp, pluralS } from '@stdlib/misc';
141141
import type { QNotifyUpdateOptions } from 'quasar';
142142
import { deletePage } from 'src/code/api-interface/pages/deletion/delete';
143143
import { movePage } from 'src/code/api-interface/pages/move';
144-
import { asyncDialog } from 'src/code/utils/misc';
144+
import { asyncDialog, handleError } from 'src/code/utils/misc';
145145
import { pageSelectionStore } from 'src/stores/page-selection';
146146
147147
import MovePageDialog from '../RightSidebar/PageProperties/MovePageDialog.vue';
148148
149149
async function movePages() {
150-
const movePageParams: Parameters<typeof movePage>[0] = await asyncDialog({
151-
component: MovePageDialog,
152-
153-
componentProps: {
154-
groupId: internals.pages.react.page.react.groupId,
155-
},
156-
});
157-
158-
const notif = $quasar().notify({
159-
group: false,
160-
timeout: 0,
161-
message: 'Moving pages...',
162-
});
163-
164-
const numTotal = pageSelectionStore().selectedPages.size;
165-
166-
let numSuccess = 0;
167-
let numFailed = 0;
168-
169-
for (const [index, pageId] of Array.from(
170-
pageSelectionStore().selectedPages,
171-
).entries()) {
172-
try {
173-
notif({
174-
caption: `${index} of ${numTotal}`,
175-
});
176-
177-
await movePage({
178-
...movePageParams,
179-
180-
pageId,
181-
});
182-
183-
numSuccess++;
184-
} catch (error) {
185-
numFailed++;
150+
try {
151+
const movePageParams: Parameters<typeof movePage>[0] = await asyncDialog({
152+
component: MovePageDialog,
153+
154+
componentProps: {
155+
groupId: internals.pages.react.page.react.groupId,
156+
},
157+
});
158+
159+
const notif = $quasar().notify({
160+
group: false,
161+
timeout: 0,
162+
message: 'Moving pages...',
163+
});
164+
165+
const numTotal = pageSelectionStore().selectedPages.size;
166+
167+
let numSuccess = 0;
168+
let numFailed = 0;
169+
170+
for (const [index, pageId] of Array.from(
171+
pageSelectionStore().selectedPages,
172+
).entries()) {
173+
try {
174+
notif({
175+
caption: `${index} of ${numTotal}`,
176+
});
177+
178+
await movePage({
179+
...movePageParams,
180+
181+
pageId,
182+
});
183+
184+
numSuccess++;
185+
} catch (error) {
186+
numFailed++;
187+
}
186188
}
187-
}
188-
189-
let notifUpdateOptions: QNotifyUpdateOptions = {
190-
timeout: undefined,
191-
caption: undefined,
192-
};
193189
194-
if (numFailed === 0) {
195-
notifUpdateOptions = {
196-
...notifUpdateOptions,
197-
message: `Page${pluralS(numSuccess)} moved successfully.`,
198-
color: 'positive',
190+
let notifUpdateOptions: QNotifyUpdateOptions = {
191+
timeout: undefined,
192+
caption: undefined,
199193
};
200-
} else {
201-
notifUpdateOptions = {
202-
...notifUpdateOptions,
203-
message: `${numSuccess > 0 ? numSuccess : 'No'} page${
204-
numSuccess === 1 ? ' was' : 's were'
205-
} moved successfully.<br/>Failed to move ${numFailed} page${pluralS(
206-
numFailed,
207-
)}.`,
208-
color: 'negative',
209-
html: true,
210-
};
211-
}
212194
213-
notif(notifUpdateOptions);
195+
if (numFailed === 0) {
196+
notifUpdateOptions = {
197+
...notifUpdateOptions,
198+
message: `Page${pluralS(numSuccess)} moved successfully.`,
199+
color: 'positive',
200+
};
201+
} else {
202+
notifUpdateOptions = {
203+
...notifUpdateOptions,
204+
message: `${numSuccess > 0 ? numSuccess : 'No'} page${
205+
numSuccess === 1 ? ' was' : 's were'
206+
} moved successfully.<br/>Failed to move ${numFailed} page${pluralS(
207+
numFailed,
208+
)}.`,
209+
color: 'negative',
210+
html: true,
211+
};
212+
}
213+
214+
notif(notifUpdateOptions);
215+
} catch (error) {
216+
handleError(error);
217+
}
214218
}
215219
216220
async function deletePages() {
217-
await asyncDialog({
218-
title: 'Delete pages',
219-
message: 'Are you sure you want to delete these pages?',
220-
221-
focus: 'cancel',
222-
cancel: { label: 'No', flat: true, color: 'primary' },
223-
ok: { label: 'Yes', flat: true, color: 'negative' },
224-
});
225-
226-
const notif = $quasar().notify({
227-
group: false,
228-
timeout: 0,
229-
message: 'Deleting pages...',
230-
});
231-
232-
const numTotal = pageSelectionStore().selectedPages.size;
233-
234-
let numSuccess = 0;
235-
let numFailed = 0;
236-
237-
for (const [index, pageId] of Array.from(
238-
pageSelectionStore().selectedPages,
239-
).entries()) {
240-
try {
241-
notif({
242-
caption: `${index} of ${numTotal}`,
243-
});
244-
245-
await deletePage(pageId);
246-
247-
numSuccess++;
248-
} catch (error) {
249-
numFailed++;
221+
try {
222+
await asyncDialog({
223+
title: 'Delete pages',
224+
message: 'Are you sure you want to delete these pages?',
225+
226+
focus: 'cancel',
227+
cancel: { label: 'No', flat: true, color: 'primary' },
228+
ok: { label: 'Yes', flat: true, color: 'negative' },
229+
});
230+
231+
const notif = $quasar().notify({
232+
group: false,
233+
timeout: 0,
234+
message: 'Deleting pages...',
235+
});
236+
237+
const numTotal = pageSelectionStore().selectedPages.size;
238+
239+
let numSuccess = 0;
240+
let numFailed = 0;
241+
242+
for (const [index, pageId] of Array.from(
243+
pageSelectionStore().selectedPages,
244+
).entries()) {
245+
try {
246+
notif({
247+
caption: `${index} of ${numTotal}`,
248+
});
249+
250+
await deletePage(pageId);
251+
252+
numSuccess++;
253+
} catch (error) {
254+
numFailed++;
255+
}
250256
}
251-
}
252-
253-
let notifUpdateOptions: QNotifyUpdateOptions = {
254-
timeout: undefined,
255-
caption: undefined,
256-
};
257257
258-
if (numFailed === 0) {
259-
notifUpdateOptions = {
260-
...notifUpdateOptions,
261-
message: `Page${pluralS(numSuccess)} deleted successfully.`,
262-
color: 'positive',
258+
let notifUpdateOptions: QNotifyUpdateOptions = {
259+
timeout: undefined,
260+
caption: undefined,
263261
};
264-
} else {
265-
notifUpdateOptions = {
266-
...notifUpdateOptions,
267-
message: `${numSuccess > 0 ? numSuccess : 'No'} page${
268-
numSuccess === 1 ? ' was' : 's were'
269-
} deleted successfully.<br/>Failed to delete ${numFailed} page${pluralS(
270-
numFailed,
271-
)}.`,
272-
color: 'negative',
273-
html: true,
274-
};
275-
}
276262
277-
notif(notifUpdateOptions);
263+
if (numFailed === 0) {
264+
notifUpdateOptions = {
265+
...notifUpdateOptions,
266+
message: `Page${pluralS(numSuccess)} deleted successfully.`,
267+
color: 'positive',
268+
};
269+
} else {
270+
notifUpdateOptions = {
271+
...notifUpdateOptions,
272+
message: `${numSuccess > 0 ? numSuccess : 'No'} page${
273+
numSuccess === 1 ? ' was' : 's were'
274+
} deleted successfully.<br/>Failed to delete ${numFailed} page${pluralS(
275+
numFailed,
276+
)}.`,
277+
color: 'negative',
278+
html: true,
279+
};
280+
}
281+
282+
notif(notifUpdateOptions);
283+
} catch (error) {
284+
handleError(error);
285+
}
278286
}
279287
</script>
280288

0 commit comments

Comments
 (0)