@jant/core 0.3.7 → 0.3.8
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/dist/app.js +4 -0
- package/dist/client.js +1 -0
- package/dist/db/schema.js +13 -0
- package/dist/i18n/locales/en.js +1 -1
- package/dist/i18n/locales/zh-Hans.js +1 -1
- package/dist/i18n/locales/zh-Hant.js +1 -1
- package/dist/lib/image.js +3 -3
- package/dist/lib/media-helpers.js +43 -0
- package/dist/lib/nav-reorder.js +27 -0
- package/dist/lib/navigation.js +35 -0
- package/dist/lib/theme-components.js +49 -0
- package/dist/routes/api/timeline.js +115 -0
- package/dist/routes/api/upload.js +9 -5
- package/dist/routes/dash/navigation.js +274 -0
- package/dist/routes/pages/archive.js +14 -27
- package/dist/routes/pages/collection.js +10 -19
- package/dist/routes/pages/home.js +83 -126
- package/dist/routes/pages/page.js +19 -38
- package/dist/routes/pages/post.js +38 -51
- package/dist/routes/pages/search.js +13 -26
- package/dist/services/index.js +3 -1
- package/dist/services/media.js +1 -1
- package/dist/services/navigation.js +115 -0
- package/dist/services/post.js +26 -1
- package/dist/theme/components/PostList.js +5 -0
- package/dist/theme/components/index.js +2 -0
- package/dist/theme/components/timeline/ArticleCard.js +50 -0
- package/dist/theme/components/timeline/ImageCard.js +86 -0
- package/dist/theme/components/timeline/LinkCard.js +62 -0
- package/dist/theme/components/timeline/NoteCard.js +37 -0
- package/dist/theme/components/timeline/QuoteCard.js +51 -0
- package/dist/theme/components/timeline/ThreadPreview.js +52 -0
- package/dist/theme/components/timeline/TimelineFeed.js +43 -0
- package/dist/theme/components/timeline/TimelineItem.js +25 -0
- package/dist/theme/components/timeline/index.js +8 -0
- package/dist/theme/layouts/DashLayout.js +8 -0
- package/dist/theme/layouts/SiteLayout.js +160 -0
- package/dist/theme/layouts/index.js +1 -0
- package/dist/types/sortablejs.d.js +5 -0
- package/package.json +3 -2
- package/src/__tests__/helpers/db.ts +10 -0
- package/src/app.tsx +4 -0
- package/src/client.ts +1 -0
- package/src/db/migrations/0003_add_navigation_links.sql +8 -0
- package/src/db/migrations/meta/0003_snapshot.json +821 -0
- package/src/db/migrations/meta/_journal.json +14 -0
- package/src/db/schema.ts +13 -0
- package/src/i18n/locales/en.po +100 -32
- package/src/i18n/locales/en.ts +1 -1
- package/src/i18n/locales/zh-Hans.po +102 -55
- package/src/i18n/locales/zh-Hans.ts +1 -1
- package/src/i18n/locales/zh-Hant.po +102 -55
- package/src/i18n/locales/zh-Hant.ts +1 -1
- package/src/index.ts +5 -0
- package/src/lib/__tests__/theme-components.test.ts +107 -0
- package/src/lib/image.ts +3 -3
- package/src/lib/media-helpers.ts +54 -0
- package/src/lib/nav-reorder.ts +26 -0
- package/src/lib/navigation.ts +46 -0
- package/src/lib/theme-components.ts +76 -0
- package/src/routes/api/__tests__/posts.test.ts +8 -8
- package/src/routes/api/__tests__/timeline.test.ts +242 -0
- package/src/routes/api/timeline.tsx +145 -0
- package/src/routes/api/upload.ts +9 -5
- package/src/routes/dash/navigation.tsx +306 -0
- package/src/routes/pages/archive.tsx +15 -23
- package/src/routes/pages/collection.tsx +8 -15
- package/src/routes/pages/home.tsx +111 -122
- package/src/routes/pages/page.tsx +17 -30
- package/src/routes/pages/post.tsx +33 -42
- package/src/routes/pages/search.tsx +18 -22
- package/src/services/__tests__/media.test.ts +34 -7
- package/src/services/__tests__/navigation.test.ts +213 -0
- package/src/services/__tests__/post-timeline.test.ts +220 -0
- package/src/services/index.ts +7 -0
- package/src/services/media.ts +2 -1
- package/src/services/navigation.ts +165 -0
- package/src/services/post.ts +48 -1
- package/src/styles/components.css +59 -0
- package/src/theme/components/PostList.tsx +7 -0
- package/src/theme/components/index.ts +12 -0
- package/src/theme/components/timeline/ArticleCard.tsx +57 -0
- package/src/theme/components/timeline/ImageCard.tsx +80 -0
- package/src/theme/components/timeline/LinkCard.tsx +66 -0
- package/src/theme/components/timeline/NoteCard.tsx +41 -0
- package/src/theme/components/timeline/QuoteCard.tsx +55 -0
- package/src/theme/components/timeline/ThreadPreview.tsx +49 -0
- package/src/theme/components/timeline/TimelineFeed.tsx +52 -0
- package/src/theme/components/timeline/TimelineItem.tsx +39 -0
- package/src/theme/components/timeline/index.ts +8 -0
- package/src/theme/layouts/DashLayout.tsx +10 -0
- package/src/theme/layouts/SiteLayout.tsx +184 -0
- package/src/theme/layouts/index.ts +1 -0
- package/src/types/sortablejs.d.ts +23 -0
- package/src/types.ts +61 -0
- package/dist/app.d.ts +0 -38
- package/dist/app.d.ts.map +0 -1
- package/dist/auth.d.ts +0 -25
- package/dist/auth.d.ts.map +0 -1
- package/dist/db/index.d.ts +0 -10
- package/dist/db/index.d.ts.map +0 -1
- package/dist/db/schema.d.ts +0 -1543
- package/dist/db/schema.d.ts.map +0 -1
- package/dist/i18n/Trans.d.ts +0 -25
- package/dist/i18n/Trans.d.ts.map +0 -1
- package/dist/i18n/context.d.ts +0 -69
- package/dist/i18n/context.d.ts.map +0 -1
- package/dist/i18n/detect.d.ts +0 -20
- package/dist/i18n/detect.d.ts.map +0 -1
- package/dist/i18n/i18n.d.ts +0 -32
- package/dist/i18n/i18n.d.ts.map +0 -1
- package/dist/i18n/index.d.ts +0 -41
- package/dist/i18n/index.d.ts.map +0 -1
- package/dist/i18n/locales/en.d.ts +0 -3
- package/dist/i18n/locales/en.d.ts.map +0 -1
- package/dist/i18n/locales/zh-Hans.d.ts +0 -3
- package/dist/i18n/locales/zh-Hans.d.ts.map +0 -1
- package/dist/i18n/locales/zh-Hant.d.ts +0 -3
- package/dist/i18n/locales/zh-Hant.d.ts.map +0 -1
- package/dist/i18n/locales.d.ts +0 -11
- package/dist/i18n/locales.d.ts.map +0 -1
- package/dist/i18n/middleware.d.ts +0 -21
- package/dist/i18n/middleware.d.ts.map +0 -1
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -83
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/constants.d.ts +0 -37
- package/dist/lib/constants.d.ts.map +0 -1
- package/dist/lib/image.d.ts +0 -73
- package/dist/lib/image.d.ts.map +0 -1
- package/dist/lib/index.d.ts +0 -9
- package/dist/lib/index.d.ts.map +0 -1
- package/dist/lib/markdown.d.ts +0 -60
- package/dist/lib/markdown.d.ts.map +0 -1
- package/dist/lib/schemas.d.ts +0 -130
- package/dist/lib/schemas.d.ts.map +0 -1
- package/dist/lib/sqid.d.ts +0 -60
- package/dist/lib/sqid.d.ts.map +0 -1
- package/dist/lib/sse.d.ts +0 -192
- package/dist/lib/sse.d.ts.map +0 -1
- package/dist/lib/theme.d.ts +0 -44
- package/dist/lib/theme.d.ts.map +0 -1
- package/dist/lib/time.d.ts +0 -90
- package/dist/lib/time.d.ts.map +0 -1
- package/dist/lib/url.d.ts +0 -82
- package/dist/lib/url.d.ts.map +0 -1
- package/dist/middleware/auth.d.ts +0 -24
- package/dist/middleware/auth.d.ts.map +0 -1
- package/dist/middleware/onboarding.d.ts +0 -26
- package/dist/middleware/onboarding.d.ts.map +0 -1
- package/dist/routes/api/posts.d.ts +0 -13
- package/dist/routes/api/posts.d.ts.map +0 -1
- package/dist/routes/api/search.d.ts +0 -13
- package/dist/routes/api/search.d.ts.map +0 -1
- package/dist/routes/api/upload.d.ts +0 -16
- package/dist/routes/api/upload.d.ts.map +0 -1
- package/dist/routes/dash/collections.d.ts +0 -13
- package/dist/routes/dash/collections.d.ts.map +0 -1
- package/dist/routes/dash/index.d.ts +0 -15
- package/dist/routes/dash/index.d.ts.map +0 -1
- package/dist/routes/dash/media.d.ts +0 -16
- package/dist/routes/dash/media.d.ts.map +0 -1
- package/dist/routes/dash/pages.d.ts +0 -15
- package/dist/routes/dash/pages.d.ts.map +0 -1
- package/dist/routes/dash/posts.d.ts +0 -13
- package/dist/routes/dash/posts.d.ts.map +0 -1
- package/dist/routes/dash/redirects.d.ts +0 -13
- package/dist/routes/dash/redirects.d.ts.map +0 -1
- package/dist/routes/dash/settings.d.ts +0 -15
- package/dist/routes/dash/settings.d.ts.map +0 -1
- package/dist/routes/feed/rss.d.ts +0 -13
- package/dist/routes/feed/rss.d.ts.map +0 -1
- package/dist/routes/feed/sitemap.d.ts +0 -13
- package/dist/routes/feed/sitemap.d.ts.map +0 -1
- package/dist/routes/pages/archive.d.ts +0 -15
- package/dist/routes/pages/archive.d.ts.map +0 -1
- package/dist/routes/pages/collection.d.ts +0 -13
- package/dist/routes/pages/collection.d.ts.map +0 -1
- package/dist/routes/pages/home.d.ts +0 -13
- package/dist/routes/pages/home.d.ts.map +0 -1
- package/dist/routes/pages/page.d.ts +0 -15
- package/dist/routes/pages/page.d.ts.map +0 -1
- package/dist/routes/pages/post.d.ts +0 -13
- package/dist/routes/pages/post.d.ts.map +0 -1
- package/dist/routes/pages/search.d.ts +0 -13
- package/dist/routes/pages/search.d.ts.map +0 -1
- package/dist/services/collection.d.ts +0 -32
- package/dist/services/collection.d.ts.map +0 -1
- package/dist/services/index.d.ts +0 -28
- package/dist/services/index.d.ts.map +0 -1
- package/dist/services/media.d.ts +0 -34
- package/dist/services/media.d.ts.map +0 -1
- package/dist/services/post.d.ts +0 -31
- package/dist/services/post.d.ts.map +0 -1
- package/dist/services/redirect.d.ts +0 -15
- package/dist/services/redirect.d.ts.map +0 -1
- package/dist/services/search.d.ts +0 -26
- package/dist/services/search.d.ts.map +0 -1
- package/dist/services/settings.d.ts +0 -18
- package/dist/services/settings.d.ts.map +0 -1
- package/dist/theme/color-themes.d.ts +0 -30
- package/dist/theme/color-themes.d.ts.map +0 -1
- package/dist/theme/components/ActionButtons.d.ts +0 -43
- package/dist/theme/components/ActionButtons.d.ts.map +0 -1
- package/dist/theme/components/CrudPageHeader.d.ts +0 -23
- package/dist/theme/components/CrudPageHeader.d.ts.map +0 -1
- package/dist/theme/components/DangerZone.d.ts +0 -36
- package/dist/theme/components/DangerZone.d.ts.map +0 -1
- package/dist/theme/components/EmptyState.d.ts +0 -27
- package/dist/theme/components/EmptyState.d.ts.map +0 -1
- package/dist/theme/components/ListItemRow.d.ts +0 -15
- package/dist/theme/components/ListItemRow.d.ts.map +0 -1
- package/dist/theme/components/MediaGallery.d.ts +0 -13
- package/dist/theme/components/MediaGallery.d.ts.map +0 -1
- package/dist/theme/components/PageForm.d.ts +0 -14
- package/dist/theme/components/PageForm.d.ts.map +0 -1
- package/dist/theme/components/Pagination.d.ts +0 -46
- package/dist/theme/components/Pagination.d.ts.map +0 -1
- package/dist/theme/components/PostForm.d.ts +0 -16
- package/dist/theme/components/PostForm.d.ts.map +0 -1
- package/dist/theme/components/PostList.d.ts +0 -10
- package/dist/theme/components/PostList.d.ts.map +0 -1
- package/dist/theme/components/ThreadView.d.ts +0 -15
- package/dist/theme/components/ThreadView.d.ts.map +0 -1
- package/dist/theme/components/TypeBadge.d.ts +0 -12
- package/dist/theme/components/TypeBadge.d.ts.map +0 -1
- package/dist/theme/components/VisibilityBadge.d.ts +0 -12
- package/dist/theme/components/VisibilityBadge.d.ts.map +0 -1
- package/dist/theme/components/index.d.ts +0 -14
- package/dist/theme/components/index.d.ts.map +0 -1
- package/dist/theme/index.d.ts +0 -21
- package/dist/theme/index.d.ts.map +0 -1
- package/dist/theme/layouts/BaseLayout.d.ts +0 -23
- package/dist/theme/layouts/BaseLayout.d.ts.map +0 -1
- package/dist/theme/layouts/DashLayout.d.ts +0 -17
- package/dist/theme/layouts/DashLayout.d.ts.map +0 -1
- package/dist/theme/layouts/index.d.ts +0 -3
- package/dist/theme/layouts/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -237
- package/dist/types.d.ts.map +0 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
2
|
+
import { getSiteName } from "../../lib/config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Dashboard Navigation Links Routes
|
|
5
|
+
*/ import { Hono } from "hono";
|
|
6
|
+
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
+
import { DashLayout } from "../../theme/layouts/index.js";
|
|
8
|
+
import { EmptyState, ListItemRow, ActionButtons, CrudPageHeader } from "../../theme/components/index.js";
|
|
9
|
+
import { dsRedirect, dsToast } from "../../lib/sse.js";
|
|
10
|
+
export const navigationRoutes = new Hono();
|
|
11
|
+
function NavigationListContent({ links }) {
|
|
12
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
14
|
+
children: [
|
|
15
|
+
/*#__PURE__*/ _jsx(CrudPageHeader, {
|
|
16
|
+
title: $__i18n._({
|
|
17
|
+
id: "UxKoFf",
|
|
18
|
+
message: "Navigation"
|
|
19
|
+
}),
|
|
20
|
+
ctaLabel: $__i18n._({
|
|
21
|
+
id: "aaGV/9",
|
|
22
|
+
message: "New Link"
|
|
23
|
+
}),
|
|
24
|
+
ctaHref: "/dash/navigation/new"
|
|
25
|
+
}),
|
|
26
|
+
links.length === 0 ? /*#__PURE__*/ _jsx(EmptyState, {
|
|
27
|
+
message: $__i18n._({
|
|
28
|
+
id: "wdGjkd",
|
|
29
|
+
message: "No navigation links configured."
|
|
30
|
+
}),
|
|
31
|
+
ctaText: $__i18n._({
|
|
32
|
+
id: "aaGV/9",
|
|
33
|
+
message: "New Link"
|
|
34
|
+
}),
|
|
35
|
+
ctaHref: "/dash/navigation/new"
|
|
36
|
+
}) : /*#__PURE__*/ _jsx(_Fragment, {
|
|
37
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
38
|
+
id: "nav-links-list",
|
|
39
|
+
class: "flex flex-col divide-y",
|
|
40
|
+
children: links.map((link)=>/*#__PURE__*/ _jsx(ListItemRow, {
|
|
41
|
+
actions: /*#__PURE__*/ _jsx(ActionButtons, {
|
|
42
|
+
editHref: `/dash/navigation/${link.id}/edit`,
|
|
43
|
+
editLabel: $__i18n._({
|
|
44
|
+
id: "ePK91l",
|
|
45
|
+
message: "Edit"
|
|
46
|
+
}),
|
|
47
|
+
deleteAction: `/dash/navigation/${link.id}/delete`,
|
|
48
|
+
deleteLabel: $__i18n._({
|
|
49
|
+
id: "cnGeoo",
|
|
50
|
+
message: "Delete"
|
|
51
|
+
})
|
|
52
|
+
}),
|
|
53
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
54
|
+
class: "flex items-center gap-3 cursor-grab",
|
|
55
|
+
"data-id": link.id,
|
|
56
|
+
children: [
|
|
57
|
+
/*#__PURE__*/ _jsx("span", {
|
|
58
|
+
class: "text-muted-foreground select-none",
|
|
59
|
+
children: "⠿"
|
|
60
|
+
}),
|
|
61
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
62
|
+
class: "flex items-center gap-2",
|
|
63
|
+
children: [
|
|
64
|
+
/*#__PURE__*/ _jsx("span", {
|
|
65
|
+
class: "font-medium",
|
|
66
|
+
children: link.label
|
|
67
|
+
}),
|
|
68
|
+
/*#__PURE__*/ _jsx("code", {
|
|
69
|
+
class: "text-sm text-muted-foreground bg-muted px-1 rounded",
|
|
70
|
+
children: link.url
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
}, link.id))
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function NavigationFormContent({ link, isEdit }) {
|
|
83
|
+
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
84
|
+
const title = isEdit ? $__i18n._({
|
|
85
|
+
id: "gDx5MG",
|
|
86
|
+
message: "Edit Link"
|
|
87
|
+
}) : $__i18n._({
|
|
88
|
+
id: "aaGV/9",
|
|
89
|
+
message: "New Link"
|
|
90
|
+
});
|
|
91
|
+
const signals = JSON.stringify({
|
|
92
|
+
label: link?.label ?? "",
|
|
93
|
+
url: link?.url ?? ""
|
|
94
|
+
}).replace(/</g, "\\u003c");
|
|
95
|
+
const action = isEdit ? `/dash/navigation/${link?.id}` : "/dash/navigation";
|
|
96
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
97
|
+
children: [
|
|
98
|
+
/*#__PURE__*/ _jsx("h1", {
|
|
99
|
+
class: "text-2xl font-semibold mb-6",
|
|
100
|
+
children: title
|
|
101
|
+
}),
|
|
102
|
+
/*#__PURE__*/ _jsxs("form", {
|
|
103
|
+
"data-signals": signals,
|
|
104
|
+
"data-on:submit__prevent": `@post('${action}')`,
|
|
105
|
+
class: "flex flex-col gap-4 max-w-lg",
|
|
106
|
+
children: [
|
|
107
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
108
|
+
class: "field",
|
|
109
|
+
children: [
|
|
110
|
+
/*#__PURE__*/ _jsx("label", {
|
|
111
|
+
class: "label",
|
|
112
|
+
children: $__i18n._({
|
|
113
|
+
id: "87a/t/",
|
|
114
|
+
message: "Label"
|
|
115
|
+
})
|
|
116
|
+
}),
|
|
117
|
+
/*#__PURE__*/ _jsx("input", {
|
|
118
|
+
type: "text",
|
|
119
|
+
"data-bind": "label",
|
|
120
|
+
class: "input",
|
|
121
|
+
placeholder: "Home",
|
|
122
|
+
required: true
|
|
123
|
+
}),
|
|
124
|
+
/*#__PURE__*/ _jsx("p", {
|
|
125
|
+
class: "text-xs text-muted-foreground mt-1",
|
|
126
|
+
children: $__i18n._({
|
|
127
|
+
id: "+bHzpy",
|
|
128
|
+
message: "Display text for the link"
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
]
|
|
132
|
+
}),
|
|
133
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
134
|
+
class: "field",
|
|
135
|
+
children: [
|
|
136
|
+
/*#__PURE__*/ _jsx("label", {
|
|
137
|
+
class: "label",
|
|
138
|
+
children: $__i18n._({
|
|
139
|
+
id: "IagCbF",
|
|
140
|
+
message: "URL"
|
|
141
|
+
})
|
|
142
|
+
}),
|
|
143
|
+
/*#__PURE__*/ _jsx("input", {
|
|
144
|
+
type: "text",
|
|
145
|
+
"data-bind": "url",
|
|
146
|
+
class: "input",
|
|
147
|
+
placeholder: "/archive or https://...",
|
|
148
|
+
required: true
|
|
149
|
+
}),
|
|
150
|
+
/*#__PURE__*/ _jsx("p", {
|
|
151
|
+
class: "text-xs text-muted-foreground mt-1",
|
|
152
|
+
children: $__i18n._({
|
|
153
|
+
id: "QEbNBb",
|
|
154
|
+
message: "Path (e.g. /archive) or full URL (e.g. https://example.com)"
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
}),
|
|
159
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
160
|
+
class: "flex gap-2",
|
|
161
|
+
children: [
|
|
162
|
+
/*#__PURE__*/ _jsx("button", {
|
|
163
|
+
type: "submit",
|
|
164
|
+
class: "btn",
|
|
165
|
+
children: isEdit ? $__i18n._({
|
|
166
|
+
id: "IUwGEM",
|
|
167
|
+
message: "Save Changes"
|
|
168
|
+
}) : $__i18n._({
|
|
169
|
+
id: "kd7eBB",
|
|
170
|
+
message: "Create Link"
|
|
171
|
+
})
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx("a", {
|
|
174
|
+
href: "/dash/navigation",
|
|
175
|
+
class: "btn-outline",
|
|
176
|
+
children: $__i18n._({
|
|
177
|
+
id: "dEgA5A",
|
|
178
|
+
message: "Cancel"
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
]
|
|
182
|
+
})
|
|
183
|
+
]
|
|
184
|
+
})
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// List navigation links
|
|
189
|
+
navigationRoutes.get("/", async (c)=>{
|
|
190
|
+
const siteName = await getSiteName(c);
|
|
191
|
+
const links = await c.var.services.navigationLinks.list();
|
|
192
|
+
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
193
|
+
c: c,
|
|
194
|
+
title: "Navigation",
|
|
195
|
+
siteName: siteName,
|
|
196
|
+
currentPath: "/dash/navigation",
|
|
197
|
+
children: /*#__PURE__*/ _jsx(NavigationListContent, {
|
|
198
|
+
links: links
|
|
199
|
+
})
|
|
200
|
+
}));
|
|
201
|
+
});
|
|
202
|
+
// New link form
|
|
203
|
+
navigationRoutes.get("/new", async (c)=>{
|
|
204
|
+
const siteName = await getSiteName(c);
|
|
205
|
+
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
206
|
+
c: c,
|
|
207
|
+
title: "New Link",
|
|
208
|
+
siteName: siteName,
|
|
209
|
+
currentPath: "/dash/navigation",
|
|
210
|
+
children: /*#__PURE__*/ _jsx(NavigationFormContent, {})
|
|
211
|
+
}));
|
|
212
|
+
});
|
|
213
|
+
// Create link
|
|
214
|
+
navigationRoutes.post("/", async (c)=>{
|
|
215
|
+
const body = await c.req.json();
|
|
216
|
+
if (!body.label || !body.url) {
|
|
217
|
+
return dsToast("Label and URL are required", "error");
|
|
218
|
+
}
|
|
219
|
+
await c.var.services.navigationLinks.create({
|
|
220
|
+
label: body.label,
|
|
221
|
+
url: body.url
|
|
222
|
+
});
|
|
223
|
+
return dsRedirect("/dash/navigation");
|
|
224
|
+
});
|
|
225
|
+
// Reorder links (must be before /:id to avoid "reorder" matching as :id)
|
|
226
|
+
navigationRoutes.post("/reorder", async (c)=>{
|
|
227
|
+
const body = await c.req.json();
|
|
228
|
+
if (!Array.isArray(body.ids)) {
|
|
229
|
+
return dsToast("Invalid request", "error");
|
|
230
|
+
}
|
|
231
|
+
await c.var.services.navigationLinks.reorder(body.ids);
|
|
232
|
+
return dsToast("Order saved");
|
|
233
|
+
});
|
|
234
|
+
// Edit link form
|
|
235
|
+
navigationRoutes.get("/:id/edit", async (c)=>{
|
|
236
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
237
|
+
if (isNaN(id)) return c.notFound();
|
|
238
|
+
const link = await c.var.services.navigationLinks.getById(id);
|
|
239
|
+
if (!link) return c.notFound();
|
|
240
|
+
const siteName = await getSiteName(c);
|
|
241
|
+
return c.html(/*#__PURE__*/ _jsx(DashLayout, {
|
|
242
|
+
c: c,
|
|
243
|
+
title: "Edit Link",
|
|
244
|
+
siteName: siteName,
|
|
245
|
+
currentPath: "/dash/navigation",
|
|
246
|
+
children: /*#__PURE__*/ _jsx(NavigationFormContent, {
|
|
247
|
+
link: link,
|
|
248
|
+
isEdit: true
|
|
249
|
+
})
|
|
250
|
+
}));
|
|
251
|
+
});
|
|
252
|
+
// Update link
|
|
253
|
+
navigationRoutes.post("/:id", async (c)=>{
|
|
254
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
255
|
+
if (isNaN(id)) return c.notFound();
|
|
256
|
+
const body = await c.req.json();
|
|
257
|
+
if (!body.label || !body.url) {
|
|
258
|
+
return dsToast("Label and URL are required", "error");
|
|
259
|
+
}
|
|
260
|
+
const updated = await c.var.services.navigationLinks.update(id, {
|
|
261
|
+
label: body.label,
|
|
262
|
+
url: body.url
|
|
263
|
+
});
|
|
264
|
+
if (!updated) return c.notFound();
|
|
265
|
+
return dsRedirect("/dash/navigation");
|
|
266
|
+
});
|
|
267
|
+
// Delete link
|
|
268
|
+
navigationRoutes.post("/:id/delete", async (c)=>{
|
|
269
|
+
const id = parseInt(c.req.param("id"), 10);
|
|
270
|
+
if (!isNaN(id)) {
|
|
271
|
+
await c.var.services.navigationLinks.delete(id);
|
|
272
|
+
}
|
|
273
|
+
return dsRedirect("/dash/navigation");
|
|
274
|
+
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
-
import { getSiteName } from "../../lib/config.js";
|
|
3
2
|
/**
|
|
4
3
|
* Archive Page Route
|
|
5
4
|
*
|
|
6
5
|
* Shows all posts, optionally filtered by type
|
|
7
6
|
*/ import { Hono } from "hono";
|
|
8
7
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
9
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
8
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
10
9
|
import { Pagination } from "../../theme/components/index.js";
|
|
11
10
|
import { POST_TYPES } from "../../types.js";
|
|
12
11
|
import * as sqid from "../../lib/sqid.js";
|
|
13
12
|
import * as time from "../../lib/time.js";
|
|
13
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
14
14
|
const PAGE_SIZE = 50;
|
|
15
15
|
export const archiveRoutes = new Hono();
|
|
16
16
|
function getTypeLabel(type) {
|
|
@@ -89,7 +89,6 @@ function ArchiveContent({ displayPosts, hasMore, nextCursor, type, grouped, repl
|
|
|
89
89
|
message: "Archive"
|
|
90
90
|
});
|
|
91
91
|
return /*#__PURE__*/ _jsxs("div", {
|
|
92
|
-
class: "container py-8",
|
|
93
92
|
children: [
|
|
94
93
|
/*#__PURE__*/ _jsxs("header", {
|
|
95
94
|
class: "mb-8",
|
|
@@ -183,21 +182,6 @@ function ArchiveContent({ displayPosts, hasMore, nextCursor, type, grouped, repl
|
|
|
183
182
|
baseUrl: type ? `/archive?type=${type}` : "/archive",
|
|
184
183
|
hasMore: hasMore,
|
|
185
184
|
nextCursor: nextCursor
|
|
186
|
-
}),
|
|
187
|
-
/*#__PURE__*/ _jsx("nav", {
|
|
188
|
-
class: "mt-4",
|
|
189
|
-
children: /*#__PURE__*/ _jsxs("a", {
|
|
190
|
-
href: "/",
|
|
191
|
-
class: "text-sm hover:underline",
|
|
192
|
-
children: [
|
|
193
|
-
"←",
|
|
194
|
-
" ",
|
|
195
|
-
$__i18n._({
|
|
196
|
-
id: "x4RuFo",
|
|
197
|
-
message: "Back to home"
|
|
198
|
-
})
|
|
199
|
-
]
|
|
200
|
-
})
|
|
201
185
|
})
|
|
202
186
|
]
|
|
203
187
|
});
|
|
@@ -209,7 +193,7 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
209
193
|
// Parse cursor
|
|
210
194
|
const cursorParam = c.req.query("cursor");
|
|
211
195
|
const cursor = cursorParam ? parseInt(cursorParam, 10) : undefined;
|
|
212
|
-
const
|
|
196
|
+
const navData = await getNavigationData(c);
|
|
213
197
|
// Fetch one extra to check for more
|
|
214
198
|
const posts = await c.var.services.posts.list({
|
|
215
199
|
type,
|
|
@@ -240,15 +224,18 @@ archiveRoutes.get("/", async (c)=>{
|
|
|
240
224
|
grouped.get(key).push(post);
|
|
241
225
|
}
|
|
242
226
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
243
|
-
title: `Archive - ${siteName}`,
|
|
227
|
+
title: `Archive - ${navData.siteName}`,
|
|
244
228
|
c: c,
|
|
245
|
-
children: /*#__PURE__*/ _jsx(
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
229
|
+
children: /*#__PURE__*/ _jsx(SiteLayout, {
|
|
230
|
+
...navData,
|
|
231
|
+
children: /*#__PURE__*/ _jsx(ArchiveContent, {
|
|
232
|
+
displayPosts: displayPosts,
|
|
233
|
+
hasMore: hasMore,
|
|
234
|
+
nextCursor: nextCursor,
|
|
235
|
+
type: type,
|
|
236
|
+
grouped: grouped,
|
|
237
|
+
replyCounts: replyCounts
|
|
238
|
+
})
|
|
252
239
|
})
|
|
253
240
|
}));
|
|
254
241
|
});
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
|
|
2
|
-
import { getSiteName } from "../../lib/config.js";
|
|
3
2
|
/**
|
|
4
3
|
* Collection Page Route
|
|
5
4
|
*/ import { Hono } from "hono";
|
|
6
5
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
7
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
6
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
8
7
|
import * as sqid from "../../lib/sqid.js";
|
|
9
8
|
import * as time from "../../lib/time.js";
|
|
9
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
10
10
|
export const collectionRoutes = new Hono();
|
|
11
11
|
function CollectionContent({ collection, posts }) {
|
|
12
12
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
13
13
|
return /*#__PURE__*/ _jsxs("div", {
|
|
14
|
-
class: "container py-8",
|
|
15
14
|
children: [
|
|
16
15
|
/*#__PURE__*/ _jsxs("header", {
|
|
17
16
|
class: "mb-8",
|
|
@@ -61,17 +60,6 @@ function CollectionContent({ collection, posts }) {
|
|
|
61
60
|
})
|
|
62
61
|
]
|
|
63
62
|
}, post.id))
|
|
64
|
-
}),
|
|
65
|
-
/*#__PURE__*/ _jsx("nav", {
|
|
66
|
-
class: "mt-8",
|
|
67
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
68
|
-
href: "/",
|
|
69
|
-
class: "text-sm hover:underline",
|
|
70
|
-
children: $__i18n._({
|
|
71
|
-
id: "biOepV",
|
|
72
|
-
message: "← Back to home"
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
63
|
})
|
|
76
64
|
]
|
|
77
65
|
});
|
|
@@ -81,14 +69,17 @@ collectionRoutes.get("/:path", async (c)=>{
|
|
|
81
69
|
const collection = await c.var.services.collections.getByPath(path);
|
|
82
70
|
if (!collection) return c.notFound();
|
|
83
71
|
const posts = await c.var.services.collections.getPosts(collection.id);
|
|
84
|
-
const
|
|
72
|
+
const navData = await getNavigationData(c);
|
|
85
73
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
86
|
-
title: `${collection.title} - ${siteName}`,
|
|
74
|
+
title: `${collection.title} - ${navData.siteName}`,
|
|
87
75
|
description: collection.description ?? undefined,
|
|
88
76
|
c: c,
|
|
89
|
-
children: /*#__PURE__*/ _jsx(
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
children: /*#__PURE__*/ _jsx(SiteLayout, {
|
|
78
|
+
...navData,
|
|
79
|
+
children: /*#__PURE__*/ _jsx(CollectionContent, {
|
|
80
|
+
collection: collection,
|
|
81
|
+
posts: posts
|
|
82
|
+
})
|
|
92
83
|
})
|
|
93
84
|
}));
|
|
94
85
|
});
|
|
@@ -1,152 +1,109 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Home Page Route
|
|
4
|
+
*
|
|
5
|
+
* Timeline feed with per-type card components and thread previews.
|
|
4
6
|
*/ import { Hono } from "hono";
|
|
5
7
|
import { useLingui as $_useLingui } from "@jant/core/i18n";
|
|
6
|
-
import { BaseLayout } from "../../theme/layouts/index.js";
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
|
|
8
|
+
import { BaseLayout, SiteLayout } from "../../theme/layouts/index.js";
|
|
9
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
10
|
+
import { resolveTimelineFeed } from "../../lib/theme-components.js";
|
|
11
|
+
import { TimelineFeed as DefaultTimelineFeed } from "../../theme/components/timeline/TimelineFeed.js";
|
|
12
|
+
import { getNavigationData } from "../../lib/navigation.js";
|
|
13
|
+
const PAGE_SIZE = 20;
|
|
12
14
|
export const homeRoutes = new Hono();
|
|
13
|
-
function HomeContent({
|
|
15
|
+
function HomeContent({ FeedComponent, feedProps }) {
|
|
14
16
|
const { i18n: $__i18n, _: $__ } = $_useLingui();
|
|
15
|
-
return /*#__PURE__*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/*#__PURE__*/ _jsx("h1", {
|
|
22
|
-
class: "text-2xl font-semibold",
|
|
23
|
-
children: siteName
|
|
24
|
-
}),
|
|
25
|
-
/*#__PURE__*/ _jsxs("nav", {
|
|
26
|
-
class: "flex items-center gap-4 text-sm",
|
|
27
|
-
children: [
|
|
28
|
-
/*#__PURE__*/ _jsx("a", {
|
|
29
|
-
href: "/archive",
|
|
30
|
-
class: "text-muted-foreground hover:text-foreground",
|
|
31
|
-
children: $__i18n._({
|
|
32
|
-
id: "B495Gs",
|
|
33
|
-
message: "Archive"
|
|
34
|
-
})
|
|
35
|
-
}),
|
|
36
|
-
/*#__PURE__*/ _jsx("a", {
|
|
37
|
-
href: "/feed",
|
|
38
|
-
class: "text-muted-foreground hover:text-foreground",
|
|
39
|
-
children: "RSS"
|
|
40
|
-
})
|
|
41
|
-
]
|
|
42
|
-
})
|
|
43
|
-
]
|
|
44
|
-
}),
|
|
45
|
-
/*#__PURE__*/ _jsx("main", {
|
|
46
|
-
class: "flex flex-col gap-6",
|
|
47
|
-
children: posts.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
48
|
-
class: "text-muted-foreground",
|
|
49
|
-
children: $__i18n._({
|
|
50
|
-
id: "ODiSoW",
|
|
51
|
-
message: "No posts yet."
|
|
52
|
-
})
|
|
53
|
-
}) : posts.map((post)=>{
|
|
54
|
-
const attachments = mediaMap.get(post.id) ?? [];
|
|
55
|
-
return /*#__PURE__*/ _jsxs("article", {
|
|
56
|
-
class: "h-entry",
|
|
57
|
-
children: [
|
|
58
|
-
post.title && /*#__PURE__*/ _jsx("h2", {
|
|
59
|
-
class: "p-name text-lg font-medium mb-2",
|
|
60
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
61
|
-
href: `/p/${sqid.encode(post.id)}`,
|
|
62
|
-
class: "u-url hover:underline",
|
|
63
|
-
children: post.title
|
|
64
|
-
})
|
|
65
|
-
}),
|
|
66
|
-
/*#__PURE__*/ _jsx("div", {
|
|
67
|
-
class: "e-content prose prose-sm",
|
|
68
|
-
dangerouslySetInnerHTML: {
|
|
69
|
-
__html: post.contentHtml || ""
|
|
70
|
-
}
|
|
71
|
-
}),
|
|
72
|
-
attachments.length > 0 && /*#__PURE__*/ _jsx(MediaGallery, {
|
|
73
|
-
attachments: attachments
|
|
74
|
-
}),
|
|
75
|
-
/*#__PURE__*/ _jsxs("footer", {
|
|
76
|
-
class: "mt-2 text-sm text-muted-foreground",
|
|
77
|
-
children: [
|
|
78
|
-
/*#__PURE__*/ _jsx("time", {
|
|
79
|
-
class: "dt-published",
|
|
80
|
-
datetime: time.toISOString(post.publishedAt),
|
|
81
|
-
children: time.formatDate(post.publishedAt)
|
|
82
|
-
}),
|
|
83
|
-
post.visibility === "featured" && /*#__PURE__*/ _jsx("span", {
|
|
84
|
-
class: "ml-2 text-xs",
|
|
85
|
-
children: $__i18n._({
|
|
86
|
-
id: "FkMol5",
|
|
87
|
-
message: "Featured"
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
]
|
|
91
|
-
})
|
|
92
|
-
]
|
|
93
|
-
}, post.id);
|
|
94
|
-
})
|
|
95
|
-
}),
|
|
96
|
-
posts.length >= 20 && /*#__PURE__*/ _jsx("nav", {
|
|
97
|
-
class: "mt-8 text-center",
|
|
98
|
-
children: /*#__PURE__*/ _jsx("a", {
|
|
99
|
-
href: "/archive",
|
|
100
|
-
class: "text-sm text-muted-foreground hover:text-foreground",
|
|
101
|
-
children: $__i18n._({
|
|
102
|
-
id: "mTOYla",
|
|
103
|
-
message: "View all posts →"
|
|
104
|
-
})
|
|
105
|
-
})
|
|
17
|
+
return /*#__PURE__*/ _jsx(_Fragment, {
|
|
18
|
+
children: feedProps.items.length === 0 ? /*#__PURE__*/ _jsx("p", {
|
|
19
|
+
class: "text-muted-foreground",
|
|
20
|
+
children: $__i18n._({
|
|
21
|
+
id: "ODiSoW",
|
|
22
|
+
message: "No posts yet."
|
|
106
23
|
})
|
|
107
|
-
|
|
24
|
+
}) : /*#__PURE__*/ _jsx(FeedComponent, {
|
|
25
|
+
...feedProps
|
|
26
|
+
})
|
|
108
27
|
});
|
|
109
28
|
}
|
|
110
29
|
homeRoutes.get("/", async (c)=>{
|
|
111
|
-
const
|
|
30
|
+
const navData = await getNavigationData(c);
|
|
31
|
+
// Fetch one extra to determine if there are more
|
|
112
32
|
const posts = await c.var.services.posts.list({
|
|
113
33
|
visibility: [
|
|
114
34
|
"featured",
|
|
115
35
|
"quiet"
|
|
116
36
|
],
|
|
117
|
-
|
|
37
|
+
excludeReplies: true,
|
|
38
|
+
excludeTypes: [
|
|
39
|
+
"page"
|
|
40
|
+
],
|
|
41
|
+
limit: PAGE_SIZE + 1
|
|
118
42
|
});
|
|
43
|
+
const hasMore = posts.length > PAGE_SIZE;
|
|
44
|
+
const displayPosts = hasMore ? posts.slice(0, PAGE_SIZE) : posts;
|
|
119
45
|
// Batch load media attachments
|
|
120
|
-
const postIds =
|
|
46
|
+
const postIds = displayPosts.map((p)=>p.id);
|
|
121
47
|
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
122
48
|
const r2PublicUrl = c.env.R2_PUBLIC_URL;
|
|
123
49
|
const imageTransformUrl = c.env.IMAGE_TRANSFORM_URL;
|
|
124
|
-
const mediaMap =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
blurhash: m.blurhash,
|
|
137
|
-
width: m.width,
|
|
138
|
-
height: m.height,
|
|
139
|
-
position: m.position,
|
|
140
|
-
mimeType: m.mimeType
|
|
141
|
-
})));
|
|
50
|
+
const mediaMap = buildMediaMap(rawMediaMap, r2PublicUrl, imageTransformUrl);
|
|
51
|
+
// Get reply counts to identify thread roots
|
|
52
|
+
const replyCounts = await c.var.services.posts.getReplyCounts(postIds);
|
|
53
|
+
const threadRootIds = postIds.filter((id)=>(replyCounts.get(id) ?? 0) > 0);
|
|
54
|
+
// Batch load thread previews
|
|
55
|
+
const threadPreviews = await c.var.services.posts.getThreadPreviews(threadRootIds, 3);
|
|
56
|
+
// Batch load media for preview replies
|
|
57
|
+
const previewReplyIds = [];
|
|
58
|
+
for (const replies of threadPreviews.values()){
|
|
59
|
+
for (const reply of replies){
|
|
60
|
+
previewReplyIds.push(reply.id);
|
|
61
|
+
}
|
|
142
62
|
}
|
|
63
|
+
const previewMediaMap = previewReplyIds.length > 0 ? buildMediaMap(await c.var.services.media.getByPostIds(previewReplyIds), r2PublicUrl, imageTransformUrl) : new Map();
|
|
64
|
+
// Assemble timeline items
|
|
65
|
+
const items = displayPosts.map((post)=>{
|
|
66
|
+
const postWithMedia = {
|
|
67
|
+
...post,
|
|
68
|
+
mediaAttachments: mediaMap.get(post.id) ?? []
|
|
69
|
+
};
|
|
70
|
+
const replyCount = replyCounts.get(post.id) ?? 0;
|
|
71
|
+
const previewReplies = threadPreviews.get(post.id);
|
|
72
|
+
if (replyCount > 0 && previewReplies) {
|
|
73
|
+
return {
|
|
74
|
+
post: postWithMedia,
|
|
75
|
+
threadPreview: {
|
|
76
|
+
replies: previewReplies.map((r)=>({
|
|
77
|
+
...r,
|
|
78
|
+
mediaAttachments: previewMediaMap.get(r.id) ?? []
|
|
79
|
+
})),
|
|
80
|
+
totalReplyCount: replyCount
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
post: postWithMedia
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
// Determine next cursor
|
|
89
|
+
const lastPost = displayPosts[displayPosts.length - 1];
|
|
90
|
+
const nextCursor = hasMore && lastPost ? lastPost.id : undefined;
|
|
91
|
+
// Resolve theme components
|
|
92
|
+
const Feed = resolveTimelineFeed(DefaultTimelineFeed, c.var.config.theme?.components);
|
|
93
|
+
const feedProps = {
|
|
94
|
+
items,
|
|
95
|
+
hasMore,
|
|
96
|
+
nextCursor
|
|
97
|
+
};
|
|
143
98
|
return c.html(/*#__PURE__*/ _jsx(BaseLayout, {
|
|
144
|
-
title: siteName,
|
|
99
|
+
title: navData.siteName,
|
|
145
100
|
c: c,
|
|
146
|
-
children: /*#__PURE__*/ _jsx(
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
101
|
+
children: /*#__PURE__*/ _jsx(SiteLayout, {
|
|
102
|
+
...navData,
|
|
103
|
+
children: /*#__PURE__*/ _jsx(HomeContent, {
|
|
104
|
+
FeedComponent: Feed,
|
|
105
|
+
feedProps: feedProps
|
|
106
|
+
})
|
|
150
107
|
})
|
|
151
108
|
}));
|
|
152
109
|
});
|