Merge 86b1fe2971 into f836666417
				
					
				
			This commit is contained in:
		
						commit
						ddc78fcf81
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -132,6 +132,7 @@ declare module '@vue/runtime-core' { | ||||
|     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||
|     NColorPicker: typeof import('naive-ui')['NColorPicker'] | ||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||
|  | ||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| @ -40,9 +40,17 @@ | ||||
|     "@it-tools/oggen": "^1.3.0", | ||||
|     "@regexper/render": "^1.0.0", | ||||
|     "@sindresorhus/slugify": "^2.2.1", | ||||
|     "@tiptap/pm": "2.1.6", | ||||
|     "@tiptap/starter-kit": "2.1.6", | ||||
|     "@tiptap/vue-3": "2.0.3", | ||||
|     "@tiptap/extension-color": "^2.7.4", | ||||
|     "@tiptap/extension-gapcursor": "^2.7.4", | ||||
|     "@tiptap/extension-highlight": "^2.7.4", | ||||
|     "@tiptap/extension-table": "^2.7.4", | ||||
|     "@tiptap/extension-table-cell": "^2.7.4", | ||||
|     "@tiptap/extension-table-header": "^2.7.4", | ||||
|     "@tiptap/extension-table-row": "^2.7.4", | ||||
|     "@tiptap/extension-text-style": "^2.7.4", | ||||
|     "@tiptap/pm": "2.7.4", | ||||
|     "@tiptap/starter-kit": "2.7.4", | ||||
|     "@tiptap/vue-3": "2.7.4", | ||||
|     "@types/figlet": "^1.5.8", | ||||
|     "@types/markdown-it": "^13.0.7", | ||||
|     "@vicons/material": "^0.12.0", | ||||
|  | ||||
							
								
								
									
										747
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										747
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -3,6 +3,13 @@ import { tryOnBeforeUnmount, useVModel } from '@vueuse/core'; | ||||
| import { Editor, EditorContent } from '@tiptap/vue-3'; | ||||
| import StarterKit from '@tiptap/starter-kit'; | ||||
| import { useThemeVars } from 'naive-ui'; | ||||
| import { Color } from '@tiptap/extension-color'; | ||||
| import TextStyle from '@tiptap/extension-text-style'; | ||||
| import Highlight from '@tiptap/extension-highlight'; | ||||
| import Table from '@tiptap/extension-table'; | ||||
| import TableCell from '@tiptap/extension-table-cell'; | ||||
| import TableHeader from '@tiptap/extension-table-header'; | ||||
| import TableRow from '@tiptap/extension-table-row'; | ||||
| import MenuBar from './menu-bar.vue'; | ||||
| 
 | ||||
| const props = defineProps<{ html: string }>(); | ||||
| @ -12,7 +19,18 @@ const html = useVModel(props, 'html', emit); | ||||
| 
 | ||||
| const editor = new Editor({ | ||||
|   content: html.value, | ||||
|   extensions: [StarterKit], | ||||
|   extensions: [ | ||||
|     StarterKit, | ||||
|     TextStyle, | ||||
|     Color, | ||||
|     Highlight.configure({ multicolor: true }), | ||||
|     Table.configure({ | ||||
|       resizable: true, | ||||
|     }), | ||||
|     TableRow, | ||||
|     TableHeader, | ||||
|     TableCell, | ||||
|   ], | ||||
| }); | ||||
| 
 | ||||
| editor.on('update', ({ editor }) => emit('update:html', editor.getHTML())); | ||||
| @ -63,6 +81,63 @@ tryOnBeforeUnmount(() => { | ||||
|     line-height: 1.1; | ||||
|   } | ||||
| 
 | ||||
|   /* Table-specific styling */ | ||||
|   table { | ||||
|     border-collapse: collapse; | ||||
|     margin: 0; | ||||
|     overflow: hidden; | ||||
|     table-layout: fixed; | ||||
|     width: 100%; | ||||
| 
 | ||||
|     td, | ||||
|     th { | ||||
|       border: 1px solid v-bind('themeVars.borderColor'); | ||||
|       box-sizing: border-box; | ||||
|       min-width: 1em; | ||||
|       padding: 6px 8px; | ||||
|       position: relative; | ||||
|       vertical-align: top; | ||||
| 
 | ||||
|       > * { | ||||
|         margin-bottom: 0; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     th { | ||||
|       background-color: v-bind('themeVars.tableHeaderColor'); | ||||
|       font-weight: bold; | ||||
|       text-align: left; | ||||
|     } | ||||
| 
 | ||||
|     .selectedCell:after { | ||||
|       content: ""; | ||||
|       left: 0; right: 0; top: 0; bottom: 0; | ||||
|       pointer-events: none; | ||||
|       position: absolute; | ||||
|       z-index: 2; | ||||
|     } | ||||
| 
 | ||||
|     .column-resize-handle { | ||||
|       background-color: v-bind('themeVars.actionColor'); | ||||
|       bottom: -2px; | ||||
|       pointer-events: none; | ||||
|       position: absolute; | ||||
|       right: -2px; | ||||
|       top: 0; | ||||
|       width: 4px; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .tableWrapper { | ||||
|     margin: 1.5rem 0; | ||||
|     overflow-x: auto; | ||||
|   } | ||||
| 
 | ||||
|   &.resize-cursor { | ||||
|     cursor: ew-resize; | ||||
|     cursor: col-resize; | ||||
|   } | ||||
| 
 | ||||
|   code { | ||||
|     background-color: v-bind('themeVars.codeColor'); | ||||
|     padding: 2px 4px; | ||||
| @ -84,10 +159,6 @@ tryOnBeforeUnmount(() => { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   mark { | ||||
|     background-color: #faf594; | ||||
|   } | ||||
| 
 | ||||
|   img { | ||||
|     max-width: 100%; | ||||
|     height: auto; | ||||
|  | ||||
| @ -1,13 +1,13 @@ | ||||
| <script setup lang="ts"> | ||||
| import type { Component } from 'vue'; | ||||
| 
 | ||||
| const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean }>(); | ||||
| const { icon, title, action, isActive } = toRefs(props); | ||||
| const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean; enabled?: () => boolean }>(); | ||||
| const { icon, title, action, isActive, enabled } = toRefs(props); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <c-tooltip :tooltip="title"> | ||||
|     <c-button circle variant="text" :type="isActive?.() ? 'primary' : 'default'" @click="action"> | ||||
|     <c-button circle variant="text" :disabled="enabled && !enabled()" :type="isActive?.() ? 'primary' : 'default'" @click="action"> | ||||
|       <n-icon :component="icon" /> | ||||
|     </c-button> | ||||
|   </c-tooltip> | ||||
|  | ||||
| @ -8,15 +8,25 @@ import { | ||||
|   ClearFormatting, | ||||
|   Code, | ||||
|   CodePlus, | ||||
|   ColorPicker, | ||||
|   ColumnInsertLeft, | ||||
|   ColumnInsertRight, | ||||
|   Cross, | ||||
|   H1, | ||||
|   H2, | ||||
|   H3, | ||||
|   H4, | ||||
|   Heading, | ||||
|   Italic, | ||||
|   LayersIntersect2, | ||||
|   LayersUnion, | ||||
|   LayoutDistributeHorizontal, | ||||
|   LayoutDistributeVertical, | ||||
|   List, | ||||
|   ListNumbers, | ||||
|   Strikethrough, | ||||
|   TextWrap, | ||||
|   RowInsertBottom, | ||||
|   RowInsertTop, | ||||
|   SeparatorVertical, Strikethrough, Table, TableOff, TextWrap, Tool, | ||||
| } from '@vicons/tabler'; | ||||
| import type { Component } from 'vue'; | ||||
| import MenuBarItem from './menu-bar-item.vue'; | ||||
| @ -29,9 +39,18 @@ type MenuItem = | ||||
|     icon: Component | ||||
|     title: string | ||||
|     action: () => void | ||||
|     value?: () => string | ||||
|     isActive?: () => boolean | ||||
|     enabled?: () => boolean | ||||
|     type: 'button' | ||||
|   } | ||||
|   | { | ||||
|     icon: Component | ||||
|     title: string | ||||
|     action: (color: string) => void | ||||
|     value: () => string | ||||
|     type: 'color' | ||||
|   } | ||||
|   | { type: 'divider' }; | ||||
| 
 | ||||
| const items: MenuItem[] = [ | ||||
| @ -141,7 +160,42 @@ const items: MenuItem[] = [ | ||||
|     title: 'Clear format', | ||||
|     action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(), | ||||
|   }, | ||||
| 
 | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'color', | ||||
|     title: 'Forecolor', | ||||
|     icon: ColorPicker, | ||||
|     action: color => editor.value.chain().focus().setColor(color).run(), | ||||
|     value: () => editor.value.getAttributes('textStyle').color, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     icon: ClearFormatting, | ||||
|     title: 'Clear Forecolor', | ||||
|     action: () => editor.value.chain().focus().unsetColor().run(), | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'color', | ||||
|     title: 'Highlight color', | ||||
|     icon: ColorPicker, | ||||
|     action: color => editor.value.chain().focus().setHighlight({ color }).run(), | ||||
|     value: () => '#FAF594', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     icon: ClearFormatting, | ||||
|     title: 'Clear Highlight', | ||||
|     action: () => editor.value.chain().focus().unsetHighlight().run(), | ||||
|     isActive: () => editor.value.isActive('highlight'), | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     icon: ArrowBack, | ||||
| @ -154,14 +208,152 @@ const items: MenuItem[] = [ | ||||
|     title: 'Redo', | ||||
|     action: () => editor.value.chain().focus().redo().run(), | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), | ||||
|     enabled: () => editor.value.can().insertTable(), | ||||
|     title: 'Insert table', | ||||
|     icon: Table, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().addColumnBefore().run(), | ||||
|     enabled: () => editor.value.can().addColumnBefore(), | ||||
|     title: 'Add column before', | ||||
|     icon: ColumnInsertLeft, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().addColumnAfter().run(), | ||||
|     enabled: () => editor.value.can().addColumnAfter(), | ||||
|     title: 'Add column after', | ||||
|     icon: ColumnInsertRight, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().deleteColumn().run(), | ||||
|     enabled: () => editor.value.can().deleteColumn(), | ||||
|     title: 'Delete column', | ||||
|     icon: Cross, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().addRowBefore().run(), | ||||
|     enabled: () => editor.value.can().addRowBefore(), | ||||
|     title: 'Add row before', | ||||
|     icon: RowInsertTop, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().addRowAfter().run(), | ||||
|     enabled: () => editor.value.can().addRowAfter(), | ||||
|     title: 'Add row after', | ||||
|     icon: RowInsertBottom, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().deleteRow().run(), | ||||
|     enabled: () => editor.value.can().deleteRow(), | ||||
|     title: 'Delete row', | ||||
|     icon: Cross, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().deleteTable().run(), | ||||
|     enabled: () => editor.value.can().deleteTable(), | ||||
|     title: 'Delete table', | ||||
|     icon: TableOff, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().mergeCells().run(), | ||||
|     enabled: () => editor.value.can().mergeCells(), | ||||
|     title: 'Merge cells', | ||||
|     icon: LayersUnion, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().splitCell().run(), | ||||
|     enabled: () => editor.value.can().splitCell(), | ||||
|     title: 'Split cell', | ||||
|     icon: SeparatorVertical, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().mergeOrSplit().run(), | ||||
|     enabled: () => editor.value.can().mergeOrSplit(), | ||||
|     title: 'Merge or split', | ||||
|     icon: LayersIntersect2, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().toggleHeaderColumn().run(), | ||||
|     enabled: () => editor.value.can().toggleHeaderColumn(), | ||||
|     title: 'Toggle header column', | ||||
|     icon: LayoutDistributeVertical, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().toggleHeaderRow().run(), | ||||
|     enabled: () => editor.value.can().toggleHeaderRow(), | ||||
|     title: 'Toggle header row', | ||||
|     icon: LayoutDistributeHorizontal, | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().toggleHeaderCell().run(), | ||||
|     enabled: () => editor.value.can().toggleHeaderCell(), | ||||
|     title: 'Toggle header cell', | ||||
|     icon: Heading, | ||||
|   }, | ||||
|   { | ||||
|     type: 'divider', | ||||
|   }, | ||||
|   { | ||||
|     type: 'button', | ||||
|     action: () => editor.value.chain().focus().fixTables().run(), | ||||
|     enabled: () => editor.value.can().fixTables(), | ||||
|     title: 'Fix tables', | ||||
|     icon: Tool, | ||||
|   }, | ||||
| ]; | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <div flex items-center> | ||||
|   <div flex flex-wrap items-center> | ||||
|     <template v-for="(item, index) in items"> | ||||
|       <n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical /> | ||||
|       <MenuBarItem v-else-if="item.type === 'button'" :key="index" v-bind="item" /> | ||||
|       <c-tooltip | ||||
|         v-if="item.type === 'color'" :key="`color${index}`" | ||||
|         :tooltip="item.title" | ||||
|       > | ||||
|         <n-color-picker | ||||
|           style="width: 120px" | ||||
|           :show-alpha="false" | ||||
|           :actions="['confirm']" | ||||
|           :value="item.value()" | ||||
|           @confirm="item.action" | ||||
|         /> | ||||
|       </c-tooltip> | ||||
|     </template> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user