@waline/client 2.4.1 → 2.5.1

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 (51) 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 +3 -1
  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.esm.js +1 -1
  10. package/dist/pageview.js +1 -1
  11. package/dist/shim.d.ts +3 -1
  12. package/dist/shim.esm.d.ts +3 -1
  13. package/dist/shim.esm.js +1 -1
  14. package/dist/shim.esm.js.map +1 -1
  15. package/dist/shim.js +1 -1
  16. package/dist/shim.js.map +1 -1
  17. package/dist/waline.cjs.d.ts +3 -1
  18. package/dist/waline.cjs.js +1 -1
  19. package/dist/waline.cjs.js.map +1 -1
  20. package/dist/waline.css +1 -1
  21. package/dist/waline.css.map +1 -1
  22. package/dist/waline.d.ts +3 -1
  23. package/dist/waline.esm.d.ts +3 -1
  24. package/dist/waline.esm.js +1 -1
  25. package/dist/waline.esm.js.map +1 -1
  26. package/dist/waline.js +1 -1
  27. package/dist/waline.js.map +1 -1
  28. package/package.json +11 -12
  29. package/src/components/CommentBox.vue +115 -4
  30. package/src/components/CommentCard.vue +1 -1
  31. package/src/components/Icons.ts +20 -0
  32. package/src/components/ImageWall.vue +152 -0
  33. package/src/composables/like.ts +1 -1
  34. package/src/config/i18n/en.ts +2 -0
  35. package/src/config/i18n/generate.ts +2 -0
  36. package/src/config/i18n/jp.ts +8 -0
  37. package/src/config/i18n/pt-BR.ts +8 -0
  38. package/src/config/i18n/ru.ts +8 -0
  39. package/src/config/i18n/vi-VN.ts +8 -0
  40. package/src/config/i18n/zh-CN.ts +2 -0
  41. package/src/config/i18n/zh-TW.ts +2 -0
  42. package/src/styles/card.scss +1 -0
  43. package/src/styles/emoji.scss +92 -94
  44. package/src/styles/gif.scss +70 -0
  45. package/src/styles/index.scss +3 -0
  46. package/src/styles/panel.scss +0 -4
  47. package/src/typings/locale.ts +2 -0
  48. package/src/typings/waline.ts +1 -1
  49. package/src/utils/fetchGif.ts +63 -0
  50. package/src/utils/index.ts +2 -0
  51. package/src/utils/throttle.ts +15 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@waline/client",
3
- "version": "2.4.1",
3
+ "version": "2.5.1",
4
4
  "description": "client for waline comment system",
5
5
  "keywords": [
6
6
  "valine",
@@ -71,31 +71,30 @@
71
71
  ]
72
72
  },
73
73
  "dependencies": {
74
- "@vueuse/core": "^8.4.2",
74
+ "@vueuse/core": "^8.5.0",
75
75
  "autosize": "^5.0.1",
76
- "marked": "^4.0.15",
77
- "vue": "^3.2.33"
76
+ "marked": "^4.0.16",
77
+ "vue": "^3.2.35"
78
78
  },
79
79
  "devDependencies": {
80
- "@babel/core": "^7.17.10",
81
- "@babel/preset-env": "^7.17.10",
80
+ "@babel/core": "^7.18.0",
81
+ "@babel/preset-env": "^7.18.0",
82
82
  "@rollup/plugin-babel": "^5.3.1",
83
83
  "@rollup/plugin-commonjs": "^22.0.0",
84
84
  "@rollup/plugin-node-resolve": "^13.3.0",
85
85
  "@rollup/plugin-replace": "^4.0.0",
86
86
  "@types/autosize": "^4.0.1",
87
87
  "@types/marked": "^4.0.3",
88
- "@types/node": "^17.0.31",
89
- "@vitejs/plugin-vue": "^2.3.2",
90
- "@vue/compiler-sfc": "^3.2.33",
88
+ "@types/node": "^17.0.35",
89
+ "@vitejs/plugin-vue": "^2.3.3",
90
+ "@vue/compiler-sfc": "^3.2.35",
91
91
  "rimraf": "^3.0.2",
92
- "rollup": "^2.72.1",
92
+ "rollup": "^2.74.1",
93
93
  "rollup-plugin-dts": "^4.2.1",
94
94
  "rollup-plugin-terser": "^7.0.2",
95
95
  "rollup-plugin-ts": "^2.0.7",
96
- "rollup-plugin-vue": "^6.0.0",
97
96
  "typescript": "^4.6.4",
98
- "vite": "^2.9.8"
97
+ "vite": "^2.9.9"
99
98
  },
100
99
  "engines": {
101
100
  "node": ">=14"
@@ -35,7 +35,7 @@
35
35
  <input
36
36
  :ref="
37
37
  (element) => {
38
- if (element) inputRefs[kind] = element;
38
+ if (element) inputRefs[kind] = element as HTMLInputElement;
39
39
  }
40
40
  "
41
41
  :id="`wl-${kind}`"
@@ -88,6 +88,16 @@
88
88
  <EmojiIcon />
89
89
  </button>
90
90
 
91
+ <button
92
+ ref="gifButtonRef"
93
+ class="wl-action"
94
+ :class="{ actived: showGif }"
95
+ :title="locale.gif"
96
+ @click="showGif = !showGif"
97
+ >
98
+ <GifIcon />
99
+ </button>
100
+
91
101
  <input
92
102
  ref="imageUploadRef"
93
103
  class="upload"
@@ -152,6 +162,31 @@
152
162
  </button>
153
163
  </div>
154
164
 
165
+ <div
166
+ ref="gifPopupRef"
167
+ class="wl-gif-popup"
168
+ :class="{ display: showGif }"
169
+ >
170
+ <input
171
+ type="text"
172
+ :placeholder="locale.gifSearchPlaceholder"
173
+ ref="gifSearchInputRef"
174
+ @input="onGifSearch"
175
+ />
176
+
177
+ <ImageWall
178
+ :items="gifData.list"
179
+ :column-width="200"
180
+ :gap="6"
181
+ @insert="insert($event)"
182
+ @scroll="onImageWallScroll"
183
+ >
184
+ </ImageWall>
185
+
186
+ <div v-if="gifData.loading" class="wl-loading">
187
+ <LoadingIcon :size="30" />
188
+ </div>
189
+ </div>
155
190
  <div
156
191
  ref="emojiPopupRef"
157
192
  class="wl-emoji-popup"
@@ -221,6 +256,7 @@ import {
221
256
  watch,
222
257
  } from 'vue';
223
258
 
259
+ import ImageWall from './ImageWall.vue';
224
260
  import {
225
261
  CloseIcon,
226
262
  EmojiIcon,
@@ -228,20 +264,27 @@ import {
228
264
  MarkdownIcon,
229
265
  PreviewIcon,
230
266
  LoadingIcon,
267
+ GifIcon,
231
268
  } from './Icons';
232
269
  import { useEditor, useUserMeta, useUserInfo } from '../composables';
233
270
  import {
271
+ fetchGif,
272
+ getEmojis,
234
273
  getImagefromDataTransfer,
235
- parseMarkdown,
236
274
  getWordNumber,
237
275
  parseEmoji,
276
+ parseMarkdown,
238
277
  postComment,
239
- getEmojis,
278
+ throttle,
240
279
  } from '../utils';
241
280
 
242
281
  import type { ComputedRef, DeepReadonly } from 'vue';
243
282
  import type { WalineCommentData, WalineImageUploader } from '../typings';
244
- import type { WalineConfig, WalineEmojiConfig } from '../utils';
283
+ import type {
284
+ FetchGifResponse,
285
+ WalineConfig,
286
+ WalineEmojiConfig,
287
+ } from '../utils';
245
288
 
246
289
  export default defineComponent({
247
290
  name: 'CommentBox',
@@ -250,9 +293,11 @@ export default defineComponent({
250
293
  CloseIcon,
251
294
  EmojiIcon,
252
295
  ImageIcon,
296
+ ImageWall,
253
297
  MarkdownIcon,
254
298
  PreviewIcon,
255
299
  LoadingIcon,
300
+ GifIcon,
256
301
  },
257
302
 
258
303
  props: {
@@ -286,13 +331,22 @@ export default defineComponent({
286
331
  const imageUploadRef = ref<HTMLInputElement | null>(null);
287
332
  const emojiButtonRef = ref<HTMLDivElement | null>(null);
288
333
  const emojiPopupRef = ref<HTMLDivElement | null>(null);
334
+ const gifButtonRef = ref<HTMLDivElement | null>(null);
335
+ const gifPopupRef = ref<HTMLDivElement | null>(null);
336
+ const gifSearchInputRef = ref<HTMLInputElement | null>(null);
289
337
 
290
338
  const emoji = ref<DeepReadonly<WalineEmojiConfig>>({ tabs: [], map: {} });
291
339
  const emojiTabIndex = ref(0);
292
340
  const showEmoji = ref(false);
341
+ const showGif = ref(false);
293
342
  const showPreview = ref(false);
294
343
  const previewText = ref('');
295
344
  const wordNumber = ref(0);
345
+ const gifData = ref<{
346
+ cursor: string;
347
+ loading: boolean;
348
+ list: FetchGifResponse['results'];
349
+ }>({ cursor: '', loading: true, list: [] });
296
350
 
297
351
  const wordLimit = ref(0);
298
352
  const isWordNumberLegal = ref(false);
@@ -548,8 +602,44 @@ export default defineComponent({
548
602
  !(emojiPopupRef.value as HTMLElement).contains(event.target as Node)
549
603
  )
550
604
  showEmoji.value = false;
605
+
606
+ if (
607
+ !(gifButtonRef.value as HTMLElement).contains(event.target as Node) &&
608
+ !(gifPopupRef.value as HTMLElement).contains(event.target as Node)
609
+ )
610
+ showGif.value = false;
611
+ };
612
+
613
+ const onImageWallScroll = async (event: Event): Promise<void> => {
614
+ const { scrollTop, clientHeight, scrollHeight } =
615
+ event.target as HTMLDivElement;
616
+ const percent = (clientHeight + scrollTop) / scrollHeight;
617
+
618
+ if (percent < 0.9 || gifData.value.loading) return;
619
+
620
+ gifData.value.loading = true;
621
+
622
+ const data = await fetchGif({
623
+ keyword: gifSearchInputRef.value?.value || '',
624
+ pos: gifData.value.cursor,
625
+ }).finally(() => {
626
+ gifData.value.loading = false;
627
+ });
628
+
629
+ gifData.value.cursor = data.next;
630
+ gifData.value.list = gifData.value.list.concat(data.results);
631
+
632
+ setTimeout(() => {
633
+ (event.target as HTMLDivElement).scrollTop = scrollTop;
634
+ }, 50);
551
635
  };
552
636
 
637
+ const onGifSearch = throttle(async (event: Event) => {
638
+ gifData.value.cursor = '';
639
+ gifData.value.list = [];
640
+ onImageWallScroll(event);
641
+ });
642
+
553
643
  // update wordNumber
554
644
  watch(
555
645
  [config, wordNumber],
@@ -575,6 +665,18 @@ export default defineComponent({
575
665
  { immediate: true }
576
666
  );
577
667
 
668
+ watch(showGif, async (showGif) => {
669
+ if (!showGif) return;
670
+
671
+ gifData.value.loading = true;
672
+ const data = await fetchGif({ keyword: '' }).finally(() => {
673
+ gifData.value.loading = false;
674
+ });
675
+
676
+ gifData.value.cursor = data.next;
677
+ gifData.value.list = gifData.value.list.concat(data.results);
678
+ });
679
+
578
680
  onMounted(() => {
579
681
  document.body.addEventListener('click', popupHandler);
580
682
 
@@ -632,6 +734,8 @@ export default defineComponent({
632
734
  onLogout,
633
735
  onProfile,
634
736
  submitComment,
737
+ onImageWallScroll,
738
+ onGifSearch,
635
739
 
636
740
  isLogin,
637
741
  userInfo,
@@ -651,6 +755,10 @@ export default defineComponent({
651
755
  emojiTabIndex,
652
756
  showEmoji,
653
757
 
758
+ // gif
759
+ gifData,
760
+ showGif,
761
+
654
762
  // image
655
763
  canUploadImage,
656
764
 
@@ -663,7 +771,10 @@ export default defineComponent({
663
771
  editorRef,
664
772
  emojiButtonRef,
665
773
  emojiPopupRef,
774
+ gifButtonRef,
775
+ gifPopupRef,
666
776
  imageUploadRef,
777
+ gifSearchInputRef,
667
778
  };
668
779
  },
669
780
  });
@@ -25,7 +25,7 @@
25
25
  <span v-if="comment.label" class="wl-badge" v-text="comment.label" />
26
26
  <span v-if="comment.sticky" class="wl-badge" v-text="locale.sticky" />
27
27
  <span
28
- v-if="comment.level && comment.level >= 0"
28
+ v-if="comment.level !== undefined && comment.level >= 0"
29
29
  :class="`wl-badge level${comment.level}`"
30
30
  v-text="locale[`level${comment.level}`] || `Level ${comment.level}`"
31
31
  />
@@ -148,3 +148,23 @@ export const LoadingIcon: FunctionalComponent<{ size: number }> = ({ size }) =>
148
148
  })
149
149
  )
150
150
  );
151
+
152
+ export const GifIcon: FunctionalComponent = () =>
153
+ h(
154
+ 'svg',
155
+ {
156
+ width: 24,
157
+ height: 24,
158
+ fill: 'currentcolor',
159
+ viewBox: '0 0 24 24',
160
+ },
161
+ [
162
+ h('path', {
163
+ style: 'transform: translateY(0.5px)',
164
+ d: 'M18.968 10.5H15.968V11.484H17.984V12.984H15.968V15H14.468V9H18.968V10.5V10.5ZM8.984 9C9.26533 9 9.49967 9.09367 9.687 9.281C9.87433 9.46833 9.968 9.70267 9.968 9.984V10.5H6.499V13.5H8.468V12H9.968V14.016C9.968 14.2973 9.87433 14.5317 9.687 14.719C9.49967 14.9063 9.26533 15 8.984 15H5.984C5.70267 15 5.46833 14.9063 5.281 14.719C5.09367 14.5317 5 14.2973 5 14.016V9.985C5 9.70367 5.09367 9.46933 5.281 9.282C5.46833 9.09467 5.70267 9.001 5.984 9.001H8.984V9ZM11.468 9H12.968V15H11.468V9V9Z',
165
+ }),
166
+ h('path', {
167
+ d: 'M18.5 3H5.75C3.6875 3 2 4.6875 2 6.75V18C2 20.0625 3.6875 21.75 5.75 21.75H18.5C20.5625 21.75 22.25 20.0625 22.25 18V6.75C22.25 4.6875 20.5625 3 18.5 3ZM20.75 18C20.75 19.2375 19.7375 20.25 18.5 20.25H5.75C4.5125 20.25 3.5 19.2375 3.5 18V6.75C3.5 5.5125 4.5125 4.5 5.75 4.5H18.5C19.7375 4.5 20.75 5.5125 20.75 6.75V18Z',
168
+ }),
169
+ ]
170
+ );
@@ -0,0 +1,152 @@
1
+ <!-- forked from https://github.com/DerYeger/vue-masonry-wall/blob/master/src/masonry-wall.vue -->
2
+
3
+ <!-- MIT License
4
+
5
+ Copyright (c) 2021 Fuxing Loh, Jan Müller
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE. -->
24
+
25
+ <template>
26
+ <div ref="wall" class="wl-gallery" :style="{ gap: `${gap}px` }">
27
+ <div
28
+ v-for="(column, columnIndex) in columns"
29
+ :key="columnIndex"
30
+ class="wl-gallery-column"
31
+ :data-index="columnIndex"
32
+ :style="{ gap: `${gap}px` }"
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
+ />
45
+ </div>
46
+ </div>
47
+ </template>
48
+
49
+ <script lang="ts">
50
+ import {
51
+ defineComponent,
52
+ nextTick,
53
+ onBeforeUnmount,
54
+ onMounted,
55
+ ref,
56
+ watch,
57
+ } from 'vue';
58
+
59
+ import type { PropType } from 'vue';
60
+
61
+ type Column = number[];
62
+
63
+ export default defineComponent({
64
+ props: {
65
+ columnWidth: { type: Number, default: 300 },
66
+ items: { type: Array as PropType<unknown[]>, default: () => [] },
67
+ gap: { type: Number, default: 0 },
68
+ rtl: { type: Boolean, default: false },
69
+ },
70
+
71
+ emit: ['insert'],
72
+
73
+ setup(props) {
74
+ let resizeObserver: ResizeObserver | null = null;
75
+ const wall = ref<HTMLDivElement | null>(null);
76
+
77
+ const columns = ref<Column[]>([]);
78
+
79
+ const getColumnCount = (): number => {
80
+ const count = Math.floor(
81
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
82
+ (wall.value!.getBoundingClientRect().width + props.gap) /
83
+ (props.columnWidth + props.gap)
84
+ );
85
+
86
+ return count > 0 ? count : 1;
87
+ };
88
+
89
+ const createColumns = (count: number): Column[] =>
90
+ new Array(count).fill(null).map(() => []);
91
+
92
+ const fillColumns = async (itemIndex: number): Promise<void> => {
93
+ if (itemIndex >= props.items.length) return;
94
+
95
+ await nextTick();
96
+
97
+ const columnDivs = Array.from(
98
+ wall.value?.children || []
99
+ ) as HTMLDivElement[];
100
+
101
+ if (props.rtl) columnDivs.reverse();
102
+
103
+ const target = columnDivs.reduce((prev, curr) =>
104
+ curr.getBoundingClientRect().height <
105
+ prev.getBoundingClientRect().height
106
+ ? curr
107
+ : prev
108
+ );
109
+
110
+ columns.value[Number(target.dataset.index)].push(itemIndex);
111
+
112
+ await fillColumns(itemIndex + 1);
113
+ };
114
+
115
+ const redraw = async (force = false): Promise<void> => {
116
+ if (columns.value.length === getColumnCount() && !force) return;
117
+
118
+ columns.value = createColumns(getColumnCount());
119
+
120
+ const scrollY = window.scrollY;
121
+
122
+ await fillColumns(0);
123
+
124
+ window.scrollTo({ top: scrollY });
125
+ };
126
+
127
+ watch(
128
+ () => [props.items, props.rtl],
129
+ () => redraw(true)
130
+ );
131
+ watch(
132
+ () => [props.columnWidth, props.gap],
133
+ () => redraw()
134
+ );
135
+
136
+ onMounted(() => {
137
+ redraw(true);
138
+ resizeObserver = new ResizeObserver(() => redraw());
139
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
140
+ resizeObserver.observe(wall.value!);
141
+ });
142
+
143
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
144
+ onBeforeUnmount(() => resizeObserver!.unobserve(wall.value!));
145
+
146
+ return {
147
+ columns,
148
+ wall,
149
+ };
150
+ },
151
+ });
152
+ </script>
@@ -2,7 +2,7 @@ import { useStorage } from '@vueuse/core';
2
2
 
3
3
  import type { Ref } from 'vue';
4
4
 
5
- const LIKE_KEY = 'WALIKE_LIKE';
5
+ const LIKE_KEY = 'WALINE_LIKE';
6
6
 
7
7
  export type LikeID = number | string;
8
8
 
@@ -39,4 +39,6 @@ export default generateLocale([
39
39
  'Wizards',
40
40
  'Elves',
41
41
  'Maiar',
42
+ 'GIF',
43
+ 'Search GIF',
42
44
  ]);
@@ -39,6 +39,8 @@ const localeKeys = [
39
39
  'level3',
40
40
  'level4',
41
41
  'level5',
42
+ 'gif',
43
+ 'gifSearchPlaceholder',
42
44
  ];
43
45
 
44
46
  export const generateLocale = (locale: string[]): WalineLocale =>
@@ -33,4 +33,12 @@ export default generateLocale([
33
33
  'ワード',
34
34
  'コメントは $0 から $1 ワードの間でなければなりません!\n 現在の単語番号: $2',
35
35
  '匿名',
36
+ 'うえにん',
37
+ 'なかにん',
38
+ 'しもおし',
39
+ '特にしもおし',
40
+ 'かげ',
41
+ 'なぬし',
42
+ 'GIF',
43
+ '探す GIF',
36
44
  ]);
@@ -33,4 +33,12 @@ export default generateLocale([
33
33
  'Palavras',
34
34
  'Favor enviar comentário com $0 a $1 palavras!\n Número de palavras atuais: $2',
35
35
  'Anônimo',
36
+ 'Dwarves',
37
+ 'Hobbits',
38
+ 'Ents',
39
+ 'Wizards',
40
+ 'Elves',
41
+ 'Maiar',
42
+ 'GIF',
43
+ 'Pesquisar GIF',
36
44
  ]);
@@ -33,4 +33,12 @@ export default generateLocale([
33
33
  'Слова',
34
34
  'Пожалуйста, введите комментарии от $0 до $1 слов!\nНомер текущего слова: $2',
35
35
  'Анонимный',
36
+ 'Dwarves',
37
+ 'Hobbits',
38
+ 'Ents',
39
+ 'Wizards',
40
+ 'Elves',
41
+ 'Maiar',
42
+ 'GIF',
43
+ 'Поиск GIF',
36
44
  ]);
@@ -33,4 +33,12 @@ export default generateLocale([
33
33
  'từ',
34
34
  'Please input comments between $0 and $1 words!\n Current word number: $2',
35
35
  'Vô danh',
36
+ 'Dwarves',
37
+ 'Hobbits',
38
+ 'Ents',
39
+ 'Wizards',
40
+ 'Elves',
41
+ 'Maiar',
42
+ 'GIF',
43
+ 'Tìm kiếm GIF',
36
44
  ]);
@@ -39,4 +39,6 @@ export default generateLocale([
39
39
  '活跃',
40
40
  '话痨',
41
41
  '传说',
42
+ '表情包',
43
+ '搜索表情包',
42
44
  ]);
@@ -39,4 +39,6 @@ export default generateLocale([
39
39
  '活躍',
40
40
  '話癆',
41
41
  '傳說',
42
+ '表情包',
43
+ '搜索表情包',
42
44
  ]);
@@ -137,6 +137,7 @@
137
137
  .wl-reply {
138
138
  display: inline-flex;
139
139
  align-items: center;
140
+
140
141
  padding: 4px;
141
142
  border: none;
142
143