@webx-ui/module-blog 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ import { Messages } from '@webx-ui/module-admin';
2
+ /**
3
+ * The English this package speaks on its own, before the server's dictionary arrives, or
4
+ * without one. The server ships the same lines in ten languages under `webx-blog::` and
5
+ * overrides these; `messages.test.ts` keeps the two sets of keys equal.
6
+ */
7
+ export declare const blogMessages: Record<string, Messages>;
@@ -0,0 +1,17 @@
1
+ import { AdminModule } from '@webx-ui/module-admin';
2
+ export interface BlogOptions {
3
+ /** Where the blog lives inside the panel. The three sections sit under it. */
4
+ path?: string;
5
+ }
6
+ /**
7
+ * The blog as sections of the panel: articles, rubrics and tags.
8
+ *
9
+ * Three modules rather than one, because the navigation is one entry per module and the blog
10
+ * wants three of them; the server puts all three in the `blog` group, which is what draws them
11
+ * under one heading. Each is returned separately so a panel can leave one out.
12
+ *
13
+ * A section whose server half is not installed never appears — the entry is built from the
14
+ * manifest — and so does one whose front end is missing. That is what makes it safe to return
15
+ * all three from one call: a panel that installed only part of the blog gets only that part.
16
+ */
17
+ export declare function blog(options?: BlogOptions): AdminModule[];
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .wx-article-address[data-v-2ee0dcfe]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-address__value[data-v-2ee0dcfe]{word-break:break-all}.wx-article-editor[data-v-9bf13356]{display:flex;flex-direction:column;gap:var(--wx-gap, var(--wx-space-16));height:100%;min-height:0;container-type:inline-size}.wx-article-editor__ghost[data-v-9bf13356]{max-width:420px}.wx-article-editor__head[data-v-9bf13356]{flex:none;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:end;gap:var(--wx-space-12);padding-block-end:var(--wx-space-12);border-block-end:1px solid var(--wx-border-default)}.wx-article-editor__back[data-v-9bf13356]{align-self:start}.wx-article-editor__id[data-v-9bf13356]{display:flex;flex-direction:column;gap:var(--wx-space-4);min-width:0}.wx-article-editor__name[data-v-9bf13356]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap;min-width:0}.wx-article-editor__title[data-v-9bf13356]{font-size:var(--wx-font-size-xl);font-weight:var(--wx-font-weight-bold);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-article-editor__actions[data-v-9bf13356]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap}.wx-article-editor__screen[data-v-9bf13356]{display:flex;flex-direction:column;flex:1;min-height:0}.wx-article-editor__screen[data-v-9bf13356] .wx-screen-host,.wx-article-editor__screen[data-v-9bf13356] .wx-screen,.wx-article-editor__screen[data-v-9bf13356] .wx-tabs,.wx-article-editor__screen[data-v-9bf13356] .wx-tabs__layout,.wx-article-editor__screen[data-v-9bf13356] .wx-tabs__panels,.wx-article-editor__screen[data-v-9bf13356] .wx-tab:not([hidden]){display:flex;flex-direction:column;flex:1;min-height:0}.wx-article-editor[data-v-9bf13356]:has(.wx-tab:not([hidden]) .wx-blocks-host.is-fill){height:var(--wx-fill-height)}.wx-article-editor__screen[data-v-9bf13356] .wx-tab:not([hidden]):has(.wx-blocks-host.is-fill){overflow:auto}.wx-article-editor__screen[data-v-9bf13356] .wx-form-item:has(.wx-blocks-host.is-fill),.wx-article-editor__screen[data-v-9bf13356] .wx-form-item:has(.wx-blocks-host.is-fill) .wx-form-item__control{display:flex;flex-direction:column;flex:1;min-height:0}.wx-article-history[data-v-0a997938]{display:flex;flex-direction:column;border:1px solid var(--wx-border-default);border-radius:var(--wx-radius-md);background:var(--wx-bg-surface);overflow:hidden}.wx-article-history__ghost[data-v-0a997938]{padding:var(--wx-space-10) var(--wx-space-12)}.wx-article-history__row[data-v-0a997938]{display:flex;align-items:center;gap:var(--wx-space-12);padding:var(--wx-space-10) var(--wx-space-12);border-block-end:1px solid var(--wx-border-default);flex-wrap:wrap}.wx-article-history__row[data-v-0a997938]:last-child{border-block-end:0}.wx-article-history__number[data-v-0a997938]{width:40px;font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-sm);color:var(--wx-text-muted)}.wx-article-history__who[data-v-0a997938]{display:flex;flex-direction:column;flex:1;min-width:160px}.wx-article-related[data-v-164e86fe]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-related__name[data-v-164e86fe]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-article-rubrics[data-v-db8c5bc8]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-rubrics__name[data-v-db8c5bc8]{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wx-article-tags[data-v-ce095e37]{display:flex;flex-direction:column;gap:var(--wx-space-8)}.wx-article-tags__chips[data-v-ce095e37]{display:flex;flex-wrap:wrap;gap:var(--wx-space-6)}.wx-article-tags__chip[data-v-ce095e37]{display:inline-flex;align-items:center;gap:var(--wx-space-4)}.wx-article-tags__pick[data-v-ce095e37]{display:flex;align-items:center;gap:var(--wx-space-8);flex-wrap:wrap}.wx-article-tags__pick[data-v-ce095e37]>.wx-select{flex:1 1 220px;min-width:0}.wx-articles[data-v-0bea5efe]{min-width:0}.wx-articles__filters[data-v-0bea5efe]{display:flex;flex-wrap:wrap;gap:var(--wx-space-8);min-width:0}.wx-articles__filters[data-v-0bea5efe]>.wx-select{flex:1 1 110px;max-width:150px}.wx-articles__cover[data-v-0bea5efe]{display:flex;align-items:center;justify-content:center;width:44px;height:32px;overflow:hidden;border-radius:var(--wx-radius-sm);background:var(--wx-bg-subtle);color:var(--wx-text-muted)}.wx-articles__cover.is-card[data-v-0bea5efe]{width:76px;height:58px}.wx-articles__cover.is-empty[data-v-0bea5efe]{border:1px dashed var(--wx-border-color);background:var(--wx-bg-base)}.wx-articles__cover img[data-v-0bea5efe]{width:100%;height:100%;min-width:0;object-fit:cover}.wx-articles__name[data-v-0bea5efe]{display:flex;align-items:center;gap:var(--wx-space-8);min-width:0}.wx-articles__name-text[data-v-0bea5efe]{display:flex;flex-direction:column;min-width:0}.wx-articles__pin[data-v-0bea5efe]{flex:none;color:var(--wx-color-primary)}.wx-articles__title[data-v-0bea5efe]{overflow:hidden;font-weight:var(--wx-font-weight-medium);white-space:nowrap;text-overflow:ellipsis}.wx-articles__address[data-v-0bea5efe]{overflow:hidden;color:var(--wx-text-muted);font-family:var(--wx-font-family-mono);font-size:var(--wx-font-size-xs);white-space:nowrap;text-overflow:ellipsis}.wx-articles__chips[data-v-0bea5efe],.wx-articles__state[data-v-0bea5efe]{display:flex;align-items:center;gap:var(--wx-space-4);min-width:0;overflow:hidden}.wx-articles[data-v-0bea5efe] .wx-articles__when{white-space:nowrap}.wx-articles__card[data-v-0bea5efe]{display:flex;align-items:flex-start;gap:var(--wx-space-10);min-width:0}.wx-articles__card[data-v-0bea5efe]>.wx-row-menu{flex:none}.wx-articles__card-body[data-v-0bea5efe]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-6);min-width:0}.wx-articles__card-title[data-v-0bea5efe]{display:flex;align-items:flex-start;gap:var(--wx-space-6);font-weight:var(--wx-font-weight-semibold);line-height:var(--wx-font-line-height-tight)}.wx-articles__card-title span[data-v-0bea5efe]{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2}.wx-articles__card-meta[data-v-0bea5efe]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-6);min-width:0}.wx-rubric-form[data-v-ace40cf4]{display:flex;flex-direction:column;height:100%;min-height:0}.wx-rubric-form__head[data-v-ace40cf4]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-10);padding:var(--wx-space-12) var(--wx-space-16);border-block-end:1px solid var(--wx-border-color)}.wx-rubric-form__body[data-v-ace40cf4]{display:flex;flex:1 1 auto;flex-direction:column;gap:var(--wx-space-16);min-height:0;padding:var(--wx-space-16);overflow-y:auto}.wx-rubric-form__row[data-v-ace40cf4]{display:flex;flex-wrap:wrap;gap:var(--wx-space-16);align-items:flex-start}.wx-rubric-form__title[data-v-ace40cf4]{flex:1 1 260px;min-width:0}.wx-rubric-form__slug[data-v-ace40cf4]{flex:1 1 220px;min-width:0}.wx-rubric-form__cover[data-v-ace40cf4]{flex:0 1 260px;min-width:0}.wx-rubric-form__side[data-v-ace40cf4]{display:flex;flex:1 1 200px;flex-direction:column;gap:var(--wx-space-6);min-width:0}.wx-rubric-form__seo[data-v-ace40cf4]{display:flex;flex-direction:column;align-items:flex-start;gap:var(--wx-space-4);padding:var(--wx-space-12);border-radius:var(--wx-radius-md);background:var(--wx-bg-subtle)}.wx-rubric-form__seo[data-v-ace40cf4]:has(.wx-seo){align-self:stretch;padding:0;background:none}.wx-rubric-form__foot[data-v-ace40cf4]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-8);padding:var(--wx-space-12) var(--wx-space-16);border-block-start:1px solid var(--wx-border-color)}.wx-rubric-form__spacer[data-v-ace40cf4]{flex:1 1 auto}.wx-rubrics{height:100%;min-height:0}.wx-rubrics>.wx-card__body{height:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;border-radius:inherit}.wx-rubrics__panes{flex:1 1 auto;min-height:0}.wx-rubrics__loading{padding:var(--wx-space-16)}.wx-rubrics__list{padding:var(--wx-space-8) var(--wx-space-12)}.wx-rubrics__list.is-plain .wx-sortable-list__row{padding-inline:var(--wx-space-8)}.wx-rubrics__list.is-plain .wx-sortable-list__head{margin-block-end:var(--wx-space-6)}.wx-rubric-row{display:flex;flex-direction:column;gap:var(--wx-space-2);align-items:flex-start;flex:1 1 auto;min-width:0;padding:0;border:0;background:none;font:inherit;color:inherit;text-align:start;cursor:pointer}.wx-rubric-row__name{display:flex;align-items:center;gap:var(--wx-space-6);min-width:0;max-width:100%}.wx-rubric-row.is-hidden{color:var(--wx-text-muted)}.wx-rubrics__list .wx-sortable-list__row:has(.wx-rubric-row.is-current){background:var(--wx-bg-subtle);border-radius:var(--wx-radius-sm)}.wx-rubric-row.is-current{color:var(--wx-color-primary)}.wx-rubric-row__count{flex:none;font-variant-numeric:tabular-nums}.wx-tag-merge[data-v-aa68b3a8]{display:flex;flex-direction:column;gap:var(--wx-space-12)}.wx-tag-merge__chips[data-v-aa68b3a8]{display:flex;flex-wrap:wrap;align-items:center;gap:var(--wx-space-6);min-width:0}.wx-tag-merge__redirect[data-v-aa68b3a8]{display:flex;flex-direction:column;gap:var(--wx-space-2)}.wx-tags[data-v-7ccee45f]{display:flex;flex-direction:column;gap:var(--wx-space-12);min-width:0}.wx-tags__rename[data-v-7ccee45f]{display:block;min-width:0}.wx-tags__sort[data-v-7ccee45f]{display:flex;gap:var(--wx-space-4);min-width:0}.wx-tags__name[data-v-7ccee45f]{max-width:100%;overflow:hidden;padding:0;border:0;background:none;color:inherit;font:inherit;text-align:start;text-overflow:ellipsis;white-space:nowrap;cursor:text}.wx-tags__name[data-v-7ccee45f]:hover{text-decoration:underline;text-decoration-style:dotted}.wx-tags__bar[data-v-7ccee45f]{flex-wrap:wrap}.wx-tags__bar-spacer[data-v-7ccee45f]{flex:1 1 auto}
@@ -0,0 +1,273 @@
1
+ import { LocalizedValue, Paginated } from '@webx-ui/core';
2
+ import { ScreenModel } from '@webx-ui/schema';
3
+ /**
4
+ * Never published · waiting for its day · on the site · on the site with edits waiting · taken
5
+ * off it (§10).
6
+ *
7
+ * The last two are the pair worth keeping apart. "Taken off" and "draft" both mean the article
8
+ * is not on the site, and only its history tells them apart — "draft" on something that was
9
+ * live at breakfast is a lie.
10
+ */
11
+ export type ArticleStatus = 'draft' | 'scheduled' | 'published' | 'modified' | 'unpublished';
12
+ /**
13
+ * A rubric or a tag as a row names one, and the same shape a filter is set from — which is why
14
+ * the author of an article is not one of these but an {@link ArticleAuthor}. A person has a
15
+ * name; a rubric has a title and an address, and calling them the same thing would cost the
16
+ * difference the first time somebody wanted the address.
17
+ */
18
+ export interface BlogNamed {
19
+ id: number;
20
+ title: string;
21
+ /** A rubric and a tag have one; a filter option does not carry it. */
22
+ slug?: string;
23
+ }
24
+ export interface ArticleAuthor {
25
+ id: number;
26
+ name: string;
27
+ }
28
+ /**
29
+ * A cover, with the two addresses `module-media` distinguishes.
30
+ *
31
+ * `url` is what the picture is shown by; `source` — the same bytes off the panel's own origin —
32
+ * is what the image editor needs. Neither is stored: both are worked out from the file on every
33
+ * read, because a signed link expires and a cropped picture keeps its key.
34
+ */
35
+ export interface ArticleCover {
36
+ id: number;
37
+ path: string;
38
+ url: string;
39
+ thumb: string | null;
40
+ }
41
+ /**
42
+ * One article as the section lists it.
43
+ *
44
+ * The title is the draft's and the address is the registry's, which is why a renamed article
45
+ * shows a new title beside its old address: that is what the site is serving until it is
46
+ * published.
47
+ */
48
+ export interface ArticleRow {
49
+ id: number;
50
+ title: string;
51
+ slug: string;
52
+ lead: string;
53
+ /** `null` — this article names no address in the language the panel is open in (§9). */
54
+ path: string | null;
55
+ url: string | null;
56
+ status: ArticleStatus;
57
+ pinned: boolean;
58
+ published_at: string | null;
59
+ updated_at: string | null;
60
+ deleted_at: string | null;
61
+ author: ArticleAuthor | null;
62
+ cover: ArticleCover | null;
63
+ /** In the order they were dragged into: the first is the main one (§2.6). */
64
+ rubrics: BlogNamed[];
65
+ tags: BlogNamed[];
66
+ /** What this article was when it was read, so a save can be refused rather than written over. */
67
+ revision: string;
68
+ [key: string]: unknown;
69
+ }
70
+ /** What the dropdowns over the list can be set to — it travels with the rows (§10). */
71
+ export interface ArticleFilters {
72
+ rubrics: BlogNamed[];
73
+ tags: BlogNamed[];
74
+ authors: BlogNamed[];
75
+ }
76
+ /**
77
+ * A page of articles, with the filters beside it.
78
+ *
79
+ * Flat, the way `WxTable` reads a paginator: the server answers with `meta` around the numbers,
80
+ * Laravel's own shape, and the API client spreads it before the table ever sees it.
81
+ */
82
+ export interface ArticlesPage extends Paginated<ArticleRow> {
83
+ filters: ArticleFilters;
84
+ }
85
+ export interface ArticleQuery {
86
+ /** Title or address, in any language the site has. */
87
+ q?: string;
88
+ rubric?: number | null;
89
+ tag?: number | null;
90
+ author?: number | null;
91
+ status?: ArticleStatus | '';
92
+ /** `-published_at` for newest first; left out, the list is pinned first and then by date. */
93
+ sort?: string | null;
94
+ /** What was deleted. The only way back to an article in the bin is through this list. */
95
+ trashed?: boolean;
96
+ page?: number;
97
+ per_page?: number;
98
+ }
99
+ /** What starting an article carries: a title, and an address if somebody typed one. */
100
+ export interface ArticleInput {
101
+ title?: string;
102
+ slug?: string;
103
+ }
104
+ /**
105
+ * What a save carries: the values of the described screen, and what was read.
106
+ *
107
+ * The values are keyed by field name — `title`, `slug`, `blocks`, `rubrics`, `seo` — because
108
+ * the form is a description (`blog.article-form`) and the server checks what comes in against
109
+ * that same description. Which is what lets `module-seo` put its card on the editor without
110
+ * either half of this module hearing about it.
111
+ *
112
+ * Only the fields that travelled are touched, so saving one tab cannot empty another.
113
+ */
114
+ export interface ArticleSave {
115
+ values: ScreenModel;
116
+ /** Left out, the save goes through: a request that read nothing has no editor to surprise. */
117
+ revision?: string;
118
+ }
119
+ /** One publication in the history. The payload is not in it — see {@link BlogApi.versions}. */
120
+ export interface ArticleVersion {
121
+ number: number;
122
+ created_at: string | null;
123
+ author: string | null;
124
+ source: string;
125
+ comment: string | null;
126
+ is_pinned: boolean;
127
+ }
128
+ /** An id with something to draw beside it: a rubric, an administrator, another article. */
129
+ export interface ArticleOption {
130
+ id: number;
131
+ title: string;
132
+ }
133
+ /** A tag as the article form offers it, with the number of articles filed under it (§10). */
134
+ export interface BlogTag {
135
+ id: number;
136
+ title: string;
137
+ slug: string;
138
+ articles_count: number;
139
+ }
140
+ /**
141
+ * One article as its editor opens it: the record, the values of the screen, and the few things
142
+ * around them that the description cannot carry.
143
+ *
144
+ * The preview link is minted per response rather than stored: it is signed and short-lived, and
145
+ * a form left open all morning would otherwise offer a link that expired before lunch.
146
+ */
147
+ export interface ArticleDetail {
148
+ article: ArticleRow;
149
+ values: ScreenModel;
150
+ /** The article as it was read, to be handed back with the next save. */
151
+ revision: string;
152
+ /** The first segment of every blog address — the same in every language (§2.2). */
153
+ prefix: string;
154
+ preview_url: string | null;
155
+ options: {
156
+ rubrics: ArticleOption[];
157
+ authors: ArticleOption[];
158
+ };
159
+ /** Titles for the ids in `values.related`; nothing else carries them. */
160
+ related: ArticleOption[];
161
+ }
162
+ /** The body of a 409: somebody else wrote while this editor was typing. */
163
+ export interface ArticleConflict {
164
+ message: string;
165
+ data: ArticleDetail;
166
+ }
167
+ /**
168
+ * One rubric, as both halves of its screen need it (§10).
169
+ *
170
+ * The left column reads `name`, `path` and `articles_count`; the form beside it edits the same
171
+ * record in every language at once, which is why the three translated fields are maps and the
172
+ * name is worked out beside them. A rubric named in one language and not in another is still a
173
+ * row somebody has to be able to click.
174
+ */
175
+ export interface RubricRow {
176
+ id: number;
177
+ /** What to show: the title of this language, its address, or its number — in that order. */
178
+ name: string;
179
+ title: LocalizedValue;
180
+ slug: LocalizedValue;
181
+ lead: LocalizedValue;
182
+ /** `null` — this rubric names no address in the language the panel is open in (§9). */
183
+ path: string | null;
184
+ url: string | null;
185
+ cover: ArticleCover | null;
186
+ is_visible: boolean;
187
+ position: number;
188
+ /** Why the delete button is out of reach, and what its explanation says (§6). */
189
+ articles_count: number;
190
+ /** What the rubric says about its own page, over whatever the SEO rules say (§12). */
191
+ seo: Record<string, unknown>;
192
+ }
193
+ /** What a rubric's form sends. Only the fields that travelled are touched. */
194
+ export interface RubricInput {
195
+ title?: LocalizedValue | string;
196
+ slug?: LocalizedValue | string;
197
+ lead?: LocalizedValue | string;
198
+ cover?: {
199
+ path: string;
200
+ } | null;
201
+ is_visible?: boolean;
202
+ seo?: Record<string, unknown> | null;
203
+ }
204
+ /**
205
+ * Whether a tag page is in the index, and why (§12).
206
+ *
207
+ * Three answers and not two. A tag is out of the index by default; an editor opens it either by
208
+ * clearing the flag or by writing a rule in the SEO section for its address — and the second one
209
+ * has to be visible here, or whoever wrote that rule spends an afternoon looking at a row that
210
+ * says `noindex` and disagreeing with it.
211
+ */
212
+ export type TagIndexing = 'open' | 'rule' | 'noindex';
213
+ /** One tag as its screen lists it — and as the article form's dropdown offers it. */
214
+ export interface TagRow {
215
+ id: number;
216
+ /** The word in the language the panel is open in, falling back on the address. */
217
+ title: string;
218
+ /** Every language of it, for a rename that must not touch the others. */
219
+ titles: LocalizedValue;
220
+ slug: string;
221
+ /** The address a SEO rule for this page would be written for: language prefix and all. */
222
+ path: string | null;
223
+ url: string | null;
224
+ noindex: boolean;
225
+ indexing: TagIndexing;
226
+ articles_count: number;
227
+ [key: string]: unknown;
228
+ }
229
+ /** What the pills over the list count, before any of them is pressed. */
230
+ export interface TagCounts {
231
+ total: number;
232
+ empty: number;
233
+ noindex: number;
234
+ }
235
+ export interface TagQuery {
236
+ q?: string;
237
+ /** Tags nothing is filed under — the half of the screen that exists to be emptied. */
238
+ empty?: boolean;
239
+ /** Out of the index by the same rule the rendered page follows, rules included (§12). */
240
+ noindex?: boolean;
241
+ sort?: 'articles' | 'name';
242
+ page?: number;
243
+ per_page?: number;
244
+ }
245
+ export interface TagsPage extends Paginated<TagRow> {
246
+ filters: TagCounts;
247
+ }
248
+ /** What a rename or the switch on the index sends; absent means "leave it alone". */
249
+ export interface TagInput {
250
+ title?: string;
251
+ slug?: string;
252
+ noindex?: boolean;
253
+ }
254
+ /** What the selection bar does to a pile of tags at once (§10). */
255
+ export type TagMassAction = 'index' | 'noindex' | 'delete';
256
+ /** What came of a merge, for the sentence the panel says afterwards. */
257
+ export interface TagMerged {
258
+ tag: TagRow;
259
+ /** How many articles ended up carrying the surviving word. */
260
+ articles_count: number;
261
+ merged: number;
262
+ }
263
+ /**
264
+ * Every rubric, with the first segment of every blog address beside them.
265
+ *
266
+ * The prefix travels with the list rather than being asked for separately: the form prints the
267
+ * whole address as it is typed, and `remont` on its own says nothing about whether the blog
268
+ * lives at the root of the site or under `/blog/`.
269
+ */
270
+ export interface RubricsPayload {
271
+ data: RubricRow[];
272
+ prefix: string;
273
+ }
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@webx-ui/module-blog",
3
+ "version": "0.1.0",
4
+ "description": "Blog for the WebX UI admin panel: articles made of blocks, rubrics and tags, scheduled publication.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": [
8
+ "*.css"
9
+ ],
10
+ "keywords": [
11
+ "webx-ui",
12
+ "vue",
13
+ "admin",
14
+ "cms",
15
+ "blog"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/webx-ui/webx-ui.git",
20
+ "directory": "packages/module-blog"
21
+ },
22
+ "homepage": "https://webx-ui.github.io/webx-ui/guide/blog.html",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "main": "./dist/index.js",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./style.css": "./dist/style.css",
38
+ "./dist/style.css": "./dist/style.css",
39
+ "./package.json": "./package.json"
40
+ },
41
+ "peerDependencies": {
42
+ "vue": "^3.5.0",
43
+ "vue-router": "^4.5.0"
44
+ },
45
+ "dependencies": {
46
+ "@webx-ui/core": "^0.24.0",
47
+ "@webx-ui/module-admin": "^0.9.0",
48
+ "@webx-ui/module-blocks": "^0.5.1",
49
+ "@webx-ui/schema": "^0.3.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^24.10.1",
53
+ "@vitejs/plugin-vue": "^6.0.1",
54
+ "typescript": "^5.9.3",
55
+ "vite": "^7.1.14",
56
+ "vite-plugin-dts": "^4.5.4",
57
+ "vue": "^3.5.24",
58
+ "vue-router": "^4.5.1",
59
+ "vue-tsc": "^3.1.3"
60
+ },
61
+ "scripts": {
62
+ "build": "vite build",
63
+ "dev": "vite build --watch",
64
+ "typecheck": "vue-tsc -p tsconfig.json --noEmit"
65
+ }
66
+ }