@tnotesjs/ui 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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +97 -0
  3. package/package.json +68 -0
  4. package/src/components/BilibiliVideo/BilibiliVideo.vue +55 -0
  5. package/src/components/Footprints/Footprints.vue +339 -0
  6. package/src/components/Footprints/parse.ts +122 -0
  7. package/src/components/Mermaid/Mermaid.vue +553 -0
  8. package/src/components/Mermaid/icons/icon__center_off.svg +1 -0
  9. package/src/components/Mermaid/icons/icon__center_on.svg +1 -0
  10. package/src/components/Mermaid/icons/icon__check.svg +3 -0
  11. package/src/components/Mermaid/icons/icon__clipboard.svg +8 -0
  12. package/src/components/Mermaid/icons/icon__fullscreen.svg +1 -0
  13. package/src/components/Mermaid/icons/icon__fullscreen_exit.svg +1 -0
  14. package/src/components/Mindmap/FocusBreadcrumbs.test.ts +265 -0
  15. package/src/components/Mindmap/FocusBreadcrumbs.vue +436 -0
  16. package/src/components/Mindmap/InlineRuns.ts +25 -0
  17. package/src/components/Mindmap/Mindmap.vue +1210 -0
  18. package/src/components/Mindmap/MindmapOutlineNode.vue +62 -0
  19. package/src/components/Mindmap/MindmapViewIcon.vue +41 -0
  20. package/src/components/Mindmap/editor/AppIcon.vue +107 -0
  21. package/src/components/Mindmap/editor/CanvasContextMenu.vue +157 -0
  22. package/src/components/Mindmap/editor/LinkPopover.vue +83 -0
  23. package/src/components/Mindmap/editor/MarkdownView.vue +163 -0
  24. package/src/components/Mindmap/editor/MindmapView.vue +253 -0
  25. package/src/components/Mindmap/editor/OutlineView.vue +2494 -0
  26. package/src/components/Mindmap/editor/RichInlineEditor.vue +393 -0
  27. package/src/components/Mindmap/editor/SelectionToolbar.vue +191 -0
  28. package/src/components/Mindmap/editor/canvasClipboard.ts +6 -0
  29. package/src/components/Mindmap/editor/imagePaste.ts +32 -0
  30. package/src/components/Mindmap/editor/mindmapClipboard.ts +93 -0
  31. package/src/components/Mindmap/editor/outlineDrag.ts +29 -0
  32. package/src/components/Mindmap/editor/platform.ts +18 -0
  33. package/src/components/Mindmap/expandLevel.ts +28 -0
  34. package/src/components/Mindmap/icons/icon__fullscreen.svg +1 -0
  35. package/src/components/Mindmap/icons/icon__fullscreen_exit.svg +1 -0
  36. package/src/components/Mindmap/icons/icon__zoom_fit.svg +1 -0
  37. package/src/components/Mindmap/markdown.ts +83 -0
  38. package/src/components/Mindmap/wheelInteraction.ts +7 -0
  39. package/src/components/NotesTable/NotesTable.vue +119 -0
  40. package/src/components/NotesTable/types.ts +6 -0
  41. package/src/components/WordList/RightClickMenu.vue +106 -0
  42. package/src/components/WordList/WordList.vue +692 -0
  43. package/src/components/WordList/wordListFeatures.ts +38 -0
  44. package/src/index.ts +30 -0
  45. package/src/styles/tokens.css +35 -0
@@ -0,0 +1,553 @@
1
+ <template>
2
+ <div ref="rootRef" class="tn-mermaid" :class="{ 'is-centered': centered }">
3
+ <div
4
+ v-if="showActions"
5
+ class="tn-mermaid__actions"
6
+ :class="{ 'is-suppressed': loading || !!error || empty }"
7
+ :data-copy-state="copyState"
8
+ >
9
+ <slot name="actions-start" />
10
+ <button
11
+ v-if="enableCenterToggle"
12
+ type="button"
13
+ class="tn-mermaid__action"
14
+ :title="centered ? '取消居中' : '居中显示'"
15
+ :aria-pressed="centered ? 'true' : 'false'"
16
+ @click="toggleCenter"
17
+ >
18
+ <img
19
+ :src="centered ? iconCenterOn : iconCenterOff"
20
+ alt=""
21
+ width="16"
22
+ height="16"
23
+ />
24
+ </button>
25
+ <button
26
+ v-if="enableFullscreen"
27
+ type="button"
28
+ class="tn-mermaid__action"
29
+ :title="isFullscreen ? '退出全屏' : '全屏查看图表'"
30
+ @click="toggleFullscreen"
31
+ >
32
+ <img
33
+ :src="isFullscreen ? iconFullscreenExit : iconFullscreen"
34
+ :alt="isFullscreen ? '退出全屏' : '全屏'"
35
+ width="16"
36
+ height="16"
37
+ />
38
+ </button>
39
+ <button
40
+ v-if="enableCopy"
41
+ type="button"
42
+ class="tn-mermaid__action tn-mermaid__action--copy"
43
+ :data-copy-state="copyState"
44
+ :title="copyTitle"
45
+ @click="copySource"
46
+ >
47
+ <img :src="copyIcon" alt="复制" width="16" height="16" />
48
+ </button>
49
+ <slot name="actions-end" />
50
+ </div>
51
+
52
+ <div
53
+ class="tn-mermaid__stage"
54
+ :class="{ 'is-fullscreen': isFullscreen }"
55
+ @mousedown="startDrag"
56
+ @mousemove="onDrag"
57
+ @mouseup="endDrag"
58
+ @mouseleave="endDrag"
59
+ >
60
+ <!-- Keep host laid out during render; display:none breaks Mermaid SVG sizing. -->
61
+ <div
62
+ ref="diagramRef"
63
+ class="tn-mermaid__diagram"
64
+ :class="{ 'is-centered': centered, 'is-obscured': loading || empty || !!error }"
65
+ :style="diagramTransform ? { transform: diagramTransform, transformOrigin: 'top left' } : undefined"
66
+ @wheel="handleWheel"
67
+ />
68
+ <div v-if="loading" class="tn-mermaid__loading tn-mermaid__status">
69
+ <div class="tn-mermaid__spinner" />
70
+ <p>加载图表中...</p>
71
+ </div>
72
+ <div v-else-if="empty" class="tn-mermaid__empty tn-mermaid__status">
73
+ 输入 Mermaid 源码后显示预览
74
+ </div>
75
+ <div v-else-if="error" class="tn-mermaid__error tn-mermaid__status">
76
+ <p>{{ error }}</p>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </template>
81
+
82
+ <script setup>
83
+ import mermaid from 'mermaid'
84
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
85
+
86
+ import iconCenterOff from './icons/icon__center_off.svg?url'
87
+ import iconCenterOn from './icons/icon__center_on.svg?url'
88
+ import iconCheck from './icons/icon__check.svg?url'
89
+ import iconClipboard from './icons/icon__clipboard.svg?url'
90
+ import iconFullscreen from './icons/icon__fullscreen.svg?url'
91
+ import iconFullscreenExit from './icons/icon__fullscreen_exit.svg?url'
92
+
93
+ let idSeq = 0
94
+
95
+ const props = defineProps({
96
+ /** Plain Mermaid source (preferred). */
97
+ source: {
98
+ type: String,
99
+ default: ''
100
+ },
101
+ /** URI-encoded source from VitePress fence (`graph="…"`). */
102
+ graph: {
103
+ type: String,
104
+ default: ''
105
+ },
106
+ id: {
107
+ type: String,
108
+ default: ''
109
+ },
110
+ /** Fence `center` keyword → true; omit → false. */
111
+ center: {
112
+ type: Boolean,
113
+ default: false
114
+ },
115
+ /** Explicit dark mode; omit to auto-detect `html.dark` / `data-theme=dark`. */
116
+ isDark: {
117
+ type: Boolean,
118
+ default: undefined
119
+ },
120
+ securityLevel: {
121
+ type: String,
122
+ default: 'loose'
123
+ },
124
+ enableCopy: {
125
+ type: Boolean,
126
+ default: true
127
+ },
128
+ enableFullscreen: {
129
+ type: Boolean,
130
+ default: true
131
+ },
132
+ enableCenterToggle: {
133
+ type: Boolean,
134
+ default: true
135
+ }
136
+ })
137
+
138
+ const emit = defineEmits(['update:center', 'centerChange'])
139
+
140
+ const rootRef = ref(null)
141
+ const diagramRef = ref(null)
142
+ const loading = ref(false)
143
+ const error = ref(null)
144
+ const isFullscreen = ref(false)
145
+ const centered = ref(Boolean(props.center))
146
+ const idPrefix = props.id || 'tn-mermaid'
147
+ let renderToken = 0
148
+ let lastThemeDark = null
149
+
150
+ const showActions = computed(
151
+ () => props.enableCopy || props.enableFullscreen || props.enableCenterToggle
152
+ )
153
+
154
+ const decodedSource = computed(() => {
155
+ if (props.source) return props.source
156
+ if (!props.graph) return ''
157
+ try {
158
+ return decodeURIComponent(props.graph)
159
+ } catch {
160
+ return props.graph
161
+ }
162
+ })
163
+
164
+ const empty = computed(() => !decodedSource.value.trim())
165
+
166
+ watch(
167
+ () => props.center,
168
+ (value) => {
169
+ centered.value = Boolean(value)
170
+ }
171
+ )
172
+
173
+ function toggleCenter() {
174
+ const next = !centered.value
175
+ centered.value = next
176
+ emit('update:center', next)
177
+ emit('centerChange', next)
178
+ }
179
+
180
+ function detectDark() {
181
+ if (typeof props.isDark === 'boolean') return props.isDark
182
+ const root = document.documentElement
183
+ return root.classList.contains('dark') || root.dataset.theme === 'dark'
184
+ }
185
+
186
+ const COPY_RESET_DELAY = 1000
187
+ const copyState = ref('idle')
188
+ const copyIcon = computed(() =>
189
+ copyState.value === 'copied' ? iconCheck : iconClipboard
190
+ )
191
+ const copyTitle = computed(() => {
192
+ if (copyState.value === 'copied') return '已复制'
193
+ if (copyState.value === 'failed') return '复制失败'
194
+ return '复制源码'
195
+ })
196
+ let copyResetTimer = null
197
+
198
+ function copySource() {
199
+ const text = decodedSource.value
200
+ navigator.clipboard
201
+ .writeText(text)
202
+ .then(() => {
203
+ copyState.value = 'copied'
204
+ })
205
+ .catch(() => {
206
+ copyState.value = 'failed'
207
+ })
208
+ .finally(() => {
209
+ if (copyResetTimer) clearTimeout(copyResetTimer)
210
+ copyResetTimer = setTimeout(() => {
211
+ copyState.value = 'idle'
212
+ }, COPY_RESET_DELAY)
213
+ })
214
+ }
215
+
216
+ const scale = ref(1)
217
+ const panX = ref(0)
218
+ const panY = ref(0)
219
+ const dragging = ref(false)
220
+ let dragStartX = 0
221
+ let dragStartY = 0
222
+ let panStartX = 0
223
+ let panStartY = 0
224
+ const MIN_SCALE = 0.1
225
+ const MAX_SCALE = 5
226
+ const SCALE_STEP = 0.01
227
+
228
+ const diagramTransform = computed(() =>
229
+ isFullscreen.value
230
+ ? `translate(${panX.value}px, ${panY.value}px) scale(${scale.value})`
231
+ : undefined
232
+ )
233
+
234
+ function startDrag(e) {
235
+ if (!isFullscreen.value) return
236
+ dragging.value = true
237
+ dragStartX = e.clientX
238
+ dragStartY = e.clientY
239
+ panStartX = panX.value
240
+ panStartY = panY.value
241
+ }
242
+
243
+ function onDrag(e) {
244
+ if (!dragging.value) return
245
+ panX.value = panStartX + (e.clientX - dragStartX)
246
+ panY.value = panStartY + (e.clientY - dragStartY)
247
+ }
248
+
249
+ function endDrag() {
250
+ dragging.value = false
251
+ }
252
+
253
+ function handleWheel(e) {
254
+ if (!isFullscreen.value) return
255
+ e.preventDefault()
256
+ const delta = e.deltaY > 0 ? -SCALE_STEP : SCALE_STEP
257
+ scale.value = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale.value + delta))
258
+ }
259
+
260
+ function toggleFullscreen() {
261
+ if (!rootRef.value) return
262
+ if (!isFullscreen.value) {
263
+ rootRef.value.requestFullscreen?.()
264
+ } else {
265
+ document.exitFullscreen?.()
266
+ }
267
+ }
268
+
269
+ function handleFullscreenChange() {
270
+ isFullscreen.value = !!document.fullscreenElement
271
+ if (!isFullscreen.value) {
272
+ scale.value = 1
273
+ panX.value = 0
274
+ panY.value = 0
275
+ }
276
+ }
277
+
278
+ async function renderDiagram() {
279
+ await nextTick()
280
+ if (!diagramRef.value) return
281
+
282
+ if (!decodedSource.value.trim()) {
283
+ loading.value = false
284
+ error.value = null
285
+ diagramRef.value.innerHTML = ''
286
+ return
287
+ }
288
+
289
+ const token = ++renderToken
290
+ const dark = detectDark()
291
+ lastThemeDark = dark
292
+
293
+ try {
294
+ loading.value = true
295
+ error.value = null
296
+ mermaid.initialize({
297
+ startOnLoad: false,
298
+ theme: dark ? 'dark' : 'default',
299
+ securityLevel: props.securityLevel,
300
+ fontFamily: 'inherit'
301
+ })
302
+ // Mermaid requires a unique DOM id per render; reusing the same id on
303
+ // theme re-render (common on hard refresh) clears/fails the second pass.
304
+ const renderId = `${idPrefix}-${++idSeq}`
305
+ const { svg, bindFunctions } = await mermaid.render(renderId, decodedSource.value)
306
+ if (token !== renderToken || !diagramRef.value) return
307
+ diagramRef.value.innerHTML = svg
308
+ if (bindFunctions) bindFunctions(diagramRef.value)
309
+ } catch (err) {
310
+ if (token !== renderToken) return
311
+ const message = err instanceof Error ? err.message : String(err)
312
+ error.value = `Failed to render diagram: ${message}`
313
+ if (diagramRef.value) diagramRef.value.innerHTML = ''
314
+ } finally {
315
+ if (token === renderToken) loading.value = false
316
+ }
317
+ }
318
+
319
+ function observeTheme() {
320
+ const observer = new MutationObserver(() => {
321
+ const dark = detectDark()
322
+ if (dark === lastThemeDark) return
323
+ void renderDiagram()
324
+ })
325
+ observer.observe(document.documentElement, {
326
+ attributes: true,
327
+ attributeFilter: ['class', 'data-theme']
328
+ })
329
+ return observer
330
+ }
331
+
332
+ let themeObserver = null
333
+
334
+ onMounted(() => {
335
+ void renderDiagram()
336
+ themeObserver = observeTheme()
337
+ document.addEventListener('fullscreenchange', handleFullscreenChange)
338
+ })
339
+
340
+ onBeforeUnmount(() => {
341
+ themeObserver?.disconnect()
342
+ document.removeEventListener('fullscreenchange', handleFullscreenChange)
343
+ if (copyResetTimer) clearTimeout(copyResetTimer)
344
+ })
345
+
346
+ watch(
347
+ () => [decodedSource.value, props.securityLevel, props.isDark],
348
+ () => {
349
+ void renderDiagram()
350
+ }
351
+ )
352
+ </script>
353
+
354
+ <style scoped lang="scss">
355
+ .tn-mermaid {
356
+ position: relative;
357
+ margin: 1rem 0;
358
+ /* Rest: no frame. Border appears with the action menu (hover / menu focus / copy feedback). */
359
+ border: 1px solid transparent;
360
+ border-radius: 8px;
361
+ background: var(--tn-c-bg);
362
+ overflow: hidden;
363
+ color: var(--tn-c-text);
364
+ transition: border-color 0.2s ease;
365
+ }
366
+
367
+ .tn-mermaid:hover,
368
+ .tn-mermaid:has(.tn-mermaid__actions:hover),
369
+ .tn-mermaid:has(.tn-mermaid__actions:focus-within),
370
+ .tn-mermaid:has(.tn-mermaid__actions[data-copy-state='copied']),
371
+ .tn-mermaid:has(.tn-mermaid__actions[data-copy-state='failed']) {
372
+ border-color: var(--tn-c-divider);
373
+ }
374
+
375
+ .tn-mermaid__actions {
376
+ position: absolute;
377
+ top: 8px;
378
+ right: 8px;
379
+ z-index: 10;
380
+ display: flex;
381
+ align-items: center;
382
+ gap: 4px;
383
+ padding: 2px;
384
+ background-color: color-mix(in srgb, var(--tn-c-bg) 92%, transparent);
385
+ border: 1px solid var(--tn-c-divider);
386
+ border-radius: 7px;
387
+ opacity: 0;
388
+ transition: opacity 0.2s;
389
+ }
390
+
391
+ .tn-mermaid:hover .tn-mermaid__actions,
392
+ .tn-mermaid__actions:hover,
393
+ .tn-mermaid__actions:focus-within,
394
+ .tn-mermaid__actions[data-copy-state='copied'],
395
+ .tn-mermaid__actions[data-copy-state='failed'] {
396
+ opacity: 1;
397
+ }
398
+
399
+ .tn-mermaid__actions.is-suppressed,
400
+ .tn-mermaid:hover .tn-mermaid__actions.is-suppressed {
401
+ opacity: 0;
402
+ pointer-events: none;
403
+ }
404
+
405
+ .tn-mermaid__action {
406
+ display: inline-flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ width: 28px;
410
+ height: 28px;
411
+ padding: 0;
412
+ background: transparent;
413
+ border: none;
414
+ border-radius: 4px;
415
+ color: var(--tn-c-text-2);
416
+ cursor: pointer;
417
+
418
+ &:hover {
419
+ background-color: var(--tn-c-default-soft);
420
+ color: var(--tn-c-brand);
421
+ }
422
+
423
+ svg,
424
+ img {
425
+ width: 16px;
426
+ height: 16px;
427
+ display: block;
428
+ }
429
+ }
430
+
431
+ .tn-mermaid__action--copy {
432
+ position: relative;
433
+
434
+ &[data-copy-state='copied'] {
435
+ background-color: var(--tn-c-success-soft);
436
+ }
437
+
438
+ &[data-copy-state='failed'] {
439
+ background-color: var(--tn-c-danger-soft);
440
+ }
441
+
442
+ &::after {
443
+ position: absolute;
444
+ top: 50%;
445
+ right: calc(100% + 6px);
446
+ padding: 4px 7px;
447
+ color: var(--tn-c-text);
448
+ background: var(--tn-c-bg-elv);
449
+ border: 1px solid var(--tn-c-divider);
450
+ border-radius: 6px;
451
+ box-shadow: var(--tn-shadow-2);
452
+ content: attr(title);
453
+ font-size: 12px;
454
+ line-height: 18px;
455
+ opacity: 0;
456
+ pointer-events: none;
457
+ transform: translateY(-50%) translateX(2px);
458
+ transition:
459
+ opacity 0.16s ease,
460
+ transform 0.16s ease;
461
+ white-space: nowrap;
462
+ }
463
+
464
+ &[data-copy-state='copied']::after,
465
+ &[data-copy-state='failed']::after {
466
+ opacity: 1;
467
+ transform: translateY(-50%) translateX(0);
468
+ }
469
+ }
470
+
471
+ .tn-mermaid__stage {
472
+ position: relative;
473
+ min-height: 160px;
474
+ padding: 20px;
475
+ background: transparent;
476
+
477
+ &.is-fullscreen {
478
+ position: fixed;
479
+ inset: 0;
480
+ z-index: 9999;
481
+ padding: 40px;
482
+ overflow: hidden;
483
+ cursor: grab;
484
+ background: var(--tn-c-bg);
485
+
486
+ &:active {
487
+ cursor: grabbing;
488
+ }
489
+ }
490
+ }
491
+
492
+ .tn-mermaid__status {
493
+ position: absolute;
494
+ inset: 0;
495
+ z-index: 2;
496
+ display: flex;
497
+ flex-direction: column;
498
+ align-items: center;
499
+ justify-content: center;
500
+ gap: 12px;
501
+ padding: 1.5rem;
502
+ color: var(--tn-c-text-2);
503
+ font-size: 14px;
504
+ text-align: center;
505
+ background: var(--tn-c-bg);
506
+
507
+ p {
508
+ margin: 0;
509
+ }
510
+ }
511
+
512
+ .tn-mermaid__error {
513
+ color: var(--tn-c-danger);
514
+ }
515
+
516
+ .tn-mermaid__diagram.is-obscured {
517
+ visibility: hidden;
518
+ pointer-events: none;
519
+ }
520
+
521
+ .tn-mermaid__spinner {
522
+ width: 36px;
523
+ height: 36px;
524
+ border: 3px solid var(--tn-c-divider);
525
+ border-top-color: var(--tn-c-brand);
526
+ border-radius: 50%;
527
+ animation: tn-mermaid-spin 0.8s linear infinite;
528
+ }
529
+
530
+ @keyframes tn-mermaid-spin {
531
+ to {
532
+ transform: rotate(360deg);
533
+ }
534
+ }
535
+
536
+ .tn-mermaid__diagram {
537
+ display: flex;
538
+ justify-content: flex-start;
539
+
540
+ &.is-centered {
541
+ justify-content: center;
542
+ }
543
+
544
+ :deep(svg) {
545
+ max-width: 100%;
546
+ height: auto;
547
+ }
548
+
549
+ :deep(svg .label p) {
550
+ line-height: 1.5;
551
+ }
552
+ }
553
+ </style>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#98989f" d="M17 4h3c1.1 0 2 .9 2 2v2h-2V6h-3zM4 8V6h3V4H4c-1.1 0-2 .9-2 2v2zm16 8v2h-3v2h3c1.1 0 2-.9 2-2v-2zM7 18H4v-2H2v2c0 1.1.9 2 2 2h3zm9-8v4H8v-4zm2-2H6v8h12z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="#646cff" d="M17 4h3c1.1 0 2 .9 2 2v2h-2V6h-3zM4 8V6h3V4H4c-1.1 0-2 .9-2 2v2zm16 8v2h-3v2h3c1.1 0 2-.9 2-2v-2zM7 18H4v-2H2v2c0 1.1.9 2 2 2h3zm9-8v4H8v-4zm2-2H6v8h12z"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <polyline points="20 6 9 17 4 12"></polyline>
3
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg t="1735090484450" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4809"
2
+ width="1em" height="1em">
3
+ <path
4
+ d="M752 176V64c0-35.3-28.7-64-64-64H144c-35.3 0-64 28.7-64 64v704c0 35.3 28.7 64 64 64h112c8.8 0 16 7.2 16 16v112c0 35.3 28.7 64 64 64h544c35.3 0 64-28.7 64-64V256c0-35.3-28.7-64-64-64H768c-8.8 0-16-7.2-16-16zM256 768h-96c-8.8 0-16-7.2-16-16V80c0-8.8 7.2-16 16-16h512c8.8 0 16 7.2 16 16v96c0 8.8-7.2 16-16 16H336c-35.3 0-64 28.7-64 64v496c0 8.8-7.2 16-16 16z m608 192H352c-8.8 0-16-7.2-16-16V272c0-8.8 7.2-16 16-16h512c8.8 0 16 7.2 16 16v672c0 8.8-7.2 16-16 16z"
5
+ p-id="4810" fill="#646cff"></path>
6
+ <path d="M704 352H512c-17.7 0-32-14.3-32-32s14.3-32 32-32h192c17.7 0 32 14.3 32 32s-14.3 32-32 32z" p-id="4811"
7
+ fill="#646cff"></path>
8
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="none"><path d="M24 0v24H0V0zM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.019-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="#646cff" d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586zM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3z"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#646cff" d="m10 15.4l-5.9 5.9q-.275.275-.7.275t-.7-.275t-.275-.7t.275-.7L8.6 14H5q-.425 0-.712-.288T4 13t.288-.712T5 12h6q.425 0 .713.288T12 13v6q0 .425-.288.713T11 20t-.712-.288T10 19zm5.4-5.4H19q.425 0 .713.288T20 11t-.288.713T19 12h-6q-.425 0-.712-.288T12 11V5q0-.425.288-.712T13 4t.713.288T14 5v3.6l5.9-5.9q.275-.275.7-.275t.7.275t.275.7t-.275.7z"/></svg>