2121 </q-item-section >
2222 </q-item >
2323
24- <div
24+ <q-item
2525 v-for =" snapshotInfo of snapshotInfos"
2626 :key =" snapshotInfo.id"
27- class =" snapshot"
27+ clickable
28+ @click.capture ="
29+ (event) => selectSnapshot(snapshotInfo.id, event as any)
30+ "
2831 >
29- <q-item
30- clickable
31- @click = " restoreVersion(snapshotInfo.id) "
32+ <q-item-section
33+ avatar
34+ style = " padding-right : 4 px "
3235 >
33- <q-item-section >
34- <q-item-label >{{
35- Intl.DateTimeFormat('en', {
36- dateStyle: 'medium',
37- timeStyle: 'short',
38- }).format(snapshotInfo.creationDate)
39- }}</q-item-label >
40-
41- <q-item-label
42- caption
43- style =" display : flex "
44- >
45- <div style =" flex : 1 " >
46- {{
47- groupMemberNames()(
48- `${page.react.groupId}:${snapshotInfo.authorId}`,
49- ).get().text
50- }}
51- </div >
52-
53- <Gap style =" width : 20px " />
54-
55- <div >{{ capitalize(snapshotInfo.type) }}</div >
56- </q-item-label >
57- </q-item-section >
58- </q-item >
59-
60- <DeepBtn
61- class =" delete-btn"
62- size =" 12px"
63- icon =" mdi-close"
64- round
65- flat
66- title =" Delete page version"
67- @click =" deleteSnapshot(snapshotInfo.id)"
68- />
69- </div >
36+ <Checkbox
37+ :model-value =" finalSelectedSnapshotIds.includes(snapshotInfo.id)"
38+ />
39+ </q-item-section >
40+
41+ <q-item-section >
42+ <q-item-label >{{
43+ Intl.DateTimeFormat('en', {
44+ dateStyle: 'medium',
45+ timeStyle: 'short',
46+ }).format(snapshotInfo.creationDate)
47+ }}</q-item-label >
48+
49+ <q-item-label
50+ caption
51+ style =" display : flex "
52+ >
53+ <div style =" flex : 1 " >
54+ {{
55+ groupMemberNames()(
56+ `${page.react.groupId}:${snapshotInfo.authorId}`,
57+ ).get().text
58+ }}
59+ </div >
60+
61+ <Gap style =" width : 20px " />
62+
63+ <div >{{ capitalize(snapshotInfo.type) }}</div >
64+ </q-item-label >
65+ </q-item-section >
66+ </q-item >
7067 </q-list >
7168
7269 <Gap style =" height : 16px " />
7370
71+ <DeepBtn
72+ label =" Restore selected version"
73+ icon =" mdi-restore"
74+ color =" secondary"
75+ :disable =" page.react.readOnly || finalSelectedSnapshotIds.length !== 1"
76+ title =" Backup the current version of the page manually"
77+ @click =" () => restoreSnapshot(finalSelectedSnapshotIds[0])"
78+ />
79+
80+ <Gap style =" height : 12px " />
81+
82+ <DeepBtn
83+ :label =" `Delete selected version${
84+ finalSelectedSnapshotIds.length > 1 ? 's' : ''
85+ }`"
86+ icon =" mdi-trash-can"
87+ color =" negative"
88+ :disable =" page.react.readOnly || finalSelectedSnapshotIds.length === 0"
89+ title =" Backup the current version of the page manually"
90+ @click =" deleteSelectedSnapshots"
91+ />
92+
93+ <Gap style =" height : 12px " />
94+
7495 <DeepBtn
7596 label =" Save current version"
7697 icon =" mdi-content-save"
7798 color =" secondary"
7899 :disable =" page.react.readOnly"
79100 title =" Backup the current version of the page manually"
80- @click =" saveVersion "
101+ @click =" saveCurrentSnapshot "
81102 />
82103 </div >
83104</template >
@@ -100,7 +121,58 @@ const snapshotInfos = computed(
100121 page .value .realtimeCtx .hget (' page-snapshots' , page .value .id , ' infos' ) ?? [],
101122);
102123
103- async function restoreVersion(snapshotId : string ) {
124+ let lastSelectedSnapshotId: string ;
125+
126+ const baseSelectedSnapshotIds = ref (new Set <string >());
127+
128+ const finalSelectedSnapshotIds = computed (() =>
129+ snapshotInfos .value
130+ .filter ((snapshotInfo ) =>
131+ baseSelectedSnapshotIds .value .has (snapshotInfo .id ),
132+ )
133+ .map ((snapshotInfo ) => snapshotInfo .id ),
134+ );
135+
136+ function selectSnapshot(snapshotId : string , event : MouseEvent ) {
137+ if (event .shiftKey || internals .mobileAltKey ) {
138+ const snapshotIds = snapshotInfos .value .map ((snapshot ) => snapshot .id );
139+
140+ const sourceSnapshotIndex = snapshotIds .indexOf (lastSelectedSnapshotId );
141+ const targetSnapshotIndex = snapshotIds .indexOf (snapshotId );
142+
143+ if (
144+ sourceSnapshotIndex >= 0 &&
145+ targetSnapshotIndex >= 0 &&
146+ sourceSnapshotIndex !== targetSnapshotIndex
147+ ) {
148+ const sign = Math .sign (targetSnapshotIndex - sourceSnapshotIndex );
149+
150+ const add = ! baseSelectedSnapshotIds .value .has (snapshotId );
151+
152+ for (
153+ let i = sourceSnapshotIndex ;
154+ i !== targetSnapshotIndex + sign ;
155+ i += sign
156+ ) {
157+ if (add ) {
158+ baseSelectedSnapshotIds .value .add (snapshotIds [i ]);
159+ } else {
160+ baseSelectedSnapshotIds .value .delete (snapshotIds [i ]);
161+ }
162+ }
163+ }
164+ } else {
165+ if (baseSelectedSnapshotIds .value .has (snapshotId )) {
166+ baseSelectedSnapshotIds .value .delete (snapshotId );
167+ } else {
168+ baseSelectedSnapshotIds .value .add (snapshotId );
169+ }
170+ }
171+
172+ lastSelectedSnapshotId = snapshotId ;
173+ }
174+
175+ async function restoreSnapshot(snapshotId : string ) {
104176 try {
105177 await asyncDialog ({
106178 title: ' Restore version' ,
@@ -128,7 +200,7 @@ async function restoreVersion(snapshotId: string) {
128200 }
129201}
130202
131- async function saveVersion () {
203+ async function saveCurrentSnapshot () {
132204 try {
133205 await asyncDialog ({
134206 title: ' Save version' ,
@@ -156,50 +228,43 @@ async function saveVersion() {
156228}
157229
158230async function deleteSnapshot(snapshotId : string ) {
159- try {
160- await asyncDialog ({
161- title: ' Delete version' ,
162- message: ' Are you sure you want to delete this version?' ,
231+ await asyncDialog ({
232+ title: ' Delete version' ,
233+ message: ' Are you sure you want to delete this version?' ,
163234
164- focus: ' cancel' ,
235+ focus: ' cancel' ,
165236
166- cancel: { label: ' No' , flat: true , color: ' primary' },
167- ok: { label: ' Yes' , flat: true , color: ' negative' },
168- });
237+ cancel: { label: ' No' , flat: true , color: ' primary' },
238+ ok: { label: ' Yes' , flat: true , color: ' negative' },
239+ });
169240
170- await deletePageSnapshot (page .value .id , snapshotId );
241+ await deletePageSnapshot (page .value .id , snapshotId );
242+ }
171243
172- $quasar ().notify ({
173- message: ' Version deleted successfully.' ,
174- color: ' positive' ,
175- });
244+ async function deleteSelectedSnapshots() {
245+ try {
246+ for (const selectedSnapshotId of finalSelectedSnapshotIds .value ) {
247+ await deleteSnapshot (selectedSnapshotId );
248+ }
176249 } catch (error : any ) {
177250 handleError (error );
178251 }
179252}
180253 </script >
181254
182255<style scoped lang="scss">
183- .snapshot {
184- position : relative ;
185-
186- > .delete-btn {
187- position : absolute ;
188-
189- top : 4px ;
190- right : 10px ;
256+ .delete-btn {
257+ position : absolute ;
191258
192- opacity : 0 ;
193- transition : opacity 0.2 s ;
259+ top : 4 px ;
260+ right : 10 px ;
194261
195- min-width : 24px ;
196- min-height : 24px ;
197- width : 24px ;
198- height : 24px ;
199- }
200- }
262+ opacity : 0 ;
263+ transition : opacity 0.2s ;
201264
202- .snapshot :hover > .delete-btn {
203- opacity : 1 ;
265+ min-width : 24px ;
266+ min-height : 24px ;
267+ width : 24px ;
268+ height : 24px ;
204269}
205270 </style >
0 commit comments