strapi-plugin-meilisearch-plus 0.0.1
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/README.md +266 -0
- package/dist/_chunks/CollectionsTable-DhFoYbHi.mjs +376 -0
- package/dist/_chunks/CollectionsTable-QsBP3lXJ.js +376 -0
- package/dist/_chunks/CredentialsTab-CNnDxmdX.js +223 -0
- package/dist/_chunks/CredentialsTab-UuDtCixJ.mjs +223 -0
- package/dist/_chunks/HomePage-CwtBGxKu.js +36 -0
- package/dist/_chunks/HomePage-VgGeNynA.mjs +36 -0
- package/dist/_chunks/IndexSettingsTab-EvPHd16e.js +270 -0
- package/dist/_chunks/IndexSettingsTab-P4Oz-QWd.mjs +270 -0
- package/dist/_chunks/en-BijiR88Y.js +42 -0
- package/dist/_chunks/en-KhozGU-M.mjs +42 -0
- package/dist/_chunks/es-B2Fg_gLt.js +42 -0
- package/dist/_chunks/es-DAneCWM5.mjs +42 -0
- package/dist/_chunks/index-DgpT8G3z.mjs +134 -0
- package/dist/_chunks/index-eoiIjp_f.js +133 -0
- package/dist/_chunks/useI18n-B9yJCqkO.js +18 -0
- package/dist/_chunks/useI18n-RYsv52c9.mjs +19 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/Icons/DangerIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/RefreshIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/XIcon.d.ts +7 -0
- package/dist/admin/src/components/Icons/index.d.ts +3 -0
- package/dist/admin/src/components/Initializer.d.ts +10 -0
- package/dist/admin/src/components/PluginIcon.d.ts +10 -0
- package/dist/admin/src/containers/CollectionsTab.d.ts +6 -0
- package/dist/admin/src/containers/CollectionsTable.d.ts +6 -0
- package/dist/admin/src/containers/ContentTypesToggle.d.ts +6 -0
- package/dist/admin/src/containers/CredentialsTab.d.ts +6 -0
- package/dist/admin/src/containers/IndexSettingsTab.d.ts +6 -0
- package/dist/admin/src/containers/PluginTabs.d.ts +6 -0
- package/dist/admin/src/hooks/index.d.ts +3 -0
- package/dist/admin/src/hooks/useCredentials.d.ts +18 -0
- package/dist/admin/src/hooks/useI18n.d.ts +7 -0
- package/dist/admin/src/hooks/useIndexedContentTypes.d.ts +15 -0
- package/dist/admin/src/index.d.ts +14 -0
- package/dist/admin/src/pages/App.d.ts +2 -0
- package/dist/admin/src/pages/HomePage.d.ts +5 -0
- package/dist/admin/src/pages/SettingsPage.d.ts +5 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/getTranslation.d.ts +2 -0
- package/dist/admin/src/utils/pluginId.d.ts +1 -0
- package/dist/server/index.js +1346 -0
- package/dist/server/index.mjs +1347 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/index.d.ts +5 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/content-types.d.ts +14 -0
- package/dist/server/src/controllers/controller.d.ts +7 -0
- package/dist/server/src/controllers/credentials.d.ts +12 -0
- package/dist/server/src/controllers/index-settings.d.ts +17 -0
- package/dist/server/src/controllers/index.d.ts +40 -0
- package/dist/server/src/controllers/indexing.d.ts +10 -0
- package/dist/server/src/controllers/search.d.ts +10 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +216 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin/index.d.ts +21 -0
- package/dist/server/src/routes/content-api/index.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +34 -0
- package/dist/server/src/services/content-types.d.ts +59 -0
- package/dist/server/src/services/index.d.ts +123 -0
- package/dist/server/src/services/lifecycle.d.ts +15 -0
- package/dist/server/src/services/meilisearch-client.d.ts +18 -0
- package/dist/server/src/services/meilisearch.d.ts +42 -0
- package/dist/server/src/services/service.d.ts +7 -0
- package/dist/server/src/services/store.d.ts +42 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# MeiliSearch Plus Plugin
|
|
2
|
+
|
|
3
|
+
A comprehensive MeiliSearch integration plugin for Strapi v5 that enables full-text search capabilities for your content.
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
#### Server-Side
|
|
8
|
+
|
|
9
|
+
- **Credential Management**: Securely store MeiliSearch host and API key
|
|
10
|
+
- **Automatic Indexing**: Automatically index documents on create, update, and delete
|
|
11
|
+
- **Published Documents Only**: Only published documents are indexed (draft documents are excluded)
|
|
12
|
+
- **Content Type Management**: Choose which content types to index
|
|
13
|
+
- **Manual Indexing**: Trigger reindexing of specific content types without affecting others
|
|
14
|
+
- **Safe Reindex**: Reindex operations only affect the specified content type
|
|
15
|
+
- **Search API**: Full-text search with filters, pagination, and sorting
|
|
16
|
+
- **Connection Testing**: Verify MeiliSearch connection before operations
|
|
17
|
+
- **Index Status**: Monitor index health and sync status with display names
|
|
18
|
+
- **Content Type Display Names**: Full content type metadata with human-readable names
|
|
19
|
+
|
|
20
|
+
### Admin API Routes
|
|
21
|
+
|
|
22
|
+
#### Credentials Management
|
|
23
|
+
|
|
24
|
+
- `GET /meilisearch-plus/credentials` - Get current credentials
|
|
25
|
+
- `POST /meilisearch-plus/credentials` - Set credentials (host, apiKey, indexName)
|
|
26
|
+
- `GET /meilisearch-plus/test-connection` - Test MeiliSearch connection
|
|
27
|
+
|
|
28
|
+
#### Search Operations
|
|
29
|
+
|
|
30
|
+
- `POST /meilisearch-plus/search` - Execute search query
|
|
31
|
+
- Body: `{ query, filters?, limit?, offset? }`
|
|
32
|
+
- `GET /meilisearch-plus/index-status` - Get index status and statistics
|
|
33
|
+
|
|
34
|
+
#### Content Type Management
|
|
35
|
+
|
|
36
|
+
- `GET /meilisearch-plus/sync-status/all` - Get all configured content types with sync status
|
|
37
|
+
- Returns: `{ contentType, displayName, isIndexed, total, indexed, syncPercentage, isSynced }`
|
|
38
|
+
- `GET /meilisearch-plus/indexed-content-types` - List indexed content types
|
|
39
|
+
- `POST /meilisearch-plus/indexed-content-types` - Add content type to index
|
|
40
|
+
- Body: `{ contentType }`
|
|
41
|
+
- `DELETE /meilisearch-plus/indexed-content-types` - Remove content type from index and MeiliSearch
|
|
42
|
+
|
|
43
|
+
#### Manual Indexing
|
|
44
|
+
|
|
45
|
+
- `POST /meilisearch-plus/reindex` - Reindex documents for a specific content type
|
|
46
|
+
- Body: `{ contentType }` - Only reindexes the specified content type
|
|
47
|
+
- `POST /meilisearch-plus/clear-index` - Clear indexed documents for a content type
|
|
48
|
+
- Body: `{ contentType }` - Only clears the specified content type
|
|
49
|
+
|
|
50
|
+
### Content API Routes (Public)
|
|
51
|
+
|
|
52
|
+
- `POST /api/plugins/meilisearch-plus/search` - Public search endpoint
|
|
53
|
+
- Body: `{ query, filters?, limit?, offset? }`
|
|
54
|
+
|
|
55
|
+
## Architecture
|
|
56
|
+
|
|
57
|
+
### Services
|
|
58
|
+
|
|
59
|
+
#### `meilisearch-client.ts`
|
|
60
|
+
|
|
61
|
+
Factory service for creating and managing MeiliSearch client instances.
|
|
62
|
+
|
|
63
|
+
- `createClient(credentials)` - Create authenticated client
|
|
64
|
+
- `testConnection(client)` - Verify connection health
|
|
65
|
+
- `getIndexUids(client)` - List all indexes
|
|
66
|
+
- `createIndex(client, indexName)` - Create new index
|
|
67
|
+
- `deleteIndex(client, indexName)` - Delete index
|
|
68
|
+
|
|
69
|
+
#### `store.ts`
|
|
70
|
+
|
|
71
|
+
Persistent storage for credentials and configuration using Strapi's store API.
|
|
72
|
+
|
|
73
|
+
- `getCredentials()` / `setCredentials()`
|
|
74
|
+
- `getApiKey()` / `setApiKey()`
|
|
75
|
+
- `getHost()` / `setHost()`
|
|
76
|
+
- `getIndexName()` / `setIndexName()`
|
|
77
|
+
- `getIndexedContentTypes()` / `setIndexedContentTypes()`
|
|
78
|
+
- `addIndexedContentType()` / `removeIndexedContentType()`
|
|
79
|
+
|
|
80
|
+
#### `meilisearch.ts`
|
|
81
|
+
|
|
82
|
+
Core indexing and search service.
|
|
83
|
+
|
|
84
|
+
- `indexDocument()` - Index single document
|
|
85
|
+
- `indexDocuments()` - Bulk index documents
|
|
86
|
+
- `deleteDocument()` - Remove document from index
|
|
87
|
+
- `deleteAllDocuments()` - Clear all documents
|
|
88
|
+
- `search()` - Search with filters and pagination
|
|
89
|
+
- `getIndexSettings()` / `updateIndexSettings()`
|
|
90
|
+
|
|
91
|
+
#### `lifecycle.ts`
|
|
92
|
+
|
|
93
|
+
Lifecycle hook subscriptions for automatic indexing.
|
|
94
|
+
|
|
95
|
+
- `subscribeContentType(contentType)` - Listen to create/update/delete events
|
|
96
|
+
- `unsubscribeContentType(contentType)` - Stop listening (metadata only)
|
|
97
|
+
|
|
98
|
+
### Controllers
|
|
99
|
+
|
|
100
|
+
#### `credentials.ts`
|
|
101
|
+
|
|
102
|
+
Admin endpoints for managing MeiliSearch credentials and connections.
|
|
103
|
+
|
|
104
|
+
#### `search.ts`
|
|
105
|
+
|
|
106
|
+
Search functionality with index status monitoring.
|
|
107
|
+
|
|
108
|
+
#### `content-types.ts`
|
|
109
|
+
|
|
110
|
+
Manage which content types are indexed.
|
|
111
|
+
|
|
112
|
+
#### `indexing.ts`
|
|
113
|
+
|
|
114
|
+
Manual indexing operations (reindex, clear).
|
|
115
|
+
|
|
116
|
+
## Admin Panel
|
|
117
|
+
|
|
118
|
+
The plugin provides a dedicated admin interface in the Strapi Settings menu with two sections:
|
|
119
|
+
|
|
120
|
+
### Settings Page
|
|
121
|
+
|
|
122
|
+
Access the plugin settings at **Settings > MeiliSearch Plus**:
|
|
123
|
+
|
|
124
|
+
#### Credentials Tab
|
|
125
|
+
|
|
126
|
+
Configure MeiliSearch connection details:
|
|
127
|
+
|
|
128
|
+
- **MeiliSearch URL** - The URL where MeiliSearch is running (e.g., `http://localhost:7700`)
|
|
129
|
+
- **API Key** - API key with permission to create indexes (master key recommended)
|
|
130
|
+
- **Index Name** - Name of the MeiliSearch index to use for this installation
|
|
131
|
+
|
|
132
|
+
Features:
|
|
133
|
+
|
|
134
|
+
- Test connection button to verify credentials
|
|
135
|
+
- Security warning about API key exposure
|
|
136
|
+
- Save configuration
|
|
137
|
+
|
|
138
|
+
#### Index Settings Tab
|
|
139
|
+
|
|
140
|
+
Configure which fields are searchable and sortable per content type:
|
|
141
|
+
|
|
142
|
+
- **Max Total Hits** - Maximum number of documents returned by MeiliSearch (default: 1000)
|
|
143
|
+
- **Filterable Attributes** - Select which fields can be used as filters in search queries
|
|
144
|
+
- **Sortable Attributes** - Select which fields can be sorted in search results
|
|
145
|
+
- **Mandatory Field**: `_contentType` is always included (required for document categorization)
|
|
146
|
+
- Organize by content type using tabbed interface
|
|
147
|
+
- Human-readable field names (converted from snake_case)
|
|
148
|
+
|
|
149
|
+
### Collections Tab
|
|
150
|
+
|
|
151
|
+
Monitor indexed content types in the main plugin view:
|
|
152
|
+
|
|
153
|
+
- View all configured content types with display names
|
|
154
|
+
- Check indexing status (complete/in-progress)
|
|
155
|
+
- See database count vs indexed documents
|
|
156
|
+
- Toggle content types on/off for indexing
|
|
157
|
+
- Update index (sync without full rebuild)
|
|
158
|
+
- Reindex individual content types (only affects the selected type)
|
|
159
|
+
- See hooks status (automatic sync enabled/disabled)
|
|
160
|
+
|
|
161
|
+
## Configuration
|
|
162
|
+
|
|
163
|
+
The plugin is enabled by default in `apps/cms/config/plugins.ts`:
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
'meilisearch-plus': {
|
|
167
|
+
enabled: true,
|
|
168
|
+
resolve: './src/plugins/meilisearch-plus',
|
|
169
|
+
},
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Environment Variables
|
|
173
|
+
|
|
174
|
+
Set these in your `.env` file:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
MEILISEARCH_HOST=http://localhost:7700
|
|
178
|
+
MEILISEARCH_API_KEY=your-api-key
|
|
179
|
+
MEILISEARCH_INDEX_NAME=your-index-name
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## UID Mapping and Content Type Handling
|
|
183
|
+
|
|
184
|
+
Content types are configured using short names in the plugin config (e.g., `page`, `job`) and are mapped to full UIDs:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// Config uses short names
|
|
188
|
+
'meilisearch-plus': {
|
|
189
|
+
page: { /* config */},
|
|
190
|
+
job: { /* config */},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Internally mapped to full UIDs
|
|
194
|
+
api::page.page
|
|
195
|
+
api::job.job
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The `/sync-status/all` endpoint returns:
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"contentType": "api::page.page",
|
|
203
|
+
"displayName": "Pagina",
|
|
204
|
+
"isIndexed": true,
|
|
205
|
+
"total": 18,
|
|
206
|
+
"indexed": 18,
|
|
207
|
+
"syncPercentage": 100,
|
|
208
|
+
"isSynced": true
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Key Implementation Details
|
|
213
|
+
|
|
214
|
+
### Content Type Identification
|
|
215
|
+
|
|
216
|
+
The plugin uses full content type UIDs throughout the application (e.g., `api::page.page`):
|
|
217
|
+
|
|
218
|
+
- UIDs are mapped from plugin configuration short names
|
|
219
|
+
- Display names are fetched from Strapi content type schema
|
|
220
|
+
- All API responses include both UID and displayName for clarity
|
|
221
|
+
|
|
222
|
+
### Document Indexing
|
|
223
|
+
|
|
224
|
+
- **Status Filter**: Only `published` documents are indexed (draft documents are excluded)
|
|
225
|
+
- **Content Type Field**: The `_contentType` field is automatically added to all documents for categorization
|
|
226
|
+
- **Batch Processing**: Documents are indexed in batches of 1000
|
|
227
|
+
- **Safe Deletion**: When toggling off a content type, all its documents are removed from MeiliSearch
|
|
228
|
+
|
|
229
|
+
### Reindex Operations
|
|
230
|
+
|
|
231
|
+
- **Content Type Scoped**: Reindex and clear operations only affect the specified content type
|
|
232
|
+
- **No Cross-Type Interference**: Toggling or reindexing one type doesn't affect others
|
|
233
|
+
- **Full Rebuild**: Reindex performs a complete delete and re-add cycle for the specific type
|
|
234
|
+
|
|
235
|
+
### Attribute Settings
|
|
236
|
+
|
|
237
|
+
- **Mandatory Fields**: `_contentType` is always included in filterable and sortable attributes (required for document lookup)
|
|
238
|
+
- **Field Name Formatting**: Field names are converted to human-readable format in the UI (e.g., `publishedAt` → "Published At")
|
|
239
|
+
- **Per-Content-Type Config**: Settings are merged across content types to create a unified index configuration
|
|
240
|
+
|
|
241
|
+
### Database & Storage
|
|
242
|
+
|
|
243
|
+
The plugin uses Strapi's store API to persist:
|
|
244
|
+
|
|
245
|
+
- MeiliSearch credentials (host, API key, index name)
|
|
246
|
+
- Indexed content types list
|
|
247
|
+
- Index settings (filterable/sortable attributes, max total hits)
|
|
248
|
+
- Lifecycle subscriptions for automatic indexing
|
|
249
|
+
|
|
250
|
+
## Error Handling
|
|
251
|
+
|
|
252
|
+
All services include comprehensive error handling with logging:
|
|
253
|
+
|
|
254
|
+
- Connection errors are logged but don't block startup
|
|
255
|
+
- Document indexing failures are logged but don't break transactions
|
|
256
|
+
- Missing credentials/index names are handled gracefully
|
|
257
|
+
- All operations have timeout protection
|
|
258
|
+
- Reindex and clear operations safely handle already-deleted documents
|
|
259
|
+
|
|
260
|
+
## Type Safety
|
|
261
|
+
|
|
262
|
+
The plugin is fully written in TypeScript with:
|
|
263
|
+
|
|
264
|
+
- Strict type checking
|
|
265
|
+
- Comprehensive JSDoc comments
|
|
266
|
+
- Service interfaces for IDE autocompletion
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import { memo, useState, useEffect } from "react";
|
|
3
|
+
import { Flex, Loader, Box, Typography, Badge, Table, Tr, Td, Tbody, Checkbox, Button } from "@strapi/design-system";
|
|
4
|
+
import { useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
5
|
+
import { Check } from "@strapi/icons";
|
|
6
|
+
import { u as useI18n } from "./useI18n-RYsv52c9.mjs";
|
|
7
|
+
import { p as pluginId } from "./index-DgpT8G3z.mjs";
|
|
8
|
+
const RefreshIcon = ({ size = 16, color = "currentColor" }) => /* @__PURE__ */ jsxs(
|
|
9
|
+
"svg",
|
|
10
|
+
{
|
|
11
|
+
width: size,
|
|
12
|
+
height: size,
|
|
13
|
+
viewBox: "0 0 24 24",
|
|
14
|
+
fill: "none",
|
|
15
|
+
stroke: color,
|
|
16
|
+
strokeWidth: "2",
|
|
17
|
+
strokeLinecap: "round",
|
|
18
|
+
strokeLinejoin: "round",
|
|
19
|
+
children: [
|
|
20
|
+
/* @__PURE__ */ jsx("polyline", { points: "23 4 23 10 17 10" }),
|
|
21
|
+
/* @__PURE__ */ jsx("polyline", { points: "1 20 1 14 7 14" }),
|
|
22
|
+
/* @__PURE__ */ jsx("path", { d: "M3.51 9a9 9 0 0 1 14.85-3.36M20.49 15a9 9 0 0 1-14.85 3.36" })
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
const XIcon = ({ size = 16, color = "currentColor" }) => /* @__PURE__ */ jsxs(
|
|
27
|
+
"svg",
|
|
28
|
+
{
|
|
29
|
+
width: size,
|
|
30
|
+
height: size,
|
|
31
|
+
viewBox: "0 0 24 24",
|
|
32
|
+
fill: "none",
|
|
33
|
+
stroke: color,
|
|
34
|
+
strokeWidth: "2",
|
|
35
|
+
strokeLinecap: "round",
|
|
36
|
+
strokeLinejoin: "round",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
39
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
const DangerIcon = ({ size = 16, color = "currentColor" }) => /* @__PURE__ */ jsx(
|
|
44
|
+
"svg",
|
|
45
|
+
{
|
|
46
|
+
width: size,
|
|
47
|
+
height: size,
|
|
48
|
+
viewBox: "0 0 24 24",
|
|
49
|
+
fill: "none",
|
|
50
|
+
stroke: color,
|
|
51
|
+
strokeWidth: "2",
|
|
52
|
+
strokeLinecap: "round",
|
|
53
|
+
strokeLinejoin: "round",
|
|
54
|
+
children: /* @__PURE__ */ jsx("polygon", { points: "12 2 22 20 2 20" })
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
const CollectionsTable = memo(() => {
|
|
58
|
+
const { i18n } = useI18n();
|
|
59
|
+
const { get, post, del } = useFetchClient();
|
|
60
|
+
const { toggleNotification } = useNotification();
|
|
61
|
+
const [contentTypes, setContentTypes] = useState([]);
|
|
62
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
63
|
+
const [loadingState, setLoadingState] = useState({});
|
|
64
|
+
const [syncData, setSyncData] = useState({});
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
loadContentTypes();
|
|
67
|
+
}, [get]);
|
|
68
|
+
const loadContentTypes = async () => {
|
|
69
|
+
setIsLoading(true);
|
|
70
|
+
try {
|
|
71
|
+
const syncResponse = await get(`/${pluginId}/sync-status/all`);
|
|
72
|
+
const syncStatuses = syncResponse.data?.data || [];
|
|
73
|
+
console.log("Sync statuses from API:", syncStatuses);
|
|
74
|
+
const configuredUids = syncStatuses.map((item) => item.contentType);
|
|
75
|
+
console.log("Configured content types from API:", configuredUids);
|
|
76
|
+
const allContentTypes = strapi.contentTypes;
|
|
77
|
+
const syncMap = {};
|
|
78
|
+
const types = syncStatuses.map((item) => {
|
|
79
|
+
syncMap[item.contentType] = {
|
|
80
|
+
total: item.total || 0,
|
|
81
|
+
indexed: item.indexed || 0,
|
|
82
|
+
syncPercentage: item.syncPercentage || 0,
|
|
83
|
+
isSynced: item.isSynced || false,
|
|
84
|
+
isIndexed: item.isIndexed || false
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
name: item.displayName || item.contentType,
|
|
88
|
+
uid: item.contentType,
|
|
89
|
+
displayName: item.displayName || item.contentType,
|
|
90
|
+
isIndexed: item.isIndexed || false,
|
|
91
|
+
total: item.total || 0,
|
|
92
|
+
indexed: item.indexed || 0,
|
|
93
|
+
syncPercentage: item.syncPercentage || 0,
|
|
94
|
+
isSynced: item.isSynced || false
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
setSyncData(syncMap);
|
|
98
|
+
setContentTypes(types);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error("[meilisearch-plus] Failed to load content types:", error);
|
|
101
|
+
toggleNotification({
|
|
102
|
+
type: "warning",
|
|
103
|
+
message: "Failed to load sync status"
|
|
104
|
+
});
|
|
105
|
+
} finally {
|
|
106
|
+
setIsLoading(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const handleToggle = async (name) => {
|
|
110
|
+
console.log(contentTypes);
|
|
111
|
+
const currentContentType = contentTypes.find((ct) => ct.name === name || ct.uid === name);
|
|
112
|
+
if (!currentContentType) return;
|
|
113
|
+
const shouldAddIndex = !currentContentType.isIndexed;
|
|
114
|
+
console.log(`[meilisearch-plus] Toggling ${name} to ${shouldAddIndex}`);
|
|
115
|
+
setLoadingState((prev) => ({ ...prev, [name]: true }));
|
|
116
|
+
try {
|
|
117
|
+
const contentTypeUid = currentContentType?.uid || name;
|
|
118
|
+
if (shouldAddIndex) {
|
|
119
|
+
console.log(`[meilisearch-plus] POST to /${pluginId}/indexed-content-types`);
|
|
120
|
+
const response = await post(`/${pluginId}/indexed-content-types`, {
|
|
121
|
+
contentType: contentTypeUid
|
|
122
|
+
});
|
|
123
|
+
console.log("[meilisearch-plus] POST response:", response);
|
|
124
|
+
console.log(`[meilisearch-plus] Reindexing ${contentTypeUid} after adding to index`);
|
|
125
|
+
await post(`/${pluginId}/reindex`, { contentType: contentTypeUid });
|
|
126
|
+
console.log(`[meilisearch-plus] Reindex complete for ${contentTypeUid}`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(
|
|
129
|
+
`[meilisearch-plus] DELETE to /${pluginId}/indexed-content-types?contentType=${contentTypeUid}`
|
|
130
|
+
);
|
|
131
|
+
const response = await del(
|
|
132
|
+
`/${pluginId}/indexed-content-types?contentType=${contentTypeUid}`
|
|
133
|
+
);
|
|
134
|
+
console.log("[meilisearch-plus] DELETE response:", response);
|
|
135
|
+
}
|
|
136
|
+
setContentTypes(
|
|
137
|
+
(prev) => prev.map((ct) => ct.name === name ? { ...ct, isIndexed: shouldAddIndex } : ct)
|
|
138
|
+
);
|
|
139
|
+
toggleNotification({
|
|
140
|
+
type: "success",
|
|
141
|
+
message: shouldAddIndex ? `${name} added to index` : `${name} removed from index`
|
|
142
|
+
});
|
|
143
|
+
await loadContentTypes();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`[meilisearch-plus] Failed to toggle ${name}:`, error);
|
|
146
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
147
|
+
console.error("[meilisearch-plus] Error details:", errorMessage);
|
|
148
|
+
toggleNotification({
|
|
149
|
+
type: "warning",
|
|
150
|
+
message: `Failed to update ${name}: ${errorMessage}`
|
|
151
|
+
});
|
|
152
|
+
} finally {
|
|
153
|
+
setLoadingState((prev) => ({ ...prev, [name]: false }));
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const handleUpdate = async (contentType) => {
|
|
157
|
+
console.log(`[meilisearch-plus] Updating ${contentType}`);
|
|
158
|
+
setLoadingState((prev) => ({ ...prev, [contentType]: true }));
|
|
159
|
+
try {
|
|
160
|
+
const ct = contentTypes.find((ct2) => ct2.name === contentType || ct2.uid === contentType);
|
|
161
|
+
const contentTypeUid = ct?.uid || contentType;
|
|
162
|
+
const response = await post(`/${pluginId}/reindex`, { contentType: contentTypeUid });
|
|
163
|
+
console.log("[meilisearch-plus] Update response:", response);
|
|
164
|
+
toggleNotification({
|
|
165
|
+
type: "success",
|
|
166
|
+
message: `${contentType} index updated`
|
|
167
|
+
});
|
|
168
|
+
await loadContentTypes();
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`[meilisearch-plus] Failed to update ${contentType}:`, error);
|
|
171
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
172
|
+
toggleNotification({
|
|
173
|
+
type: "warning",
|
|
174
|
+
message: `Failed to update ${contentType}: ${errorMessage}`
|
|
175
|
+
});
|
|
176
|
+
} finally {
|
|
177
|
+
setLoadingState((prev) => ({ ...prev, [contentType]: false }));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
const handleReindex = async (contentType) => {
|
|
181
|
+
console.log(`[meilisearch-plus] Reindexing ${contentType}`);
|
|
182
|
+
setLoadingState((prev) => ({ ...prev, [contentType]: true }));
|
|
183
|
+
try {
|
|
184
|
+
const ct = contentTypes.find((ct2) => ct2.name === contentType || ct2.uid === contentType);
|
|
185
|
+
const contentTypeUid = ct?.uid || contentType;
|
|
186
|
+
await post(`/${pluginId}/clear-index`, { contentType: contentTypeUid });
|
|
187
|
+
console.log("[meilisearch-plus] Index cleared for", contentTypeUid);
|
|
188
|
+
const response = await post(`/${pluginId}/reindex`, { contentType: contentTypeUid });
|
|
189
|
+
console.log("[meilisearch-plus] Reindex response:", response);
|
|
190
|
+
toggleNotification({
|
|
191
|
+
type: "success",
|
|
192
|
+
message: `${contentType} index reindexed`
|
|
193
|
+
});
|
|
194
|
+
await loadContentTypes();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(`[meilisearch-plus] Failed to reindex ${contentType}:`, error);
|
|
197
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
198
|
+
toggleNotification({
|
|
199
|
+
type: "warning",
|
|
200
|
+
message: `Failed to reindex ${contentType}: ${errorMessage}`
|
|
201
|
+
});
|
|
202
|
+
} finally {
|
|
203
|
+
setLoadingState((prev) => ({ ...prev, [contentType]: false }));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
if (isLoading) {
|
|
207
|
+
return /* @__PURE__ */ jsx(Flex, { justifyContent: "center", alignItems: "center", minHeight: "400px", children: /* @__PURE__ */ jsx(Loader, {}) });
|
|
208
|
+
}
|
|
209
|
+
const COL_COUNT = 8;
|
|
210
|
+
const ROW_COUNT = Math.max(6, contentTypes.length);
|
|
211
|
+
return /* @__PURE__ */ jsxs(Box, { background: "neutral100", padding: 4, children: [
|
|
212
|
+
/* @__PURE__ */ jsxs(Box, { padding: 6, marginBottom: 6, background: "neutral0", hasRadius: true, children: [
|
|
213
|
+
/* @__PURE__ */ jsx(Typography, { variant: "beta", marginBottom: 6, paddingBottom: 4, children: i18n("plugin.legend.title", "Legend") }),
|
|
214
|
+
/* @__PURE__ */ jsxs(
|
|
215
|
+
Flex,
|
|
216
|
+
{
|
|
217
|
+
direction: "column",
|
|
218
|
+
alignItems: "flex-start",
|
|
219
|
+
justifyContent: "between",
|
|
220
|
+
gap: 6,
|
|
221
|
+
style: { width: "100%" },
|
|
222
|
+
children: [
|
|
223
|
+
/* @__PURE__ */ jsxs(Flex, { alignItems: "flex-start", justifyContent: "between", gap: 6, style: { width: "100%" }, children: [
|
|
224
|
+
/* @__PURE__ */ jsxs(Box, { flex: 1, children: [
|
|
225
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, children: i18n("plugin.legend.in-meilisearch", "IN MEILISEARCH") }),
|
|
226
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 3, paddingTop: 2, children: [
|
|
227
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
228
|
+
/* @__PURE__ */ jsx(Badge, { background: "success600", textColor: "neutral0", children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
229
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n("plugin.legend.indexed", "Content type is indexed") })
|
|
230
|
+
] }),
|
|
231
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
232
|
+
/* @__PURE__ */ jsx(Badge, { background: "neutral900", textColor: "neutral0", children: /* @__PURE__ */ jsx(XIcon, { size: 16, color: "white" }) }),
|
|
233
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n("plugin.legend.not-indexed", "Content type is not indexed") })
|
|
234
|
+
] })
|
|
235
|
+
] })
|
|
236
|
+
] }),
|
|
237
|
+
/* @__PURE__ */ jsxs(Box, { flex: 1, children: [
|
|
238
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, children: i18n("plugin.legend.indexing-status", "INDEXING STATUS") }),
|
|
239
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, alignItems: "flex-start", paddingTop: 2, children: [
|
|
240
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Badge, { background: "success600", textColor: "neutral0", children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
242
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n("plugin.legend.indexing-complete", "Indexing complete") })
|
|
243
|
+
] }),
|
|
244
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
245
|
+
/* @__PURE__ */ jsx(Badge, { background: "warning600", textColor: "neutral0", children: /* @__PURE__ */ jsx(RefreshIcon, { size: 16, color: "white" }) }),
|
|
246
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n("plugin.legend.indexing-in-progress", "Indexing in progress") })
|
|
247
|
+
] })
|
|
248
|
+
] })
|
|
249
|
+
] }),
|
|
250
|
+
/* @__PURE__ */ jsxs(Box, { flex: 1, children: [
|
|
251
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, children: i18n("plugin.legend.hooks", "HOOKS") }),
|
|
252
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 3, paddingTop: 2, alignItems: "flex-start", children: [
|
|
253
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
254
|
+
/* @__PURE__ */ jsx(Badge, { background: "success600", textColor: "neutral0", children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
255
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n("plugin.legend.hooks-active", "Hooks active - automatic sync enabled") })
|
|
256
|
+
] }),
|
|
257
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "flex-start", children: [
|
|
258
|
+
/* @__PURE__ */ jsx(Badge, { background: "neutral900", textColor: "neutral0", children: /* @__PURE__ */ jsx(DangerIcon, { size: 16, color: "white" }) }),
|
|
259
|
+
/* @__PURE__ */ jsx(Typography, { size: "sm", children: i18n(
|
|
260
|
+
"plugin.legend.hooks-inactive",
|
|
261
|
+
"Hooks not active - manual reload required"
|
|
262
|
+
) })
|
|
263
|
+
] })
|
|
264
|
+
] })
|
|
265
|
+
] })
|
|
266
|
+
] }),
|
|
267
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
268
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", marginBottom: 3, children: i18n("plugin.legend.actions", "ACTIONS") }),
|
|
269
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", gap: 2, alignItems: "flex-start", children: [
|
|
270
|
+
/* @__PURE__ */ jsxs(Typography, { size: "sm", children: [
|
|
271
|
+
/* @__PURE__ */ jsx("strong", { children: i18n("plugin.button.update", "Update") }),
|
|
272
|
+
" —",
|
|
273
|
+
" ",
|
|
274
|
+
i18n(
|
|
275
|
+
"plugin.legend.update",
|
|
276
|
+
"Sync & refresh the index with the latest entries without deleting existing data."
|
|
277
|
+
)
|
|
278
|
+
] }),
|
|
279
|
+
/* @__PURE__ */ jsxs(Typography, { size: "sm", children: [
|
|
280
|
+
/* @__PURE__ */ jsx("strong", { children: i18n("plugin.button.reindex", "Reindex") }),
|
|
281
|
+
" —",
|
|
282
|
+
" ",
|
|
283
|
+
i18n(
|
|
284
|
+
"plugin.legend.reindex",
|
|
285
|
+
"Full rebuild: delete all indexed documents and reindex everything from scratch."
|
|
286
|
+
)
|
|
287
|
+
] })
|
|
288
|
+
] })
|
|
289
|
+
] })
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
] }),
|
|
294
|
+
/* @__PURE__ */ jsx(Box, { background: "neutral0", hasRadius: true, children: /* @__PURE__ */ jsxs(Table, { colCount: COL_COUNT, rowCount: ROW_COUNT, children: [
|
|
295
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs(Tr, { children: [
|
|
296
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.index", "Index") }) }),
|
|
297
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.name", "Name") }) }),
|
|
298
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.in-meilisearch", "In MeiliSearch") }) }),
|
|
299
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.database", "Database") }) }),
|
|
300
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.indexed", "Indexed") }) }),
|
|
301
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.sync", "Sync %") }) }),
|
|
302
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: i18n("plugin.table.header.actions", "Actions") }) }),
|
|
303
|
+
/* @__PURE__ */ jsx(Td, {})
|
|
304
|
+
] }) }),
|
|
305
|
+
/* @__PURE__ */ jsx(Tbody, { children: contentTypes.length === 0 ? /* @__PURE__ */ jsx(Tr, { children: /* @__PURE__ */ jsx(Td, { colSpan: COL_COUNT, children: /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Typography, { children: i18n("plugin.noConfiguredTypes", "No content types configured") }) }) }) }) : contentTypes.map((contentType) => /* @__PURE__ */ jsxs(Tr, { children: [
|
|
306
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(
|
|
307
|
+
Checkbox,
|
|
308
|
+
{
|
|
309
|
+
checked: contentType.isIndexed,
|
|
310
|
+
onCheckedChange: () => {
|
|
311
|
+
handleToggle(contentType.name);
|
|
312
|
+
},
|
|
313
|
+
disabled: loadingState[contentType.name],
|
|
314
|
+
"aria-label": `Toggle indexing for ${contentType.name}`
|
|
315
|
+
}
|
|
316
|
+
) }),
|
|
317
|
+
/* @__PURE__ */ jsxs(Td, { children: [
|
|
318
|
+
/* @__PURE__ */ jsx(Typography, { weight: "bold", children: contentType.displayName || contentType.name }),
|
|
319
|
+
/* @__PURE__ */ jsx(
|
|
320
|
+
Typography,
|
|
321
|
+
{
|
|
322
|
+
variant: "pi",
|
|
323
|
+
textColor: "neutral600",
|
|
324
|
+
fontSize: 12,
|
|
325
|
+
style: { display: "block" },
|
|
326
|
+
children: contentType.uid
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
] }),
|
|
330
|
+
/* @__PURE__ */ jsx(Td, { children: contentType.isIndexed ? /* @__PURE__ */ jsx(Badge, { background: "success600", textColor: "neutral0", children: /* @__PURE__ */ jsx(Check, {}) }) : /* @__PURE__ */ jsx(Badge, { background: "neutral900", textColor: "neutral0", children: /* @__PURE__ */ jsx(XIcon, { size: 16, color: "white" }) }) }),
|
|
331
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { size: "sm", children: contentType.total || 0 }) }),
|
|
332
|
+
/* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { size: "sm", children: contentType.indexed || 0 }) }),
|
|
333
|
+
/* @__PURE__ */ jsx(Td, { children: contentType.isSynced ? /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
334
|
+
/* @__PURE__ */ jsx(Badge, { background: "success600", textColor: "neutral0", children: /* @__PURE__ */ jsx(Check, {}) }),
|
|
335
|
+
/* @__PURE__ */ jsxs(Typography, { size: "sm", weight: "bold", children: [
|
|
336
|
+
contentType.syncPercentage || 0,
|
|
337
|
+
"%"
|
|
338
|
+
] })
|
|
339
|
+
] }) : /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
340
|
+
/* @__PURE__ */ jsx(Badge, { background: "warning600", textColor: "neutral0", children: /* @__PURE__ */ jsx(DangerIcon, { size: 16, color: "white" }) }),
|
|
341
|
+
/* @__PURE__ */ jsxs(Typography, { size: "sm", children: [
|
|
342
|
+
contentType.syncPercentage || 0,
|
|
343
|
+
"%"
|
|
344
|
+
] })
|
|
345
|
+
] }) }),
|
|
346
|
+
/* @__PURE__ */ jsx(Td, { children: contentType.isIndexed && /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
347
|
+
/* @__PURE__ */ jsx(
|
|
348
|
+
Button,
|
|
349
|
+
{
|
|
350
|
+
size: "S",
|
|
351
|
+
variant: "secondary",
|
|
352
|
+
disabled: loadingState[contentType.name],
|
|
353
|
+
onClick: () => handleUpdate(contentType.name),
|
|
354
|
+
children: i18n("plugin.button.update", "Update")
|
|
355
|
+
}
|
|
356
|
+
),
|
|
357
|
+
/* @__PURE__ */ jsx(
|
|
358
|
+
Button,
|
|
359
|
+
{
|
|
360
|
+
size: "S",
|
|
361
|
+
variant: "tertiary",
|
|
362
|
+
disabled: loadingState[contentType.name],
|
|
363
|
+
onClick: () => handleReindex(contentType.name),
|
|
364
|
+
children: i18n("plugin.button.reindex", "Reindex")
|
|
365
|
+
}
|
|
366
|
+
)
|
|
367
|
+
] }) }),
|
|
368
|
+
/* @__PURE__ */ jsx(Td, { children: loadingState[contentType.name] && /* @__PURE__ */ jsx(Loader, {}) })
|
|
369
|
+
] }, contentType.uid || contentType.name)) })
|
|
370
|
+
] }) })
|
|
371
|
+
] });
|
|
372
|
+
});
|
|
373
|
+
CollectionsTable.displayName = "CollectionsTable";
|
|
374
|
+
export {
|
|
375
|
+
CollectionsTable as default
|
|
376
|
+
};
|