main
js 95 lines 2.88 KB
Raw
1 import { createStore } from "/js/AlpineStore.js";
2 import { callJsonApi } from "/js/api.js";
3 import { toastFrontendError } from "/components/notifications/notification-store.js";
4 import { store as sidebarStore } from "/components/sidebar/sidebar-store.js";
5
6 const PLUGIN_ID = "_pin_to_top";
7
8 const model = {
9 pins: { chat: {}, task: {} },
10 _initialized: false,
11
12 async init() {
13 if (this._initialized) return;
14 this._initialized = true;
15
16 for (const kind of ["chat", "task"]) {
17 sidebarStore.registerRowListExtension(kind, PLUGIN_ID, {
18 sort: (items) => this.sortItems(kind, items),
19 dividerBefore: (item, index, items) =>
20 this.dividerBefore(kind, item, index, items),
21 });
22 }
23
24 await this.loadPins();
25 },
26
27 async loadPins() {
28 try {
29 const response = await callJsonApi(`/plugins/${PLUGIN_ID}/get_pins`, {});
30 this.pins = {
31 chat: { ...(response?.pins?.chat || {}) },
32 task: { ...(response?.pins?.task || {}) },
33 };
34 } catch (error) {
35 void toastFrontendError(error?.message || "Failed to load pinned items.", "Pin to Top");
36 }
37 },
38
39 async toggleFromMenu(menuId, kind) {
40 const itemId = this.itemIdFromMenu(menuId, kind);
41 if (!itemId) return;
42
43 try {
44 const response = await callJsonApi(`/plugins/${PLUGIN_ID}/toggle_pin`, {
45 kind,
46 item_id: itemId,
47 });
48 const kindPins = { ...(this.pins[kind] || {}) };
49 if (response?.pinned) kindPins[itemId] = response.timestamp;
50 else delete kindPins[itemId];
51 this.pins = { ...this.pins, [kind]: kindPins };
52 } catch (error) {
53 void toastFrontendError(error?.message || "Failed to update the pin.", "Pin to Top");
54 }
55 },
56
57 itemIdFromMenu(menuId, kind) {
58 const prefix = `${kind}:`;
59 return typeof menuId === "string" && menuId.startsWith(prefix)
60 ? menuId.slice(prefix.length)
61 : "";
62 },
63
64 isMenuItemPinned(menuId, kind) {
65 return this.isPinned(kind, this.itemIdFromMenu(menuId, kind));
66 },
67
68 isPinned(kind, itemId) {
69 return Object.prototype.hasOwnProperty.call(this.pins[kind] || {}, itemId);
70 },
71
72 sortItems(kind, items) {
73 const kindPins = this.pins[kind] || {};
74 return items
75 .map((item, index) => ({ item, index }))
76 .sort((left, right) => {
77 const leftPin = kindPins[left.item.id];
78 const rightPin = kindPins[right.item.id];
79 const leftPinned = leftPin !== undefined;
80 const rightPinned = rightPin !== undefined;
81 if (leftPinned !== rightPinned) return leftPinned ? -1 : 1;
82 if (leftPinned && leftPin !== rightPin) return leftPin - rightPin;
83 return left.index - right.index;
84 })
85 .map(({ item }) => item);
86 },
87
88 dividerBefore(kind, item, index, items) {
89 return index > 0
90 && !this.isPinned(kind, item.id)
91 && this.isPinned(kind, items[index - 1]?.id);
92 },
93 };
94
95 export const store = createStore("pinToTop", model);