@onyx-p/imlib-web 3.0.2 → 3.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/database/main.cjs CHANGED
@@ -65,6 +65,8 @@ const READ_METHOD_NAMES = [
65
65
  'getMessageById',
66
66
  'getMessageByUId',
67
67
  'getMessageIdRange',
68
+ 'getMessagesByType',
69
+ 'getPinnedMessages',
68
70
  'searchMessages',
69
71
  'searchTextMessages',
70
72
  'getLegacyMigrationState'
@@ -75,6 +77,8 @@ const WRITE_METHOD_NAMES = [
75
77
  'clearAllCache',
76
78
  'removeMessagesByUId',
77
79
  'updateMessageReceiptStatus',
80
+ 'setMessagePinned',
81
+ 'unpinAllMessages',
78
82
  'clearBurnAfterReadingExpiredMessages',
79
83
  'upsertMessage',
80
84
  'convertToRecallMessages',
@@ -12,6 +12,8 @@ const READ_METHOD_NAMES = [
12
12
  'getMessageById',
13
13
  'getMessageByUId',
14
14
  'getMessageIdRange',
15
+ 'getMessagesByType',
16
+ 'getPinnedMessages',
15
17
  'searchMessages',
16
18
  'searchTextMessages',
17
19
  'getLegacyMigrationState'
@@ -22,6 +24,8 @@ const WRITE_METHOD_NAMES = [
22
24
  'clearAllCache',
23
25
  'removeMessagesByUId',
24
26
  'updateMessageReceiptStatus',
27
+ 'setMessagePinned',
28
+ 'unpinAllMessages',
25
29
  'clearBurnAfterReadingExpiredMessages',
26
30
  'upsertMessage',
27
31
  'convertToRecallMessages',
@@ -15,6 +15,8 @@ const READ_METHOD_NAMES = [
15
15
  'getMessageById',
16
16
  'getMessageByUId',
17
17
  'getMessageIdRange',
18
+ 'getMessagesByType',
19
+ 'getPinnedMessages',
18
20
  'searchMessages',
19
21
  'searchTextMessages',
20
22
  'getLegacyMigrationState'
@@ -25,6 +27,8 @@ const WRITE_METHOD_NAMES = [
25
27
  'clearAllCache',
26
28
  'removeMessagesByUId',
27
29
  'updateMessageReceiptStatus',
30
+ 'setMessagePinned',
31
+ 'unpinAllMessages',
28
32
  'clearBurnAfterReadingExpiredMessages',
29
33
  'upsertMessage',
30
34
  'convertToRecallMessages',
@@ -133,6 +137,26 @@ const SCHEMA_MIGRATIONS = [
133
137
  ON messages (sentTime DESC, messageId DESC);
134
138
  `);
135
139
  }
140
+ },
141
+ {
142
+ version: 5,
143
+ up(database) {
144
+ database.exec(`
145
+ CREATE INDEX messages_by_dialog_type_time
146
+ ON messages (dialogId, messageType, sentTime DESC, messageId DESC);
147
+ `);
148
+ }
149
+ },
150
+ {
151
+ version: 6,
152
+ up(database) {
153
+ database.exec(`
154
+ ALTER TABLE messages ADD COLUMN pinned INTEGER NOT NULL DEFAULT 0;
155
+
156
+ CREATE INDEX messages_by_dialog_pinned_time
157
+ ON messages (dialogId, pinned, sentTime DESC, messageId DESC);
158
+ `);
159
+ }
136
160
  }
137
161
  ];
138
162
  SCHEMA_MIGRATIONS[SCHEMA_MIGRATIONS.length - 1]?.version ?? 0;
@@ -165,7 +189,12 @@ function runMigrations(database, migrations = SCHEMA_MIGRATIONS) {
165
189
 
166
190
  const LEGACY_MIGRATION_KEY = 'legacy_indexeddb_migration';
167
191
  function parseMessage(row) {
168
- return row ? JSON.parse(row.json) : null;
192
+ if (!row) {
193
+ return null;
194
+ }
195
+ const message = JSON.parse(row.json);
196
+ message.pinned = message.pinned ?? false;
197
+ return message;
169
198
  }
170
199
  function extractSearchText(message) {
171
200
  const content = message.content;
@@ -193,10 +222,10 @@ class MessageStore {
193
222
  const upsert = this.database.prepare(`
194
223
  INSERT INTO messages (
195
224
  messageId, messageUId, dialogId, sentTime, receivedStatus,
196
- burnAfterReadingFlag, burnAfterReadingTime, messageType, json, searchText
225
+ burnAfterReadingFlag, burnAfterReadingTime, messageType, pinned, json, searchText
197
226
  ) VALUES (
198
227
  $messageId, $messageUId, $dialogId, $sentTime, $receivedStatus,
199
- $burnAfterReadingFlag, $burnAfterReadingTime, $messageType, $json, $searchText
228
+ $burnAfterReadingFlag, $burnAfterReadingTime, $messageType, $pinned, $json, $searchText
200
229
  )
201
230
  ON CONFLICT(messageId) DO UPDATE SET
202
231
  messageUId = excluded.messageUId,
@@ -206,6 +235,7 @@ class MessageStore {
206
235
  burnAfterReadingFlag = excluded.burnAfterReadingFlag,
207
236
  burnAfterReadingTime = excluded.burnAfterReadingTime,
208
237
  messageType = excluded.messageType,
238
+ pinned = excluded.pinned,
209
239
  json = excluded.json,
210
240
  searchText = excluded.searchText;
211
241
  `);
@@ -225,6 +255,7 @@ class MessageStore {
225
255
  burnAfterReadingFlag: message.burnAfterReadingFlag ? 1 : 0,
226
256
  burnAfterReadingTime: message.burnAfterReadingTime ?? null,
227
257
  messageType: message.messageType ?? null,
258
+ pinned: message.pinned ? 1 : 0,
228
259
  json: JSON.stringify(message),
229
260
  searchText: extractSearchText(message)
230
261
  });
@@ -268,6 +299,44 @@ class MessageStore {
268
299
  hasMore: hasAdditionalRow || !this.getDialogLoadedState(dialogId)
269
300
  };
270
301
  }
302
+ getMessagesByType(dialogId, messageTypes, timestamp = '0', count = 50, isForward = true) {
303
+ const types = [
304
+ ...new Set(messageTypes.map(type => normalizeInteger(type, 'messageType')))
305
+ ];
306
+ const safeCount = Math.max(0, Math.floor(count));
307
+ if (types.length === 0 || safeCount === 0) {
308
+ return { messages: [], hasMore: false };
309
+ }
310
+ const params = [dialogId, ...types];
311
+ const typePlaceholders = types.map(() => '?').join(', ');
312
+ let timeClause = '';
313
+ if (timestamp !== '0') {
314
+ params.push(normalizeInteger(timestamp, 'timestamp'));
315
+ timeClause = isForward ? 'AND sentTime < ?' : 'AND sentTime > ?';
316
+ }
317
+ params.push(safeCount + 1);
318
+ const direction = isForward ? 'DESC' : 'ASC';
319
+ const rows = this.database
320
+ .prepare(`SELECT json FROM messages
321
+ WHERE dialogId = ?
322
+ AND messageType IN (${typePlaceholders})
323
+ ${timeClause}
324
+ ORDER BY sentTime ${direction}, messageId ${direction}
325
+ LIMIT ?;`)
326
+ .all(params);
327
+ const hasAdditionalRow = rows.length > safeCount;
328
+ const messages = rows
329
+ .slice(0, safeCount)
330
+ .map(row => parseMessage(row))
331
+ .sort((left, right) => {
332
+ const byTime = Number(left.sentTime) - Number(right.sentTime);
333
+ return byTime || left.messageId - right.messageId;
334
+ });
335
+ return {
336
+ messages,
337
+ hasMore: hasAdditionalRow
338
+ };
339
+ }
271
340
  getLatestMessage(dialogId) {
272
341
  return parseMessage(this.database
273
342
  .prepare(`SELECT json FROM messages
@@ -310,6 +379,23 @@ class MessageStore {
310
379
  .prepare('SELECT json FROM messages WHERE messageUId = $messageUId;')
311
380
  .get({ messageUId }));
312
381
  }
382
+ getPinnedMessages(dialogId, count = 50) {
383
+ const safeCount = Math.max(0, Math.floor(count));
384
+ if (safeCount === 0) {
385
+ return [];
386
+ }
387
+ return this.database
388
+ .prepare(`SELECT json FROM messages
389
+ WHERE dialogId = $dialogId AND pinned = 1
390
+ ORDER BY sentTime DESC, messageId DESC
391
+ LIMIT $limit;`)
392
+ .all({ dialogId, limit: safeCount })
393
+ .map(row => parseMessage(row))
394
+ .sort((left, right) => {
395
+ const byTime = Number(left.sentTime) - Number(right.sentTime);
396
+ return byTime || left.messageId - right.messageId;
397
+ });
398
+ }
313
399
  getMessageIdRange() {
314
400
  const row = this.database
315
401
  .prepare('SELECT MIN(messageId) AS oldest, MAX(messageId) AS newest FROM messages;')
@@ -363,6 +449,48 @@ class MessageStore {
363
449
  }
364
450
  })();
365
451
  }
452
+ setMessagePinned(dialogId, messageUId, pinned) {
453
+ const row = this.database
454
+ .prepare(`SELECT json FROM messages
455
+ WHERE dialogId = $dialogId AND messageUId = $messageUId;`)
456
+ .get({ dialogId, messageUId });
457
+ const message = parseMessage(row);
458
+ if (!message) {
459
+ return;
460
+ }
461
+ message.pinned = pinned;
462
+ this.database
463
+ .prepare(`UPDATE messages SET pinned = $pinned, json = $json
464
+ WHERE dialogId = $dialogId AND messageUId = $messageUId;`)
465
+ .run({
466
+ dialogId,
467
+ messageUId,
468
+ pinned: pinned ? 1 : 0,
469
+ json: JSON.stringify(message)
470
+ });
471
+ }
472
+ unpinAllMessages(dialogId) {
473
+ const rows = this.database
474
+ .prepare(`SELECT messageUId, json FROM messages
475
+ WHERE dialogId = $dialogId AND pinned = 1;`)
476
+ .all({ dialogId });
477
+ const update = this.database.prepare(`UPDATE messages SET pinned = 0, json = $json
478
+ WHERE dialogId = $dialogId AND messageUId = $messageUId;`);
479
+ this.database.transaction(() => {
480
+ for (const row of rows) {
481
+ const message = parseMessage(row);
482
+ if (!message) {
483
+ continue;
484
+ }
485
+ message.pinned = false;
486
+ update.run({
487
+ dialogId,
488
+ messageUId: row.messageUId,
489
+ json: JSON.stringify(message)
490
+ });
491
+ }
492
+ })();
493
+ }
366
494
  clearBurnAfterReadingExpiredMessages(dialogId, now = Date.now()) {
367
495
  const rows = this.database
368
496
  .prepare(`SELECT messageUId FROM messages
@@ -383,7 +511,7 @@ class MessageStore {
383
511
  convertToRecallMessages(messageUIds, recallMessageType) {
384
512
  const select = this.database.prepare('SELECT json FROM messages WHERE messageUId = $messageUId;');
385
513
  const update = this.database.prepare(`
386
- UPDATE messages SET messageType = $messageType, json = $json, searchText = ''
514
+ UPDATE messages SET messageType = $messageType, pinned = 0, json = $json, searchText = ''
387
515
  WHERE messageUId = $messageUId;
388
516
  `);
389
517
  this.database.transaction(() => {
@@ -399,6 +527,7 @@ class MessageStore {
399
527
  message.isCounted = false;
400
528
  message.isMentioned = false;
401
529
  message.disableNotification = true;
530
+ message.pinned = false;
402
531
  update.run({
403
532
  messageUId,
404
533
  messageType: recallMessageType,