You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An Open Educational Resources (OER) discovery system built on Nostr, providing:
6
+
An Open Educational Resources (OER) discovery system built on Nostr and built on [AMB](https://dini-ag-kim.github.io/amb/draft/), providing:
7
7
8
8
1.**Proxy Service**: Forwards search queries to configurable source adapters and returns unified OER results via a public API. Supports searching an AMB Nostr relay, Openverse, ARASAAC, RPI-Virtuell, and more through an **extendable adapter system** - add your own adapters to integrate any external API.
9
9
2.**Source Adapters**: Pluggable adapters for OER sources (e.g., AMB relay, ARASAAC, Openverse) that integrate seamlessly with search results. The adapter plugin system makes it easy to add new sources.
This package depends on `@edufeed-org/oer-finder-plugin` internally — you do not need to install the base plugin separately.
14
14
15
-
### Direct Client Mode
15
+
##Operating Modes
16
16
17
-
For direct client mode (no `apiUrl`), register adapters before rendering:
17
+
The plugin supports two operating modes, determined by whether the `apiUrl` prop is provided:
18
+
19
+
### Server-Proxy Mode (with `apiUrl`)
20
+
21
+
When `apiUrl` is set, all search requests are routed through your backend proxy. The server handles adapter logic, so **no adapter code is bundled into your client application**.
22
+
23
+
**Use this mode when:**
24
+
- You have a deployed OER Proxy backend
25
+
- You want to keep client bundle size small
26
+
- You need server-side features like image proxying, rate limiting, or signed URLs
27
+
28
+
```tsx
29
+
<OerSearch
30
+
apiUrl="https://your-api-url.com"
31
+
sources={SOURCES}
32
+
// ...event handlers
33
+
>
34
+
```
35
+
36
+
No adapter registration is needed — just provide the `apiUrl` and configure your sources.
37
+
38
+
### Direct Client Mode (without `apiUrl`)
39
+
40
+
When `apiUrl` is **omitted**, adapters run directly in the browser. No backend server is required.
41
+
42
+
**Use this mode when:**
43
+
- You want a serverless setup with no backend dependency
44
+
- You are prototyping or building a static site
45
+
- You want full client-side control over adapter behavior
46
+
47
+
You **must register adapters** before the component renders. Call the registration function once at your app's entry point:
import {registerAllBuiltInAdapters} from '@edufeed-org/oer-finder-plugin/adapters';
21
52
registerAllBuiltInAdapters();
22
53
```
23
54
55
+
Or register only the adapters you need to reduce bundle size:
56
+
57
+
```typescript
58
+
import {registerOpenverseAdapter} from '@edufeed-org/oer-finder-plugin/adapter/openverse';
59
+
import {registerArasaacAdapter} from '@edufeed-org/oer-finder-plugin/adapter/arasaac';
60
+
registerOpenverseAdapter();
61
+
registerArasaacAdapter();
62
+
```
63
+
64
+
Then render `OerSearch` without `apiUrl`:
65
+
66
+
```tsx
67
+
<OerSearch
68
+
sources={SOURCES}
69
+
// ...event handlers (no apiUrl prop)
70
+
>
71
+
```
72
+
73
+
Only registered adapters will be available — unregistered source IDs in the `sources` config are silently skipped.
74
+
24
75
## Basic Usage
25
76
26
-
The recommended pattern is to slot `OerList` and `OerLoadMore` inside `OerSearch`:
77
+
The recommended pattern is to slot `OerList` and `OerLoadMore` inside `OerSearch`. Below are complete examples for each mode.
78
+
79
+
### Server-Proxy Mode Example
27
80
28
81
```tsx
29
82
import {useState, useCallback} from 'react';
@@ -123,7 +176,161 @@ function OerFinder() {
123
176
}
124
177
```
125
178
126
-
## React Props vs Web Component Attributes
179
+
### Direct Client Mode Example
180
+
181
+
The component code is identical to the [server-proxy example above](#server-proxy-mode-example) with two differences: adapters must be registered at startup, and the `apiUrl` prop is omitted.
182
+
183
+
**1. Register adapters once at your app entry point (e.g., `main.tsx`):**
184
+
185
+
```tsx
186
+
import {registerOpenverseAdapter} from '@edufeed-org/oer-finder-plugin/adapter/openverse';
187
+
import {registerArasaacAdapter} from '@edufeed-org/oer-finder-plugin/adapter/arasaac';
All state management, event handlers, and child components remain the same.
210
+
211
+
## Slot Architecture and Event Bubbling
212
+
213
+
`OerSearch` acts as the orchestrator. When you render `OerList` and `OerLoadMore` as children of `OerSearch`, they are slotted into the underlying `<oer-search>` web component. This enables **automatic event bubbling**: the `load-more` event fired by `OerLoadMore` bubbles up through the DOM and is caught by the parent `OerSearch`, which then fetches the next page of results and emits a new `search-results` event. You do not need to wire up any load-more handler yourself — just place the components in the correct parent-child relationship:
214
+
215
+
```tsx
216
+
<OerSearch/* handles pagination automatically */>
217
+
<OerList/* displays results */ />
218
+
<OerLoadMore/* fires load-more, caught by OerSearch */ />
219
+
</OerSearch>
220
+
```
221
+
222
+
If you render `OerLoadMore` outside of `OerSearch`, the event will not bubble to the search component and pagination will not work automatically. In that case you would need to handle the `onLoadMore` event yourself.
| `apiUrl` | `string` | No | — | Base URL of the OER Proxy API. When provided, activates server-proxy mode. When omitted, activates direct client mode (adapters must be registered). |
231
+
| `sources` | `SourceConfig[]` | No | `[openverse, arasaac]` | Available sources shown in the UI. See [Source Configuration](./client-packages.md#source-configuration). |
232
+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
233
+
| `pageSize` | `number` | No | `20` | Number of results per page. |
234
+
| `lockedType` | `string` | No | — | Lock the type filter to a specific value (e.g., `'image'`). |
235
+
| `showTypeFilter` | `boolean` | No | `true` | Show or hide the type filter dropdown. |
236
+
| `lockedSource` | `string` | No | — | Lock the source filter to a specific value. |
237
+
| `showSourceFilter` | `boolean` | No | `true` | Show or hide the source filter checkboxes. |
| `metadata` | `LoadMoreMeta \| null` | No | `null` | Pagination metadata (`{total, shown, hasMore}`). Controls the "Showing X of Y" indicator and whether the button is visible. |
260
+
| `loading` | `boolean` | No | `false` | When `true`, disables the button and shows a loading state. |
261
+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
262
+
263
+
## Event Handlers
264
+
265
+
### `OerSearch` Events
266
+
267
+
| Prop | Callback Signature | Description |
268
+
|------|-------------------|-------------|
269
+
| `onSearchLoading` | `(event: CustomEvent<void>) => void` | Fired when a search request starts. Use this to set loading state. |
| `onSearchError` | `(event: CustomEvent<{error: string}>) => void` | Fired when a search fails. `event.detail.error` contains the error message. |
272
+
| `onSearchCleared` | `(event: CustomEvent<void>) => void` | Fired when the user clears the search input. |
273
+
274
+
### `OerList` Events
275
+
276
+
| Prop | Callback Signature | Description |
277
+
|------|-------------------|-------------|
278
+
| `onCardClick` | `(event: OerCardClickEvent) => void` | Fired when a card is clicked. `event.detail.oer` contains the clicked `OerItem`. Bubbles up from child `OerCard` components. |
279
+
280
+
### `OerCard` Events
281
+
282
+
| Prop | Callback Signature | Description |
283
+
|------|-------------------|-------------|
284
+
| `onCardClick` | `(event: OerCardClickEvent) => void` | Fired when the card image is clicked. `event.detail.oer` contains the `OerItem`. |
285
+
286
+
### `OerLoadMore` Events
287
+
288
+
| Prop | Callback Signature | Description |
289
+
|------|-------------------|-------------|
290
+
| `onLoadMore` | `(event: CustomEvent<void>) => void` | Fired when the "Load more" button is clicked. When slotted inside `OerSearch`, this event bubbles up automatically to trigger the next page fetch — no manual handler needed. |
291
+
292
+
## Key Types
293
+
294
+
All types are importable from `@edufeed-org/oer-finder-plugin-react`:
0 commit comments