Skip to content

Commit

Permalink
feat: text content menu bar
Browse files Browse the repository at this point in the history
  • Loading branch information
Seedsa committed May 21, 2024
1 parent 81012de commit 825518d
Show file tree
Hide file tree
Showing 14 changed files with 198 additions and 105 deletions.
File renamed without changes.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ English | [中文](./README.zh-CN.md)
- TypeScript support
- I18n support(`en`, `zhHans`)
- Create your own extensions
- Tailwind CSS support

## Installation

Expand Down
1 change: 1 addition & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- 支持 TypeScript
- 国际化支持(`en`, `zhHans`
- 支持自定义扩展
- Tailwind CSS

## 安装

Expand Down
5 changes: 5 additions & 0 deletions examples/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ async function AICompletions(text?: string) {
// 从.env中获取key 请自行替换
// @ts-ignore
const apiKey = import.meta.env.VITE_OPENAI_API_KEY
if (!apiKey) {
console.error('请配置VITE_OPENAI_API_KEY')
return
}
console.log(apiKey)
const openai = new OpenAI({
apiKey: apiKey,
dangerouslyAllowBrowser: true,
Expand Down
98 changes: 0 additions & 98 deletions examples/demo-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,104 +47,6 @@ export const DEMO_CONTENT = {
},
{ type: 'paragraph', attrs: { class: null, textAlign: 'start', indent: 0, lineHeight: null } },
{ type: 'paragraph', attrs: { class: null, textAlign: 'start', indent: 0, lineHeight: null } },
{
type: 'columns',
attrs: { layout: 'two-column' },
content: [
{
type: 'column',
attrs: { position: 'left' },
content: [
{
type: 'paragraph',
attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null },
content: [{ type: 'text', text: '多栏布局' }],
},
],
},
{
type: 'column',
attrs: { position: 'right' },
content: [
{
type: 'paragraph',
attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null },
content: [{ type: 'text', text: '多栏布局' }],
},
],
},
],
},
{
type: 'table',
content: [
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: { backgroundColor: null, colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
{
type: 'tableCell',
attrs: { backgroundColor: null, colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
{
type: 'tableCell',
attrs: { backgroundColor: null, colspan: 1, rowspan: 3, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: { backgroundColor: null, colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
{
type: 'tableCell',
attrs: { backgroundColor: '#52C41A', colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
],
},
{
type: 'tableRow',
content: [
{
type: 'tableCell',
attrs: { backgroundColor: null, colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [{ type: 'paragraph', attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null } }],
},
{
type: 'tableCell',
attrs: { backgroundColor: '#52C41A', colspan: 1, rowspan: 1, colwidth: null, style: null },
content: [
{
type: 'paragraph',
attrs: { class: null, textAlign: 'left', indent: 0, lineHeight: null },
content: [{ type: 'text', text: 'table' }],
},
],
},
],
},
],
},
{
type: 'iframes',
attrs: {
src: 'https://player.bilibili.com/player.html?bvid=BV1EJ411u7DN',
service: 'bilibili',
frameborder: 0,
allowfullscreen: true,
},
},
{ type: 'horizontalRule' },
{
type: 'heading',
Expand Down
4 changes: 2 additions & 2 deletions src/components/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ const items = computed(() => {
let menus: Menu[] = []
for (const extension of sortExtensions) {
const { button, divider = false, spacer = false } = extension.options
if (!button || !isFunction(button)) continue
const { button, divider = false, spacer = false, toolbar = true } = extension.options
if (!button || !isFunction(button) || !toolbar) continue
const _button: ButtonViewReturn = button({
editor: props.editor,
Expand Down
2 changes: 1 addition & 1 deletion src/components/menus/ContentMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ watch(
<DropdownMenuContent class="w-48" align="start" side="bottom">
<DropdownMenuItem
@click="deleteNode"
class="flex gap-3 focus:text-red-500 dark:hover:text-red-500 bg-opacity-10 hover:bg-opacity-20 focus:bg-opacity-30 dark:hover:bg-opacity-20"
class="flex gap-3 focus:text-red-500 focus:bg-red-400 hover:bg-red-400 dark:hover:text-red-500 bg-opacity-10 hover:bg-opacity-20 focus:bg-opacity-30 dark:hover:bg-opacity-20"
>
<Icon name="Trash2" />
<span>删除</span>
Expand Down
6 changes: 4 additions & 2 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ export const NODE_TYPE_MENU: any = {
'remove',
],
text: [
'Ai',
'divider',
'text-bubble',
'divider',
'bold',
'italic',
'underline',
Expand All @@ -145,8 +149,6 @@ export const NODE_TYPE_MENU: any = {
'color',
'highlight',
'textAlign',
'divider',
'ai',
],
video: ['video-size-small', 'video-size-medium', 'video-size-large', 'divider', 'remove'],
table: ['removeTable'],
Expand Down
1 change: 1 addition & 0 deletions src/extensions/Ai/Ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const AI = Node.create<AIOptions>({
addOptions() {
return {
...this.parent?.(),
toolbar: false,
HTMLAttributes: {
class: `node-${this.name}`,
},
Expand Down
20 changes: 18 additions & 2 deletions src/extensions/BaseKit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import type { IframeOptions } from './Iframe/Iframe'
import type { BubbleOptions } from '../components/menus/bubble'
import { defaultBubbleList, generateBubbleTypeMenu } from '../components/menus/bubble'

import { TextBubble } from './TextBubble'
import type { TextBubbleOptions } from './TextBubble'

import { NODE_TYPE_MENU } from '@/constants'

/**
Expand Down Expand Up @@ -127,10 +130,20 @@ export interface BaseKitOptions {
/**
* Trailing node options or false, indicating whether to enable the trailing node
*
* @default false
* @default true
*/
trailingNode: Partial<TrailingNodeOptions> | false

/**
* textBubble options or false, indicating whether to enable the textBubble
*
* @default true
*/
textBubble: Partial<TextBubbleOptions> | false
/**
* selection options or false, indicating whether to enable the selection
*
* @default true
*/
selection: any | false
}

Expand Down Expand Up @@ -194,6 +207,9 @@ export const BaseKit = Extension.create<BaseKitOptions>({
if (this.options.text !== false) {
extensions.push(Text.configure())
}
if (this.options.textBubble !== false) {
extensions.push(TextBubble.configure())
}

if (this.options.gapcursor !== false) {
extensions.push(Gapcursor.configure())
Expand Down
23 changes: 23 additions & 0 deletions src/extensions/TextBubble/TextBubble.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Extension } from '@tiptap/core'

import TextDropdown from './components/TextDropdown.vue'

import type { GeneralOptions } from '@/type'

export interface TextBubbleOptions extends GeneralOptions<TextBubbleOptions> {}

export const TextBubble = Extension.create<TextBubbleOptions>({
name: 'text-bubble',
addOptions() {
return {
...this.parent?.(),
toolbar: false,
button: () => ({
component: TextDropdown,
componentProps: {},
}),
}
},
})

export default TextBubble
139 changes: 139 additions & 0 deletions src/extensions/TextBubble/components/TextDropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuCheckboxItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Editor } from '@tiptap/vue-3'
import { Icon, icons } from '@/components/icons'
import { useLocale } from '@/locales'
interface ContentTypeMenu {
name: string
label: string
iconName: keyof typeof icons
action?: (value?: unknown) => void
isActive: () => boolean
}
interface Props {
editor: Editor
disabled?: boolean
color?: string
maxHeight?: string | number
icon?: any
tooltip?: string
}
const props = withDefaults(defineProps<Props>(), {
disabled: false,
color: undefined,
maxHeight: undefined,
icon: undefined,
tooltip: '',
items: () => [],
})
const { t } = useLocale()
const menus: ContentTypeMenu[] = [
{
name: 'paragraph',
label: t.value('editor.paragraph.tooltip'),
iconName: 'Heading1',
isActive: () =>
props.editor.isActive('paragraph') &&
!props.editor.isActive('orderedList') &&
!props.editor.isActive('bulletList') &&
!props.editor.isActive('taskList'),
action: () => props.editor.chain().focus().clearNodes().run(),
},
{
name: 'heading1',
label: t.value('editor.heading.h1.tooltip'),
isActive: () => props.editor.isActive('heading', { level: 1 }),
iconName: 'Heading1',
action: () => props.editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
},
{
name: 'heading2',
label: t.value('editor.heading.h2.tooltip'),
isActive: () => props.editor.isActive('heading', { level: 2 }),
iconName: 'Heading2',
action: () => props.editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
},
{
name: 'heading3',
label: t.value('editor.heading.h3.tooltip'),
isActive: () => props.editor.isActive('heading', { level: 3 }),
iconName: 'Heading3',
action: () => props.editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
},
{
name: 'bulletList',
label: t.value('editor.bulletlist.tooltip'),
isActive: () => props.editor.isActive('bulletList'),
iconName: 'List',
action: () => props.editor.chain().focus().clearNodes().toggleBulletList().run(),
},
{
name: 'numberedList',
label: t.value('editor.orderedlist.tooltip'),
isActive: () => props.editor.isActive('orderedList'),
iconName: 'ListOrdered',
action: () => props.editor.chain().focus().clearNodes().toggleOrderedList().run(),
},
{
name: 'taskList',
label: t.value('editor.tasklist.tooltip'),
isActive: () => props.editor.isActive('taskList'),
iconName: 'ListTodo',
action: () => props.editor.chain().focus().clearNodes().toggleTaskList().run(),
},
{
name: 'blockquote',
label: t.value('editor.blockquote.tooltip'),
isActive: () => props.editor.isActive('blockquote'),
iconName: 'TextQuote',
action: () => props.editor.chain().focus().clearNodes().toggleBlockquote().run(),
},
{
name: 'codeBlock',
label: t.value('editor.codeblock.tooltip'),
isActive: () => props.editor.isActive('codeBlock'),
iconName: 'Code2',
action: () => props.editor.chain().focus().clearNodes().toggleCodeBlock().run(),
},
]
const activeItem = computed(() => {
return (
menus.filter(item => item.isActive()).pop() ?? {
label: '修改',
}
)
})
</script>

<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="h-[32px] flex gap-1 px-1.5">
<span class="whitespace-nowrap text-sm font-normal"> {{ activeItem?.label }}</span>
<Icon name="ChevronDown" class="w-4 h-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-full p-1" align="start" :sideOffset="5">
<DropdownMenuCheckboxItem
v-for="(item, index) in menus"
:key="index"
@click="item.action"
class="cursor-pointer"
:checked="item.isActive?.() || false"
>
<div class="flex items-center gap-2 px-2">
<Icon :name="item.iconName" class="h3 w-3" />
<span> {{ item.label }}</span>
</div></DropdownMenuCheckboxItem
>
</DropdownMenuContent>
</DropdownMenu>
</template>
1 change: 1 addition & 0 deletions src/extensions/TextBubble/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './TextBubble'
2 changes: 2 additions & 0 deletions src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface GeneralOptions<T> {
spacer: boolean
/** Button view function */
button: ButtonView<T>
/** Show on Toolbar */
toolbar: boolean
}

/**
Expand Down

0 comments on commit 825518d

Please sign in to comment.