coreless-lib 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 +217 -0
- package/dist/api/client.d.ts +33 -0
- package/dist/api/client.js +100 -0
- package/dist/core/client.d.ts +81 -0
- package/dist/core/client.js +123 -0
- package/dist/core/coreless.d.ts +28 -0
- package/dist/core/coreless.js +120 -0
- package/dist/core/types.d.ts +32 -0
- package/dist/core/types.js +1 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +20 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +10 -0
- package/dist/react/context.d.ts +12 -0
- package/dist/react/context.js +14 -0
- package/dist/react/hooks.d.ts +12 -0
- package/dist/react/hooks.js +104 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Coreless Lib - Документация для начинающих
|
|
2
|
+
|
|
3
|
+
Библиотека для управления контентом вашего сайта без сложностей.
|
|
4
|
+
Представьте: весь текст, картинки и меню вашего сайта хранятся на сервере, а вы просто "заказываете" их по имени.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 🟢 1. Чистый JavaScript (Без сборщиков)
|
|
9
|
+
|
|
10
|
+
Самый простой способ — подключить библиотеку прямо в HTML-файл через CDN.
|
|
11
|
+
|
|
12
|
+
**Шаг 1. Создайте `index.html`**
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<!DOCTYPE html>
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<head>
|
|
18
|
+
<meta charset="UTF-8">
|
|
19
|
+
<title>Мой сайт</title>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h1 id="title">Загрузка...</h1>
|
|
23
|
+
<p id="desc"></p>
|
|
24
|
+
<img id="image" src="" style="display:none; width: 200px;">
|
|
25
|
+
|
|
26
|
+
<!-- Подключаем библиотеку как модуль -->
|
|
27
|
+
<script type="module">
|
|
28
|
+
// Импортируем класс Coreless прямо из сети (или локально ./dist/index.js)
|
|
29
|
+
import { Coreless } from 'https://unpkg.com/coreless-lib?module';
|
|
30
|
+
|
|
31
|
+
// 1. Настройка: куда стучаться за данными?
|
|
32
|
+
const cms = new Coreless({
|
|
33
|
+
apiHost: 'http://localhost:8080', // Адрес вашего API
|
|
34
|
+
projectSlug: '123' // ID вашего проекта
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function start() {
|
|
38
|
+
try {
|
|
39
|
+
// 2. Получаем страницу "home"
|
|
40
|
+
const page = await cms.getPage('home');
|
|
41
|
+
|
|
42
|
+
// 3. Используем данные
|
|
43
|
+
// text() - для текста
|
|
44
|
+
document.getElementById('title').innerText = page.text('hero/title');
|
|
45
|
+
document.getElementById('desc').innerText = page.text('hero/description');
|
|
46
|
+
|
|
47
|
+
// image() - для картинок
|
|
48
|
+
const photo = page.image('photo1');
|
|
49
|
+
if (photo) {
|
|
50
|
+
const img = document.getElementById('image');
|
|
51
|
+
img.src = photo.url;
|
|
52
|
+
img.style.display = 'block';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.error('Ошибка:', error);
|
|
57
|
+
document.getElementById('title').innerText = 'Ошибка загрузки';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
start();
|
|
62
|
+
</script>
|
|
63
|
+
</body>
|
|
64
|
+
</html>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🔵 2. React (Client Side Rendering)
|
|
70
|
+
|
|
71
|
+
Если вы делаете стандартное React-приложение (Create React App, Vite).
|
|
72
|
+
|
|
73
|
+
**Установка:**
|
|
74
|
+
```bash
|
|
75
|
+
npm install coreless-lib
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Шаг 1. Настройте провайдер в `App.tsx` (или `main.tsx`)**
|
|
79
|
+
Это нужно сделать один раз на самом верху приложения.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { CorelessProvider, HttpContentFetcher } from 'coreless-lib';
|
|
83
|
+
|
|
84
|
+
// Создаем "связиста"
|
|
85
|
+
const fetcher = new HttpContentFetcher({
|
|
86
|
+
apiHost: 'http://localhost:8080',
|
|
87
|
+
projectSlug: '123'
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
function App() {
|
|
91
|
+
return (
|
|
92
|
+
// Оборачиваем все приложение
|
|
93
|
+
<CorelessProvider fetcher={fetcher}>
|
|
94
|
+
<HomePage />
|
|
95
|
+
</CorelessProvider>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Шаг 2. Используйте хук `useContent` в компонентах**
|
|
101
|
+
В любом компоненте внутри провайдера.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { useContent } from 'coreless-lib';
|
|
105
|
+
|
|
106
|
+
export const HomePage = () => {
|
|
107
|
+
// Просим данные страницы "home"
|
|
108
|
+
const content = useContent('home');
|
|
109
|
+
|
|
110
|
+
// Пока грузится — показываем спиннер
|
|
111
|
+
if (content.loading) return <div>Загрузка...</div>;
|
|
112
|
+
|
|
113
|
+
// Если ошибка — показываем сообщение
|
|
114
|
+
if (content.error) return <div>Ошибка: {content.error.message}</div>;
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<main>
|
|
118
|
+
{/* Текст */}
|
|
119
|
+
<h1>{content.text('header/title')}</h1>
|
|
120
|
+
|
|
121
|
+
{/* Картинка */}
|
|
122
|
+
<img src={content.image('header/logo')?.url} alt="Logo" />
|
|
123
|
+
|
|
124
|
+
{/* Список (например, блог) */}
|
|
125
|
+
{content.list('blog').map(post => (
|
|
126
|
+
<article key={post.slug}>
|
|
127
|
+
<h2>{post.data.title}</h2>
|
|
128
|
+
</article>
|
|
129
|
+
))}
|
|
130
|
+
</main>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## ⚫ 3. Next.js (Server Side Rendering / App Router)
|
|
138
|
+
|
|
139
|
+
Самый современный подход. Данные грузятся **на сервере**, а пользователь получает готовый HTML. Это супер быстро и полезно для SEO.
|
|
140
|
+
|
|
141
|
+
**Шаг 1. Создайте файл `lib/cms.ts`**
|
|
142
|
+
Чтобы не создавать подключение каждый раз, сделаем его один раз.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// lib/cms.ts
|
|
146
|
+
import { Coreless } from 'coreless-lib';
|
|
147
|
+
|
|
148
|
+
export const cms = new Coreless({
|
|
149
|
+
apiHost: process.env.CMS_API_HOST || 'http://localhost:8080',
|
|
150
|
+
projectSlug: '123'
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Шаг 2. Используйте на странице `app/page.tsx`**
|
|
155
|
+
Заметьте: тут **нет** `useEffect` или `loading`. Функция `async` сама всё подождет.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
// app/page.tsx
|
|
159
|
+
import { cms } from '@/lib/cms';
|
|
160
|
+
|
|
161
|
+
// Компонент должен быть async!
|
|
162
|
+
export default async function Page() {
|
|
163
|
+
// Грузим данные прямо на сервере
|
|
164
|
+
const page = await cms.getPage('home');
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<main>
|
|
168
|
+
<h1>{page.text('hero/title')}</h1>
|
|
169
|
+
<p>{page.text('hero/description')}</p>
|
|
170
|
+
|
|
171
|
+
{/* Таблица */}
|
|
172
|
+
{(() => {
|
|
173
|
+
const table = page.table('methods');
|
|
174
|
+
if (!table) return null;
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<table>
|
|
178
|
+
<thead>
|
|
179
|
+
<tr>
|
|
180
|
+
{table.fields.map(f => <th key={f.slug}>{f.name}</th>)}
|
|
181
|
+
</tr>
|
|
182
|
+
</thead>
|
|
183
|
+
<tbody>
|
|
184
|
+
{table.rows.map((row: any) => (
|
|
185
|
+
<tr key={row._id}>
|
|
186
|
+
<td>{row.title}</td>
|
|
187
|
+
<td>{row.description}</td>
|
|
188
|
+
</tr>
|
|
189
|
+
))}
|
|
190
|
+
</tbody>
|
|
191
|
+
</table>
|
|
192
|
+
);
|
|
193
|
+
})()}
|
|
194
|
+
</main>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 📚 Шпаргалка по методам (API Reference)
|
|
202
|
+
|
|
203
|
+
Какой метод выбрать?
|
|
204
|
+
|
|
205
|
+
| Тип данных | Метод | Что возвращает | Пример использования |
|
|
206
|
+
| :--- | :--- | :--- | :--- |
|
|
207
|
+
| **Текст** | `content.text('path')` | `string` | Заголовки, описания, имена |
|
|
208
|
+
| **Картинка** | `content.image('path')` | `{ url, width, height... }` | `<img src={...} />` |
|
|
209
|
+
| **Видео** | `content.video('path')` | `{ url, duration... }` | `<video src={...} />` |
|
|
210
|
+
| **Файл** | `content.file('path')` | `{ url, size, name... }` | Скачать PDF, отчет |
|
|
211
|
+
| **Список** | `content.list('path')` | `Array<ContentNode>` | Список новостей, меню, слайдер |
|
|
212
|
+
| **Таблица** | `content.table('path')` | `{ fields, rows }` | Таблицы цен, характеристик |
|
|
213
|
+
| **Сырые данные** | `content.field('path')` | `any` | Если нужно что-то специфичное (настройки, цвета) |
|
|
214
|
+
|
|
215
|
+
**Важно:**
|
|
216
|
+
* Все методы (кроме `list` и `table`) могут вернуть `undefined`, если данных нет.
|
|
217
|
+
* Используйте `?.` (optional chaining) в JS: `content.image('logo')?.url`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ContentNode } from '../core/types';
|
|
2
|
+
export interface ContentFetcher {
|
|
3
|
+
/**
|
|
4
|
+
* Загрузка страницы (дерева узлов) по slug проекта
|
|
5
|
+
*/
|
|
6
|
+
fetch(pageSlug: string): Promise<ContentNode>;
|
|
7
|
+
/**
|
|
8
|
+
* Обновление узла (PATCH) по его url
|
|
9
|
+
*/
|
|
10
|
+
update(url: string, data: Partial<ContentNode>): Promise<ContentNode>;
|
|
11
|
+
}
|
|
12
|
+
export interface HttpContentFetcherConfig {
|
|
13
|
+
/**
|
|
14
|
+
* Хост API, например 'http://localhost:8080'
|
|
15
|
+
*/
|
|
16
|
+
apiHost: string;
|
|
17
|
+
/**
|
|
18
|
+
* Идентификатор/slug проекта
|
|
19
|
+
*/
|
|
20
|
+
projectSlug: string;
|
|
21
|
+
/**
|
|
22
|
+
* Дополнительные заголовки запроса
|
|
23
|
+
*/
|
|
24
|
+
headers?: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export declare class HttpContentFetcher implements ContentFetcher {
|
|
27
|
+
private apiHost;
|
|
28
|
+
private projectSlug;
|
|
29
|
+
private headers;
|
|
30
|
+
constructor(config: HttpContentFetcherConfig);
|
|
31
|
+
fetch(pageSlug: string): Promise<ContentNode>;
|
|
32
|
+
update(url: string, data: Partial<ContentNode>): Promise<ContentNode>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
13
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
14
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
15
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
16
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
17
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
18
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
22
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
23
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
24
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
25
|
+
function step(op) {
|
|
26
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
27
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
28
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
29
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
30
|
+
switch (op[0]) {
|
|
31
|
+
case 0: case 1: t = op; break;
|
|
32
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
33
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
34
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
35
|
+
default:
|
|
36
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
37
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
38
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
39
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
40
|
+
if (t[2]) _.ops.pop();
|
|
41
|
+
_.trys.pop(); continue;
|
|
42
|
+
}
|
|
43
|
+
op = body.call(thisArg, _);
|
|
44
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
45
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var HttpContentFetcher = /** @class */ (function () {
|
|
49
|
+
function HttpContentFetcher(config) {
|
|
50
|
+
// Удаляем trailing slash если есть
|
|
51
|
+
this.apiHost = config.apiHost.replace(/\/$/, '');
|
|
52
|
+
this.projectSlug = config.projectSlug;
|
|
53
|
+
this.headers = config.headers || {};
|
|
54
|
+
}
|
|
55
|
+
HttpContentFetcher.prototype.fetch = function (pageSlug) {
|
|
56
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
57
|
+
var url, response;
|
|
58
|
+
return __generator(this, function (_a) {
|
|
59
|
+
switch (_a.label) {
|
|
60
|
+
case 0:
|
|
61
|
+
url = "".concat(this.apiHost, "/content/").concat(this.projectSlug, "/").concat(pageSlug);
|
|
62
|
+
return [4 /*yield*/, fetch(url, {
|
|
63
|
+
method: 'GET',
|
|
64
|
+
headers: __assign({ 'Content-Type': 'application/json' }, this.headers),
|
|
65
|
+
})];
|
|
66
|
+
case 1:
|
|
67
|
+
response = _a.sent();
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error("Failed to fetch content for page: ".concat(pageSlug, " (project: ").concat(this.projectSlug, ")"));
|
|
70
|
+
}
|
|
71
|
+
return [2 /*return*/, response.json()];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
HttpContentFetcher.prototype.update = function (url, data) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
78
|
+
var fullUrl, response;
|
|
79
|
+
return __generator(this, function (_a) {
|
|
80
|
+
switch (_a.label) {
|
|
81
|
+
case 0:
|
|
82
|
+
fullUrl = url.startsWith('http') ? url : "".concat(this.apiHost).concat(url);
|
|
83
|
+
return [4 /*yield*/, fetch(fullUrl, {
|
|
84
|
+
method: 'PATCH',
|
|
85
|
+
headers: __assign({ 'Content-Type': 'application/json' }, this.headers),
|
|
86
|
+
body: JSON.stringify(data),
|
|
87
|
+
})];
|
|
88
|
+
case 1:
|
|
89
|
+
response = _a.sent();
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error("Failed to update node at url: ".concat(fullUrl));
|
|
92
|
+
}
|
|
93
|
+
return [2 /*return*/, response.json()];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
return HttpContentFetcher;
|
|
99
|
+
}());
|
|
100
|
+
export { HttpContentFetcher };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ContentNode, ContentImage, ContentVideo, ContentFile, ContentTable } from './types';
|
|
2
|
+
export interface IContentClient {
|
|
3
|
+
node(path: string): ContentNode | null;
|
|
4
|
+
text(path: string): string | undefined;
|
|
5
|
+
list(path: string): ContentNode[];
|
|
6
|
+
image(path: string): ContentImage | undefined;
|
|
7
|
+
video(path: string): ContentVideo | undefined;
|
|
8
|
+
file(path: string): ContentFile | undefined;
|
|
9
|
+
table<T = Record<string, any>>(path: string): ContentTable<T> | undefined;
|
|
10
|
+
field<T>(path: string): T | undefined;
|
|
11
|
+
}
|
|
12
|
+
export declare class ContentClient implements IContentClient {
|
|
13
|
+
private root;
|
|
14
|
+
constructor(root: ContentNode);
|
|
15
|
+
/**
|
|
16
|
+
* Получить весь узел целиком по пути.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* content.node('header/logo')
|
|
20
|
+
* // возвращает { type: 'image', data: { url: '...' }, ... }
|
|
21
|
+
*/
|
|
22
|
+
node(path: string): ContentNode | null;
|
|
23
|
+
/**
|
|
24
|
+
* Получить текст из поля data.content.
|
|
25
|
+
* Удобно для заголовков, описаний и простых текстовых блоков.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Если в 'header/title' лежит { data: { content: "Привет" } }
|
|
29
|
+
* content.text('header/title')
|
|
30
|
+
* // вернет "Привет"
|
|
31
|
+
*/
|
|
32
|
+
text(path: string): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Получить список дочерних элементов.
|
|
35
|
+
* Используется для списков, галерей, меню.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // Если 'blog' содержит посты 'post-1', 'post-2'
|
|
39
|
+
* content.list('blog')
|
|
40
|
+
* // вернет массив [ContentNode, ContentNode]
|
|
41
|
+
*/
|
|
42
|
+
list(path: string): ContentNode[];
|
|
43
|
+
/**
|
|
44
|
+
* Получить данные узла как файл.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* content.file('downloads/report')
|
|
48
|
+
*/
|
|
49
|
+
file(path: string): ContentFile | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Получить данные узла как изображение.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* content.image('gallery/photo1')
|
|
55
|
+
*/
|
|
56
|
+
image(path: string): ContentImage | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Получить данные узла как видео.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* content.video('hero/bg-video')
|
|
62
|
+
*/
|
|
63
|
+
video(path: string): ContentVideo | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Получить данные таблицы.
|
|
66
|
+
* Возвращает описание полей и массив строк с данными.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const { fields, rows } = content.table('methods') || {};
|
|
70
|
+
*/
|
|
71
|
+
table<T = Record<string, any>>(path: string): ContentTable<T> | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* Получить конкретные данные узла с типизацией.
|
|
74
|
+
* Используется для картинок, сложных объектов и кастомных полей.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* content.field<{ url: string }>('header/logo')
|
|
78
|
+
* // вернет { url: '/img/logo.png' }
|
|
79
|
+
*/
|
|
80
|
+
field<T>(path: string): T | undefined;
|
|
81
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { resolveNode } from './utils';
|
|
13
|
+
var ContentClient = /** @class */ (function () {
|
|
14
|
+
function ContentClient(root) {
|
|
15
|
+
this.root = root;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Получить весь узел целиком по пути.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* content.node('header/logo')
|
|
22
|
+
* // возвращает { type: 'image', data: { url: '...' }, ... }
|
|
23
|
+
*/
|
|
24
|
+
ContentClient.prototype.node = function (path) {
|
|
25
|
+
return resolveNode(this.root, path);
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Получить текст из поля data.content.
|
|
29
|
+
* Удобно для заголовков, описаний и простых текстовых блоков.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // Если в 'header/title' лежит { data: { content: "Привет" } }
|
|
33
|
+
* content.text('header/title')
|
|
34
|
+
* // вернет "Привет"
|
|
35
|
+
*/
|
|
36
|
+
ContentClient.prototype.text = function (path) {
|
|
37
|
+
var _a;
|
|
38
|
+
var node = this.node(path);
|
|
39
|
+
if (!node)
|
|
40
|
+
return undefined;
|
|
41
|
+
return (_a = node.data) === null || _a === void 0 ? void 0 : _a.content;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Получить список дочерних элементов.
|
|
45
|
+
* Используется для списков, галерей, меню.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Если 'blog' содержит посты 'post-1', 'post-2'
|
|
49
|
+
* content.list('blog')
|
|
50
|
+
* // вернет массив [ContentNode, ContentNode]
|
|
51
|
+
*/
|
|
52
|
+
ContentClient.prototype.list = function (path) {
|
|
53
|
+
var node = this.node(path);
|
|
54
|
+
if (!node || !node.children)
|
|
55
|
+
return [];
|
|
56
|
+
return Object.values(node.children);
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Получить данные узла как файл.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* content.file('downloads/report')
|
|
63
|
+
*/
|
|
64
|
+
ContentClient.prototype.file = function (path) {
|
|
65
|
+
return this.field(path);
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Получить данные узла как изображение.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* content.image('gallery/photo1')
|
|
72
|
+
*/
|
|
73
|
+
ContentClient.prototype.image = function (path) {
|
|
74
|
+
return this.field(path);
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Получить данные узла как видео.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* content.video('hero/bg-video')
|
|
81
|
+
*/
|
|
82
|
+
ContentClient.prototype.video = function (path) {
|
|
83
|
+
return this.field(path);
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Получить данные таблицы.
|
|
87
|
+
* Возвращает описание полей и массив строк с данными.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* const { fields, rows } = content.table('methods') || {};
|
|
91
|
+
*/
|
|
92
|
+
ContentClient.prototype.table = function (path) {
|
|
93
|
+
var node = this.node(path);
|
|
94
|
+
if (!node)
|
|
95
|
+
return undefined;
|
|
96
|
+
// Сортируем строки по order_index
|
|
97
|
+
var rows = node.children
|
|
98
|
+
? Object.values(node.children)
|
|
99
|
+
.sort(function (a, b) { var _a, _b; return ((_a = a.order_index) !== null && _a !== void 0 ? _a : 0) - ((_b = b.order_index) !== null && _b !== void 0 ? _b : 0); })
|
|
100
|
+
.map(function (child) { return (__assign(__assign({}, child.data), { _id: child.id, _slug: child.slug })); })
|
|
101
|
+
: [];
|
|
102
|
+
return {
|
|
103
|
+
fields: (node.data.fields || []),
|
|
104
|
+
rows: rows
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Получить конкретные данные узла с типизацией.
|
|
109
|
+
* Используется для картинок, сложных объектов и кастомных полей.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* content.field<{ url: string }>('header/logo')
|
|
113
|
+
* // вернет { url: '/img/logo.png' }
|
|
114
|
+
*/
|
|
115
|
+
ContentClient.prototype.field = function (path) {
|
|
116
|
+
var node = this.node(path);
|
|
117
|
+
if (!node)
|
|
118
|
+
return undefined;
|
|
119
|
+
return node.data;
|
|
120
|
+
};
|
|
121
|
+
return ContentClient;
|
|
122
|
+
}());
|
|
123
|
+
export { ContentClient };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { HttpContentFetcherConfig } from '../api/client';
|
|
2
|
+
import { ContentClient, IContentClient } from './client';
|
|
3
|
+
import { ContentNode, ContentFile, ContentImage, ContentVideo, ContentTable } from './types';
|
|
4
|
+
export declare class Coreless implements IContentClient {
|
|
5
|
+
private fetcher;
|
|
6
|
+
private currentClient;
|
|
7
|
+
constructor(config: HttpContentFetcherConfig);
|
|
8
|
+
/**
|
|
9
|
+
* Загружает страницу и устанавливает ее как текущую для работы
|
|
10
|
+
*/
|
|
11
|
+
load(pageSlug: string): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Загружает страницу и возвращает готовый клиент для работы с ней.
|
|
14
|
+
*/
|
|
15
|
+
getPage(pageSlug: string): Promise<ContentClient>;
|
|
16
|
+
/**
|
|
17
|
+
* Проверяет, загружен ли контент
|
|
18
|
+
*/
|
|
19
|
+
private ensureLoaded;
|
|
20
|
+
node(path: string): ContentNode | null;
|
|
21
|
+
text(path: string): string | undefined;
|
|
22
|
+
list(path: string): ContentNode[];
|
|
23
|
+
image(path: string): ContentImage | undefined;
|
|
24
|
+
video(path: string): ContentVideo | undefined;
|
|
25
|
+
file(path: string): ContentFile | undefined;
|
|
26
|
+
table<T = Record<string, any>>(path: string): ContentTable<T> | undefined;
|
|
27
|
+
field<T>(path: string): T | undefined;
|
|
28
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
12
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { HttpContentFetcher } from '../api/client';
|
|
38
|
+
import { ContentClient } from './client';
|
|
39
|
+
var Coreless = /** @class */ (function () {
|
|
40
|
+
function Coreless(config) {
|
|
41
|
+
this.currentClient = null;
|
|
42
|
+
this.fetcher = new HttpContentFetcher(config);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Загружает страницу и устанавливает ее как текущую для работы
|
|
46
|
+
*/
|
|
47
|
+
Coreless.prototype.load = function (pageSlug) {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
49
|
+
var rootNode;
|
|
50
|
+
return __generator(this, function (_a) {
|
|
51
|
+
switch (_a.label) {
|
|
52
|
+
case 0: return [4 /*yield*/, this.fetcher.fetch(pageSlug)];
|
|
53
|
+
case 1:
|
|
54
|
+
rootNode = _a.sent();
|
|
55
|
+
this.currentClient = new ContentClient(rootNode);
|
|
56
|
+
return [2 /*return*/];
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Загружает страницу и возвращает готовый клиент для работы с ней.
|
|
63
|
+
*/
|
|
64
|
+
Coreless.prototype.getPage = function (pageSlug) {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
66
|
+
var rootNode;
|
|
67
|
+
return __generator(this, function (_a) {
|
|
68
|
+
switch (_a.label) {
|
|
69
|
+
case 0: return [4 /*yield*/, this.fetcher.fetch(pageSlug)];
|
|
70
|
+
case 1:
|
|
71
|
+
rootNode = _a.sent();
|
|
72
|
+
return [2 /*return*/, new ContentClient(rootNode)];
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Проверяет, загружен ли контент
|
|
79
|
+
*/
|
|
80
|
+
Coreless.prototype.ensureLoaded = function () {
|
|
81
|
+
if (!this.currentClient) {
|
|
82
|
+
throw new Error('Content not loaded. Call load(pageSlug) first or use getPage(pageSlug).');
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// Делегируем методы ContentClient
|
|
86
|
+
Coreless.prototype.node = function (path) {
|
|
87
|
+
this.ensureLoaded();
|
|
88
|
+
return this.currentClient.node(path);
|
|
89
|
+
};
|
|
90
|
+
Coreless.prototype.text = function (path) {
|
|
91
|
+
this.ensureLoaded();
|
|
92
|
+
return this.currentClient.text(path);
|
|
93
|
+
};
|
|
94
|
+
Coreless.prototype.list = function (path) {
|
|
95
|
+
this.ensureLoaded();
|
|
96
|
+
return this.currentClient.list(path);
|
|
97
|
+
};
|
|
98
|
+
Coreless.prototype.image = function (path) {
|
|
99
|
+
this.ensureLoaded();
|
|
100
|
+
return this.currentClient.image(path);
|
|
101
|
+
};
|
|
102
|
+
Coreless.prototype.video = function (path) {
|
|
103
|
+
this.ensureLoaded();
|
|
104
|
+
return this.currentClient.video(path);
|
|
105
|
+
};
|
|
106
|
+
Coreless.prototype.file = function (path) {
|
|
107
|
+
this.ensureLoaded();
|
|
108
|
+
return this.currentClient.file(path);
|
|
109
|
+
};
|
|
110
|
+
Coreless.prototype.table = function (path) {
|
|
111
|
+
this.ensureLoaded();
|
|
112
|
+
return this.currentClient.table(path);
|
|
113
|
+
};
|
|
114
|
+
Coreless.prototype.field = function (path) {
|
|
115
|
+
this.ensureLoaded();
|
|
116
|
+
return this.currentClient.field(path);
|
|
117
|
+
};
|
|
118
|
+
return Coreless;
|
|
119
|
+
}());
|
|
120
|
+
export { Coreless };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type ContentNode = {
|
|
2
|
+
id: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
type: string;
|
|
5
|
+
data: Record<string, any>;
|
|
6
|
+
url: string;
|
|
7
|
+
order_index?: number;
|
|
8
|
+
children?: Record<string, ContentNode>;
|
|
9
|
+
};
|
|
10
|
+
export interface TableField {
|
|
11
|
+
name: string;
|
|
12
|
+
slug: string;
|
|
13
|
+
default?: any;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
field_type: string;
|
|
16
|
+
hide_in_table?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ContentTable<T = Record<string, any>> {
|
|
19
|
+
fields: TableField[];
|
|
20
|
+
rows: T[];
|
|
21
|
+
}
|
|
22
|
+
export interface ContentFile {
|
|
23
|
+
url: string;
|
|
24
|
+
size?: number;
|
|
25
|
+
filename?: string;
|
|
26
|
+
content_type?: string;
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
export type ContentImage = ContentFile;
|
|
32
|
+
export type ContentVideo = ContentFile;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ContentNode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Резолвит узел по строковому пути.
|
|
4
|
+
* @param root Корневой узел, от которого начинается поиск
|
|
5
|
+
* @param path Путь вида "blog/post-1"
|
|
6
|
+
* @returns Найденный узел или null
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveNode(root: ContentNode, path: string): ContentNode | null;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Резолвит узел по строковому пути.
|
|
3
|
+
* @param root Корневой узел, от которого начинается поиск
|
|
4
|
+
* @param path Путь вида "blog/post-1"
|
|
5
|
+
* @returns Найденный узел или null
|
|
6
|
+
*/
|
|
7
|
+
export function resolveNode(root, path) {
|
|
8
|
+
if (!path || path === '')
|
|
9
|
+
return root;
|
|
10
|
+
var segments = path.split('/').filter(Boolean);
|
|
11
|
+
var current = root;
|
|
12
|
+
for (var _i = 0, segments_1 = segments; _i < segments_1.length; _i++) {
|
|
13
|
+
var segment = segments_1[_i];
|
|
14
|
+
if (!current.children || !current.children[segment]) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
current = current.children[segment];
|
|
18
|
+
}
|
|
19
|
+
return current;
|
|
20
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ContentFetcher } from '../api/client';
|
|
3
|
+
interface CorelessContextValue {
|
|
4
|
+
fetcher: ContentFetcher;
|
|
5
|
+
}
|
|
6
|
+
export declare const useCorelessContext: () => CorelessContextValue;
|
|
7
|
+
interface CorelessProviderProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
fetcher: ContentFetcher;
|
|
10
|
+
}
|
|
11
|
+
export declare const CorelessProvider: React.FC<CorelessProviderProps>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
var CorelessContext = createContext(null);
|
|
4
|
+
export var useCorelessContext = function () {
|
|
5
|
+
var context = useContext(CorelessContext);
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error('useCorelessContext must be used within a CorelessProvider');
|
|
8
|
+
}
|
|
9
|
+
return context;
|
|
10
|
+
};
|
|
11
|
+
export var CorelessProvider = function (_a) {
|
|
12
|
+
var children = _a.children, fetcher = _a.fetcher;
|
|
13
|
+
return (_jsx(CorelessContext.Provider, { value: { fetcher: fetcher }, children: children }));
|
|
14
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IContentClient } from '../core/client';
|
|
2
|
+
export interface UseContentResult extends IContentClient {
|
|
3
|
+
loading: boolean;
|
|
4
|
+
error: Error | null;
|
|
5
|
+
refresh: () => Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Хук для получения контента страницы.
|
|
9
|
+
* Возвращает интерфейс ContentClient, который безопасен для использования даже во время загрузки
|
|
10
|
+
* (методы будут возвращать undefined/пустые массивы).
|
|
11
|
+
*/
|
|
12
|
+
export declare function useContent(slug: string): UseContentResult;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
12
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
17
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { useEffect, useState, useMemo } from 'react';
|
|
38
|
+
import { useCorelessContext } from './context';
|
|
39
|
+
import { ContentClient } from '../core/client';
|
|
40
|
+
/**
|
|
41
|
+
* Хук для получения контента страницы.
|
|
42
|
+
* Возвращает интерфейс ContentClient, который безопасен для использования даже во время загрузки
|
|
43
|
+
* (методы будут возвращать undefined/пустые массивы).
|
|
44
|
+
*/
|
|
45
|
+
export function useContent(slug) {
|
|
46
|
+
var _this = this;
|
|
47
|
+
var fetcher = useCorelessContext().fetcher;
|
|
48
|
+
var _a = useState(null), data = _a[0], setData = _a[1];
|
|
49
|
+
var _b = useState(true), loading = _b[0], setLoading = _b[1];
|
|
50
|
+
var _c = useState(null), error = _c[0], setError = _c[1];
|
|
51
|
+
var load = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
52
|
+
var result, err_1;
|
|
53
|
+
return __generator(this, function (_a) {
|
|
54
|
+
switch (_a.label) {
|
|
55
|
+
case 0:
|
|
56
|
+
setLoading(true);
|
|
57
|
+
setError(null);
|
|
58
|
+
_a.label = 1;
|
|
59
|
+
case 1:
|
|
60
|
+
_a.trys.push([1, 3, 4, 5]);
|
|
61
|
+
return [4 /*yield*/, fetcher.fetch(slug)];
|
|
62
|
+
case 2:
|
|
63
|
+
result = _a.sent();
|
|
64
|
+
setData(result);
|
|
65
|
+
return [3 /*break*/, 5];
|
|
66
|
+
case 3:
|
|
67
|
+
err_1 = _a.sent();
|
|
68
|
+
setError(err_1 instanceof Error ? err_1 : new Error('Unknown error'));
|
|
69
|
+
return [3 /*break*/, 5];
|
|
70
|
+
case 4:
|
|
71
|
+
setLoading(false);
|
|
72
|
+
return [7 /*endfinally*/];
|
|
73
|
+
case 5: return [2 /*return*/];
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}); };
|
|
77
|
+
useEffect(function () {
|
|
78
|
+
load();
|
|
79
|
+
}, [slug, fetcher]);
|
|
80
|
+
var client = useMemo(function () {
|
|
81
|
+
var rootNode = data || {
|
|
82
|
+
id: '',
|
|
83
|
+
slug: '',
|
|
84
|
+
type: 'empty',
|
|
85
|
+
url: '',
|
|
86
|
+
data: {},
|
|
87
|
+
children: {}
|
|
88
|
+
};
|
|
89
|
+
return new ContentClient(rootNode);
|
|
90
|
+
}, [data]);
|
|
91
|
+
return {
|
|
92
|
+
loading: loading,
|
|
93
|
+
error: error,
|
|
94
|
+
refresh: load,
|
|
95
|
+
node: function (path) { return client.node(path); },
|
|
96
|
+
text: function (path) { return client.text(path); },
|
|
97
|
+
list: function (path) { return client.list(path); },
|
|
98
|
+
image: function (path) { return client.image(path); },
|
|
99
|
+
video: function (path) { return client.video(path); },
|
|
100
|
+
file: function (path) { return client.file(path); },
|
|
101
|
+
table: function (path) { return client.table(path); },
|
|
102
|
+
field: function (path) { return client.field(path); },
|
|
103
|
+
};
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coreless-lib",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Headless CMS Framework Agnostic Library",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"react": ">=16.8.0",
|
|
22
|
+
"react-dom": ">=16.8.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependenciesMeta": {
|
|
25
|
+
"react": {
|
|
26
|
+
"optional": true
|
|
27
|
+
},
|
|
28
|
+
"react-dom": {
|
|
29
|
+
"optional": true
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/react": "^18.2.0",
|
|
34
|
+
"@types/react-dom": "^18.2.0",
|
|
35
|
+
"react": "^18.2.0",
|
|
36
|
+
"react-dom": "^18.2.0",
|
|
37
|
+
"typescript": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md"
|
|
42
|
+
]
|
|
43
|
+
}
|