vitepress-velonor 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/README.md +9 -0
- package/dist/chunk-BCwAaXi7.cjs +31 -0
- package/dist/client.cjs +261 -0
- package/dist/client.d.cts +72 -0
- package/dist/client.d.ts +72 -0
- package/dist/client.js +249 -0
- package/dist/constants-CnBvTOWr.cjs +83 -0
- package/dist/constants-Dc4Co5gF.js +52 -0
- package/dist/date-BySfVUhd.d.ts +67 -0
- package/dist/date-CCP55OAZ.d.cts +67 -0
- package/dist/date-DAUKykKr.js +21 -0
- package/dist/date-ygOc7hIU.cjs +34 -0
- package/dist/index-B6RX4u89.d.cts +7 -0
- package/dist/index-hfvJJTlf.d.ts +7 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +5 -0
- package/dist/loader.cjs +145 -0
- package/dist/loader.d.cts +73 -0
- package/dist/loader.d.ts +73 -0
- package/dist/loader.js +139 -0
- package/dist/post-helpers-BygKdsMh.cjs +42 -0
- package/dist/post-helpers-C_A-ioOF.js +36 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
//#region rolldown:runtime
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
|
|
26
|
+
Object.defineProperty(exports, '__toESM', {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
get: function () {
|
|
29
|
+
return __toESM;
|
|
30
|
+
}
|
|
31
|
+
});
|
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const require_chunk = require('./chunk-BCwAaXi7.cjs');
|
|
3
|
+
const require_constants = require('./constants-CnBvTOWr.cjs');
|
|
4
|
+
const require_date = require('./date-ygOc7hIU.cjs');
|
|
5
|
+
const vue = require_chunk.__toESM(require("vue"));
|
|
6
|
+
const vitepress_client = require_chunk.__toESM(require("vitepress/client"));
|
|
7
|
+
|
|
8
|
+
//#region src/utils/url.ts
|
|
9
|
+
const isClient = typeof window !== "undefined";
|
|
10
|
+
const getQueryParam = (key) => {
|
|
11
|
+
if (!isClient) return null;
|
|
12
|
+
const url = new URL(window.location.href);
|
|
13
|
+
return url.searchParams.get(key);
|
|
14
|
+
};
|
|
15
|
+
const setQueryParam = (key, value, options) => {
|
|
16
|
+
if (!isClient) return;
|
|
17
|
+
const url = new URL(window.location.href);
|
|
18
|
+
if (value === null || value === "") url.searchParams.delete(key);
|
|
19
|
+
else url.searchParams.set(key, value);
|
|
20
|
+
const replace = options?.replace ?? true;
|
|
21
|
+
if (replace) window.history.replaceState(null, "", url.toString());
|
|
22
|
+
else window.history.pushState(null, "", url.toString());
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/composables/useQueryParam.ts
|
|
27
|
+
const useSyncedQueryParam = (options) => {
|
|
28
|
+
const { key, defaultValue, parse, serialize } = options;
|
|
29
|
+
const route = (0, vitepress_client.useRoute)();
|
|
30
|
+
const state = (0, vue.ref)(defaultValue);
|
|
31
|
+
const read = () => {
|
|
32
|
+
const raw = getQueryParam(key);
|
|
33
|
+
const next = parse ? parse(raw) : raw ?? defaultValue;
|
|
34
|
+
state.value = next;
|
|
35
|
+
};
|
|
36
|
+
const write = (value) => {
|
|
37
|
+
const current = getQueryParam(key);
|
|
38
|
+
const next = serialize ? serialize(value) : value === defaultValue ? null : String(value);
|
|
39
|
+
if (next === current || next === null && current === null) return;
|
|
40
|
+
setQueryParam(key, next, { replace: true });
|
|
41
|
+
};
|
|
42
|
+
const canUseDom = typeof window !== "undefined";
|
|
43
|
+
if (canUseDom) read();
|
|
44
|
+
(0, vue.watch)(() => route.path, () => {
|
|
45
|
+
if (canUseDom) read();
|
|
46
|
+
});
|
|
47
|
+
(0, vue.watch)(state, (value) => {
|
|
48
|
+
if (canUseDom) write(value);
|
|
49
|
+
}, { deep: false });
|
|
50
|
+
(0, vue.onMounted)(() => {
|
|
51
|
+
if (!canUseDom) return;
|
|
52
|
+
window.addEventListener("popstate", read, { passive: true });
|
|
53
|
+
});
|
|
54
|
+
(0, vue.onUnmounted)(() => {
|
|
55
|
+
if (!canUseDom) return;
|
|
56
|
+
window.removeEventListener("popstate", read);
|
|
57
|
+
});
|
|
58
|
+
return state;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/composables/useTags.ts
|
|
63
|
+
const createTagsState = (posts) => {
|
|
64
|
+
const activeTag = useSyncedQueryParam({
|
|
65
|
+
key: "tag",
|
|
66
|
+
defaultValue: "",
|
|
67
|
+
parse: (raw) => raw || "",
|
|
68
|
+
serialize: (value) => value ? value : null
|
|
69
|
+
});
|
|
70
|
+
const tagsMap = (0, vue.computed)(() => {
|
|
71
|
+
const map = { "": posts.length };
|
|
72
|
+
posts.forEach((post) => {
|
|
73
|
+
const tags = post.frontmatter.tags;
|
|
74
|
+
if (tags) tags.forEach((tag) => {
|
|
75
|
+
map[tag] = (map[tag] || 0) + 1;
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
return map;
|
|
79
|
+
});
|
|
80
|
+
const getTagArray = () => {
|
|
81
|
+
const arr = Object.entries(tagsMap.value);
|
|
82
|
+
arr.sort((a, b) => b[1] - a[1]);
|
|
83
|
+
return arr;
|
|
84
|
+
};
|
|
85
|
+
const uniqueTagCount = (0, vue.computed)(() => {
|
|
86
|
+
const set = new Set();
|
|
87
|
+
Object.keys(tagsMap.value).forEach((k) => {
|
|
88
|
+
if (k) set.add(k);
|
|
89
|
+
});
|
|
90
|
+
return set.size;
|
|
91
|
+
});
|
|
92
|
+
const filterPostsByActiveTag = (tag) => {
|
|
93
|
+
const t = tag ?? activeTag.value;
|
|
94
|
+
if (!t) return posts;
|
|
95
|
+
return posts.filter((item) => item.frontmatter.tags && item.frontmatter.tags.includes(t));
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
tagsMap,
|
|
99
|
+
activeTag,
|
|
100
|
+
getTagArray,
|
|
101
|
+
uniqueTagCount,
|
|
102
|
+
filterPostsByActiveTag
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const createTagsStore = (posts) => {
|
|
106
|
+
let sharedState = null;
|
|
107
|
+
return () => {
|
|
108
|
+
if (!sharedState) sharedState = createTagsState(posts);
|
|
109
|
+
return sharedState;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/composables/useCategories.ts
|
|
115
|
+
const extractCategoryFromUrl = (url, otherLabel) => {
|
|
116
|
+
const match = url.match(/^\/posts\/([^/]+)\//);
|
|
117
|
+
if (match && match[1]) return match[1];
|
|
118
|
+
if (url.startsWith("/posts/") && /^\/posts\/[^/]+(\.html)?$/.test(url)) return otherLabel;
|
|
119
|
+
return "";
|
|
120
|
+
};
|
|
121
|
+
const createCategoriesState = (posts, otherLabel) => {
|
|
122
|
+
const otherLabelRef = (0, vue.ref)(otherLabel);
|
|
123
|
+
const activeCategory = useSyncedQueryParam({
|
|
124
|
+
key: "category",
|
|
125
|
+
defaultValue: "",
|
|
126
|
+
parse: (raw) => raw || "",
|
|
127
|
+
serialize: (value) => value ? value : null
|
|
128
|
+
});
|
|
129
|
+
const getPostCategory = (post) => {
|
|
130
|
+
const fmCategory = post.frontmatter?.category;
|
|
131
|
+
if (fmCategory) return fmCategory;
|
|
132
|
+
return extractCategoryFromUrl(post.url, otherLabelRef.value);
|
|
133
|
+
};
|
|
134
|
+
const categoriesMap = (0, vue.computed)(() => {
|
|
135
|
+
const map = { "": posts.length };
|
|
136
|
+
posts.forEach((post) => {
|
|
137
|
+
const category = getPostCategory(post);
|
|
138
|
+
if (category) map[category] = (map[category] || 0) + 1;
|
|
139
|
+
});
|
|
140
|
+
return map;
|
|
141
|
+
});
|
|
142
|
+
const getCategoryArray = () => {
|
|
143
|
+
const arr = Object.entries(categoriesMap.value);
|
|
144
|
+
arr.sort((a, b) => {
|
|
145
|
+
if (a[0] === "") return -1;
|
|
146
|
+
if (b[0] === "") return 1;
|
|
147
|
+
return b[1] - a[1];
|
|
148
|
+
});
|
|
149
|
+
return arr;
|
|
150
|
+
};
|
|
151
|
+
const uniqueCategoryCount = (0, vue.computed)(() => {
|
|
152
|
+
const set = new Set();
|
|
153
|
+
Object.keys(categoriesMap.value).forEach((k) => {
|
|
154
|
+
if (k) set.add(k);
|
|
155
|
+
});
|
|
156
|
+
return set.size;
|
|
157
|
+
});
|
|
158
|
+
const filterPostsByActiveCategory = (category) => {
|
|
159
|
+
const c = category ?? activeCategory.value;
|
|
160
|
+
if (!c) return posts;
|
|
161
|
+
return posts.filter((item) => getPostCategory(item) === c);
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
categoriesMap,
|
|
165
|
+
activeCategory,
|
|
166
|
+
getCategoryArray,
|
|
167
|
+
uniqueCategoryCount,
|
|
168
|
+
filterPostsByActiveCategory,
|
|
169
|
+
getCategoryFromUrl: (url) => extractCategoryFromUrl(url, otherLabelRef.value),
|
|
170
|
+
updateOtherLabel: (label) => {
|
|
171
|
+
otherLabelRef.value = label;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
const createCategoriesStore = (posts) => {
|
|
176
|
+
let sharedState = null;
|
|
177
|
+
return (options) => {
|
|
178
|
+
const otherLabel = options?.otherLabel || "Other";
|
|
179
|
+
if (!sharedState) sharedState = createCategoriesState(posts, otherLabel);
|
|
180
|
+
else if (options?.otherLabel) sharedState.updateOtherLabel(options.otherLabel);
|
|
181
|
+
return sharedState;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/composables/usePagination.ts
|
|
187
|
+
const usePagination = (items, options) => {
|
|
188
|
+
const pageParam = options.pageParam ?? "page";
|
|
189
|
+
const pageSize = (0, vue.computed)(() => typeof options.pageSize === "number" ? options.pageSize : options.pageSize.value);
|
|
190
|
+
const currentPage = useSyncedQueryParam({
|
|
191
|
+
key: pageParam,
|
|
192
|
+
defaultValue: 1,
|
|
193
|
+
parse: (raw) => {
|
|
194
|
+
const n = Number.parseInt(raw || "1", 10);
|
|
195
|
+
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
196
|
+
},
|
|
197
|
+
serialize: (value) => value <= 1 ? null : String(value)
|
|
198
|
+
});
|
|
199
|
+
const totalPages = (0, vue.computed)(() => {
|
|
200
|
+
if (!items.value.length) return 0;
|
|
201
|
+
return Math.ceil(items.value.length / pageSize.value);
|
|
202
|
+
});
|
|
203
|
+
(0, vue.watch)(totalPages, (count) => {
|
|
204
|
+
if (count === 0) currentPage.value = 1;
|
|
205
|
+
else if (currentPage.value > count) currentPage.value = count;
|
|
206
|
+
}, { immediate: true });
|
|
207
|
+
const paginatedItems = (0, vue.computed)(() => {
|
|
208
|
+
const start = (currentPage.value - 1) * pageSize.value;
|
|
209
|
+
const end = start + pageSize.value;
|
|
210
|
+
return items.value.slice(start, end);
|
|
211
|
+
});
|
|
212
|
+
const pageRange = (0, vue.computed)(() => {
|
|
213
|
+
const count = totalPages.value;
|
|
214
|
+
if (count <= 1) return [];
|
|
215
|
+
const groupSize = Math.max(3, options.pageGroupSize || require_constants.DEFAULT_PAGE_GROUP_SIZE);
|
|
216
|
+
const half = Math.floor(groupSize / 2);
|
|
217
|
+
let start = Math.max(1, currentPage.value - half);
|
|
218
|
+
let end = Math.min(count, start + groupSize - 1);
|
|
219
|
+
start = Math.max(1, end - groupSize + 1);
|
|
220
|
+
const pages = [];
|
|
221
|
+
for (let i = start; i <= end; i++) pages.push(i);
|
|
222
|
+
return pages;
|
|
223
|
+
});
|
|
224
|
+
const setPage = (page) => {
|
|
225
|
+
const count = totalPages.value || 1;
|
|
226
|
+
const target = Math.min(Math.max(page, 1), count);
|
|
227
|
+
currentPage.value = target;
|
|
228
|
+
};
|
|
229
|
+
const nextPage = () => setPage(currentPage.value + 1);
|
|
230
|
+
const prevPage = () => setPage(currentPage.value - 1);
|
|
231
|
+
const jumpInput = (0, vue.ref)("");
|
|
232
|
+
const jumpToInput = () => {
|
|
233
|
+
const n = Number.parseInt(jumpInput.value, 10);
|
|
234
|
+
if (!Number.isFinite(n)) return;
|
|
235
|
+
setPage(n);
|
|
236
|
+
};
|
|
237
|
+
return {
|
|
238
|
+
currentPage,
|
|
239
|
+
totalPages,
|
|
240
|
+
pageRange,
|
|
241
|
+
paginatedItems,
|
|
242
|
+
setPage,
|
|
243
|
+
nextPage,
|
|
244
|
+
prevPage,
|
|
245
|
+
jumpInput,
|
|
246
|
+
jumpToInput
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
exports.DEFAULT_PAGE_GROUP_SIZE = require_constants.DEFAULT_PAGE_GROUP_SIZE
|
|
252
|
+
exports.DEFAULT_PAGE_SIZE = require_constants.DEFAULT_PAGE_SIZE
|
|
253
|
+
exports.LOCALIZED_STRINGS = require_constants.LOCALIZED_STRINGS
|
|
254
|
+
exports.MAX_DISPLAYED_TAGS = require_constants.MAX_DISPLAYED_TAGS
|
|
255
|
+
exports.createCategoriesStore = createCategoriesStore
|
|
256
|
+
exports.createTagsStore = createTagsStore
|
|
257
|
+
exports.formatDate = require_date.formatDate
|
|
258
|
+
exports.getLocalizedString = require_constants.getLocalizedString
|
|
259
|
+
exports.parseDateValue = require_date.parseDateValue
|
|
260
|
+
exports.usePagination = usePagination
|
|
261
|
+
exports.useSyncedQueryParam = useSyncedQueryParam
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { BlogFrontmatter, BlogPost, DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS, SupportedLanguage, formatDate, getLocalizedString, parseDateValue } from "./date-CCP55OAZ.cjs";
|
|
2
|
+
import * as vue2 from "vue";
|
|
3
|
+
import * as vue3 from "vue";
|
|
4
|
+
import * as vue4 from "vue";
|
|
5
|
+
import * as vue5 from "vue";
|
|
6
|
+
import * as vue1 from "vue";
|
|
7
|
+
import { ComputedRef } from "vue";
|
|
8
|
+
|
|
9
|
+
//#region src/composables/useQueryParam.d.ts
|
|
10
|
+
type Parser<T> = (raw: string | null) => T;
|
|
11
|
+
type Serializer<T> = (value: T) => string | null;
|
|
12
|
+
interface SyncedQueryParamOptions<T> {
|
|
13
|
+
key: string;
|
|
14
|
+
defaultValue: T;
|
|
15
|
+
parse?: Parser<T>;
|
|
16
|
+
serialize?: Serializer<T>;
|
|
17
|
+
}
|
|
18
|
+
declare const useSyncedQueryParam: <T>(options: SyncedQueryParamOptions<T>) => {
|
|
19
|
+
value: T;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/composables/useTags.d.ts
|
|
24
|
+
declare const createTagsStore: (posts: BlogPost[]) => () => {
|
|
25
|
+
readonly tagsMap: vue2.ComputedRef<Record<string, number>>;
|
|
26
|
+
readonly activeTag: {
|
|
27
|
+
value: string;
|
|
28
|
+
};
|
|
29
|
+
readonly getTagArray: () => [string, number][];
|
|
30
|
+
readonly uniqueTagCount: vue3.ComputedRef<number>;
|
|
31
|
+
readonly filterPostsByActiveTag: (tag?: string) => BlogPost[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/composables/useCategories.d.ts
|
|
36
|
+
declare const createCategoriesStore: (posts: BlogPost[]) => (options?: {
|
|
37
|
+
otherLabel?: string;
|
|
38
|
+
}) => {
|
|
39
|
+
readonly categoriesMap: vue4.ComputedRef<Record<string, number>>;
|
|
40
|
+
readonly activeCategory: {
|
|
41
|
+
value: string;
|
|
42
|
+
};
|
|
43
|
+
readonly getCategoryArray: () => [string, number][];
|
|
44
|
+
readonly uniqueCategoryCount: vue5.ComputedRef<number>;
|
|
45
|
+
readonly filterPostsByActiveCategory: (category?: string) => BlogPost[];
|
|
46
|
+
readonly getCategoryFromUrl: (url: string) => string;
|
|
47
|
+
readonly updateOtherLabel: (label: string) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/composables/usePagination.d.ts
|
|
52
|
+
interface PaginationOptions {
|
|
53
|
+
pageSize: number | ComputedRef<number>;
|
|
54
|
+
pageGroupSize?: number;
|
|
55
|
+
pageParam?: string;
|
|
56
|
+
}
|
|
57
|
+
declare const usePagination: <T>(items: ComputedRef<T[]>, options: PaginationOptions) => {
|
|
58
|
+
readonly currentPage: {
|
|
59
|
+
value: number;
|
|
60
|
+
};
|
|
61
|
+
readonly totalPages: ComputedRef<number>;
|
|
62
|
+
readonly pageRange: ComputedRef<number[]>;
|
|
63
|
+
readonly paginatedItems: ComputedRef<T[]>;
|
|
64
|
+
readonly setPage: (page: number) => void;
|
|
65
|
+
readonly nextPage: () => void;
|
|
66
|
+
readonly prevPage: () => void;
|
|
67
|
+
readonly jumpInput: vue1.Ref<string, string>;
|
|
68
|
+
readonly jumpToInput: () => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { BlogFrontmatter, BlogPost, DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS, SupportedLanguage, createCategoriesStore, createTagsStore, formatDate, getLocalizedString, parseDateValue, usePagination, useSyncedQueryParam };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { BlogFrontmatter, BlogPost, DEFAULT_PAGE_GROUP_SIZE$1 as DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE$1 as DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS$1 as LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS$1 as MAX_DISPLAYED_TAGS, SupportedLanguage, formatDate$1 as formatDate, getLocalizedString$1 as getLocalizedString, parseDateValue$1 as parseDateValue } from "./date-BySfVUhd.js";
|
|
2
|
+
import * as vue1 from "vue";
|
|
3
|
+
import * as vue2 from "vue";
|
|
4
|
+
import * as vue4 from "vue";
|
|
5
|
+
import * as vue5 from "vue";
|
|
6
|
+
import * as vue3 from "vue";
|
|
7
|
+
import { ComputedRef } from "vue";
|
|
8
|
+
|
|
9
|
+
//#region src/composables/useQueryParam.d.ts
|
|
10
|
+
type Parser<T> = (raw: string | null) => T;
|
|
11
|
+
type Serializer<T> = (value: T) => string | null;
|
|
12
|
+
interface SyncedQueryParamOptions<T> {
|
|
13
|
+
key: string;
|
|
14
|
+
defaultValue: T;
|
|
15
|
+
parse?: Parser<T>;
|
|
16
|
+
serialize?: Serializer<T>;
|
|
17
|
+
}
|
|
18
|
+
declare const useSyncedQueryParam: <T>(options: SyncedQueryParamOptions<T>) => {
|
|
19
|
+
value: T;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/composables/useTags.d.ts
|
|
24
|
+
declare const createTagsStore: (posts: BlogPost[]) => () => {
|
|
25
|
+
readonly tagsMap: vue1.ComputedRef<Record<string, number>>;
|
|
26
|
+
readonly activeTag: {
|
|
27
|
+
value: string;
|
|
28
|
+
};
|
|
29
|
+
readonly getTagArray: () => [string, number][];
|
|
30
|
+
readonly uniqueTagCount: vue2.ComputedRef<number>;
|
|
31
|
+
readonly filterPostsByActiveTag: (tag?: string) => BlogPost[];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/composables/useCategories.d.ts
|
|
36
|
+
declare const createCategoriesStore: (posts: BlogPost[]) => (options?: {
|
|
37
|
+
otherLabel?: string;
|
|
38
|
+
}) => {
|
|
39
|
+
readonly categoriesMap: vue4.ComputedRef<Record<string, number>>;
|
|
40
|
+
readonly activeCategory: {
|
|
41
|
+
value: string;
|
|
42
|
+
};
|
|
43
|
+
readonly getCategoryArray: () => [string, number][];
|
|
44
|
+
readonly uniqueCategoryCount: vue5.ComputedRef<number>;
|
|
45
|
+
readonly filterPostsByActiveCategory: (category?: string) => BlogPost[];
|
|
46
|
+
readonly getCategoryFromUrl: (url: string) => string;
|
|
47
|
+
readonly updateOtherLabel: (label: string) => void;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/composables/usePagination.d.ts
|
|
52
|
+
interface PaginationOptions {
|
|
53
|
+
pageSize: number | ComputedRef<number>;
|
|
54
|
+
pageGroupSize?: number;
|
|
55
|
+
pageParam?: string;
|
|
56
|
+
}
|
|
57
|
+
declare const usePagination: <T>(items: ComputedRef<T[]>, options: PaginationOptions) => {
|
|
58
|
+
readonly currentPage: {
|
|
59
|
+
value: number;
|
|
60
|
+
};
|
|
61
|
+
readonly totalPages: ComputedRef<number>;
|
|
62
|
+
readonly pageRange: ComputedRef<number[]>;
|
|
63
|
+
readonly paginatedItems: ComputedRef<T[]>;
|
|
64
|
+
readonly setPage: (page: number) => void;
|
|
65
|
+
readonly nextPage: () => void;
|
|
66
|
+
readonly prevPage: () => void;
|
|
67
|
+
readonly jumpInput: vue3.Ref<string, string>;
|
|
68
|
+
readonly jumpToInput: () => void;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
//#endregion
|
|
72
|
+
export { BlogFrontmatter, BlogPost, DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS, SupportedLanguage, createCategoriesStore, createTagsStore, formatDate, getLocalizedString, parseDateValue, usePagination, useSyncedQueryParam };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS, getLocalizedString } from "./constants-Dc4Co5gF.js";
|
|
2
|
+
import { formatDate, parseDateValue } from "./date-DAUKykKr.js";
|
|
3
|
+
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
|
4
|
+
import { useRoute } from "vitepress/client";
|
|
5
|
+
|
|
6
|
+
//#region src/utils/url.ts
|
|
7
|
+
const isClient = typeof window !== "undefined";
|
|
8
|
+
const getQueryParam = (key) => {
|
|
9
|
+
if (!isClient) return null;
|
|
10
|
+
const url = new URL(window.location.href);
|
|
11
|
+
return url.searchParams.get(key);
|
|
12
|
+
};
|
|
13
|
+
const setQueryParam = (key, value, options) => {
|
|
14
|
+
if (!isClient) return;
|
|
15
|
+
const url = new URL(window.location.href);
|
|
16
|
+
if (value === null || value === "") url.searchParams.delete(key);
|
|
17
|
+
else url.searchParams.set(key, value);
|
|
18
|
+
const replace = options?.replace ?? true;
|
|
19
|
+
if (replace) window.history.replaceState(null, "", url.toString());
|
|
20
|
+
else window.history.pushState(null, "", url.toString());
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/composables/useQueryParam.ts
|
|
25
|
+
const useSyncedQueryParam = (options) => {
|
|
26
|
+
const { key, defaultValue, parse, serialize } = options;
|
|
27
|
+
const route = useRoute();
|
|
28
|
+
const state = ref(defaultValue);
|
|
29
|
+
const read = () => {
|
|
30
|
+
const raw = getQueryParam(key);
|
|
31
|
+
const next = parse ? parse(raw) : raw ?? defaultValue;
|
|
32
|
+
state.value = next;
|
|
33
|
+
};
|
|
34
|
+
const write = (value) => {
|
|
35
|
+
const current = getQueryParam(key);
|
|
36
|
+
const next = serialize ? serialize(value) : value === defaultValue ? null : String(value);
|
|
37
|
+
if (next === current || next === null && current === null) return;
|
|
38
|
+
setQueryParam(key, next, { replace: true });
|
|
39
|
+
};
|
|
40
|
+
const canUseDom = typeof window !== "undefined";
|
|
41
|
+
if (canUseDom) read();
|
|
42
|
+
watch(() => route.path, () => {
|
|
43
|
+
if (canUseDom) read();
|
|
44
|
+
});
|
|
45
|
+
watch(state, (value) => {
|
|
46
|
+
if (canUseDom) write(value);
|
|
47
|
+
}, { deep: false });
|
|
48
|
+
onMounted(() => {
|
|
49
|
+
if (!canUseDom) return;
|
|
50
|
+
window.addEventListener("popstate", read, { passive: true });
|
|
51
|
+
});
|
|
52
|
+
onUnmounted(() => {
|
|
53
|
+
if (!canUseDom) return;
|
|
54
|
+
window.removeEventListener("popstate", read);
|
|
55
|
+
});
|
|
56
|
+
return state;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/composables/useTags.ts
|
|
61
|
+
const createTagsState = (posts) => {
|
|
62
|
+
const activeTag = useSyncedQueryParam({
|
|
63
|
+
key: "tag",
|
|
64
|
+
defaultValue: "",
|
|
65
|
+
parse: (raw) => raw || "",
|
|
66
|
+
serialize: (value) => value ? value : null
|
|
67
|
+
});
|
|
68
|
+
const tagsMap = computed(() => {
|
|
69
|
+
const map = { "": posts.length };
|
|
70
|
+
posts.forEach((post) => {
|
|
71
|
+
const tags = post.frontmatter.tags;
|
|
72
|
+
if (tags) tags.forEach((tag) => {
|
|
73
|
+
map[tag] = (map[tag] || 0) + 1;
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
return map;
|
|
77
|
+
});
|
|
78
|
+
const getTagArray = () => {
|
|
79
|
+
const arr = Object.entries(tagsMap.value);
|
|
80
|
+
arr.sort((a, b) => b[1] - a[1]);
|
|
81
|
+
return arr;
|
|
82
|
+
};
|
|
83
|
+
const uniqueTagCount = computed(() => {
|
|
84
|
+
const set = new Set();
|
|
85
|
+
Object.keys(tagsMap.value).forEach((k) => {
|
|
86
|
+
if (k) set.add(k);
|
|
87
|
+
});
|
|
88
|
+
return set.size;
|
|
89
|
+
});
|
|
90
|
+
const filterPostsByActiveTag = (tag) => {
|
|
91
|
+
const t = tag ?? activeTag.value;
|
|
92
|
+
if (!t) return posts;
|
|
93
|
+
return posts.filter((item) => item.frontmatter.tags && item.frontmatter.tags.includes(t));
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
tagsMap,
|
|
97
|
+
activeTag,
|
|
98
|
+
getTagArray,
|
|
99
|
+
uniqueTagCount,
|
|
100
|
+
filterPostsByActiveTag
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
const createTagsStore = (posts) => {
|
|
104
|
+
let sharedState = null;
|
|
105
|
+
return () => {
|
|
106
|
+
if (!sharedState) sharedState = createTagsState(posts);
|
|
107
|
+
return sharedState;
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/composables/useCategories.ts
|
|
113
|
+
const extractCategoryFromUrl = (url, otherLabel) => {
|
|
114
|
+
const match = url.match(/^\/posts\/([^/]+)\//);
|
|
115
|
+
if (match && match[1]) return match[1];
|
|
116
|
+
if (url.startsWith("/posts/") && /^\/posts\/[^/]+(\.html)?$/.test(url)) return otherLabel;
|
|
117
|
+
return "";
|
|
118
|
+
};
|
|
119
|
+
const createCategoriesState = (posts, otherLabel) => {
|
|
120
|
+
const otherLabelRef = ref(otherLabel);
|
|
121
|
+
const activeCategory = useSyncedQueryParam({
|
|
122
|
+
key: "category",
|
|
123
|
+
defaultValue: "",
|
|
124
|
+
parse: (raw) => raw || "",
|
|
125
|
+
serialize: (value) => value ? value : null
|
|
126
|
+
});
|
|
127
|
+
const getPostCategory = (post) => {
|
|
128
|
+
const fmCategory = post.frontmatter?.category;
|
|
129
|
+
if (fmCategory) return fmCategory;
|
|
130
|
+
return extractCategoryFromUrl(post.url, otherLabelRef.value);
|
|
131
|
+
};
|
|
132
|
+
const categoriesMap = computed(() => {
|
|
133
|
+
const map = { "": posts.length };
|
|
134
|
+
posts.forEach((post) => {
|
|
135
|
+
const category = getPostCategory(post);
|
|
136
|
+
if (category) map[category] = (map[category] || 0) + 1;
|
|
137
|
+
});
|
|
138
|
+
return map;
|
|
139
|
+
});
|
|
140
|
+
const getCategoryArray = () => {
|
|
141
|
+
const arr = Object.entries(categoriesMap.value);
|
|
142
|
+
arr.sort((a, b) => {
|
|
143
|
+
if (a[0] === "") return -1;
|
|
144
|
+
if (b[0] === "") return 1;
|
|
145
|
+
return b[1] - a[1];
|
|
146
|
+
});
|
|
147
|
+
return arr;
|
|
148
|
+
};
|
|
149
|
+
const uniqueCategoryCount = computed(() => {
|
|
150
|
+
const set = new Set();
|
|
151
|
+
Object.keys(categoriesMap.value).forEach((k) => {
|
|
152
|
+
if (k) set.add(k);
|
|
153
|
+
});
|
|
154
|
+
return set.size;
|
|
155
|
+
});
|
|
156
|
+
const filterPostsByActiveCategory = (category) => {
|
|
157
|
+
const c = category ?? activeCategory.value;
|
|
158
|
+
if (!c) return posts;
|
|
159
|
+
return posts.filter((item) => getPostCategory(item) === c);
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
categoriesMap,
|
|
163
|
+
activeCategory,
|
|
164
|
+
getCategoryArray,
|
|
165
|
+
uniqueCategoryCount,
|
|
166
|
+
filterPostsByActiveCategory,
|
|
167
|
+
getCategoryFromUrl: (url) => extractCategoryFromUrl(url, otherLabelRef.value),
|
|
168
|
+
updateOtherLabel: (label) => {
|
|
169
|
+
otherLabelRef.value = label;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
const createCategoriesStore = (posts) => {
|
|
174
|
+
let sharedState = null;
|
|
175
|
+
return (options) => {
|
|
176
|
+
const otherLabel = options?.otherLabel || "Other";
|
|
177
|
+
if (!sharedState) sharedState = createCategoriesState(posts, otherLabel);
|
|
178
|
+
else if (options?.otherLabel) sharedState.updateOtherLabel(options.otherLabel);
|
|
179
|
+
return sharedState;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region src/composables/usePagination.ts
|
|
185
|
+
const usePagination = (items, options) => {
|
|
186
|
+
const pageParam = options.pageParam ?? "page";
|
|
187
|
+
const pageSize = computed(() => typeof options.pageSize === "number" ? options.pageSize : options.pageSize.value);
|
|
188
|
+
const currentPage = useSyncedQueryParam({
|
|
189
|
+
key: pageParam,
|
|
190
|
+
defaultValue: 1,
|
|
191
|
+
parse: (raw) => {
|
|
192
|
+
const n = Number.parseInt(raw || "1", 10);
|
|
193
|
+
return Number.isFinite(n) && n > 0 ? n : 1;
|
|
194
|
+
},
|
|
195
|
+
serialize: (value) => value <= 1 ? null : String(value)
|
|
196
|
+
});
|
|
197
|
+
const totalPages = computed(() => {
|
|
198
|
+
if (!items.value.length) return 0;
|
|
199
|
+
return Math.ceil(items.value.length / pageSize.value);
|
|
200
|
+
});
|
|
201
|
+
watch(totalPages, (count) => {
|
|
202
|
+
if (count === 0) currentPage.value = 1;
|
|
203
|
+
else if (currentPage.value > count) currentPage.value = count;
|
|
204
|
+
}, { immediate: true });
|
|
205
|
+
const paginatedItems = computed(() => {
|
|
206
|
+
const start = (currentPage.value - 1) * pageSize.value;
|
|
207
|
+
const end = start + pageSize.value;
|
|
208
|
+
return items.value.slice(start, end);
|
|
209
|
+
});
|
|
210
|
+
const pageRange = computed(() => {
|
|
211
|
+
const count = totalPages.value;
|
|
212
|
+
if (count <= 1) return [];
|
|
213
|
+
const groupSize = Math.max(3, options.pageGroupSize || DEFAULT_PAGE_GROUP_SIZE);
|
|
214
|
+
const half = Math.floor(groupSize / 2);
|
|
215
|
+
let start = Math.max(1, currentPage.value - half);
|
|
216
|
+
let end = Math.min(count, start + groupSize - 1);
|
|
217
|
+
start = Math.max(1, end - groupSize + 1);
|
|
218
|
+
const pages = [];
|
|
219
|
+
for (let i = start; i <= end; i++) pages.push(i);
|
|
220
|
+
return pages;
|
|
221
|
+
});
|
|
222
|
+
const setPage = (page) => {
|
|
223
|
+
const count = totalPages.value || 1;
|
|
224
|
+
const target = Math.min(Math.max(page, 1), count);
|
|
225
|
+
currentPage.value = target;
|
|
226
|
+
};
|
|
227
|
+
const nextPage = () => setPage(currentPage.value + 1);
|
|
228
|
+
const prevPage = () => setPage(currentPage.value - 1);
|
|
229
|
+
const jumpInput = ref("");
|
|
230
|
+
const jumpToInput = () => {
|
|
231
|
+
const n = Number.parseInt(jumpInput.value, 10);
|
|
232
|
+
if (!Number.isFinite(n)) return;
|
|
233
|
+
setPage(n);
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
currentPage,
|
|
237
|
+
totalPages,
|
|
238
|
+
pageRange,
|
|
239
|
+
paginatedItems,
|
|
240
|
+
setPage,
|
|
241
|
+
nextPage,
|
|
242
|
+
prevPage,
|
|
243
|
+
jumpInput,
|
|
244
|
+
jumpToInput
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
//#endregion
|
|
249
|
+
export { DEFAULT_PAGE_GROUP_SIZE, DEFAULT_PAGE_SIZE, LOCALIZED_STRINGS, MAX_DISPLAYED_TAGS, createCategoriesStore, createTagsStore, formatDate, getLocalizedString, parseDateValue, usePagination, useSyncedQueryParam };
|