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'] |     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] |     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] |     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||||
|  |     NColorPicker: typeof import('naive-ui')['NColorPicker'] | ||||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] |     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||||
|     NDivider: typeof import('naive-ui')['NDivider'] |     NDivider: typeof import('naive-ui')['NDivider'] | ||||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] |     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| @ -40,9 +40,17 @@ | |||||||
|     "@it-tools/oggen": "^1.3.0", |     "@it-tools/oggen": "^1.3.0", | ||||||
|     "@regexper/render": "^1.0.0", |     "@regexper/render": "^1.0.0", | ||||||
|     "@sindresorhus/slugify": "^2.2.1", |     "@sindresorhus/slugify": "^2.2.1", | ||||||
|     "@tiptap/pm": "2.1.6", |     "@tiptap/extension-color": "^2.7.4", | ||||||
|     "@tiptap/starter-kit": "2.1.6", |     "@tiptap/extension-gapcursor": "^2.7.4", | ||||||
|     "@tiptap/vue-3": "2.0.3", |     "@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/figlet": "^1.5.8", | ||||||
|     "@types/markdown-it": "^13.0.7", |     "@types/markdown-it": "^13.0.7", | ||||||
|     "@vicons/material": "^0.12.0", |     "@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 { Editor, EditorContent } from '@tiptap/vue-3'; | ||||||
| import StarterKit from '@tiptap/starter-kit'; | import StarterKit from '@tiptap/starter-kit'; | ||||||
| import { useThemeVars } from 'naive-ui'; | 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'; | import MenuBar from './menu-bar.vue'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ html: string }>(); | const props = defineProps<{ html: string }>(); | ||||||
| @ -12,7 +19,18 @@ const html = useVModel(props, 'html', emit); | |||||||
| 
 | 
 | ||||||
| const editor = new Editor({ | const editor = new Editor({ | ||||||
|   content: html.value, |   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())); | editor.on('update', ({ editor }) => emit('update:html', editor.getHTML())); | ||||||
| @ -63,6 +81,63 @@ tryOnBeforeUnmount(() => { | |||||||
|     line-height: 1.1; |     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 { |   code { | ||||||
|     background-color: v-bind('themeVars.codeColor'); |     background-color: v-bind('themeVars.codeColor'); | ||||||
|     padding: 2px 4px; |     padding: 2px 4px; | ||||||
| @ -84,10 +159,6 @@ tryOnBeforeUnmount(() => { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   mark { |  | ||||||
|     background-color: #faf594; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   img { |   img { | ||||||
|     max-width: 100%; |     max-width: 100%; | ||||||
|     height: auto; |     height: auto; | ||||||
|  | |||||||
| @ -1,13 +1,13 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| import type { Component } from 'vue'; | import type { Component } from 'vue'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean }>(); | const props = defineProps<{ icon: Component; title: string; action: () => void; isActive?: () => boolean; enabled?: () => boolean }>(); | ||||||
| const { icon, title, action, isActive } = toRefs(props); | const { icon, title, action, isActive, enabled } = toRefs(props); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <c-tooltip :tooltip="title"> |   <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" /> |       <n-icon :component="icon" /> | ||||||
|     </c-button> |     </c-button> | ||||||
|   </c-tooltip> |   </c-tooltip> | ||||||
|  | |||||||
| @ -8,15 +8,25 @@ import { | |||||||
|   ClearFormatting, |   ClearFormatting, | ||||||
|   Code, |   Code, | ||||||
|   CodePlus, |   CodePlus, | ||||||
|  |   ColorPicker, | ||||||
|  |   ColumnInsertLeft, | ||||||
|  |   ColumnInsertRight, | ||||||
|  |   Cross, | ||||||
|   H1, |   H1, | ||||||
|   H2, |   H2, | ||||||
|   H3, |   H3, | ||||||
|   H4, |   H4, | ||||||
|  |   Heading, | ||||||
|   Italic, |   Italic, | ||||||
|  |   LayersIntersect2, | ||||||
|  |   LayersUnion, | ||||||
|  |   LayoutDistributeHorizontal, | ||||||
|  |   LayoutDistributeVertical, | ||||||
|   List, |   List, | ||||||
|   ListNumbers, |   ListNumbers, | ||||||
|   Strikethrough, |   RowInsertBottom, | ||||||
|   TextWrap, |   RowInsertTop, | ||||||
|  |   SeparatorVertical, Strikethrough, Table, TableOff, TextWrap, Tool, | ||||||
| } from '@vicons/tabler'; | } from '@vicons/tabler'; | ||||||
| import type { Component } from 'vue'; | import type { Component } from 'vue'; | ||||||
| import MenuBarItem from './menu-bar-item.vue'; | import MenuBarItem from './menu-bar-item.vue'; | ||||||
| @ -29,9 +39,18 @@ type MenuItem = | |||||||
|     icon: Component |     icon: Component | ||||||
|     title: string |     title: string | ||||||
|     action: () => void |     action: () => void | ||||||
|  |     value?: () => string | ||||||
|     isActive?: () => boolean |     isActive?: () => boolean | ||||||
|  |     enabled?: () => boolean | ||||||
|     type: 'button' |     type: 'button' | ||||||
|   } |   } | ||||||
|  |   | { | ||||||
|  |     icon: Component | ||||||
|  |     title: string | ||||||
|  |     action: (color: string) => void | ||||||
|  |     value: () => string | ||||||
|  |     type: 'color' | ||||||
|  |   } | ||||||
|   | { type: 'divider' }; |   | { type: 'divider' }; | ||||||
| 
 | 
 | ||||||
| const items: MenuItem[] = [ | const items: MenuItem[] = [ | ||||||
| @ -141,7 +160,42 @@ const items: MenuItem[] = [ | |||||||
|     title: 'Clear format', |     title: 'Clear format', | ||||||
|     action: () => editor.value.chain().focus().clearNodes().unsetAllMarks().run(), |     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', |     type: 'button', | ||||||
|     icon: ArrowBack, |     icon: ArrowBack, | ||||||
| @ -154,14 +208,152 @@ const items: MenuItem[] = [ | |||||||
|     title: 'Redo', |     title: 'Redo', | ||||||
|     action: () => editor.value.chain().focus().redo().run(), |     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> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <div flex items-center> |   <div flex flex-wrap items-center> | ||||||
|     <template v-for="(item, index) in items"> |     <template v-for="(item, index) in items"> | ||||||
|       <n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical /> |       <n-divider v-if="item.type === 'divider'" :key="`divider${index}`" vertical /> | ||||||
|       <MenuBarItem v-else-if="item.type === 'button'" :key="index" v-bind="item" /> |       <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> |     </template> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user