@waline/client 2.11.3 → 2.13.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.
Files changed (55) hide show
  1. package/dist/api.cjs +1 -1
  2. package/dist/api.cjs.map +1 -1
  3. package/dist/api.d.cts +205 -0
  4. package/dist/api.d.mts +205 -0
  5. package/dist/api.d.ts +205 -0
  6. package/dist/api.mjs +1 -1
  7. package/dist/api.mjs.map +1 -1
  8. package/dist/comment.cjs +1 -1
  9. package/dist/comment.cjs.map +1 -1
  10. package/dist/comment.js +1 -1
  11. package/dist/comment.js.map +1 -1
  12. package/dist/comment.mjs +1 -1
  13. package/dist/comment.mjs.map +1 -1
  14. package/dist/component.mjs +1 -1
  15. package/dist/component.mjs.map +1 -1
  16. package/dist/legacy.umd.js +1 -1
  17. package/dist/legacy.umd.js.map +1 -1
  18. package/dist/pageview.cjs +1 -1
  19. package/dist/pageview.cjs.map +1 -1
  20. package/dist/pageview.js +1 -1
  21. package/dist/pageview.js.map +1 -1
  22. package/dist/pageview.mjs +1 -1
  23. package/dist/pageview.mjs.map +1 -1
  24. package/dist/shim.cjs +1 -1
  25. package/dist/shim.cjs.map +1 -1
  26. package/dist/shim.d.cts +65 -1
  27. package/dist/shim.d.mts +65 -1
  28. package/dist/shim.mjs +1 -1
  29. package/dist/shim.mjs.map +1 -1
  30. package/dist/waline.cjs +1 -1
  31. package/dist/waline.cjs.map +1 -1
  32. package/dist/waline.css +1 -1
  33. package/dist/waline.css.map +1 -1
  34. package/dist/waline.d.cts +65 -1
  35. package/dist/waline.d.mts +65 -1
  36. package/dist/waline.d.ts +65 -1
  37. package/dist/waline.js +1 -1
  38. package/dist/waline.js.map +1 -1
  39. package/dist/waline.mjs +1 -1
  40. package/dist/waline.mjs.map +1 -1
  41. package/package.json +23 -21
  42. package/src/api/index.ts +1 -0
  43. package/src/api/user.ts +35 -0
  44. package/src/components/CommentBox.vue +6 -5
  45. package/src/components/ImageWall.vue +6 -1
  46. package/src/composables/userInfo.ts +1 -2
  47. package/src/composables/vote.ts +1 -1
  48. package/src/config/default.ts +45 -83
  49. package/src/entrys/api.ts +1 -1
  50. package/src/styles/index.scss +1 -0
  51. package/src/styles/userlist.scss +116 -0
  52. package/src/utils/config.ts +2 -2
  53. package/src/widgets/index.ts +1 -0
  54. package/src/widgets/userList.ts +138 -0
  55. package/LICENSE +0 -339
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/client",
3
- "version": "2.11.3",
3
+ "version": "2.13.0",
4
4
  "description": "client for waline comment system",
5
5
  "keywords": [
6
6
  "valine",
@@ -93,6 +93,17 @@
93
93
  "dist",
94
94
  "src"
95
95
  ],
96
+ "scripts": {
97
+ "build": "pnpm rollup && pnpm style",
98
+ "clean": "rimraf ./dist",
99
+ "dev": "vite -c config/vite.config.js",
100
+ "lint": "eslint --ext .ts,.vue .",
101
+ "prepublishOnly": "pnpm clean && pnpm build",
102
+ "rollup": "rollup -c ./config/rollup.config.js",
103
+ "style": "pnpm style:main && pnpm style:meta",
104
+ "style:main": "sass ./src/styles/index.scss ./dist/waline.css --style=compressed",
105
+ "style:meta": "sass ./src/styles/meta.scss ./dist/waline-meta.css --style=compressed"
106
+ },
96
107
  "browserslist": {
97
108
  "production": [
98
109
  ">0.5%",
@@ -108,42 +119,33 @@
108
119
  ]
109
120
  },
110
121
  "dependencies": {
111
- "@vueuse/core": "^9.2.0",
122
+ "@vueuse/core": "^9.3.0",
112
123
  "autosize": "^5.0.1",
113
- "marked": "^4.1.0",
114
- "vue": "^3.2.39"
124
+ "marked": "^4.1.1",
125
+ "vue": "^3.2.40"
115
126
  },
116
127
  "devDependencies": {
117
- "@babel/core": "7.19.1",
118
- "@babel/preset-env": "7.19.1",
128
+ "@babel/core": "7.19.3",
129
+ "@babel/preset-env": "7.19.3",
130
+ "@giphy/js-types": "4.2.1",
119
131
  "@rollup/plugin-babel": "5.3.1",
120
132
  "@rollup/plugin-commonjs": "22.0.2",
121
133
  "@rollup/plugin-node-resolve": "14.1.0",
122
134
  "@rollup/plugin-replace": "4.0.0",
123
135
  "@types/autosize": "4.0.1",
124
136
  "@types/marked": "4.0.7",
125
- "@types/node": "18.7.21",
126
- "@vitejs/plugin-vue": "3.1.0",
137
+ "@types/node": "18.8.2",
138
+ "@vitejs/plugin-vue": "3.1.2",
127
139
  "recaptcha-v3": "1.10.0",
128
140
  "rimraf": "3.0.2",
129
141
  "rollup": "2.79.1",
130
142
  "rollup-plugin-dts": "4.2.2",
131
143
  "rollup-plugin-terser": "7.0.2",
132
144
  "rollup-plugin-ts": "3.0.2",
133
- "typescript": "4.8.3",
134
- "vite": "3.1.3"
145
+ "typescript": "4.8.4",
146
+ "vite": "3.1.4"
135
147
  },
136
148
  "engines": {
137
149
  "node": ">=14"
138
- },
139
- "scripts": {
140
- "build": "pnpm rollup && pnpm style",
141
- "clean": "rimraf ./dist",
142
- "dev": "vite -c config/vite.config.js",
143
- "lint": "eslint --ext .ts,.vue .",
144
- "rollup": "rollup -c ./config/rollup.config.js",
145
- "style": "pnpm style:main && pnpm style:meta",
146
- "style:main": "sass ./src/styles/index.scss ./dist/waline.css --style=compressed",
147
- "style:meta": "sass ./src/styles/meta.scss ./dist/waline-meta.css --style=compressed"
148
150
  }
149
- }
151
+ }
package/src/api/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from './commentCount';
4
4
  export * from './login';
5
5
  export * from './pageview';
6
6
  export * from './recentComment';
7
+ export * from './user';
@@ -0,0 +1,35 @@
1
+ import { WalineComment } from '../typings';
2
+ import { errorCheck } from './utils';
3
+
4
+ export interface FetchUserListOptions {
5
+ serverURL: string;
6
+ pageSize: number;
7
+ signal: AbortSignal;
8
+ lang: string;
9
+ }
10
+
11
+ export interface WalineUser
12
+ extends Pick<WalineComment, 'nick' | 'link' | 'avatar' | 'label' | 'level'> {
13
+ count: number;
14
+ }
15
+
16
+ export const fetchUserList = ({
17
+ serverURL,
18
+ signal,
19
+ pageSize,
20
+ lang,
21
+ }: FetchUserListOptions): Promise<WalineUser[]> => {
22
+ return fetch(`${serverURL}/user?pageSize=${pageSize}&lang=${lang}`, {
23
+ signal,
24
+ })
25
+ .then(
26
+ (resp) =>
27
+ resp.json() as Promise<{
28
+ errno: number;
29
+ message: string;
30
+ data: WalineUser[];
31
+ }>
32
+ )
33
+ .then((resp) => errorCheck(resp, 'user list'))
34
+ .then((resp) => resp.data);
35
+ };
@@ -36,7 +36,7 @@
36
36
  >
37
37
  <div v-for="kind in config.meta" :key="kind" class="wl-header-item">
38
38
  <label
39
- :for="kind"
39
+ :for="`wl-${kind}`"
40
40
  v-text="
41
41
  locale[kind] +
42
42
  (config.requiredMeta.includes(kind) || !config.requiredMeta.length
@@ -624,11 +624,12 @@ export default defineComponent({
624
624
 
625
625
  searchResults.loading = true;
626
626
 
627
- searchResults.list.push(
628
- ...(searchOptions.more
627
+ searchResults.list = [
628
+ ...searchResults.list,
629
+ ...(searchOptions.more && searchResults.list.length
629
630
  ? await searchOptions.more(keyword, searchResults.list.length)
630
- : await searchOptions.search(keyword))
631
- );
631
+ : await searchOptions.search(keyword)),
632
+ ];
632
633
 
633
634
  searchResults.loading = false;
634
635
 
@@ -42,7 +42,7 @@ SOFTWARE. -->
42
42
  :src="items[itemIndex].src"
43
43
  :title="items[itemIndex].title"
44
44
  loading="lazy"
45
- @load="state[items[itemIndex].src] = true"
45
+ @load="imageLoad"
46
46
  @click="$emit('insert', `![](${items[itemIndex].src})`)"
47
47
  />
48
48
  </template>
@@ -133,6 +133,10 @@ export default defineComponent({
133
133
  window.scrollTo({ top: scrollY });
134
134
  };
135
135
 
136
+ const imageLoad = (e: Event) => {
137
+ state.value[(e.target as HTMLImageElement).src] = true;
138
+ };
139
+
136
140
  watch(
137
141
  () => [props.items],
138
142
  () => {
@@ -158,6 +162,7 @@ export default defineComponent({
158
162
  columns,
159
163
  state,
160
164
  wall,
165
+ imageLoad,
161
166
  };
162
167
  },
163
168
  });
@@ -10,8 +10,7 @@ export type UserInfoRef = Ref<UserInfo | Record<string, never>>;
10
10
  let userInfoStorage: UserInfoRef | null = null;
11
11
 
12
12
  export const useUserInfo = (): UserInfoRef =>
13
- userInfoStorage ||
14
- (userInfoStorage = useStorage<UserInfo | Record<string, never>>(
13
+ (userInfoStorage ??= useStorage<UserInfo | Record<string, never>>(
15
14
  USER_KEY,
16
15
  {}
17
16
  ));
@@ -17,4 +17,4 @@ export type VoteRef = Ref<VoteLogItem[]>;
17
17
  let voteStorage: VoteRef | null = null;
18
18
 
19
19
  export const useVoteStorage = (): VoteRef =>
20
- voteStorage || (voteStorage = useStorage<VoteLogItem[]>(VOTE_KEY, []));
20
+ (voteStorage ??= useStorage<VoteLogItem[]>(VOTE_KEY, []));
@@ -1,4 +1,9 @@
1
- import type { WalineMeta, WalineSearchOptions } from '../typings';
1
+ import type { IGif } from '@giphy/js-types';
2
+ import type {
3
+ WalineMeta,
4
+ WalineSearchOptions,
5
+ WalineSearchResult,
6
+ } from '../typings';
2
7
 
3
8
  const availableMeta: WalineMeta[] = ['nick', 'mail', 'link'];
4
9
 
@@ -25,94 +30,51 @@ export const defaultTexRenderer = (blockMode: boolean): string =>
25
30
  ? '<p class="wl-tex">Tex is not available in preview</p>'
26
31
  : '<span class="wl-tex">Tex is not available in preview</span>';
27
32
 
28
- export const getDefaultSearchOptions = (): WalineSearchOptions => {
29
- interface FetchGifRequest {
30
- keyword: string;
31
- pos?: string;
33
+ export const getDefaultSearchOptions = (lang: string): WalineSearchOptions => {
34
+ interface Result {
35
+ meta: {
36
+ msg: string;
37
+ response_id: string;
38
+ status: number;
39
+ };
40
+ pagination: {
41
+ count: number;
42
+ total_count: number;
43
+ offset: number;
44
+ };
32
45
  }
33
-
34
- type GifFormat =
35
- | 'gif'
36
- | 'mediumgif'
37
- | 'tinygif'
38
- | 'nanogif'
39
- | 'mp4'
40
- | 'loopedmp4'
41
- | 'tinymp4'
42
- | 'nanomp4'
43
- | 'webm'
44
- | 'tinywebm'
45
- | 'nanowebm';
46
-
47
- interface MediaObject {
48
- preview: string;
49
- url: string;
50
- dims: number[];
51
- size: number;
52
- }
53
-
54
- interface GifObject {
55
- created: number;
56
- hasaudio: boolean;
57
- id: string;
58
- media: Record<GifFormat, MediaObject>[];
59
- tags: string[];
60
- title: string;
61
- itemurl: string;
62
- hascaption: boolean;
63
- url: string;
64
- }
65
-
66
- interface FetchGifResponse {
67
- next: string;
68
- results: GifObject[];
46
+ interface GifsResult extends Result {
47
+ data: IGif[];
69
48
  }
70
49
 
71
- const state = {
72
- next: '',
73
- };
74
-
75
- const fetchGif = ({
76
- keyword,
77
- pos,
78
- }: FetchGifRequest): Promise<FetchGifResponse> => {
79
- const baseUrl = `https://g.tenor.com/v1/search`;
80
- const query = new URLSearchParams('media_filter=minimal');
81
-
82
- query.set('key', 'PAY5JLFIH6V6');
83
- query.set('limit', '20');
84
- query.set('pos', pos || '');
85
- query.set('q', keyword);
86
-
87
- return fetch(`${baseUrl}?${query.toString()}`, {
88
- headers: {
89
- // eslint-disable-next-line @typescript-eslint/naming-convention
90
- 'Content-Type': 'application/json',
91
- },
92
- })
93
- .then((resp) => resp.json() as Promise<FetchGifResponse>)
94
- .catch(() => ({ next: pos || '', results: [] }));
50
+ const fetchGiphy = async (
51
+ url: string,
52
+ params: Record<string, string> = {}
53
+ ): Promise<WalineSearchResult> => {
54
+ const querystring = new URLSearchParams({
55
+ lang,
56
+ limit: '20',
57
+ rating: 'g',
58
+ api_key: '6CIMLkNMMOhRcXPoMCPkFy4Ybk2XUiMp',
59
+ ...params,
60
+ }).toString();
61
+
62
+ const resp = await fetch(
63
+ `https://api.giphy.com/v1/gifs/${url}?${querystring}`
64
+ ).then((resp) => resp.json() as Promise<GifsResult>);
65
+
66
+ return resp.data.map((gif) => ({
67
+ title: gif.title,
68
+ src: gif.images.downsized_medium.url,
69
+ }));
95
70
  };
96
71
 
97
72
  return {
98
- search: (word = '') =>
99
- fetchGif({ keyword: word }).then((resp) => {
100
- state.next = resp.next;
101
-
102
- return resp.results.map((item) => ({
103
- title: item.title,
104
- src: item.media[0].tinygif.url,
105
- }));
106
- }),
107
- more: (word) =>
108
- fetchGif({ keyword: word, pos: state.next }).then((resp) => {
109
- state.next = resp.next;
110
-
111
- return resp.results.map((item) => ({
112
- title: item.title,
113
- src: item.media[0].tinygif.url,
114
- }));
115
- }),
73
+ search: (word: string): Promise<WalineSearchResult> =>
74
+ fetchGiphy('search', { q: word, offset: '0' }),
75
+ default: (): Promise<WalineSearchResult> => fetchGiphy('trending', {}),
76
+ more: (word: string, offset = 0): Promise<WalineSearchResult> =>
77
+ fetchGiphy('search', { q: word, offset: offset.toString() }),
116
78
  };
117
79
  };
118
80
 
package/src/entrys/api.ts CHANGED
@@ -1 +1 @@
1
- export * from './api';
1
+ export * from '../api';
@@ -13,4 +13,5 @@
13
13
  @use 'highlight';
14
14
 
15
15
  @use 'recent';
16
+ @use 'userlist';
16
17
  @use 'reaction';
@@ -0,0 +1,116 @@
1
+ :root {
2
+ --waline-rank-gold-bgcolor: #FA3939;
3
+ --waline-rank-silver-bgcolor: #FB811C;
4
+ --waline-rank-copper-bgcolor: #FEB207;
5
+ }
6
+
7
+ .wl-user-list {
8
+ list-style: none;
9
+ padding: 0;
10
+
11
+ > li + li {
12
+ margin-top: 10px;
13
+ }
14
+
15
+ > li, > li > a {
16
+ display: flex;
17
+ flex-direction: row;
18
+ gap: 10px;
19
+ }
20
+
21
+ a,
22
+ a:hover,
23
+ a:visited {
24
+ text-decoration: none;
25
+ color: var(--waline-color);
26
+ }
27
+
28
+ .wl-user-avatar {
29
+ position: relative;
30
+ line-height: 0;
31
+
32
+ > i {
33
+ position: absolute;
34
+ bottom: 0;
35
+ right: 0;
36
+ min-width: 1.5em;
37
+ height: 1.5em;
38
+ line-height: 1.5em;
39
+ background: var(--waline-info-bgcolor);
40
+ text-align: center;
41
+ font-size: 10px;
42
+ border-radius: 2px;
43
+ font-style: normal;
44
+ color: var(--waline-info-color);
45
+ }
46
+ }
47
+
48
+ img {
49
+ width: 48px;
50
+ height: 48px;
51
+ border-radius: 2px;
52
+ }
53
+
54
+ .wl-user-name {
55
+ display: flex;
56
+ flex-direction: row;
57
+ align-items: center;
58
+ gap: 10px;
59
+ }
60
+ .wl-user-tag {
61
+ padding-bottom: 0;
62
+ border-bottom: none;
63
+ width: auto;
64
+ }
65
+
66
+
67
+ > li:nth-child(1) i {
68
+ background: var(--waline-rank-gold-bgcolor);
69
+ color: var(--waline-white);
70
+ font-weight: bold;
71
+ }
72
+
73
+ > li:nth-child(2) i {
74
+ background: var(--waline-rank-silver-bgcolor);
75
+ color: var(--waline-white);
76
+ font-weight: bold;
77
+ }
78
+
79
+ > li:nth-child(3) i {
80
+ background: var(--waline-rank-copper-bgcolor);
81
+ color: var(--waline-white);
82
+ font-weight: bold;
83
+ }
84
+ }
85
+
86
+ .wl-user-wall {
87
+ display: flex;
88
+ flex-direction: row;
89
+ list-style: none;
90
+ padding: 0;
91
+
92
+ > li {
93
+ width: 48px;
94
+ height: 48px;
95
+ overflow: hidden;
96
+ }
97
+
98
+ > li:hover img {
99
+ transform: scale(1.25);
100
+ }
101
+
102
+ img {
103
+ width: 48px;
104
+ height: 48px;
105
+ border-radius: 0;
106
+ transition: transform ease-in-out 400ms;
107
+ }
108
+
109
+ .wl-user-avatar > i,
110
+ .wl-user-meta {
111
+ display: none;
112
+ }
113
+ }
114
+
115
+
116
+
@@ -64,7 +64,7 @@ export const getConfig = ({
64
64
  texRenderer,
65
65
  copyright = true,
66
66
  login = 'enable',
67
- search = getDefaultSearchOptions(),
67
+ search,
68
68
  reaction,
69
69
  recaptchaV3Key = '',
70
70
  ...more
@@ -87,7 +87,7 @@ export const getConfig = ({
87
87
  pageSize,
88
88
  login,
89
89
  copyright,
90
- search,
90
+ search: search || getDefaultSearchOptions(lang),
91
91
  recaptchaV3Key,
92
92
  reaction: Array.isArray(reaction)
93
93
  ? reaction
@@ -1 +1,2 @@
1
1
  export * from './recentComments';
2
+ export * from './userList';
@@ -0,0 +1,138 @@
1
+ import { fetchUserList, WalineUser } from '../api';
2
+ import { defaultLang, defaultLocales } from '../config';
3
+ import { WalineLocale } from '../typings';
4
+ import { getRoot } from '../utils';
5
+
6
+ export interface WalineUserListOptions {
7
+ /**
8
+ * Waline 服务端地址
9
+ *
10
+ * Waline serverURL
11
+ */
12
+ serverURL: string;
13
+
14
+ /**
15
+ * 获取用户列表的数量
16
+ *
17
+ * fetch number of user list
18
+ */
19
+ count: number;
20
+
21
+ /**
22
+ * 需要挂载的元素
23
+ *
24
+ * Element to be mounted
25
+ */
26
+ el?: string | HTMLElement;
27
+
28
+ /**
29
+ * 错误提示消息所使用的语言
30
+ *
31
+ * Language of error message
32
+ *
33
+ * @default 'zh-CN'
34
+ */
35
+ lang?: string;
36
+
37
+ /**
38
+ * 自定义 waline 语言显示
39
+ *
40
+ * @see [自定义语言](https://waline.js.org/client/i18n.html)
41
+ *
42
+ * Custom display language in waline
43
+ *
44
+ * @see [I18n](https://waline.js.org/en/client/i18n.html)
45
+ */
46
+ locale?: WalineLocale;
47
+
48
+ /**
49
+ * 列表模式还是头像墙模式
50
+ *
51
+ * list mode or avatar wall mode
52
+ */
53
+ mode: 'list' | 'wall';
54
+ }
55
+
56
+ export interface WalineUserListResult {
57
+ /**
58
+ * 用户数据
59
+ *
60
+ * User Data
61
+ */
62
+ users: WalineUser[];
63
+
64
+ /**
65
+ * 取消挂载挂件
66
+ *
67
+ * Umount widget
68
+ */
69
+ destroy: () => void;
70
+ }
71
+
72
+ export const UserList = ({
73
+ el,
74
+ serverURL,
75
+ count,
76
+ locale,
77
+ lang = defaultLang,
78
+ mode = 'list',
79
+ }: WalineUserListOptions): Promise<WalineUserListResult> => {
80
+ const root = getRoot(el);
81
+ const controller = new AbortController();
82
+
83
+ return fetchUserList({
84
+ serverURL,
85
+ pageSize: count,
86
+ lang,
87
+ signal: controller.signal,
88
+ }).then((users) => {
89
+ if (!root || !users.length) {
90
+ return {
91
+ users,
92
+ destroy: (): void => controller.abort(),
93
+ };
94
+ }
95
+
96
+ locale = {
97
+ ...(defaultLocales[lang] || defaultLocales[defaultLang]),
98
+ ...(typeof locale === 'object' ? locale : {}),
99
+ } as WalineLocale;
100
+
101
+ root.innerHTML = `<ul class="wl-user-${mode}">${users
102
+ .map((user, index) =>
103
+ [
104
+ '<li class="wl-user-item">',
105
+ user.link && `<a href="${user.link}" target="_blank">`,
106
+ '<div class="wl-user-avatar">',
107
+ `<img src="${user.avatar}" alt="${user.nick}">`,
108
+ `<i>${index + 1}</i>`,
109
+ '</div>',
110
+ '<div class="wl-user-meta">',
111
+ `<div class="wl-user-name"><div>${user.nick}</div>`,
112
+ '<div class="wl-user-tag wl-card">',
113
+ user.level &&
114
+ `<span class="wl-badge">${
115
+ locale ? locale[`level${user.level}`] : `Level ${user.level}`
116
+ }</span>`,
117
+ user.label && `<span class="wl-badge">${user.label}</span>`,
118
+ '</div>',
119
+ '</div>',
120
+ user.link && `<span class="wl-user-meta">${user.link}</span>`,
121
+ '</div>',
122
+ user.link && '</a>',
123
+ '</li>',
124
+ ]
125
+ .filter((v) => v)
126
+ .join('')
127
+ )
128
+ .join('')}</ul>`;
129
+
130
+ return {
131
+ users,
132
+ destroy: (): void => {
133
+ controller.abort();
134
+ root.innerHTML = '';
135
+ },
136
+ };
137
+ });
138
+ };