@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 webx-ui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @webx-ui/module-blog
2
+
3
+ The front end of the blog section of the [WebX UI](https://github.com/webx-ui/webx-ui) admin
4
+ panel: articles, and — as they are written — rubrics and tags.
5
+
6
+ The other half is the Composer package `webx-ui/module-blog`, which owns the articles, their
7
+ addresses, their publication dates and the API. A section appears in the panel when both halves
8
+ are installed.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @webx-ui/module-blog
14
+ ```
15
+
16
+ ```ts
17
+ import { createAdmin } from '@webx-ui/module-admin'
18
+ import { blog } from '@webx-ui/module-blog'
19
+ import '@webx-ui/module-blog/style.css'
20
+
21
+ createAdmin({
22
+ basePath: '/cms',
23
+ modules: [...blog()],
24
+ }).mount()
25
+ ```
26
+
27
+ `blog()` answers with a section per entry of the navigation rather than with one module, because
28
+ the panel draws one entry per module and the blog wants three — articles, rubrics and tags,
29
+ under one heading. The server puts all three in the `blog` group; a section whose screen is not
30
+ here yet is silently left out of the menu.
31
+
32
+ `blog({ path: '/writing' })` puts the sections somewhere else inside the panel.
33
+
34
+ ## The list
35
+
36
+ A page of articles, newest first, with whatever is pinned above them. Over it: the views of the
37
+ same list as tabs — everything, live, scheduled, drafts, taken off the site, and the bin — a
38
+ search box, and three dropdowns for the rubric, the tag and the author. All of it lives in the
39
+ address, so opening an article and coming back lands on the same page of the same filter.
40
+
41
+ Five states, and the pair worth knowing apart is the last two: an article that was never
42
+ published and one that was taken off the site this morning both have no publication date, and
43
+ only its history tells them apart. Calling the second one a draft would be a lie.
44
+
45
+ Below 640 pixels the row becomes a card: the cover on the left, the title, one rubric, the
46
+ state, the date and the author beside it. Ten articles on a phone screen instead of two.
47
+
48
+ ## Licence
49
+
50
+ MIT
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ disabled?: boolean;
3
+ };
4
+ type __VLS_PublicProps = {
5
+ modelValue?: number | null;
6
+ } & __VLS_Props;
7
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ "update:modelValue": (value: number | null) => any;
9
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
10
+ "onUpdate:modelValue"?: ((value: number | null) => any) | undefined;
11
+ }>, {
12
+ disabled: boolean;
13
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
14
+ export default _default;
@@ -0,0 +1,4 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ field: HTMLDivElement;
3
+ }, any>;
4
+ export default _default;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The editor of one article: a head that stays put, and the described screen under it.
3
+ *
4
+ * What this page owns is the head — what the article is called, what state it is in, and the
5
+ * way out to the site — and the bar along the bottom, which is where it is saved and published.
6
+ * Everything between them is `blog.article-form`, so a module or a project adds a tab to the
7
+ * editor with a patch (§12) rather than with a fork of this file.
8
+ *
9
+ * Saving is by autosave: a pause after the last keystroke, and the moment a field is left. The
10
+ * explicit button is still there, because a button that says "saved" is the only way a person
11
+ * can be sure — but nothing is ever lost by not pressing it.
12
+ *
13
+ * The one thing here that a page editor does not have is the day. An article carries a date
14
+ * that may be in the future, so "publish" means "publish under the date in the settings tab",
15
+ * and the bar says which day that is before it is pressed (§7).
16
+ */
17
+ type __VLS_Props = {
18
+ base?: string;
19
+ };
20
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
21
+ base: string;
22
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
23
+ root: HTMLDivElement;
24
+ }, any>;
25
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, HTMLDivElement>;
2
+ export default _default;
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ disabled?: boolean;
3
+ };
4
+ type __VLS_PublicProps = {
5
+ modelValue?: number[];
6
+ } & __VLS_Props;
7
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ "update:modelValue": (value: number[]) => any;
9
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
10
+ "onUpdate:modelValue"?: ((value: number[]) => any) | undefined;
11
+ }>, {
12
+ disabled: boolean;
13
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
14
+ export default _default;
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ disabled?: boolean;
3
+ };
4
+ type __VLS_PublicProps = {
5
+ modelValue?: number[];
6
+ } & __VLS_Props;
7
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ "update:modelValue": (value: number[]) => any;
9
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
10
+ "onUpdate:modelValue"?: ((value: number[]) => any) | undefined;
11
+ }>, {
12
+ disabled: boolean;
13
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
14
+ export default _default;
@@ -0,0 +1,14 @@
1
+ type __VLS_Props = {
2
+ disabled?: boolean;
3
+ };
4
+ type __VLS_PublicProps = {
5
+ modelValue?: number[];
6
+ } & __VLS_Props;
7
+ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
8
+ "update:modelValue": (value: number[]) => any;
9
+ }, string, import('vue').PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
10
+ "onUpdate:modelValue"?: ((value: number[]) => any) | undefined;
11
+ }>, {
12
+ disabled: boolean;
13
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
14
+ export default _default;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The section: a page of articles with the filters of §10 over it.
3
+ *
4
+ * A paginator rather than a tree, which is the whole difference from `Pages`: articles all sit
5
+ * at the same depth and there are a hundred of them, so the screen is twenty rows, a filter bar
6
+ * and a page number.
7
+ *
8
+ * Below {@link CARDS} the row becomes a card, and the card is not the table's own stack of
9
+ * labelled lines: at 375px five of those are four hundred pixels for one article. It is the
10
+ * cover on the left and everything else beside it, which is 96px and ten articles on a screen
11
+ * instead of two (§10). The table draws whatever columns it is given, so the switch is made
12
+ * here — one column at that width, seven above it — and both halves agree on the number because
13
+ * it is the same constant.
14
+ */
15
+ type __VLS_Props = {
16
+ base?: string;
17
+ };
18
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
19
+ base: string;
20
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
21
+ root: HTMLDivElement;
22
+ }, HTMLDivElement>;
23
+ export default _default;
@@ -0,0 +1,39 @@
1
+ import { RubricRow } from './types';
2
+ /**
3
+ * One rubric, in the pane beside the list (§10).
4
+ *
5
+ * Six fields and nothing described: nothing patches a rubric, so there is no screen to extend
6
+ * and no registry to go through. What it does borrow from the described screens are the two
7
+ * fields it has no business drawing itself — the library picker and the SEO card, both looked
8
+ * up in the panel's own type registry. A panel without `module-media` still edits everything
9
+ * else; it simply has no picture.
10
+ *
11
+ * The SEO card starts folded, with a sentence about where the page's title comes from without
12
+ * it. That is the truth for most rubrics — the rules cover them — and a card of ten empty
13
+ * fields under every form teaches editors to fill in ten empty fields (§12).
14
+ */
15
+ type __VLS_Props = {
16
+ /** `null` while a new one is being made: the form of a row that is not in the list yet. */
17
+ rubric: RubricRow | null;
18
+ /** The first segment of every blog address, for the line under the slug (§4). */
19
+ prefix?: string;
20
+ /** False when the pane is a drawer over the list, which is where the way back lives. */
21
+ inline?: boolean;
22
+ disabled?: boolean;
23
+ };
24
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
25
+ articles: (rubric: RubricRow) => any;
26
+ deleted: () => any;
27
+ back: () => any;
28
+ saved: (rubric: RubricRow) => any;
29
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
30
+ onArticles?: ((rubric: RubricRow) => any) | undefined;
31
+ onDeleted?: (() => any) | undefined;
32
+ onBack?: (() => any) | undefined;
33
+ onSaved?: ((rubric: RubricRow) => any) | undefined;
34
+ }>, {
35
+ disabled: boolean;
36
+ prefix: string;
37
+ inline: boolean;
38
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
39
+ export default _default;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The sections of the blog: the menu on the left, the one that is open on the right (§10).
3
+ *
4
+ * `WxListDetail` rather than a list and a form on two screens, and the reason is the drag. The
5
+ * order of this list *is* the order of the menu on the site (§6), so it is edited by moving
6
+ * rows — and a screen where moving a row means leaving the record you were editing is a screen
7
+ * where nobody reorders anything.
8
+ *
9
+ * There is no paginator and no search: a rubric is a section, a site has eight of them, and a
10
+ * menu you have to search is a menu that is already wrong.
11
+ */
12
+ type __VLS_Props = {
13
+ base?: string;
14
+ };
15
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
16
+ base: string;
17
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
18
+ export default _default;
@@ -0,0 +1,4 @@
1
+ declare const _default: import('vue').DefineComponent<{}, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
2
+ field: HTMLDivElement;
3
+ }, any>;
4
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { TagRow } from './types';
2
+ /**
3
+ * Several tags into one (§6).
4
+ *
5
+ * The dialog asks the one question the selection cannot answer — which of them stays — and says
6
+ * the two things about the operation that an editor has to know before pressing it: how many
7
+ * articles change hands, and that nothing here can be undone. The pivot rows that go are gone,
8
+ * and putting a tag back would not put back which articles carried it.
9
+ *
10
+ * The checkbox is the other half. The aliases of `webx-ui/routing` die with the tag — they are
11
+ * rows keyed to the entity — so an address that is to survive the merge has to survive as
12
+ * something that is not tied to it, which is a redirect. That is a permanent fact about the
13
+ * site, and a tag made by mistake this morning does not deserve one, so it is asked rather than
14
+ * done quietly.
15
+ */
16
+ type __VLS_Props = {
17
+ tags: TagRow[];
18
+ };
19
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
20
+ export default _default;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Tags, and the raking over of them (§10).
3
+ *
4
+ * Entered from the article form by the hundred, which is what the whole screen is shaped by:
5
+ * within six months the table holds "belts", "belt" and "drive belts", and all three are right.
6
+ * So renaming is done in place — a tag is one word, and a form to change one word is a form
7
+ * nobody opens — and everything else is done to a pile of rows at once.
8
+ *
9
+ * It stays a table at every width, with columns dropping out by `hideBelow` until the word and
10
+ * the `···` are left. Cards would be 250px each for a word and a number (CLAUDE.md §4), and
11
+ * this is a screen somebody scrolls through sixty rows of.
12
+ *
13
+ * "Not indexed" is the one filter that is not a `where`: it is the answer of the same rule the
14
+ * rendered page follows, rules in the SEO section included, so the column, the tab's count and
15
+ * the filter are one answer worked out on the server (§12).
16
+ */
17
+ type __VLS_Props = {
18
+ base?: string;
19
+ };
20
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
21
+ base: string;
22
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
23
+ box: HTMLSpanElement;
24
+ }, HTMLDivElement>;
25
+ export default _default;
package/dist/api.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { AdminContext } from '@webx-ui/module-admin';
2
+ import { ArticleDetail, ArticleInput, ArticleQuery, ArticleRow, ArticleSave, ArticleVersion, ArticlesPage, BlogTag, RubricInput, RubricRow, RubricsPayload, TagInput, TagMassAction, TagMerged, TagQuery, TagRow, TagsPage } from './types';
3
+ export interface BlogApi {
4
+ /** A page of articles, with what the filters over them can be set to. */
5
+ articles(query?: ArticleQuery): Promise<ArticlesPage>;
6
+ /** One article as its editor opens it: the record, the screen's values and the revision. */
7
+ get(id: number): Promise<ArticleDetail>;
8
+ create(input: ArticleInput): Promise<ArticleRow>;
9
+ /**
10
+ * Save. Refused with a 409 when the revision is not the current one — the body is an
11
+ * {@link ArticleConflict} carrying the article as it now is, so the panel can offer to
12
+ * re-read rather than quietly keep one of two edits.
13
+ */
14
+ save(id: number, input: ArticleSave): Promise<ArticleDetail>;
15
+ /** Throw away what is waiting and keep what the site is showing. */
16
+ discard(id: number): Promise<ArticleDetail>;
17
+ /** The publications, newest first. */
18
+ versions(id: number): Promise<ArticleVersion[]>;
19
+ /** An old publication becomes the draft; putting it on the site is a separate step. */
20
+ restoreVersion(id: number, number: number): Promise<ArticleDetail>;
21
+ /** Tags matching what is being typed, most used first. */
22
+ tags(term?: string): Promise<BlogTag[]>;
23
+ /**
24
+ * A tag made from the article being written, because that is where tags come from (§2.8) —
25
+ * and from the tags screen, which is the other place somebody spells a new word.
26
+ */
27
+ createTag(input: TagInput): Promise<TagRow>;
28
+ /** On the site. `at` in the future schedules it; left out, it goes on now (§7). */
29
+ publish(id: number, at?: string | null): Promise<ArticleRow>;
30
+ unpublish(id: number): Promise<ArticleRow>;
31
+ remove(id: number): Promise<void>;
32
+ restore(id: number): Promise<ArticleRow>;
33
+ /** Every rubric there is, in the order the menu of the site has them (§6). */
34
+ rubrics(): Promise<RubricsPayload>;
35
+ createRubric(input: RubricInput): Promise<RubricRow>;
36
+ saveRubric(id: number, input: RubricInput): Promise<RubricRow>;
37
+ /**
38
+ * Into the bin — refused with a 422 naming the number of articles while it still holds any
39
+ * (§6). The panel keeps the button out of reach, so reaching this is somebody else's save
40
+ * landing between the list and the click.
41
+ */
42
+ removeRubric(id: number): Promise<void>;
43
+ /** The whole order, as it is on screen. */
44
+ sortRubrics(ids: number[]): Promise<void>;
45
+ /** A page of tags with the counts the pills wear (§10). */
46
+ tagsPage(query?: TagQuery): Promise<TagsPage>;
47
+ /** A rename, or the switch on the index. Only what travels is touched. */
48
+ saveTag(id: number, input: TagInput): Promise<TagRow>;
49
+ removeTag(id: number): Promise<void>;
50
+ /** What the selection bar does to a pile of them at once. */
51
+ massTags(ids: number[], action: TagMassAction): Promise<number>;
52
+ /** Several into one. Irreversible, and `redirect` is what outlives the tags that go (§6). */
53
+ mergeTags(ids: number[], keep: number, redirect: boolean): Promise<TagMerged>;
54
+ }
55
+ /** Everything under `/blog`, below the panel's API path. */
56
+ export declare function createBlogApi(admin: AdminContext): BlogApi;
@@ -0,0 +1,56 @@
1
+ import { InjectionKey, Ref } from 'vue';
2
+ import { ScreenModel } from '@webx-ui/schema';
3
+ import { ArticleDetail, ArticleOption, ArticleRow } from './types';
4
+ /**
5
+ * What the editor knows and the nodes of its screen do not.
6
+ *
7
+ * The form is a described screen (`blog.article-form`), so the parts of it that need more than
8
+ * a value — the address printed whole, the rubrics with their titles, the history — arrive as
9
+ * node types of their own. Each of those needs the article being edited and the lists the
10
+ * server sent with it, and none of them can be handed either through the description: a screen
11
+ * is a description, not a binding. So the page that hosts the screen provides it, the same way
12
+ * it provides the preview to `wx-blocks`.
13
+ */
14
+ export interface ArticleEditorContext {
15
+ /** The article as the server last answered it, or `null` while the first request is in flight. */
16
+ article: Ref<ArticleRow | null>;
17
+ /**
18
+ * The values of the screen as they are right now, edits included.
19
+ *
20
+ * A node that only draws still has to follow what is being typed: the address is a sentence
21
+ * made of the slug field, and one that updated on save would be showing the old address at
22
+ * the moment the new one is most worth reading.
23
+ */
24
+ values: Ref<ScreenModel>;
25
+ /** What the dropdowns on the screen can be set to, as the record carried them. */
26
+ options: Ref<ArticleDetail['options']>;
27
+ /** Titles for the ids in `values.related` — the only place they come from. */
28
+ related: Ref<ArticleOption[]>;
29
+ /** The first segment of every blog address, for the field that edits the last one. */
30
+ prefix: Ref<string>;
31
+ /** Where the section lives, for the links the nodes draw. */
32
+ base: string;
33
+ /** Whether this administrator may write at all. */
34
+ canManage: boolean;
35
+ /**
36
+ * Whether the screen is closed for writing right now — no permission, or a publication in
37
+ * flight.
38
+ *
39
+ * Through the editor rather than through the form, because `WxForm`'s own `disabled` reaches
40
+ * a control only through `useFormField`, which the design system does not hand out: a field
41
+ * built outside `@webx-ui/core` has no way to ask. The screen is told the same thing twice —
42
+ * once for the fields that are core's and once here — and both are computed from one place.
43
+ */
44
+ disabled: Ref<boolean>;
45
+ /** Ask the server for the article again — after a restore, a publication, a discard. */
46
+ reload(): Promise<void>;
47
+ /** Write the draft now rather than at the end of the next pause. */
48
+ save(): Promise<void>;
49
+ }
50
+ export declare const articleEditorKey: InjectionKey<ArticleEditorContext>;
51
+ export declare function provideArticleEditor(editor: ArticleEditorContext): void;
52
+ /**
53
+ * The editor above this node, or `null` outside one — a node of `blog.article-form` drawn in a
54
+ * demo or a test has no article to talk about, and must draw nothing rather than throw.
55
+ */
56
+ export declare function useArticleEditor(): ArticleEditorContext | null;
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** Puts this package's English under the panel's dictionary, once per panel. */
2
+ export declare function useBlogMessages(): void;
@@ -0,0 +1,19 @@
1
+ export { blog, type BlogOptions } from './module';
2
+ export { createBlogApi, type BlogApi } from './api';
3
+ export { blogMessages } from './messages';
4
+ export { articleEditorKey, provideArticleEditor, useArticleEditor, type ArticleEditorContext, } from './editor';
5
+ export { default as WxArticlesPage } from './ArticlesPage';
6
+ export { default as WxRubricsPage } from './RubricsPage';
7
+ export { default as WxRubricForm } from './RubricForm';
8
+ export { default as WxTagsPage } from './TagsPage';
9
+ export { default as WxTagCreateDialog } from './TagCreateDialog';
10
+ export { default as WxTagMergeDialog } from './TagMergeDialog';
11
+ export { default as WxArticleCreateDialog } from './ArticleCreateDialog';
12
+ export { default as WxArticleEditorPage } from './ArticleEditorPage';
13
+ export { default as WxArticleAddress } from './ArticleAddress';
14
+ export { default as WxArticleAuthorField } from './ArticleAuthor';
15
+ export { default as WxArticleRubrics } from './ArticleRubrics';
16
+ export { default as WxArticleTags } from './ArticleTags';
17
+ export { default as WxArticleRelated } from './ArticleRelated';
18
+ export { default as WxArticleHistory } from './ArticleHistory';
19
+ export type { ArticleAuthor, ArticleConflict, ArticleCover, ArticleDetail, ArticleFilters, ArticleInput, ArticleOption, ArticleQuery, ArticleRow, ArticleSave, ArticleStatus, ArticleVersion, ArticlesPage, BlogNamed, BlogTag, RubricInput, RubricRow, RubricsPayload, TagCounts, TagIndexing, TagInput, TagMassAction, TagMerged, TagQuery, TagRow, TagsPage, } from './types';