@jant/core 0.3.27 → 0.3.29
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/bin/reset-password.js +22 -0
- package/dist/client/client.css +1 -0
- package/dist/client/client.js +31561 -0
- package/dist/index.js +15209 -15
- package/package.json +25 -15
- package/src/__tests__/helpers/app.ts +19 -3
- package/src/__tests__/helpers/db.ts +44 -0
- package/src/__tests__/helpers/lingui-core-macro-mock.ts +33 -0
- package/src/app.tsx +111 -174
- package/src/client.ts +13 -0
- package/src/db/migrations/0007_post_collections_m2m.sql +94 -0
- package/src/db/migrations/0008_add_collection_dividers.sql +8 -0
- package/src/db/migrations/0009_drop_collection_show_divider.sql +2 -0
- package/src/db/migrations/0010_add_performance_indexes.sql +16 -0
- package/src/db/schema.ts +24 -4
- package/src/i18n/locales/en.po +810 -385
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +733 -522
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +733 -522
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/i18n/middleware.ts +7 -11
- package/src/index.ts +1 -1
- package/src/lib/__tests__/icons.test.ts +178 -0
- package/src/lib/__tests__/resolve-config.test.ts +184 -0
- package/src/lib/__tests__/schemas.test.ts +12 -6
- package/src/lib/__tests__/theme.test.ts +62 -0
- package/src/lib/__tests__/timezones.test.ts +1 -1
- package/src/lib/__tests__/url.test.ts +12 -0
- package/src/lib/__tests__/view.test.ts +1 -5
- package/src/lib/avatar-upload.ts +18 -10
- package/src/lib/collection-form-bridge.ts +52 -0
- package/src/lib/collections-reorder.ts +28 -0
- package/src/lib/compose-bridge.ts +251 -0
- package/src/lib/errors.ts +116 -0
- package/src/lib/excerpt.ts +1 -1
- package/src/lib/favicon.ts +3 -5
- package/src/lib/html.ts +22 -0
- package/src/lib/icon-catalog.ts +181 -0
- package/src/lib/icons.ts +202 -0
- package/src/lib/navigation.ts +18 -33
- package/src/lib/pagination.ts +3 -2
- package/src/lib/post-form-bridge.ts +136 -0
- package/src/lib/render.tsx +11 -4
- package/src/lib/resolve-config.ts +157 -0
- package/src/lib/schemas.ts +76 -12
- package/src/lib/settings-bridge.ts +139 -0
- package/src/lib/storage.ts +37 -16
- package/src/lib/theme.ts +5 -7
- package/src/lib/timeline.ts +4 -8
- package/src/lib/toast.ts +134 -0
- package/src/lib/upload.ts +71 -0
- package/src/lib/url.ts +9 -1
- package/src/lib/version.ts +16 -0
- package/src/lib/view.ts +9 -10
- package/src/middleware/__tests__/auth.test.ts +6 -28
- package/src/middleware/__tests__/onboarding.test.ts +1 -1
- package/src/middleware/auth.ts +6 -12
- package/src/middleware/config.ts +51 -0
- package/src/middleware/error-handler.ts +56 -0
- package/src/middleware/onboarding.ts +1 -1
- package/src/preset.css +6 -0
- package/src/routes/__tests__/compose.test.ts +104 -17
- package/src/routes/api/__tests__/collections.test.ts +93 -2
- package/src/routes/api/__tests__/posts.test.ts +2 -1
- package/src/routes/api/__tests__/settings.test.ts +1 -1
- package/src/routes/api/collections.ts +64 -68
- package/src/routes/api/nav-items.ts +21 -59
- package/src/routes/api/pages.ts +18 -46
- package/src/routes/api/posts.ts +64 -86
- package/src/routes/api/search.ts +6 -4
- package/src/routes/api/settings.ts +8 -24
- package/src/routes/api/upload.ts +55 -53
- package/src/routes/auth/__tests__/setup.test.ts +118 -0
- package/src/routes/auth/reset.tsx +17 -66
- package/src/routes/auth/setup.tsx +67 -11
- package/src/routes/auth/signin.tsx +44 -8
- package/src/routes/compose.tsx +194 -0
- package/src/routes/dash/__tests__/font-theme.test.ts +110 -0
- package/src/routes/dash/__tests__/pages.test.ts +2 -2
- package/src/routes/dash/__tests__/settings-avatar.test.ts +23 -12
- package/src/routes/dash/appearance.tsx +173 -0
- package/src/routes/dash/collections.tsx +80 -14
- package/src/routes/dash/index.tsx +12 -14
- package/src/routes/dash/media.tsx +46 -49
- package/src/routes/dash/pages.tsx +85 -37
- package/src/routes/dash/posts.tsx +60 -23
- package/src/routes/dash/redirects.tsx +43 -33
- package/src/routes/dash/settings.tsx +234 -214
- package/src/routes/feed/__tests__/rss.test.ts +7 -3
- package/src/routes/feed/rss.ts +11 -16
- package/src/routes/feed/sitemap.ts +15 -9
- package/src/routes/pages/__tests__/collections.test.ts +9 -8
- package/src/routes/pages/archive.tsx +2 -2
- package/src/routes/pages/collection.tsx +76 -9
- package/src/routes/pages/collections.tsx +3 -1
- package/src/routes/pages/featured.tsx +2 -2
- package/src/routes/pages/home.tsx +3 -3
- package/src/routes/pages/latest.tsx +2 -2
- package/src/routes/pages/page.tsx +2 -2
- package/src/routes/pages/post.tsx +2 -2
- package/src/routes/pages/search.tsx +2 -2
- package/src/services/__tests__/collection.test.ts +324 -34
- package/src/services/__tests__/media.test.ts +1 -1
- package/src/services/__tests__/page.test.ts +116 -1
- package/src/services/auth.ts +88 -0
- package/src/services/collection.ts +169 -30
- package/src/services/index.ts +8 -3
- package/src/services/media.ts +39 -12
- package/src/services/navigation.ts +17 -5
- package/src/services/page.ts +24 -4
- package/src/services/post.ts +87 -19
- package/src/services/search.ts +0 -1
- package/src/services/settings.ts +21 -13
- package/src/style.css +3 -0
- package/src/styles/components.css +42 -1
- package/src/styles/tokens.css +4 -0
- package/src/styles/ui.css +902 -73
- package/src/types/app-context.ts +25 -0
- package/src/types/bindings.ts +1 -0
- package/src/types/config.ts +60 -23
- package/src/types/entities.ts +12 -2
- package/src/types/lingui-react-macro.d.ts +3 -3
- package/src/types/operations.ts +2 -4
- package/src/types/views.ts +1 -3
- package/src/ui/__tests__/font-themes.test.ts +27 -8
- package/src/ui/color-themes.ts +1 -1
- package/src/ui/components/__tests__/jant-collection-form.test.ts +153 -0
- package/src/ui/components/__tests__/jant-compose-dialog.test.ts +512 -0
- package/src/ui/components/__tests__/jant-compose-editor.test.ts +272 -0
- package/src/ui/components/__tests__/jant-post-form.test.ts +172 -0
- package/src/ui/components/__tests__/jant-settings-avatar.test.ts +235 -0
- package/src/ui/components/__tests__/jant-settings-general.test.ts +319 -0
- package/src/ui/components/collection-types.ts +45 -0
- package/src/ui/components/compose-types.ts +75 -0
- package/src/ui/components/jant-collection-form.ts +512 -0
- package/src/ui/components/jant-compose-dialog.ts +494 -0
- package/src/ui/components/jant-compose-editor.ts +799 -0
- package/src/ui/components/jant-post-form.ts +290 -0
- package/src/ui/components/jant-settings-avatar.ts +231 -0
- package/src/ui/components/jant-settings-general.ts +436 -0
- package/src/ui/components/post-form-template.ts +260 -0
- package/src/ui/components/post-form-types.ts +87 -0
- package/src/ui/components/settings-types.ts +62 -0
- package/src/ui/compose/ComposeDialog.tsx +141 -385
- package/src/ui/compose/ComposePrompt.tsx +3 -3
- package/src/ui/dash/PostList.tsx +55 -61
- package/src/ui/dash/appearance/AdvancedContent.tsx +80 -0
- package/src/ui/dash/appearance/AppearanceNav.tsx +56 -0
- package/src/ui/dash/appearance/ColorThemeContent.tsx +129 -0
- package/src/ui/dash/appearance/FontThemeContent.tsx +98 -0
- package/src/ui/dash/collections/CollectionForm.tsx +130 -117
- package/src/ui/dash/collections/CollectionsListContent.tsx +102 -41
- package/src/ui/dash/collections/IconPickerGrid.tsx +50 -0
- package/src/ui/dash/collections/ViewCollectionContent.tsx +14 -3
- package/src/ui/dash/index.ts +1 -1
- package/src/ui/dash/posts/PostForm.tsx +248 -0
- package/src/ui/dash/settings/AccountContent.tsx +69 -80
- package/src/ui/dash/settings/GeneralContent.tsx +159 -478
- package/src/ui/dash/settings/SettingsNav.tsx +4 -4
- package/src/ui/font-themes.ts +115 -32
- package/src/ui/layouts/BaseLayout.tsx +49 -19
- package/src/ui/layouts/DashLayout.tsx +14 -9
- package/src/ui/layouts/SiteLayout.tsx +38 -23
- package/src/ui/pages/CollectionPage.tsx +12 -2
- package/src/ui/pages/CollectionsPage.tsx +27 -27
- package/src/ui/pages/HomePage.tsx +15 -6
- package/src/ui/pages/SearchPage.tsx +1 -2
- package/src/ui/shared/CollectionsSidebar.tsx +59 -0
- package/src/ui/shared/Pagination.tsx +2 -2
- package/dist/app.js +0 -267
- package/dist/auth.js +0 -39
- package/dist/client.js +0 -13
- package/dist/db/index.js +0 -10
- package/dist/db/schema.js +0 -224
- package/dist/i18n/Trans.js +0 -24
- package/dist/i18n/context.js +0 -58
- package/dist/i18n/detect.js +0 -26
- package/dist/i18n/i18n.js +0 -49
- package/dist/i18n/index.js +0 -44
- package/dist/i18n/locales/en.js +0 -1
- package/dist/i18n/locales/zh-Hans.js +0 -1
- package/dist/i18n/locales/zh-Hant.js +0 -1
- package/dist/i18n/locales.js +0 -13
- package/dist/i18n/middleware.js +0 -30
- package/dist/lib/avatar-upload.js +0 -134
- package/dist/lib/config.js +0 -143
- package/dist/lib/constants.js +0 -50
- package/dist/lib/excerpt.js +0 -76
- package/dist/lib/favicon.js +0 -102
- package/dist/lib/feed.js +0 -123
- package/dist/lib/image-processor.js +0 -187
- package/dist/lib/image.js +0 -97
- package/dist/lib/index.js +0 -7
- package/dist/lib/markdown.js +0 -83
- package/dist/lib/media-helpers.js +0 -49
- package/dist/lib/media-upload.js +0 -104
- package/dist/lib/nav-reorder.js +0 -27
- package/dist/lib/navigation.js +0 -79
- package/dist/lib/pagination.js +0 -44
- package/dist/lib/render.js +0 -53
- package/dist/lib/schemas.js +0 -174
- package/dist/lib/sqid.js +0 -72
- package/dist/lib/sse.js +0 -218
- package/dist/lib/storage.js +0 -164
- package/dist/lib/theme.js +0 -65
- package/dist/lib/time.js +0 -159
- package/dist/lib/timeline.js +0 -95
- package/dist/lib/timezones.js +0 -388
- package/dist/lib/url.js +0 -89
- package/dist/lib/view.js +0 -217
- package/dist/middleware/auth.js +0 -52
- package/dist/middleware/onboarding.js +0 -41
- package/dist/routes/api/collections.js +0 -124
- package/dist/routes/api/nav-items.js +0 -104
- package/dist/routes/api/pages.js +0 -91
- package/dist/routes/api/posts.js +0 -218
- package/dist/routes/api/search.js +0 -48
- package/dist/routes/api/settings.js +0 -68
- package/dist/routes/api/upload.js +0 -246
- package/dist/routes/auth/reset.js +0 -221
- package/dist/routes/auth/setup.js +0 -194
- package/dist/routes/auth/signin.js +0 -176
- package/dist/routes/compose.js +0 -48
- package/dist/routes/dash/collections.js +0 -115
- package/dist/routes/dash/index.js +0 -118
- package/dist/routes/dash/media.js +0 -106
- package/dist/routes/dash/pages.js +0 -294
- package/dist/routes/dash/posts.js +0 -244
- package/dist/routes/dash/redirects.js +0 -257
- package/dist/routes/dash/settings.js +0 -379
- package/dist/routes/feed/rss.js +0 -62
- package/dist/routes/feed/sitemap.js +0 -49
- package/dist/routes/pages/archive.js +0 -62
- package/dist/routes/pages/collection.js +0 -34
- package/dist/routes/pages/collections.js +0 -28
- package/dist/routes/pages/featured.js +0 -36
- package/dist/routes/pages/home.js +0 -64
- package/dist/routes/pages/latest.js +0 -45
- package/dist/routes/pages/page.js +0 -68
- package/dist/routes/pages/post.js +0 -44
- package/dist/routes/pages/search.js +0 -54
- package/dist/services/collection.js +0 -109
- package/dist/services/index.js +0 -24
- package/dist/services/media.js +0 -117
- package/dist/services/navigation.js +0 -91
- package/dist/services/page.js +0 -84
- package/dist/services/post.js +0 -229
- package/dist/services/redirect.js +0 -48
- package/dist/services/search.js +0 -67
- package/dist/services/settings.js +0 -68
- package/dist/types/bindings.js +0 -3
- package/dist/types/config.js +0 -147
- package/dist/types/constants.js +0 -27
- package/dist/types/entities.js +0 -3
- package/dist/types/lingui-react-macro.d.js +0 -9
- package/dist/types/operations.js +0 -3
- package/dist/types/props.js +0 -3
- package/dist/types/sortablejs.d.js +0 -5
- package/dist/types/views.js +0 -5
- package/dist/types.js +0 -11
- package/dist/ui/color-themes.js +0 -268
- package/dist/ui/compose/ComposeDialog.js +0 -467
- package/dist/ui/compose/ComposePrompt.js +0 -55
- package/dist/ui/dash/ActionButtons.js +0 -46
- package/dist/ui/dash/CrudPageHeader.js +0 -22
- package/dist/ui/dash/DangerZone.js +0 -36
- package/dist/ui/dash/FormatBadge.js +0 -27
- package/dist/ui/dash/ListItemRow.js +0 -21
- package/dist/ui/dash/PageForm.js +0 -195
- package/dist/ui/dash/PostForm.js +0 -395
- package/dist/ui/dash/PostList.js +0 -83
- package/dist/ui/dash/StatusBadge.js +0 -46
- package/dist/ui/dash/collections/CollectionForm.js +0 -152
- package/dist/ui/dash/collections/CollectionsListContent.js +0 -68
- package/dist/ui/dash/collections/ViewCollectionContent.js +0 -96
- package/dist/ui/dash/index.js +0 -10
- package/dist/ui/dash/media/MediaListContent.js +0 -166
- package/dist/ui/dash/media/ViewMediaContent.js +0 -212
- package/dist/ui/dash/pages/LinkFormContent.js +0 -130
- package/dist/ui/dash/pages/UnifiedPagesContent.js +0 -193
- package/dist/ui/dash/settings/AccountContent.js +0 -209
- package/dist/ui/dash/settings/AppearanceContent.js +0 -259
- package/dist/ui/dash/settings/GeneralContent.js +0 -536
- package/dist/ui/dash/settings/SettingsNav.js +0 -41
- package/dist/ui/feed/LinkCard.js +0 -72
- package/dist/ui/feed/NoteCard.js +0 -58
- package/dist/ui/feed/QuoteCard.js +0 -63
- package/dist/ui/feed/ThreadPreview.js +0 -48
- package/dist/ui/feed/TimelineFeed.js +0 -41
- package/dist/ui/feed/TimelineItem.js +0 -27
- package/dist/ui/font-themes.js +0 -36
- package/dist/ui/layouts/BaseLayout.js +0 -153
- package/dist/ui/layouts/DashLayout.js +0 -141
- package/dist/ui/layouts/SiteLayout.js +0 -169
- package/dist/ui/pages/ArchivePage.js +0 -143
- package/dist/ui/pages/CollectionPage.js +0 -70
- package/dist/ui/pages/CollectionsPage.js +0 -76
- package/dist/ui/pages/FeaturedPage.js +0 -24
- package/dist/ui/pages/HomePage.js +0 -24
- package/dist/ui/pages/PostPage.js +0 -55
- package/dist/ui/pages/SearchPage.js +0 -122
- package/dist/ui/pages/SinglePage.js +0 -23
- package/dist/ui/shared/EmptyState.js +0 -27
- package/dist/ui/shared/MediaGallery.js +0 -35
- package/dist/ui/shared/Pagination.js +0 -195
- package/dist/ui/shared/ThreadView.js +0 -108
- package/dist/ui/shared/index.js +0 -5
- package/dist/vendor/datastar.js +0 -1606
- package/src/lib/__tests__/config.test.ts +0 -192
- package/src/lib/config.ts +0 -167
- package/src/routes/compose.ts +0 -63
- package/src/ui/dash/PostForm.tsx +0 -360
- package/src/ui/dash/settings/AppearanceContent.tsx +0 -254
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
4
|
+
import type {
|
|
5
|
+
SettingsLabels,
|
|
6
|
+
SettingsTimezone,
|
|
7
|
+
SettingsLanguage,
|
|
8
|
+
SettingsSaveDetail,
|
|
9
|
+
} from "../settings-types.js";
|
|
10
|
+
import "../jant-settings-general.js";
|
|
11
|
+
import type { JantSettingsGeneral } from "../jant-settings-general.js";
|
|
12
|
+
|
|
13
|
+
function requireElement<T extends globalThis.Element>(
|
|
14
|
+
element: T | null,
|
|
15
|
+
message: string,
|
|
16
|
+
): T {
|
|
17
|
+
if (!element) {
|
|
18
|
+
throw new Error(message);
|
|
19
|
+
}
|
|
20
|
+
return element;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const labels: SettingsLabels = {
|
|
24
|
+
blogAvatar: "Blog Avatar",
|
|
25
|
+
uploadAvatar: "Upload Avatar",
|
|
26
|
+
remove: "Remove",
|
|
27
|
+
avatarHelp: "For best results, upload a square image.",
|
|
28
|
+
displayInHeader: "Display avatar in my site header",
|
|
29
|
+
processing: "Processing...",
|
|
30
|
+
uploading: "Uploading...",
|
|
31
|
+
uploadError: "Upload failed.",
|
|
32
|
+
general: "General",
|
|
33
|
+
siteName: "Site Name",
|
|
34
|
+
aboutBlog: "About this blog",
|
|
35
|
+
aboutBlogHelp: "Displayed above your blog posts.",
|
|
36
|
+
language: "Language",
|
|
37
|
+
defaultHomepageView: "Default Homepage View",
|
|
38
|
+
latest: "Latest",
|
|
39
|
+
featured: "Featured",
|
|
40
|
+
timeZone: "Time Zone",
|
|
41
|
+
siteFooter: "Site Footer",
|
|
42
|
+
footerHelp: "Displayed at the bottom of posts.",
|
|
43
|
+
markdownSupported: "Markdown supported",
|
|
44
|
+
seo: "SEO",
|
|
45
|
+
allowIndexing: "It's OK for search engines to index my site",
|
|
46
|
+
save: "Save",
|
|
47
|
+
cancel: "Cancel",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const timezones: SettingsTimezone[] = [
|
|
51
|
+
{ value: "UTC", label: "(UTC) UTC" },
|
|
52
|
+
{ value: "Eastern Time (US & Canada)", label: "(UTC-05:00) Eastern Time" },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const languages: SettingsLanguage[] = [
|
|
56
|
+
{ value: "en", label: "English" },
|
|
57
|
+
{ value: "zh-Hans", label: "简体中文" },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const initialData = {
|
|
61
|
+
siteName: "My Blog",
|
|
62
|
+
siteDescription: "A test blog",
|
|
63
|
+
siteLanguage: "en",
|
|
64
|
+
homeDefaultView: "latest",
|
|
65
|
+
timeZone: "UTC",
|
|
66
|
+
siteFooter: "Footer text",
|
|
67
|
+
noindex: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
async function createElement(): Promise<JantSettingsGeneral> {
|
|
71
|
+
const el = document.createElement(
|
|
72
|
+
"jant-settings-general",
|
|
73
|
+
) as JantSettingsGeneral;
|
|
74
|
+
el.labels = labels;
|
|
75
|
+
el.timezones = timezones;
|
|
76
|
+
el.languages = languages;
|
|
77
|
+
el.siteNameFallback = "Fallback Name";
|
|
78
|
+
el.siteDescriptionFallback = "Fallback Description";
|
|
79
|
+
document.body.appendChild(el);
|
|
80
|
+
await el.updateComplete;
|
|
81
|
+
el.initData(initialData);
|
|
82
|
+
await el.updateComplete;
|
|
83
|
+
return el;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
describe("JantSettingsGeneral", () => {
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
document.body.innerHTML = "";
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("renders general and SEO sections", async () => {
|
|
92
|
+
const el = await createElement();
|
|
93
|
+
const headings = el.querySelectorAll("h2");
|
|
94
|
+
const headingTexts = Array.from(headings).map((h) => h.textContent);
|
|
95
|
+
expect(headingTexts).toContain("General");
|
|
96
|
+
expect(headingTexts).toContain("SEO");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("renders form fields with initial values", async () => {
|
|
100
|
+
const el = await createElement();
|
|
101
|
+
const siteNameInput = requireElement(
|
|
102
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
103
|
+
"expected site name input",
|
|
104
|
+
);
|
|
105
|
+
expect(siteNameInput.value).toBe("My Blog");
|
|
106
|
+
|
|
107
|
+
const textareas = el.querySelectorAll("textarea");
|
|
108
|
+
expect(textareas[0]?.value).toBe("A test blog");
|
|
109
|
+
expect(textareas[1]?.value).toBe("Footer text");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("renders timezone options", async () => {
|
|
113
|
+
const el = await createElement();
|
|
114
|
+
const selects = el.querySelectorAll("select");
|
|
115
|
+
// Third select is timezone (language, homepage view, timezone)
|
|
116
|
+
const tzSelect = selects[2];
|
|
117
|
+
const options = tzSelect?.querySelectorAll("option");
|
|
118
|
+
expect(options?.length).toBe(2);
|
|
119
|
+
expect(options?.[0]?.value).toBe("UTC");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("renders language options", async () => {
|
|
123
|
+
const el = await createElement();
|
|
124
|
+
const selects = el.querySelectorAll("select");
|
|
125
|
+
const langSelect = selects[0];
|
|
126
|
+
const options = langSelect?.querySelectorAll("option");
|
|
127
|
+
expect(options?.length).toBe(2);
|
|
128
|
+
expect(options?.[0]?.value).toBe("en");
|
|
129
|
+
expect(options?.[1]?.value).toBe("zh-Hans");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("tracks general form dirty state on input", async () => {
|
|
133
|
+
const el = await createElement();
|
|
134
|
+
const siteNameInput = requireElement(
|
|
135
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
136
|
+
"expected site name input",
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Simulate input
|
|
140
|
+
siteNameInput.value = "New Name";
|
|
141
|
+
siteNameInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
142
|
+
await el.updateComplete;
|
|
143
|
+
|
|
144
|
+
// Save button should be enabled
|
|
145
|
+
const saveBtn = el.querySelector<HTMLButtonElement>(".btn");
|
|
146
|
+
expect(saveBtn?.disabled).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("cancel reverts general form to original values", async () => {
|
|
150
|
+
const el = await createElement();
|
|
151
|
+
const siteNameInput = requireElement(
|
|
152
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
153
|
+
"expected site name input",
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
// Change the value
|
|
157
|
+
siteNameInput.value = "Changed";
|
|
158
|
+
siteNameInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
159
|
+
await el.updateComplete;
|
|
160
|
+
|
|
161
|
+
// Click cancel (second button after Save)
|
|
162
|
+
const cancelBtn = el.querySelector<HTMLButtonElement>(".btn-outline");
|
|
163
|
+
cancelBtn?.click();
|
|
164
|
+
await el.updateComplete;
|
|
165
|
+
|
|
166
|
+
// Value should be reverted
|
|
167
|
+
expect(siteNameInput?.value).toBe("My Blog");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("dispatches jant:settings-save for general section", async () => {
|
|
171
|
+
const el = await createElement();
|
|
172
|
+
const siteNameInput = requireElement(
|
|
173
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
174
|
+
"expected site name input",
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// Make dirty
|
|
178
|
+
siteNameInput.value = "New Name";
|
|
179
|
+
siteNameInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
180
|
+
await el.updateComplete;
|
|
181
|
+
|
|
182
|
+
let detail: SettingsSaveDetail | null = null;
|
|
183
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
184
|
+
const customEvent = event as CustomEvent<SettingsSaveDetail>;
|
|
185
|
+
detail = customEvent.detail;
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Click save
|
|
189
|
+
const saveBtn = el.querySelector<HTMLButtonElement>(".btn");
|
|
190
|
+
saveBtn?.click();
|
|
191
|
+
await el.updateComplete;
|
|
192
|
+
|
|
193
|
+
expect(detail).not.toBeNull();
|
|
194
|
+
const d = detail as unknown as SettingsSaveDetail;
|
|
195
|
+
expect(d.endpoint).toBe("/dash/settings");
|
|
196
|
+
expect(d.section).toBe("general");
|
|
197
|
+
expect(d.data.siteName).toBe("New Name");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("sectionSaved resets dirty state and updates originals", async () => {
|
|
201
|
+
const el = await createElement();
|
|
202
|
+
const siteNameInput = requireElement(
|
|
203
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
204
|
+
"expected site name input",
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Make dirty and save
|
|
208
|
+
siteNameInput.value = "Saved Name";
|
|
209
|
+
siteNameInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
210
|
+
await el.updateComplete;
|
|
211
|
+
|
|
212
|
+
// Simulate bridge calling sectionSaved
|
|
213
|
+
el.sectionSaved("general");
|
|
214
|
+
await el.updateComplete;
|
|
215
|
+
|
|
216
|
+
// Save button should be disabled again
|
|
217
|
+
const saveBtn = el.querySelector<HTMLButtonElement>(".btn");
|
|
218
|
+
expect(saveBtn?.disabled).toBe(true);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("SEO checkbox toggles noindex state", async () => {
|
|
222
|
+
const el = await createElement();
|
|
223
|
+
// noindex is false initially, so checkbox should be checked (allow indexing)
|
|
224
|
+
const checkboxes = el.querySelectorAll<HTMLInputElement>(
|
|
225
|
+
'input[type="checkbox"]',
|
|
226
|
+
);
|
|
227
|
+
const seoCheckbox = checkboxes[0]; // Only checkbox in this component
|
|
228
|
+
expect(seoCheckbox?.checked).toBe(true);
|
|
229
|
+
|
|
230
|
+
// Toggle
|
|
231
|
+
seoCheckbox?.click();
|
|
232
|
+
await el.updateComplete;
|
|
233
|
+
|
|
234
|
+
// Should now be unchecked
|
|
235
|
+
expect(seoCheckbox?.checked).toBe(false);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("includes footer in general section save", async () => {
|
|
239
|
+
const el = await createElement();
|
|
240
|
+
const textareas = el.querySelectorAll("textarea");
|
|
241
|
+
const footerTextarea = textareas[1]; // Second textarea is footer
|
|
242
|
+
|
|
243
|
+
// Make dirty
|
|
244
|
+
const footer = requireElement(footerTextarea, "expected footer textarea");
|
|
245
|
+
footer.value = "New footer";
|
|
246
|
+
footer.dispatchEvent(new Event("input", { bubbles: true }));
|
|
247
|
+
await el.updateComplete;
|
|
248
|
+
|
|
249
|
+
let detail: SettingsSaveDetail | null = null;
|
|
250
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
251
|
+
const customEvent = event as CustomEvent<SettingsSaveDetail>;
|
|
252
|
+
detail = customEvent.detail;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Click save in the general card (footer is now part of general form)
|
|
256
|
+
const saveBtn = el.querySelector<HTMLButtonElement>(".btn");
|
|
257
|
+
saveBtn?.click();
|
|
258
|
+
await el.updateComplete;
|
|
259
|
+
|
|
260
|
+
expect(detail).not.toBeNull();
|
|
261
|
+
const d = detail as unknown as SettingsSaveDetail;
|
|
262
|
+
expect(d.endpoint).toBe("/dash/settings");
|
|
263
|
+
expect(d.section).toBe("general");
|
|
264
|
+
expect(d.data.siteFooter).toBe("New footer");
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("dispatches jant:settings-save for SEO section", async () => {
|
|
268
|
+
const el = await createElement();
|
|
269
|
+
const checkboxes = el.querySelectorAll<HTMLInputElement>(
|
|
270
|
+
'input[type="checkbox"]',
|
|
271
|
+
);
|
|
272
|
+
const seoCheckbox = checkboxes[0];
|
|
273
|
+
|
|
274
|
+
// Toggle to make dirty
|
|
275
|
+
seoCheckbox?.click();
|
|
276
|
+
await el.updateComplete;
|
|
277
|
+
|
|
278
|
+
let detail: SettingsSaveDetail | null = null;
|
|
279
|
+
el.addEventListener("jant:settings-save", (event) => {
|
|
280
|
+
const customEvent = event as CustomEvent<SettingsSaveDetail>;
|
|
281
|
+
detail = customEvent.detail;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Find SEO Save button (section after the <hr> divider)
|
|
285
|
+
const hr = el.querySelector("hr");
|
|
286
|
+
const seoSection = hr?.nextElementSibling;
|
|
287
|
+
const seoSaveBtn = seoSection?.querySelector<HTMLButtonElement>(".btn");
|
|
288
|
+
seoSaveBtn?.click();
|
|
289
|
+
await el.updateComplete;
|
|
290
|
+
|
|
291
|
+
expect(detail).not.toBeNull();
|
|
292
|
+
const d = detail as unknown as SettingsSaveDetail;
|
|
293
|
+
expect(d.endpoint).toBe("/dash/settings/seo");
|
|
294
|
+
expect(d.section).toBe("seo");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("shows loading spinner during save", async () => {
|
|
298
|
+
const el = await createElement();
|
|
299
|
+
const siteNameInput = requireElement(
|
|
300
|
+
el.querySelector<HTMLInputElement>('input[type="text"]'),
|
|
301
|
+
"expected site name input",
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Make dirty and save
|
|
305
|
+
siteNameInput.value = "Loading test";
|
|
306
|
+
siteNameInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
307
|
+
await el.updateComplete;
|
|
308
|
+
|
|
309
|
+
const saveBtn = el.querySelector<HTMLButtonElement>(".btn");
|
|
310
|
+
saveBtn?.click();
|
|
311
|
+
await el.updateComplete;
|
|
312
|
+
|
|
313
|
+
// Save button should be disabled during loading
|
|
314
|
+
expect(saveBtn?.disabled).toBe(true);
|
|
315
|
+
// Spinner should be visible
|
|
316
|
+
const spinner = saveBtn?.querySelector("svg.animate-spin");
|
|
317
|
+
expect(spinner).not.toBeNull();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for the collection form Lit component.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CollectionFormLabels {
|
|
6
|
+
titleLabel: string;
|
|
7
|
+
titlePlaceholder: string;
|
|
8
|
+
slugLabel: string;
|
|
9
|
+
slugHelp: string;
|
|
10
|
+
descriptionLabel: string;
|
|
11
|
+
descriptionPlaceholder: string;
|
|
12
|
+
iconLabel: string;
|
|
13
|
+
chooseIcon: string;
|
|
14
|
+
removeIcon: string;
|
|
15
|
+
dialogTitle: string;
|
|
16
|
+
dialogClose: string;
|
|
17
|
+
searchIconsPlaceholder: string;
|
|
18
|
+
sortOrderLabel: string;
|
|
19
|
+
sortNewest: string;
|
|
20
|
+
sortOldest: string;
|
|
21
|
+
sortRatingDesc: string;
|
|
22
|
+
sortRatingAsc: string;
|
|
23
|
+
submitLabel: string;
|
|
24
|
+
cancelLabel: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CollectionFormInitial {
|
|
28
|
+
title: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
description: string;
|
|
31
|
+
sortOrder: string;
|
|
32
|
+
icon: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CollectionSubmitDetail {
|
|
36
|
+
endpoint: string;
|
|
37
|
+
data: {
|
|
38
|
+
title: string;
|
|
39
|
+
slug: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
sortOrder?: string;
|
|
43
|
+
};
|
|
44
|
+
isEdit: boolean;
|
|
45
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose Dialog Types
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for jant-compose-dialog and jant-compose-editor
|
|
5
|
+
* Lit Web Components, and the compose bridge script.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type ComposeFormat = "note" | "link" | "quote";
|
|
9
|
+
|
|
10
|
+
export interface ComposeAttachment {
|
|
11
|
+
clientId: string;
|
|
12
|
+
file: File;
|
|
13
|
+
previewUrl: string;
|
|
14
|
+
status: "pending" | "uploading" | "done" | "error";
|
|
15
|
+
mediaId: string | null;
|
|
16
|
+
alt: string;
|
|
17
|
+
error: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ComposeLabels {
|
|
21
|
+
cancel: string;
|
|
22
|
+
note: string;
|
|
23
|
+
link: string;
|
|
24
|
+
quote: string;
|
|
25
|
+
saveDraft: string;
|
|
26
|
+
saveAsDraft: string;
|
|
27
|
+
discard: string;
|
|
28
|
+
titlePlaceholder: string;
|
|
29
|
+
bodyPlaceholder: string;
|
|
30
|
+
urlPlaceholder: string;
|
|
31
|
+
linkTitlePlaceholder: string;
|
|
32
|
+
thoughtsPlaceholder: string;
|
|
33
|
+
quotePlaceholder: string;
|
|
34
|
+
authorPlaceholder: string;
|
|
35
|
+
sourcePlaceholder: string;
|
|
36
|
+
attachedText: string;
|
|
37
|
+
attachedTextPlaceholder: string;
|
|
38
|
+
attachedTextHint: string;
|
|
39
|
+
done: string;
|
|
40
|
+
media: string;
|
|
41
|
+
score: string;
|
|
42
|
+
title: string;
|
|
43
|
+
collection: string;
|
|
44
|
+
searchCollections: string;
|
|
45
|
+
noCollections: string;
|
|
46
|
+
post: string;
|
|
47
|
+
addAlt: string;
|
|
48
|
+
addAltTitle: string;
|
|
49
|
+
altPlaceholder: string;
|
|
50
|
+
altHint: string;
|
|
51
|
+
addMore: string;
|
|
52
|
+
uploading: string;
|
|
53
|
+
published: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ComposeSubmitDetail {
|
|
57
|
+
format: ComposeFormat;
|
|
58
|
+
title: string;
|
|
59
|
+
body: string;
|
|
60
|
+
url: string;
|
|
61
|
+
quoteText: string;
|
|
62
|
+
quoteAuthor: string;
|
|
63
|
+
status: "published" | "draft";
|
|
64
|
+
rating: number;
|
|
65
|
+
collectionIds: number[];
|
|
66
|
+
mediaIds: string[];
|
|
67
|
+
mediaAlts: Record<string, string>;
|
|
68
|
+
attachedText: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ComposeCollection {
|
|
72
|
+
id: number;
|
|
73
|
+
title: string;
|
|
74
|
+
iconHtml: string;
|
|
75
|
+
}
|