@waline/client 2.5.1 → 2.6.2

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 (59) hide show
  1. package/dist/component.esm.js +1 -1
  2. package/dist/component.esm.js.map +1 -1
  3. package/dist/component.js +1 -1
  4. package/dist/component.js.map +1 -1
  5. package/dist/legacy.d.ts +41 -0
  6. package/dist/legacy.js +1 -1
  7. package/dist/legacy.js.map +1 -1
  8. package/dist/pageview.cjs.js +1 -1
  9. package/dist/pageview.cjs.js.map +1 -1
  10. package/dist/pageview.esm.js +1 -1
  11. package/dist/pageview.esm.js.map +1 -1
  12. package/dist/pageview.js +1 -1
  13. package/dist/pageview.js.map +1 -1
  14. package/dist/shim.d.ts +42 -1
  15. package/dist/shim.esm.d.ts +42 -1
  16. package/dist/shim.esm.js +1 -1
  17. package/dist/shim.esm.js.map +1 -1
  18. package/dist/shim.js +1 -1
  19. package/dist/shim.js.map +1 -1
  20. package/dist/waline.cjs.d.ts +42 -1
  21. package/dist/waline.cjs.js +1 -1
  22. package/dist/waline.cjs.js.map +1 -1
  23. package/dist/waline.css +1 -1
  24. package/dist/waline.css.map +1 -1
  25. package/dist/waline.d.ts +42 -1
  26. package/dist/waline.esm.d.ts +42 -1
  27. package/dist/waline.esm.js +1 -1
  28. package/dist/waline.esm.js.map +1 -1
  29. package/dist/waline.js +1 -1
  30. package/dist/waline.js.map +1 -1
  31. package/package.json +31 -32
  32. package/src/comment.ts +7 -2
  33. package/src/components/CommentBox.vue +88 -71
  34. package/src/components/CommentCard.vue +19 -17
  35. package/src/components/ImageWall.vue +34 -22
  36. package/src/composables/userInfo.ts +1 -1
  37. package/src/config/default.ts +93 -1
  38. package/src/config/i18n/en.ts +1 -0
  39. package/src/config/i18n/generate.ts +1 -0
  40. package/src/config/i18n/jp.ts +1 -0
  41. package/src/config/i18n/pt-BR.ts +1 -0
  42. package/src/config/i18n/ru.ts +1 -0
  43. package/src/config/i18n/vi-VN.ts +1 -0
  44. package/src/config/i18n/zh-CN.ts +1 -0
  45. package/src/config/i18n/zh-TW.ts +3 -2
  46. package/src/init.ts +0 -4
  47. package/src/pageview.ts +7 -2
  48. package/src/styles/emoji.scss +23 -0
  49. package/src/styles/gif.scss +6 -5
  50. package/src/typings/base.ts +40 -0
  51. package/src/typings/locale.ts +1 -0
  52. package/src/typings/waline.ts +8 -0
  53. package/src/utils/config.ts +5 -2
  54. package/src/utils/fetch.ts +3 -0
  55. package/src/utils/index.ts +0 -2
  56. package/src/utils/markedMathExtension.ts +1 -0
  57. package/LICENSE +0 -339
  58. package/src/utils/fetchGif.ts +0 -63
  59. package/src/utils/throttle.ts +0 -15
@@ -31,17 +31,21 @@ SOFTWARE. -->
31
31
  :data-index="columnIndex"
32
32
  :style="{ gap: `${gap}px` }"
33
33
  >
34
- <img
35
- v-for="itemIndex in column"
36
- :key="itemIndex"
37
- class="wl-gallery-item"
38
- :src="items[itemIndex].media[0].tinygif.url"
39
- :title="items[itemIndex].title"
40
- loading="lazy"
41
- @click="
42
- $emit('insert', `![](${items[itemIndex].media[0].tinygif.url})`)
43
- "
44
- />
34
+ <template v-for="itemIndex in column" :key="itemIndex">
35
+ <LoadingIcon
36
+ v-if="!state[items[itemIndex].src]"
37
+ :size="36"
38
+ style="margin: 20px auto"
39
+ />
40
+ <img
41
+ class="wl-gallery-item"
42
+ :src="items[itemIndex].src"
43
+ :title="items[itemIndex].title"
44
+ loading="lazy"
45
+ @load="state[items[itemIndex].src] = true"
46
+ @click="$emit('insert', `![](${items[itemIndex].src})`)"
47
+ />
48
+ </template>
45
49
  </div>
46
50
  </div>
47
51
  </template>
@@ -56,29 +60,36 @@ import {
56
60
  watch,
57
61
  } from 'vue';
58
62
 
63
+ import { LoadingIcon } from './Icons';
64
+
59
65
  import type { PropType } from 'vue';
66
+ import type { WalineSearchResult } from '../typings';
60
67
 
61
68
  type Column = number[];
62
69
 
63
70
  export default defineComponent({
71
+ name: 'ImageWall',
72
+
73
+ components: {
74
+ LoadingIcon,
75
+ },
76
+
64
77
  props: {
78
+ items: { type: Array as PropType<WalineSearchResult[]>, default: () => [] },
65
79
  columnWidth: { type: Number, default: 300 },
66
- items: { type: Array as PropType<unknown[]>, default: () => [] },
67
80
  gap: { type: Number, default: 0 },
68
- rtl: { type: Boolean, default: false },
69
81
  },
70
82
 
71
- emit: ['insert'],
83
+ emits: ['insert'],
72
84
 
73
85
  setup(props) {
74
86
  let resizeObserver: ResizeObserver | null = null;
75
87
  const wall = ref<HTMLDivElement | null>(null);
76
-
88
+ const state = ref<Record<string, boolean>>({});
77
89
  const columns = ref<Column[]>([]);
78
90
 
79
91
  const getColumnCount = (): number => {
80
92
  const count = Math.floor(
81
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
82
93
  (wall.value!.getBoundingClientRect().width + props.gap) /
83
94
  (props.columnWidth + props.gap)
84
95
  );
@@ -98,8 +109,6 @@ export default defineComponent({
98
109
  wall.value?.children || []
99
110
  ) as HTMLDivElement[];
100
111
 
101
- if (props.rtl) columnDivs.reverse();
102
-
103
112
  const target = columnDivs.reduce((prev, curr) =>
104
113
  curr.getBoundingClientRect().height <
105
114
  prev.getBoundingClientRect().height
@@ -125,8 +134,11 @@ export default defineComponent({
125
134
  };
126
135
 
127
136
  watch(
128
- () => [props.items, props.rtl],
129
- () => redraw(true)
137
+ () => [props.items],
138
+ () => {
139
+ state.value = {};
140
+ redraw(true);
141
+ }
130
142
  );
131
143
  watch(
132
144
  () => [props.columnWidth, props.gap],
@@ -136,15 +148,15 @@ export default defineComponent({
136
148
  onMounted(() => {
137
149
  redraw(true);
138
150
  resizeObserver = new ResizeObserver(() => redraw());
139
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
151
+
140
152
  resizeObserver.observe(wall.value!);
141
153
  });
142
154
 
143
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
144
155
  onBeforeUnmount(() => resizeObserver!.unobserve(wall.value!));
145
156
 
146
157
  return {
147
158
  columns,
159
+ state,
148
160
  wall,
149
161
  };
150
162
  },
@@ -23,6 +23,6 @@ let userInfoStorage: UserInfoRef | null = null;
23
23
  export const useUserInfo = (): UserInfoRef =>
24
24
  userInfoStorage ||
25
25
  (userInfoStorage = useStorage<UserInfo | Record<string, never>>(
26
- 'USER_KEY',
26
+ USER_KEY,
27
27
  {}
28
28
  ));
@@ -1,4 +1,4 @@
1
- import type { WalineMeta } from '../typings';
1
+ import type { WalineMeta, WalineSearchOptions } from '../typings';
2
2
 
3
3
  const availableMeta: WalineMeta[] = ['nick', 'mail', 'link'];
4
4
 
@@ -10,6 +10,7 @@ export const defaultLang = 'zh-CN';
10
10
  export const defaultUploadImage = (file: File): Promise<string> =>
11
11
  new Promise((resolve, reject) => {
12
12
  const reader = new FileReader();
13
+
13
14
  reader.readAsDataURL(file);
14
15
  reader.onload = (): void => resolve(reader.result?.toString() || '');
15
16
  reader.onerror = reject;
@@ -19,3 +20,94 @@ export const defaultTexRenderer = (blockMode: boolean): string =>
19
20
  blockMode === true
20
21
  ? '<p class="wl-tex">Tex is not available in preview</p>'
21
22
  : '<span class="wl-tex">Tex is not available in preview</span>';
23
+
24
+ export const getDefaultSearchOptions = (): WalineSearchOptions => {
25
+ interface FetchGifRequest {
26
+ keyword: string;
27
+ pos?: string;
28
+ }
29
+
30
+ type GifFormat =
31
+ | 'gif'
32
+ | 'mediumgif'
33
+ | 'tinygif'
34
+ | 'nanogif'
35
+ | 'mp4'
36
+ | 'loopedmp4'
37
+ | 'tinymp4'
38
+ | 'nanomp4'
39
+ | 'webm'
40
+ | 'tinywebm'
41
+ | 'nanowebm';
42
+
43
+ interface MediaObject {
44
+ preview: string;
45
+ url: string;
46
+ dims: number[];
47
+ size: number;
48
+ }
49
+
50
+ interface GifObject {
51
+ created: number;
52
+ hasaudio: boolean;
53
+ id: string;
54
+ media: Record<GifFormat, MediaObject>[];
55
+ tags: string[];
56
+ title: string;
57
+ itemurl: string;
58
+ hascaption: boolean;
59
+ url: string;
60
+ }
61
+
62
+ interface FetchGifResponse {
63
+ next: string;
64
+ results: GifObject[];
65
+ }
66
+
67
+ const state = {
68
+ next: '',
69
+ };
70
+
71
+ const fetchGif = ({
72
+ keyword,
73
+ pos,
74
+ }: FetchGifRequest): Promise<FetchGifResponse> => {
75
+ const baseUrl = `https://g.tenor.com/v1/search`;
76
+ const query = new URLSearchParams('media_filter=minimal');
77
+
78
+ query.set('key', 'PAY5JLFIH6V6');
79
+ query.set('limit', '20');
80
+ query.set('pos', pos || '');
81
+ query.set('q', keyword);
82
+
83
+ return fetch(`${baseUrl}?${query.toString()}`, {
84
+ headers: {
85
+ // eslint-disable-next-line @typescript-eslint/naming-convention
86
+ 'Content-Type': 'application/json',
87
+ },
88
+ })
89
+ .then((resp) => resp.json() as Promise<FetchGifResponse>)
90
+ .catch(() => ({ next: pos || '', results: [] }));
91
+ };
92
+
93
+ return {
94
+ search: (word = '') =>
95
+ fetchGif({ keyword: word }).then((resp) => {
96
+ state.next = resp.next;
97
+
98
+ return resp.results.map((item) => ({
99
+ title: item.title,
100
+ src: item.media[0].tinygif.url,
101
+ }));
102
+ }),
103
+ more: (word) =>
104
+ fetchGif({ keyword: word, pos: state.next }).then((resp) => {
105
+ state.next = resp.next;
106
+
107
+ return resp.results.map((item) => ({
108
+ title: item.title,
109
+ src: item.media[0].tinygif.url,
110
+ }));
111
+ }),
112
+ };
113
+ };
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  'Maiar',
42
42
  'GIF',
43
43
  'Search GIF',
44
+ 'Profile',
44
45
  ]);
@@ -41,6 +41,7 @@ const localeKeys = [
41
41
  'level5',
42
42
  'gif',
43
43
  'gifSearchPlaceholder',
44
+ 'profile',
44
45
  ];
45
46
 
46
47
  export const generateLocale = (locale: string[]): WalineLocale =>
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  'なぬし',
42
42
  'GIF',
43
43
  '探す GIF',
44
+ '個人情報',
44
45
  ]);
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  'Maiar',
42
42
  'GIF',
43
43
  'Pesquisar GIF',
44
+ 'informação pessoal',
44
45
  ]);
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  'Maiar',
42
42
  'GIF',
43
43
  'Поиск GIF',
44
+ 'Персональные данные',
44
45
  ]);
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  'Maiar',
42
42
  'GIF',
43
43
  'Tìm kiếm GIF',
44
+ 'thông tin cá nhân',
44
45
  ]);
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  '传说',
42
42
  '表情包',
43
43
  '搜索表情包',
44
+ '个人资料',
44
45
  ]);
@@ -2,11 +2,11 @@ import { generateLocale } from './generate';
2
2
 
3
3
  export default generateLocale([
4
4
  '暱稱',
5
+ '暱稱不能少於3個字元',
5
6
  '郵箱',
7
+ '請填寫正確的郵件地址',
6
8
  '網址',
7
9
  '可選',
8
- '暱稱不能少於3個字元',
9
- '請填寫正確的郵件地址',
10
10
  '歡迎評論',
11
11
  '來發評論吧~',
12
12
  '提交',
@@ -41,4 +41,5 @@ export default generateLocale([
41
41
  '傳說',
42
42
  '表情包',
43
43
  '搜索表情包',
44
+ '個人資料',
44
45
  ]);
package/src/init.ts CHANGED
@@ -80,12 +80,8 @@ export const init = ({
80
80
  ? createApp(() => h(Waline, { path: state.path, ...props }))
81
81
  : null;
82
82
 
83
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
84
83
  if (app) app.mount(root!);
85
84
 
86
- updateCommentCount();
87
- updatePageviewCount();
88
-
89
85
  const stopComment = watchEffect(updateCommentCount);
90
86
  const stopPageview = watchEffect(updatePageviewCount);
91
87
 
package/src/pageview.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  errorHandler,
3
3
  fetchPageviews,
4
4
  getQuery,
5
+ getServerURL,
5
6
  updatePageviews,
6
7
  } from './utils';
7
8
 
@@ -83,7 +84,7 @@ export const pageviewCount = ({
83
84
 
84
85
  const fetch = (elements: HTMLElement[]): Promise<void> =>
85
86
  fetchPageviews({
86
- serverURL,
87
+ serverURL: getServerURL(serverURL),
87
88
  paths: elements.map((element) => getQuery(element) || path),
88
89
  lang,
89
90
  signal: controller.signal,
@@ -96,7 +97,11 @@ export const pageviewCount = ({
96
97
  const normalElements = elements.filter((element) => !filter(element));
97
98
  const elementsNeedstoBeFetched = elements.filter(filter);
98
99
 
99
- void updatePageviews({ serverURL, path, lang }).then((count) =>
100
+ void updatePageviews({
101
+ serverURL: getServerURL(serverURL),
102
+ path,
103
+ lang,
104
+ }).then((count) =>
100
105
  renderVisitorCount(
101
106
  new Array<number>(normalElements.length).fill(count),
102
107
  normalElements
@@ -79,9 +79,14 @@
79
79
 
80
80
  .wl-tabs {
81
81
  position: relative;
82
+
83
+ overflow-x: scroll;
84
+
82
85
  height: 2em;
83
86
  padding: 0 6px 1px;
84
87
 
88
+ white-space: nowrap;
89
+
85
90
  &::before {
86
91
  content: ' ';
87
92
 
@@ -95,6 +100,24 @@
95
100
 
96
101
  background: var(--waline-border-color);
97
102
  }
103
+
104
+ &::-webkit-scrollbar {
105
+ width: 6px;
106
+ height: 6px;
107
+ }
108
+
109
+ &::-webkit-scrollbar-track-piece:horizontal {
110
+ -webkit-border-radius: 6px;
111
+ border-radius: 6px;
112
+ background: rgb(0 0 0 / 10%);
113
+ }
114
+
115
+ &::-webkit-scrollbar-thumb:horizontal {
116
+ height: 6px;
117
+ -webkit-border-radius: 6px;
118
+ border-radius: 6px;
119
+ background: var(--waline-theme-color);
120
+ }
98
121
  }
99
122
 
100
123
  .wl-tab {
@@ -54,17 +54,18 @@
54
54
  }
55
55
 
56
56
  .wl-gallery {
57
- max-height: 80vh;
58
57
  display: flex;
59
58
  overflow-y: auto;
59
+ max-height: 80vh;
60
60
  }
61
61
 
62
62
  .wl-gallery-column {
63
+ display: flex;
64
+ flex: 1;
65
+ flex-direction: column;
66
+
67
+ // stylelint-disable
63
68
  height: -webkit-max-content;
64
69
  height: -moz-max-content;
65
70
  height: max-content;
66
-
67
- flex: 1;
68
- display: flex;
69
- flex-direction: column;
70
71
  }
@@ -39,6 +39,46 @@ export interface WalineEmojiInfo {
39
39
 
40
40
  export type WalineEmojiMaps = Record<string, string>;
41
41
 
42
+ export interface WalineSearchResult extends Record<string, unknown> {
43
+ /**
44
+ * Image link
45
+ */
46
+ src: string;
47
+
48
+ /**
49
+ * Image title, optional
50
+ */
51
+ title?: string;
52
+
53
+ /**
54
+ * Image preview link, optional
55
+ *
56
+ * @default src
57
+ */
58
+ preview?: string;
59
+ }
60
+
61
+ export interface WalineSearchOptions {
62
+ /**
63
+ * Search action
64
+ */
65
+ search: (word: string) => Promise<WalineSearchResult[]>;
66
+
67
+ /**
68
+ * Default search action
69
+ *
70
+ * @default () => search('')
71
+ */
72
+ default?: () => Promise<WalineSearchResult[]>;
73
+
74
+ /**
75
+ * Fetch more action
76
+ *
77
+ * @default (word) => search(word)
78
+ */
79
+ more?: (word: string, currectCount: number) => Promise<WalineSearchResult[]>;
80
+ }
81
+
42
82
  export type WalineMeta = 'nick' | 'mail' | 'link';
43
83
 
44
84
  export type WalineImageUploader = (image: File) => Promise<string>;
@@ -38,4 +38,5 @@ export interface WalineLocale extends WalineDateLocale, WalineLevelLocale {
38
38
  anonymous: string;
39
39
  gif: string;
40
40
  gifSearchPlaceholder: string;
41
+ profile: string;
41
42
  }
@@ -4,6 +4,7 @@ import type {
4
4
  WalineImageUploader,
5
5
  WalineMeta,
6
6
  WalineTexRenderer,
7
+ WalineSearchOptions,
7
8
  } from './base';
8
9
  import type { WalineLocale } from './locale';
9
10
 
@@ -148,6 +149,13 @@ export interface WalineProps {
148
149
  */
149
150
  emoji?: (string | WalineEmojiInfo)[] | false;
150
151
 
152
+ /**
153
+ * 设置搜索功能
154
+ *
155
+ * Customize Search feature
156
+ */
157
+ search?: WalineSearchOptions | false;
158
+
151
159
  /**
152
160
  * 代码高亮
153
161
  *
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  defaultLang,
3
+ defaultLocales,
3
4
  defaultUploadImage,
4
5
  defaultHighlighter,
5
6
  defaultTexRenderer,
7
+ getDefaultSearchOptions,
6
8
  getMeta,
7
- defaultLocales,
8
9
  } from '../config';
9
10
 
10
11
  import { decodePath, isLinkHttp, removeEndingSplash } from './path';
@@ -27,7 +28,7 @@ export interface WalineConfig extends Required<Omit<WalineProps, 'wordLimit'>> {
27
28
  // emoji: Promise<EmojiConfig>;
28
29
  }
29
30
 
30
- const getServerURL = (serverURL: string): string => {
31
+ export const getServerURL = (serverURL: string): string => {
31
32
  const result = removeEndingSplash(serverURL);
32
33
 
33
34
  return isLinkHttp(result) ? result : `https://${result}`;
@@ -61,6 +62,7 @@ export const getConfig = ({
61
62
  texRenderer,
62
63
  copyright = true,
63
64
  login = 'enable',
65
+ search = getDefaultSearchOptions(),
64
66
  ...more
65
67
  }: WalineProps): WalineConfig => ({
66
68
  serverURL: getServerURL(serverURL),
@@ -81,5 +83,6 @@ export const getConfig = ({
81
83
  pageSize,
82
84
  login,
83
85
  copyright,
86
+ search,
84
87
  ...more,
85
88
  });
@@ -37,6 +37,7 @@ export const fetchCommentCount = ({
37
37
  token,
38
38
  }: FetchCountOptions): Promise<number[]> => {
39
39
  const headers: Record<string, string> = {};
40
+
40
41
  if (token) headers.Authorization = `Bearer ${token}`;
41
42
 
42
43
  return (
@@ -68,6 +69,7 @@ export const fetchRecentComment = ({
68
69
  token,
69
70
  }: FetchRecentOptions): Promise<WalineComment[]> => {
70
71
  const headers: Record<string, string> = {};
72
+
71
73
  if (token) headers.Authorization = `Bearer ${token}`;
72
74
 
73
75
  return fetch(`${serverURL}/comment?type=recent&count=${count}&lang=${lang}`, {
@@ -104,6 +106,7 @@ export const fetchCommentList = ({
104
106
  token,
105
107
  }: FetchListOptions): Promise<FetchListResult> => {
106
108
  const headers: Record<string, string> = {};
109
+
107
110
  if (token) headers.Authorization = `Bearer ${token}`;
108
111
 
109
112
  return fetch(
@@ -10,5 +10,3 @@ export * from './markdown';
10
10
  export * from './path';
11
11
  export * from './query';
12
12
  export * from './wordCount';
13
- export * from './fetchGif';
14
- export * from './throttle';
@@ -31,6 +31,7 @@ export const markedTexExtensions = (
31
31
  level: 'inline',
32
32
  start(src: string) {
33
33
  const idx = src.search(inlineMathStart);
34
+
34
35
  return idx !== -1 ? idx : src.length;
35
36
  },
36
37
  tokenizer(src: string) {