@kungal/editor-nuxt 0.11.0 → 0.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.
- package/CHANGELOG.md +63 -0
- package/nuxt.config.ts +47 -9
- package/package.json +5 -2
- package/runtime/KunEditorToolbar.vue +246 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @kungal/editor-nuxt
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8ce8210: The Nuxt layer now preconfigures Vite's dev dependency optimizer, so consumers
|
|
8
|
+
never hit the micromark/`debug` dev error.
|
|
9
|
+
|
|
10
|
+
Under `nuxt dev`, Milkdown's tokenizer (micromark) resolves to its DEV build,
|
|
11
|
+
which does `import debug from 'debug'`. `debug` is CommonJS; unless Vite
|
|
12
|
+
pre-bundles it, its browser build is served raw with no `default` export and the
|
|
13
|
+
editor throws at load — `SyntaxError: 'debug' does not provide an export named
|
|
14
|
+
'default'` (create-tokenizer.js), which also aborts app init. Whether it fires is
|
|
15
|
+
non-deterministic per app (it depends on whether Vite's dep scan reaches the
|
|
16
|
+
editor before it evaluates).
|
|
17
|
+
|
|
18
|
+
The layer now sets `vite.optimizeDeps.include` for the editor + Milkdown entry
|
|
19
|
+
points, which Nuxt merges into every consuming app — so esbuild pre-bundles that
|
|
20
|
+
subgraph and resolves micromark's CJS `debug` inside the bundle. No app needs to
|
|
21
|
+
configure this itself. Dev-only: the production build strips micromark's debug
|
|
22
|
+
calls, so `nuxt build` is unaffected. (Plain non-Nuxt Vite apps that use
|
|
23
|
+
`@kungal/editor-vue` directly still add the same `optimizeDeps.include` manually —
|
|
24
|
+
a layer can inject Vite config, a plain package cannot.)
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- @kungal/editor-core@0.13.0
|
|
29
|
+
- @kungal/editor-vue@0.13.0
|
|
30
|
+
|
|
31
|
+
## 0.12.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- 5786594: Add a `#toolbar` slot and a KunUI toolbar — the toolbar is now a swappable layer.
|
|
36
|
+
|
|
37
|
+
`<KunEditor>` exposes a `#toolbar` scoped slot whose props are the command API
|
|
38
|
+
(`KunEditorToolbarApi`: `run(cmdKey, payload)` / `insertText` / `insertQuote` /
|
|
39
|
+
`insertMention` / `focus` / `adapters` / `features` / `locale`). The API is
|
|
40
|
+
computed inside the Milkdown providers and handed down, so a custom toolbar can
|
|
41
|
+
live in the consumer's tree without `useInstance`/`inject`. The hand-rolled
|
|
42
|
+
default toolbar is the slot's fallback — omit the slot and nothing changes.
|
|
43
|
+
|
|
44
|
+
`@kungal/editor-nuxt` ships `<KunEditorToolbar>` (auto-imported): the same
|
|
45
|
+
toolbar built with **KunButton / KunIcon / KunTooltip / KunPopover** (real tooltip
|
|
46
|
+
delay, popover focus/collision, a11y):
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<KunEditor v-model="md" :adapters="adapters">
|
|
50
|
+
<template #toolbar="api"><KunEditorToolbar v-bind="api" /></template>
|
|
51
|
+
</KunEditor>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This keeps `@kungal/editor-vue` **headless / zero UI-kit dependency** (any Vue app
|
|
55
|
+
can use it with the default toolbar) while the KunUI ecosystem (forum, moyu) gets
|
|
56
|
+
native chrome — the "headless core + optional UI layer" pattern (TipTap UI,
|
|
57
|
+
BlockNote's swappable toolbar). Also exports `EMOJI` from `@kungal/editor-vue` for
|
|
58
|
+
building custom pickers.
|
|
59
|
+
|
|
60
|
+
### Patch Changes
|
|
61
|
+
|
|
62
|
+
- Updated dependencies [5786594]
|
|
63
|
+
- @kungal/editor-vue@0.12.0
|
|
64
|
+
- @kungal/editor-core@0.12.0
|
|
65
|
+
|
|
3
66
|
## 0.11.0
|
|
4
67
|
|
|
5
68
|
### Patch Changes
|
package/nuxt.config.ts
CHANGED
|
@@ -1,28 +1,66 @@
|
|
|
1
|
-
import { defineNuxtModule, addComponent } from '@nuxt/kit'
|
|
1
|
+
import { defineNuxtModule, addComponent, createResolver } from '@nuxt/kit'
|
|
2
2
|
|
|
3
|
-
// KunEditor Nuxt layer.
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// KunEditor Nuxt layer. <KunEditor> itself lives in the framework-agnostic,
|
|
4
|
+
// headless @kungal/editor-vue; this layer registers it as a Nuxt auto-import so
|
|
5
|
+
// downstream templates use `<KunEditor>` with no import (and Nuxt generates its
|
|
6
|
+
// types, keeping the tag type-checked in consumer templates).
|
|
7
|
+
//
|
|
8
|
+
// It also ships <KunEditorToolbar>: the KunUI-built toolbar for <KunEditor>'s
|
|
9
|
+
// #toolbar slot. It belongs HERE (this layer already assumes @kungal/ui-nuxt),
|
|
10
|
+
// not in headless @kungal/editor-vue — so editor-vue stays UI-kit-free while the
|
|
11
|
+
// KunUI ecosystem gets native chrome. Same shape as TipTap's optional UI pkg.
|
|
8
12
|
export default defineNuxtConfig({
|
|
9
13
|
modules: [
|
|
10
14
|
defineNuxtModule({
|
|
11
15
|
meta: { name: 'kun-editor-components' },
|
|
12
16
|
setup() {
|
|
17
|
+
const { resolve } = createResolver(import.meta.url)
|
|
13
18
|
addComponent({
|
|
14
19
|
name: 'KunEditor',
|
|
15
20
|
export: 'KunEditor',
|
|
16
21
|
filePath: '@kungal/editor-vue'
|
|
17
22
|
})
|
|
23
|
+
addComponent({
|
|
24
|
+
name: 'KunEditorToolbar',
|
|
25
|
+
filePath: resolve('./runtime/KunEditorToolbar.vue')
|
|
26
|
+
})
|
|
18
27
|
}
|
|
19
28
|
})
|
|
20
|
-
]
|
|
29
|
+
],
|
|
30
|
+
|
|
31
|
+
// Under `nuxt dev`, Milkdown's markdown tokenizer (micromark) resolves to its
|
|
32
|
+
// DEV build, which does `import debug from 'debug'`. `debug` is CommonJS, so
|
|
33
|
+
// unless Vite pre-bundles it, its browser.js is served raw with no `default`
|
|
34
|
+
// export and the editor throws at load:
|
|
35
|
+
// SyntaxError: … 'debug' does not provide an export named 'default'
|
|
36
|
+
// (create-tokenizer.js)
|
|
37
|
+
// which also aborts app init. Whether it fires is non-deterministic per app —
|
|
38
|
+
// it depends on whether Vite's dep scan reaches the editor before it evaluates
|
|
39
|
+
// (the docs happens to get auto-scanned; other consumers don't). So pin it
|
|
40
|
+
// HERE, in the layer: force-including the editor/milkdown ENTRY points makes
|
|
41
|
+
// esbuild pre-bundle their whole subgraph and resolve micromark's CJS `debug`
|
|
42
|
+
// INSIDE the bundle. Merged into every consumer that extends this layer, so no
|
|
43
|
+
// app configures it. Dev-only: the prod build strips micromark's debug calls.
|
|
44
|
+
//
|
|
45
|
+
// NB: include the resolvable ENTRY points, not `'debug'` — under pnpm's
|
|
46
|
+
// non-hoisted layout `debug` isn't resolvable from the app root, so Vite would
|
|
47
|
+
// silently skip it. Unresolvable entries below are skipped too (harmless).
|
|
48
|
+
vite: {
|
|
49
|
+
optimizeDeps: {
|
|
50
|
+
include: [
|
|
51
|
+
'@kungal/editor-vue',
|
|
52
|
+
'@kungal/editor-core',
|
|
53
|
+
'@kungal/editor-core/preset',
|
|
54
|
+
'@milkdown/kit/preset/commonmark',
|
|
55
|
+
'@milkdown/kit/preset/gfm'
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
21
59
|
})
|
|
22
60
|
|
|
23
61
|
// NOTE: this layer does NOT own a Tailwind entry or import editor styles. The
|
|
24
62
|
// consuming app owns one stylesheet and adds `@kungal/editor-vue/style.css`
|
|
25
63
|
// alongside its @kungal/ui-tokens + @kungal/ui-vue setup — the @source scan
|
|
26
64
|
// path is node_modules-layout-specific, so only the app can write it. It also
|
|
27
|
-
// assumes @kungal/ui-nuxt is already extended (
|
|
28
|
-
// See this package's README.
|
|
65
|
+
// assumes @kungal/ui-nuxt is already extended: <KunEditorToolbar> (and the
|
|
66
|
+
// reference editor styles) render KunUI chrome. See this package's README.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kungal/editor-nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "KunEditor Nuxt Layer — wraps @kungal/editor-vue and auto-imports <KunEditor> so an existing Nuxt app keeps its exact DX (no import needed).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -26,13 +26,16 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"CHANGELOG.md",
|
|
28
28
|
"app",
|
|
29
|
+
"runtime",
|
|
29
30
|
"nuxt.config.ts"
|
|
30
31
|
],
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@kungal/editor-
|
|
33
|
+
"@kungal/editor-core": "0.13.0",
|
|
34
|
+
"@kungal/editor-vue": "0.13.0"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"@kungal/ui-vue": "^2.7.0",
|
|
38
|
+
"@milkdown/kit": "^7.21.2",
|
|
36
39
|
"nuxt": "^4.0.0",
|
|
37
40
|
"vue": "^3.5.0"
|
|
38
41
|
},
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// A KunUI-built toolbar for <KunEditor>, dropped into its #toolbar scoped slot:
|
|
3
|
+
//
|
|
4
|
+
// <KunEditor v-model="md" :adapters="a">
|
|
5
|
+
// <template #toolbar="api"><KunEditorToolbar v-bind="api" /></template>
|
|
6
|
+
// </KunEditor>
|
|
7
|
+
//
|
|
8
|
+
// This lives in @kungal/editor-nuxt (the layer that already assumes @kungal/
|
|
9
|
+
// ui-nuxt), NOT in the headless @kungal/editor-vue — so editor-vue keeps its
|
|
10
|
+
// zero-UI-dep promise while the KunUI ecosystem (forum, moyu) gets native
|
|
11
|
+
// KunButton / KunIcon / KunTooltip / KunPopover chrome. It drives the editor
|
|
12
|
+
// purely through the slot API props (KunEditorToolbarApi) — no useInstance.
|
|
13
|
+
//
|
|
14
|
+
// KunButton/KunIcon/KunTooltip/KunPopover are auto-imported by @kungal/ui-nuxt in
|
|
15
|
+
// the consuming app; icons use the app's iconify set (e.g. lucide via @nuxt/icon).
|
|
16
|
+
import { computed, ref } from 'vue'
|
|
17
|
+
import {
|
|
18
|
+
createCodeBlockCommand,
|
|
19
|
+
insertHrCommand,
|
|
20
|
+
insertImageCommand,
|
|
21
|
+
toggleEmphasisCommand,
|
|
22
|
+
toggleInlineCodeCommand,
|
|
23
|
+
toggleStrongCommand,
|
|
24
|
+
wrapInBlockquoteCommand,
|
|
25
|
+
wrapInBulletListCommand,
|
|
26
|
+
wrapInHeadingCommand,
|
|
27
|
+
wrapInOrderedListCommand
|
|
28
|
+
} from '@milkdown/kit/preset/commonmark'
|
|
29
|
+
import { toggleStrikethroughCommand } from '@milkdown/kit/preset/gfm'
|
|
30
|
+
import {
|
|
31
|
+
insertKunSpoilerCommand,
|
|
32
|
+
toggleLatexCommand
|
|
33
|
+
} from '@kungal/editor-core/preset'
|
|
34
|
+
import { EMOJI } from '@kungal/editor-vue'
|
|
35
|
+
import type { KunEditorToolbarApi, StickerPack } from '@kungal/editor-vue'
|
|
36
|
+
|
|
37
|
+
const props = defineProps<KunEditorToolbarApi>()
|
|
38
|
+
|
|
39
|
+
const en = computed(() => props.locale.toLowerCase().startsWith('en'))
|
|
40
|
+
const t = computed(() => ({
|
|
41
|
+
bold: en.value ? 'Bold' : '加粗',
|
|
42
|
+
italic: en.value ? 'Italic' : '斜体',
|
|
43
|
+
strike: en.value ? 'Strikethrough' : '删除线',
|
|
44
|
+
code: en.value ? 'Inline code' : '行内代码',
|
|
45
|
+
h1: en.value ? 'Heading 1' : '一级标题',
|
|
46
|
+
h2: en.value ? 'Heading 2' : '二级标题',
|
|
47
|
+
h3: en.value ? 'Heading 3' : '三级标题',
|
|
48
|
+
bulletList: en.value ? 'Bullet list' : '无序列表',
|
|
49
|
+
orderedList: en.value ? 'Ordered list' : '有序列表',
|
|
50
|
+
quote: en.value ? 'Blockquote' : '引用块',
|
|
51
|
+
codeBlock: en.value ? 'Code block' : '代码块',
|
|
52
|
+
hr: en.value ? 'Divider' : '分隔线',
|
|
53
|
+
spoiler: en.value ? 'Spoiler' : '隐藏文本',
|
|
54
|
+
latex: en.value ? 'Math (LaTeX)' : '公式',
|
|
55
|
+
image: en.value ? 'Upload image' : '上传图片',
|
|
56
|
+
picker: en.value ? 'Emoji & stickers' : '表情与贴纸',
|
|
57
|
+
emoji: en.value ? 'Emoji' : '表情',
|
|
58
|
+
sticker: en.value ? 'Stickers' : '贴纸',
|
|
59
|
+
empty: en.value ? 'No stickers' : '暂无贴纸',
|
|
60
|
+
failed: en.value ? 'Failed to load stickers' : '贴纸加载失败'
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
interface Tool {
|
|
64
|
+
icon: string
|
|
65
|
+
title: string
|
|
66
|
+
run: () => void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Same command set + grouping as the default toolbar; `.key` is read at click
|
|
70
|
+
// time via api.run (populated once the editor is created).
|
|
71
|
+
const groups = computed<Tool[][]>(() => [
|
|
72
|
+
[
|
|
73
|
+
{ icon: 'lucide:bold', title: t.value.bold, run: () => props.run(toggleStrongCommand.key) },
|
|
74
|
+
{ icon: 'lucide:italic', title: t.value.italic, run: () => props.run(toggleEmphasisCommand.key) },
|
|
75
|
+
{ icon: 'lucide:strikethrough', title: t.value.strike, run: () => props.run(toggleStrikethroughCommand.key) },
|
|
76
|
+
{ icon: 'lucide:code', title: t.value.code, run: () => props.run(toggleInlineCodeCommand.key) }
|
|
77
|
+
],
|
|
78
|
+
[
|
|
79
|
+
{ icon: 'lucide:heading-1', title: t.value.h1, run: () => props.run(wrapInHeadingCommand.key, 1) },
|
|
80
|
+
{ icon: 'lucide:heading-2', title: t.value.h2, run: () => props.run(wrapInHeadingCommand.key, 2) },
|
|
81
|
+
{ icon: 'lucide:heading-3', title: t.value.h3, run: () => props.run(wrapInHeadingCommand.key, 3) }
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
{ icon: 'lucide:list', title: t.value.bulletList, run: () => props.run(wrapInBulletListCommand.key) },
|
|
85
|
+
{ icon: 'lucide:list-ordered', title: t.value.orderedList, run: () => props.run(wrapInOrderedListCommand.key) },
|
|
86
|
+
{ icon: 'lucide:text-quote', title: t.value.quote, run: () => props.run(wrapInBlockquoteCommand.key) },
|
|
87
|
+
{ icon: 'lucide:square-code', title: t.value.codeBlock, run: () => props.run(createCodeBlockCommand.key, '') },
|
|
88
|
+
{ icon: 'lucide:minus', title: t.value.hr, run: () => props.run(insertHrCommand.key) }
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
{ icon: 'lucide:eye-off', title: t.value.spoiler, run: () => props.run(insertKunSpoilerCommand.key) },
|
|
92
|
+
{ icon: 'lucide:sigma', title: t.value.latex, run: () => props.run(toggleLatexCommand.key) }
|
|
93
|
+
]
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
// Image upload — only when the host supplies uploadImage. Same adapter as
|
|
97
|
+
// paste/drop; on success inserts an image node at the cursor.
|
|
98
|
+
const uploadImage = computed(() => props.adapters.uploadImage)
|
|
99
|
+
const fileInput = ref<HTMLInputElement | null>(null)
|
|
100
|
+
const pickImage = () => fileInput.value?.click()
|
|
101
|
+
const onFileChange = async (e: Event) => {
|
|
102
|
+
const input = e.target as HTMLInputElement
|
|
103
|
+
const file = input.files?.[0]
|
|
104
|
+
input.value = ''
|
|
105
|
+
const upload = uploadImage.value
|
|
106
|
+
if (!file || !upload) return
|
|
107
|
+
try {
|
|
108
|
+
const src = await upload(file)
|
|
109
|
+
props.run(insertImageCommand.key, { src, title: file.name, alt: file.name })
|
|
110
|
+
} catch {
|
|
111
|
+
props.adapters.notify?.(en.value ? 'Image upload failed' : '图片上传失败', 'error')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Emoji (built-in) + sticker (adapter) picker in a KunPopover.
|
|
116
|
+
const showPicker = computed(() => props.features.sticker !== false)
|
|
117
|
+
const stickerSource = computed(() => props.adapters.stickerSource)
|
|
118
|
+
const hasSticker = computed(() => !!stickerSource.value)
|
|
119
|
+
const tab = ref<'emoji' | 'sticker'>('emoji')
|
|
120
|
+
const packs = ref<StickerPack[]>([])
|
|
121
|
+
const loaded = ref(false)
|
|
122
|
+
const loadFailed = ref(false)
|
|
123
|
+
const loadStickers = async () => {
|
|
124
|
+
const source = stickerSource.value
|
|
125
|
+
if (loaded.value || !source) return
|
|
126
|
+
loaded.value = true
|
|
127
|
+
try {
|
|
128
|
+
packs.value = await source()
|
|
129
|
+
} catch {
|
|
130
|
+
loadFailed.value = true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const insertSticker = (src: string, name: string) =>
|
|
134
|
+
props.run(insertImageCommand.key, { src, title: name, alt: name })
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
<template>
|
|
138
|
+
<div class="flex flex-wrap items-center gap-0.5">
|
|
139
|
+
<template v-for="(group, gi) in groups" :key="gi">
|
|
140
|
+
<span v-if="gi > 0" class="bg-default-200 mx-1 h-5 w-px" aria-hidden="true" />
|
|
141
|
+
<KunTooltip v-for="(b, bi) in group" :key="bi" :text="b.title" :delay-show="300">
|
|
142
|
+
<KunButton
|
|
143
|
+
variant="light"
|
|
144
|
+
size="sm"
|
|
145
|
+
:is-icon-only="true"
|
|
146
|
+
:aria-label="b.title"
|
|
147
|
+
@click="b.run()"
|
|
148
|
+
>
|
|
149
|
+
<KunIcon :name="b.icon" />
|
|
150
|
+
</KunButton>
|
|
151
|
+
</KunTooltip>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<template v-if="uploadImage">
|
|
155
|
+
<span class="bg-default-200 mx-1 h-5 w-px" aria-hidden="true" />
|
|
156
|
+
<KunTooltip :text="t.image" :delay-show="300">
|
|
157
|
+
<KunButton
|
|
158
|
+
variant="light"
|
|
159
|
+
size="sm"
|
|
160
|
+
:is-icon-only="true"
|
|
161
|
+
:aria-label="t.image"
|
|
162
|
+
@click="pickImage"
|
|
163
|
+
>
|
|
164
|
+
<KunIcon name="lucide:image" />
|
|
165
|
+
</KunButton>
|
|
166
|
+
</KunTooltip>
|
|
167
|
+
<input
|
|
168
|
+
ref="fileInput"
|
|
169
|
+
type="file"
|
|
170
|
+
accept=".jpg,.jpeg,.png,.webp,.gif"
|
|
171
|
+
hidden
|
|
172
|
+
@change="onFileChange"
|
|
173
|
+
/>
|
|
174
|
+
</template>
|
|
175
|
+
|
|
176
|
+
<template v-if="showPicker">
|
|
177
|
+
<span class="bg-default-200 mx-1 h-5 w-px" aria-hidden="true" />
|
|
178
|
+
<KunPopover position="bottom-start" :opaque="true" inner-class="w-72 p-2">
|
|
179
|
+
<template #trigger>
|
|
180
|
+
<KunButton
|
|
181
|
+
variant="light"
|
|
182
|
+
size="sm"
|
|
183
|
+
:is-icon-only="true"
|
|
184
|
+
:aria-label="t.picker"
|
|
185
|
+
@click="hasSticker && loadStickers()"
|
|
186
|
+
>
|
|
187
|
+
<KunIcon name="lucide:smile-plus" />
|
|
188
|
+
</KunButton>
|
|
189
|
+
</template>
|
|
190
|
+
|
|
191
|
+
<div v-if="hasSticker" class="mb-2 flex gap-1">
|
|
192
|
+
<KunButton
|
|
193
|
+
:variant="tab === 'emoji' ? 'flat' : 'light'"
|
|
194
|
+
size="sm"
|
|
195
|
+
@click="tab = 'emoji'"
|
|
196
|
+
>{{ t.emoji }}</KunButton
|
|
197
|
+
>
|
|
198
|
+
<KunButton
|
|
199
|
+
:variant="tab === 'sticker' ? 'flat' : 'light'"
|
|
200
|
+
size="sm"
|
|
201
|
+
@click="tab = 'sticker'"
|
|
202
|
+
>{{ t.sticker }}</KunButton
|
|
203
|
+
>
|
|
204
|
+
</div>
|
|
205
|
+
|
|
206
|
+
<div
|
|
207
|
+
v-show="!hasSticker || tab === 'emoji'"
|
|
208
|
+
class="grid max-h-56 grid-cols-8 gap-0.5 overflow-y-auto"
|
|
209
|
+
>
|
|
210
|
+
<button
|
|
211
|
+
v-for="(e, i) in EMOJI"
|
|
212
|
+
:key="i"
|
|
213
|
+
type="button"
|
|
214
|
+
class="hover:bg-default-100 rounded p-1 text-lg leading-none"
|
|
215
|
+
@click="props.insertText(e)"
|
|
216
|
+
>
|
|
217
|
+
{{ e }}
|
|
218
|
+
</button>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div v-if="hasSticker" v-show="tab === 'sticker'" class="max-h-56 overflow-y-auto">
|
|
222
|
+
<template v-if="packs.length">
|
|
223
|
+
<div v-for="pack in packs" :key="pack.name" class="mb-2">
|
|
224
|
+
<div class="text-default-500 mb-1 text-xs">{{ pack.name }}</div>
|
|
225
|
+
<div class="grid grid-cols-4 gap-1">
|
|
226
|
+
<button
|
|
227
|
+
v-for="(s, i) in pack.stickers"
|
|
228
|
+
:key="i"
|
|
229
|
+
type="button"
|
|
230
|
+
class="hover:bg-default-100 rounded p-1"
|
|
231
|
+
:title="s.name"
|
|
232
|
+
@click="insertSticker(s.src, s.name)"
|
|
233
|
+
>
|
|
234
|
+
<img :src="s.src" :alt="s.name" loading="lazy" class="h-14 w-14 object-contain" />
|
|
235
|
+
</button>
|
|
236
|
+
</div>
|
|
237
|
+
</div>
|
|
238
|
+
</template>
|
|
239
|
+
<div v-else class="text-default-500 p-4 text-center text-sm">
|
|
240
|
+
{{ loadFailed ? t.failed : t.empty }}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</KunPopover>
|
|
244
|
+
</template>
|
|
245
|
+
</div>
|
|
246
|
+
</template>
|